Skip to content

Commit 6a5c5d2

Browse files
committed
More documentation; build scripts added.
1 parent d501635 commit 6a5c5d2

17 files changed

Lines changed: 916 additions & 8 deletions

Assets/Plugins/UnityTutorialSystem/Scripts/Aggregators/EventMessageAggregator.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ protected EventMessageAggregator()
8282
/// </summary>
8383
/// <param name="buffer">A receive buffer. If null, a new list will be created. If the list is non-empty, the list will be cleared.</param>
8484
/// <returns>The buffer provided or a new list if the buffer given is null.</returns>
85-
public abstract List<EventMessageState> ListEvents(List<EventMessageState> buffer);
85+
public abstract List<EventMessageState> ListEvents(List<EventMessageState> buffer = null);
8686

8787
/// <summary>
8888
/// Returns the current state of the matching process.

Assets/Plugins/UnityTutorialSystem/Scripts/Aggregators/EventMessageCounter.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public override void ResetMatch()
3535
}
3636

3737
/// <inheritdoc />
38-
public override List<EventMessageState> ListEvents(List<EventMessageState> buffer)
38+
public override List<EventMessageState> ListEvents(List<EventMessageState> buffer = null)
3939
{
4040
buffer = EnsureBufferValid(buffer, Messages.Count);
4141

@@ -49,6 +49,7 @@ public override List<EventMessageState> ListEvents(List<EventMessageState> buffe
4949
return buffer;
5050
}
5151

52+
/// <inheritdoc />
5253
protected override bool OnEventReceived(BasicEventStreamMessage received)
5354
{
5455
if (!messagePool.Contains(new UnityObjectWrapper<BasicEventStreamMessage>(received)))

Assets/Plugins/UnityTutorialSystem/Scripts/Aggregators/EventSequenceAggregator.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ public override void ResetMatch()
7979
}
8080

8181
/// <inheritdoc />
82-
public override List<EventMessageState> ListEvents(List<EventMessageState> buffer)
82+
public override List<EventMessageState> ListEvents(List<EventMessageState> buffer = null)
8383
{
8484
buffer = EnsureBufferValid(buffer, Messages.Count);
8585

Assets/Plugins/UnityTutorialSystem/Scripts/Aggregators/EventSetAggregator.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ protected override bool OnEventReceived(BasicEventStreamMessage messageReceived)
113113
}
114114

115115
/// <inheritdoc />
116-
public override List<EventMessageState> ListEvents(List<EventMessageState> buffer)
116+
public override List<EventMessageState> ListEvents(List<EventMessageState> buffer = null)
117117
{
118118
buffer = EnsureBufferValid(buffer, messages.Count);
119119

Assets/Plugins/UnityTutorialSystem/Scripts/Aggregators/IEventMessageAggregator.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,6 @@ public interface IEventMessageAggregator
4646
/// </summary>
4747
/// <param name="buffer">A receive buffer. If null, a new list will be created. If the list is non-empty, the list will be cleared.</param>
4848
/// <returns>The buffer provided or a new list if the buffer given is null.</returns>
49-
List<EventMessageState> ListEvents(List<EventMessageState> buffer);
49+
List<EventMessageState> ListEvents(List<EventMessageState> buffer = null);
5050
}
5151
}

Assets/Plugins/UnityTutorialSystem/Scripts/Aggregators/OneOfEventSetAggregator.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ protected override bool OnEventReceived(BasicEventStreamMessage messageReceived)
5757
}
5858

5959
/// <inheritdoc />
60-
public override List<EventMessageState> ListEvents(List<EventMessageState> buffer)
60+
public override List<EventMessageState> ListEvents(List<EventMessageState> buffer = null)
6161
{
6262
if (buffer == null)
6363
{

Assets/Plugins/UnityTutorialSystem/Scripts/Tutorial/TutorialEventTreeBinding.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ public class TutorialEventTreeBinding : MonoBehaviour
1313
[SerializeField] EventStreamTreeModelBuilder modelSource;
1414
[SerializeField] TutorialEventTreeView treeView;
1515

16-
void Awake()
16+
void Start()
1717
{
1818
treeView.Model = modelSource.Model;
1919
}

Assets/Plugins/UnityTutorialSystem/Scripts/UI/EventStreamTreeModelBuilder.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,17 @@ void Start()
8080
{
8181
started = true;
8282

83+
RebuildModel();
84+
}
85+
86+
/// <summary>
87+
/// Rebuilds the complete tree model. Use this only as last resort after
88+
/// you removed or added EventMessageAggregator instances in the scene.
89+
/// But try to keep all EventMessageAggregator definitions static if
90+
/// humanly possible, its better for performance and every one involved.
91+
/// </summary>
92+
public void RebuildModel()
93+
{
8394
var data = CollectTreeData();
8495
PrintTree(data);
8596
(rootNode, nodeMapper) = CreateTreeNodes(data);

Assets/Plugins/UnityTutorialSystem/Scripts/UI/Trees/TreePath.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public static TreePath<TNode> From<TNode>(params TNode[] n)
4646
/// the element at N+1 is a child of the tree node found at level N.
4747
/// </summary>
4848
/// <typeparam name="TNode">The TreeNode content type</typeparam>
49-
public class TreePath<TNode> : IReadOnlyList<TNode>, IEquatable<TreePath<TNode>>
49+
public sealed class TreePath<TNode> : IReadOnlyList<TNode>, IEquatable<TreePath<TNode>>
5050
{
5151
readonly TNode[] backend;
5252

Implementation.md

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
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

Comments
 (0)