Interaction System

This week, I spend time on developing the interaction system. This system allows the player to interact with interaction objects. These objects trigger events such as opening doors and opening dialog. An important aspect of this system is ease of creating new interactions. The level designer (me) should be able to create interactions and events without touching the player GameObject.

There are two types of interactions that this system handles — dialogs and environment interactions. At the core of the system, there is an Interactions object child to the Player object with a trigger collider and an InteractionManager attached to it. InteractionManager handles triggers from Interaction layer objects. Based on the tag of the colliding Interaction trigger, the InteractionManager goes down either the environment interaction path or the dialog path.

Environment Interaction

When the level designer (me again) wants to add an environment interaction to their object, they will create a component that extends from InteractionEvent. InteractionEvent is a abstract base component that has the abstract method DoEvent(). DoEvent() also returns an IEnumerator so it can be called as a coroutine. The level designer will put the logic of the event in DoEvent(). InteractionEvent also requires a CircleCollider2D and automatically sets it to a trigger.

public abstract class InteractionEvent : MonoBehaviour

{

public abstract IEnumerator DoEvent();

}

When the player enters the trigger of an object with a InteractionEvent component, they can push the Interact button. The InteractionManager then checks if the player is in the trigger of an environment interaction. If the player is, the InteractionManager then calls the DoEvent() coroutine of that InteractionEvent.

//An example of an InteractionEvent that opens a door

public class OpenDoorIE : InteractionEvent

{

public GameObject door_;

private bool is_opened_ = false; public override IEnumerator DoEvent()

{

is_opened_ = !is_opened_;

door_.SetActive(!is_opened_);

yield break;

}

}

I am really happy on how this system came out. It is simple and may need some tweaking when implementing complex events, but its simplicity gives the level designer (me) a fast way to implement new events.

Dialog

The dialog system is the most complex system in the game so far. It allows the player to open branching dialog with the NPCs of the game. There are 5 parts of this system

DialogSystem objects

Dialog components

The DialogManager component

The UIManager and the DialogPanel components.

JSONs that hold dialog info

DialogSystem objects are where the logic of the dialog lives. The constructor takes a dialog JSON TextAsset and builds a tree of DialogNodes. DialogNodes objects contain an ID, the text, a List of the answers the player can choose, and the id of the next node after. The answers are stored as a DialogAnswer struct that contains an ID, the text of the answer, and the next node if the player chooses that answer.

public class DialogNode

{

public int id;

public string text;

public Dictionary<int, DialogAnswer> answers;

public int next_node; //null = has answers, -1 = //sets up dialog node with answers

public DialogNode(int i, string t, Dictionary<int, DialogAnswer> a)

{

id = i;

text = t;

answers = new Dictionary<int, DialogAnswer>(a);

next_node = 0;

}

//sets up dialog node without answers

public DialogNode(int i, string t, int nn)

{

id = i;

text = t;

answers = null;

next_node = nn;

}

} public struct DialogAnswer

{

public int id;

public string text;

public int next_node; public DialogAnswer(int i, string t, int nn)

{

id = i;

text = t;

next_node = nn;

}

}

When the player is interacting with dialog and presses either “next” or an answer, The DialogManager calls the Step() method on the current DialogSystem. The Step() method can be called with no arguments (no answer is required) or with an int (id of the answer chose). The Step() method looks at the current DialogNode in the tree and changes it based on the next_node of that node or answer, and returns the next DialogNode to the DialogManager.

The DialogManager’s purpose is the bridge the gap between the UI and the DialogSystem. It takes the current DialogNode of the current DialogSystem and gives it to the UIManager so it can be displayed to the DialogPanel. It also listens to events from the DialogPanel that are fired when the player either presses next or an answer, and steps the DialogSystem.

When the UIManager receives an new DialogNode to display, it unwraps the data from the object and gives it to the DialogPanel piece-by-piece though methods in the DialogPanel component. The DialogPanel then sets the text of each object its responsible for — the text and the answers.

The DialogPanel is dumb — it only knows how to set the text of its Texts components, and sets the activity of the answer buttons based on how many answers there are. The UIManager is a little smarter and knows what a DialogNode is and how to unwrap the data inside of it. This is an an example of Model-View-Controller design. The DialogPanel is the view and is responsible for displaying to the screen, the UIManager and the DialogManager are the controllers of the view, and the DialogSystem is the model and holds data of the system.

The last two components of the Dialog component and the dialog JSON. The Dialog component sits on top of an dialog object. It holds the dialog JSON TextAsset and DialogSystem. When Awake() is invoked, the component initializes the DialogSystem with the TextAsset. When the player presses the Interact button next to the NPC, the InteractionManager takes the DialogSystem in the Dialog component and gives it to the DialogManager.

The JSONs that hold dialog infomation are pretty simple. They hold the infomation found in DialogNodes in JSON format. To unpack the JSONs I used JSONObject on the Unity Asset Store. This asset is awesome and I recommend it to anyone who needs to handle JSON data.

{

"dialogs_nodes" : [

{

"id": 0,

"text" : "This is some test dialog to test the dialog system.",

"answers" : null,

"next_node" : 1

},

{

"id": 1,

"text" : "Some just text to see if the dialog system will handle this.",

"answers" : null,

"next_node" : 2

}, ...

]

}

TLDR:

Dialog component creates DialogSystem from dialog JSON Player presses Interact button near Dialog object InteractionManager takes DialogSystem from Dialog object and turns on DialogManager. DialogManager gets current DialogNode from DialogSystem and gives it to UIManager. UIManager unwraps DialogNode and gives it to DialogPanel, which updates UI elements. Player presses next or an answer on the DialogPanel and an event is fired. DialogManager hears event and steps DialogSystem. Goto 4 until DialogSystem is reached end.

In the future I might the ability to invoke InteractionEvents or other event to the dialog system based on what path the player takes with dialog. This would allow for the dialog system to change the game world. For right now, This system allows for creating dialog and implementing relativity easily (except for writing JSONs).