|
| 1 | +# Implementation notes |
| 2 | + |
| 3 | +## Prerequisites |
| 4 | + |
| 5 | +This project assumes that the target project (the project |
| 6 | +that will be using the ``UnityTutorialSystem`` library) exposes |
| 7 | +relevant ``UnityEvent``s or can be modified to invoke the event |
| 8 | +triggers on the UnityTutorialSystem when the relevant |
| 9 | +program state has changed. |
| 10 | + |
| 11 | +The ``UnityTutorialSystem`` works best if your project has a |
| 12 | +clear set of states or conditions that can act as triggers |
| 13 | +for tutorial event transitions. Natural examples of these |
| 14 | +states or conditions are tasks the player has completed or |
| 15 | +events that have been triggered in the game world (ie |
| 16 | +an enemy attacks, or supplies run low, etc). |
| 17 | + |
| 18 | +## Defining Events |
| 19 | + |
| 20 | +The ``UnityTutorialSystem`` is driven by a set of event streams. |
| 21 | +The ``BasicEventStream`` class is a ``ScriptableObject`` that |
| 22 | +manages a predefined set of ``BasicEventStreamMessages``. |
| 23 | +You can sub-class the ``BasicEventStream`` to generate more |
| 24 | +specialised ``BasicEventStreamMessage`` types. As every |
| 25 | +``BasicEventStreamMessage`` is an ``ScriptableObject`` itself you |
| 26 | +can define additional properties and methods on these |
| 27 | +objects if necessary. (By using a scriptable object as |
| 28 | +basis of this implementation you can also reference those |
| 29 | +events in prefabs and other scriptable object assets; and |
| 30 | +it completely avoids the use of Singletons or other scene |
| 31 | +based crutches to manage the flow of events.) |
| 32 | + |
| 33 | +Each ``BasicEventStreamMessage`` carries a reference to its |
| 34 | +declaring BasicEventStream. This means you can trigger a |
| 35 | +message by simply calling '``BasicEventStreamMessage#Publish``' |
| 36 | +at any time. |
| 37 | + |
| 38 | +``BasicEventStream`` accepts event listeners that will be |
| 39 | +called whenever a message managed by this stream has been |
| 40 | +published. As such the ``BasicEventStream`` acts as a simple |
| 41 | +event bus for its defined messages. You can have multiple |
| 42 | +event streams in your project and I recommend that you |
| 43 | +create one ``BasicEventStream`` instance for each distinct |
| 44 | +sequence of tasks you want to track. |
| 45 | + |
| 46 | +The stream can handle a limited amount of reentrant events. |
| 47 | +This means that a message that is published via the stream |
| 48 | +can generate new events that are published via the same |
| 49 | +stream, and all of those messages will be sent out to all |
| 50 | +listeners. |
| 51 | + |
| 52 | +To avoid infinite loops from configuration errors where |
| 53 | +a message sends out new messages in an infinite loop, the |
| 54 | +stream will stop processing after 250 messages have been |
| 55 | +processed in the current frame. |
| 56 | + |
| 57 | +The ``UnityTutorialSystem`` provides a ``PublishStreamEvent`` |
| 58 | +mono-behaviour that can be used to publish messages |
| 59 | +in response to other ``UnityEvent`` invokations. |
| 60 | + |
| 61 | +``BasicEventStreamMessage`` objects are defined in the Unity-Editor |
| 62 | +by editing the ``BasicEventStream`` itself. Each message entry |
| 63 | +will generate a new ``BasicEventStreamMessage`` object as |
| 64 | +sub-asset of the ``BasicEventStream``. Each stream will only |
| 65 | +process messages that it defined itself. However |
| 66 | +``EventStreamMessageAggregator``s can combine messages from many |
| 67 | +``BasicEventStream`` instances into higher level tracking events. |
| 68 | + |
| 69 | +## Event Message Aggregators |
| 70 | + |
| 71 | +An event message aggregator is a component that analyses |
| 72 | +the events it has received to match predefined sequences |
| 73 | +of events. |
| 74 | + |
| 75 | +Each aggregator maintains a list of ``BasicEventStreamMessage`` |
| 76 | +objects it expects to see. During start up, the stream |
| 77 | +will attempt to register itself with the ``BasicEventStream`` |
| 78 | +that publishes those messages. |
| 79 | + |
| 80 | +The ``EventMessageAggregators`` implemented here are stateful |
| 81 | +trackers that attempt to maintain only minimal state during |
| 82 | +the matching process. Each time a new event is received, |
| 83 | +the ``EventMessageAggregator`` will update its internal state |
| 84 | +and will fire events to notify any listener of its eventual |
| 85 | +state change. |
| 86 | + |
| 87 | +Due to the structure of the matching done the ``EventMessageAggregator`` |
| 88 | +can tell which ``BasicEventStreamMessage`` would need to be |
| 89 | +received next to move the state closer to a succesful |
| 90 | +match. (This is very similar to a stream based pattern matcher |
| 91 | +or regular expression matching.) Internally the ``EventMessageAggregator`` |
| 92 | +implementations use a state machine that can be in one of |
| 93 | +three states: Waiting for data, success, or failure. |
| 94 | + |
| 95 | +The ``EventMessageAggregator`` can provide detailed information |
| 96 | +about its internal state, including which of the messages |
| 97 | +have been seen, which will be (hopefully) seen next, and which |
| 98 | +are not yet matched. |
| 99 | + |
| 100 | +All of this is implemented via the ``EventMessageAggregator#ListEvents`` |
| 101 | +method. This method accepts a buffer of ``EventMessageState`` |
| 102 | +data objects so that all calls can be completely non-allocating. |
| 103 | + |
| 104 | +This information will be used by both the predictor components |
| 105 | +and the ``TreeModelBuilder``. |
| 106 | + |
| 107 | +When an ``EventMessageAggregator`` successfully matched all |
| 108 | +expected events, it will fire an internal ``success`` event. |
| 109 | +You can use an additional ``EventMessageAggregatorStatePublisher`` |
| 110 | +to publish a ``BasicEventStreamMessage`` when that happens. |
| 111 | +The ``EventStreamTreeModelBuilder`` will interpret the fact that |
| 112 | +a success of an aggregator caused an message to be published |
| 113 | +as a hint that this ``EventMessageAggregator`` is a dependent |
| 114 | +aggregator of any ``EventMessageAggregator``that waits for |
| 115 | +that message. |
| 116 | + |
| 117 | + |
| 118 | +## Predictors |
| 119 | + |
| 120 | +The whole point of this library is to point players towards the |
| 121 | +next goal in tutorial and other guided sequences. This is |
| 122 | +achieved with the help of the ``predictor`` components. |
| 123 | + |
| 124 | +This library ships with two predictor components: |
| 125 | + |
| 126 | +* NextEventSelector |
| 127 | + |
| 128 | + This is simple class monitors an set of ``EventMessageAggregator`` |
| 129 | + instances to wait for a notification that the aggregator expects |
| 130 | + the given ``BasicEventStreamMessage`` as its next received message. |
| 131 | + |
| 132 | + When that happens this NextEventSelector fires a UnityEvent |
| 133 | + that you can use to enable or disable visual indicators or |
| 134 | + to trigger any other action to guide the player to the next goal |
| 135 | + (or maybe to spawn enemies to prevent the player to get there). |
| 136 | + |
| 137 | +* NextEventAggregationActivator |
| 138 | + |
| 139 | + This is specialized version of the NextEventSelector that |
| 140 | + simplifies the wiring up of ``EventMessageAggregator`` |
| 141 | + hierarchies. It is placed next to an |
| 142 | + ``EventMessageAggregatorStatePublisher`` and will activate |
| 143 | + or deactivate the associated ``EventMessageAggregator`` when |
| 144 | + its ``success`` message is expected to be received next. |
| 145 | + |
| 146 | +## User Interface |
| 147 | + |
| 148 | +The ``UnityTutorialSystem`` library comes with a TreeView component |
| 149 | +that can render all ``BasicEventStreamMessage``s known to the |
| 150 | +aggregators, their hierarchy and relationship between each other |
| 151 | +and their current tracking state. |
| 152 | + |
| 153 | +The UI package contains the necessary code to render a TreeView |
| 154 | +(or a list if you set the 'Indent' property to zero) of all |
| 155 | +events using Unity's inbuilt UI system. |
| 156 | + |
| 157 | +The ``EventStreamTreeModelBuilder`` is responsible for monitoring |
| 158 | +all ``EventMessageAggregator`` instances in a scene and produces |
| 159 | +a TreeModel of ``EventStreamTreeModelData`` objects that reflects |
| 160 | +the current state of the tracking. The model is updated as soon |
| 161 | +as any of the aggregators reports a new state change. |
| 162 | + |
| 163 | +Unity does not like generics in serialized objects in a scene, |
| 164 | +so to use the TreeView with your own data, you have to create |
| 165 | +a non-generic sub-class of the ``TreeView<TData>`` class. |
| 166 | +The ``TutorialEventTreeView`` is such an example. |
| 167 | + |
| 168 | +The ``EventStreamTreeModelBuilder`` requires a list of |
| 169 | +``EventMessageAggregator`` instances to work. If you allow it, |
| 170 | +it can fetch all ``EventMessageAggregator`` instances from the |
| 171 | +active scene, which is usually what you'd want anyway. |
| 172 | + |
| 173 | +The Builder then builds up a static model of the events |
| 174 | +processed by each of the aggregators and the relationships between |
| 175 | +the aggregators. (Note: This happens only once, so any change |
| 176 | +you might make to the set of aggregators afterwards, either |
| 177 | +by adding more events or new aggregators, will NOT be reflected |
| 178 | +in the tree model. Always define your event messages and |
| 179 | +aggregations so that all events are available when the |
| 180 | +scene starts. (And if you really MUST make changes, call |
| 181 | +``EventStreamTreeModelBuilder#RebuildModel`` afterwards.) |
| 182 | + |
| 183 | +## Tutorial Events |
| 184 | + |
| 185 | +So far, all messages were pretty much generic. This library's |
| 186 | +primary purpose is to make it easier to write tutorial levels |
| 187 | +for games. However, no player wants to see 'Kill the Orc' after |
| 188 | +they already slain the green humanoid. |
| 189 | + |
| 190 | +The ``Tutorial`` package contains a specialised ``TutorialEventStream`` |
| 191 | +that contains ``TutorialEventMessage`` objects. It also nicely |
| 192 | +demonstrates how to use customized messages in this library. |
| 193 | +A ``TutorialEventMessage`` has three description texts for the |
| 194 | +event - one for when the task is not done yey ("Go kill that orc"), |
| 195 | +one for when the task was a success ("You've slain the orc!") |
| 196 | +and one for when the task failed ("The orc has slain you!"). |
| 197 | + |
| 198 | +A specialised TreeView (because Unity really does not like generics |
| 199 | +and refuses to save references of such fields) offers some |
| 200 | +additional logic to possibly hide completed tasks. |
| 201 | + |
| 202 | +The ``TutorialTreeItemRenderer`` is responsible for updating the |
| 203 | +various UI components with the data from the ``EventStreamTreeModelData`` |
| 204 | +and its contained ``TutorialEventMessage`` with its three different |
| 205 | +messages depending on what state the message is in. |
| 206 | + |
| 207 | +To connect the ``TutorialEventTreeView`` with the |
| 208 | +``EventStreamTreeModelBuilder`` that supplies the data that is |
| 209 | +displayed, we have to utilize a ``TutorialEventTreeBinding`` |
| 210 | +MonoBehaviour. This class simply takes the model produced by |
| 211 | +the ``EventStreamTreeModelBuilder`` and registers it in |
| 212 | +the TreeView. Multiple TreeViews can share the same model. |
| 213 | + |
0 commit comments