A designer-friendly, editor-driven framework for connecting data and events.
Need a flexible way to connect everything in your game without singletons, hard-coding or overly-complex architecture? SmartData might be what you're looking for. It is based on Ryan Hipple's (twitter | github) amazing talk from Unite Austin 2017, which you are strongly recommended to watch before proceeding.
The type generator / regenerator has changed slightly. Please make sure you're backed up, update to the latest commit, use Create > SmartData > Generate Types > Regenerate... and double check the changes made. This update is to support moving generated files around - SmartData will from now on regenerate files in-place.
- Grab SmartData.unitypackage from the Releases page for everything you need!
- OR, to use the git repository, you'll need our events implementation, Relay[1]. github.com/SixWays/Relay
- Requires Unity 2018.2 or above.
If you have issues with anything not discussed in this readme, please let us know - but check the wiki first!
SmartData also has a Unity forum thread here for further discussion.
Firstly, SmartData is in beta. With that out of the way, SmartData is a framework that uses ScriptableObject
s to pass data around a game at the global level. Let's take a common example - showing the player's HP on the HUD. Let's assume the Player and the HUD are prefabs, dynamically instantiated at runtime, rather than placing them in each scene at edit-time and manually dragging references.
Note that the following pros and cons are entirely subjective. And there are many more methods we could use!
Our Player
class is a singleton. We instantiate the Player prefab, then the HUD prefab, and the HUD uses Player.instance.hp
.
Pros: Easy to code.
Cons: Encourages spaghetti code. Rigid coupling between classes. HUD depends on the Player instance existing. Not exposed to designers.
We don't like singletons, so instead the HUD searches for the Player using GameObject.Find
in Awake()
.
Pros: Coupling is slightly less rigid.
Cons: Player must be called the right thing. HUD depends on the Player instance existing. Not exposed to designers.
We create a FloatVar
asset called PlayerHp. In Player
we declare a FloatWriter
and in HUD
a FloatReader
.
In-editor, each script now has a field in which we can drag-and-drop our PlayerHp asset. Now they both refer to the same data.
HUD
now has three ways to get the player's health:
- Get
playerHp.value
in an update loop - the PlayerHp asset value will stay up-to-date withPlayer
- Use the exposed
UnityEvent
in the editor and check Auto Listen - the event will fire whenPlayer
updates the value - Use
playerHp.BindListener()
to register for the event in code whenPlayer
updates the value
Pros: No code coupling. HUD can exist without Player (PlayerHp asset always exists). Exposed to designers.
Cons: More complex.
While SmartData takes a little more setup, it's far more flexible, powerful, safe and designer-friendly - and after a couple of uses, that setup is easy.
Now imagine a real codebase. You have dozens of singletons, your code is spaghetti, you need a coder every time a designer wants to get any of that data, and unless you instantiate everything in your scene - in the right order! - your game explodes, making low-level testing and iteration hellish.
With SmartData, global data is just a set of assets. Designers can access it at will with a set of Components, modify its behaviour and select at edit-time what references point to what data. Coders still retain control of their code, specifying read-only or read/write access from a given reference, with attributes to control what options a designer sees in the editor.
SmartData supports any underlying data type via its code generator GUI. It comes with a selection of basic SmartTypes pre-built such as float
and string
amongst others.
Note that any single SmartType only has one underlying data type. The multi-type editor above is simply for generating multiple SmartTypes at once for convenience.
EventVars
, accessible via EventListener
and EventDispatcher
references, simply raise events without any data payload. These are particularly useful for game-level events, such as starting, pausing the game, a player dying etc.
Designers can use components which give code-free access to SmartObjects. Read/Listen components give a list of SmartRefs with UnityEvents. Write/Dispatch components give one SmartRef and the ability to set values and/or dispatch events from UnityEvents or other third-party design tools.
SmartData includes a visual debugger graph which shows all the current connections between SmartRef
s (FloatReader
, FloatWriter
etc) and SmartObject
s (FloatVar
etc)[2]. At runtime, it will also animate to show updates and events.
SmartObject
s will also show a list of connections in the inspector, and at runtime a list of objects listening to events from code.
NOTE: Plain references to SmartObjects will not show up in the node graph. You must use SmartRefs - e.g. FloatReader
, FloatWriter
.
The above is just scratching the surface. SmartData has many other features which will gradually be documented in the wiki.
- SmartSet - List of data with OnAdd and OnRemove events
- e.g. a list of all AIs - AIs add/remove themselves and the AI manager (and anything else) listens.
- SmartMulti - List of SmartVars created on demand
- e.g. for local multiplayer games - PlayerHp[0-3]
- Decorators - Like components for SmartObject assets, these modify behaviour
- e.g. for clamped/ranged values or even automatic network replication
- Code generation templates
- Add your own templates to the Generate Types editor to generate custom code for each type.
- A visual scripting language / editor
- A coding-free solution
- Particularly good at runtime-instantiated SmartObjects - yet (see What's Next)
- The answer to all your problems[3]
The first focus will be more documentation and examples, since there's a lot here.
The next major planned feature is SmartGroups - a comprehensive solution for dynamically instantiating SmartObjects. Currently SmartData is all about edit-time data - make assets, link them by hand, profit. At runtime, you can create SmartObjects in code, and link to them in code, and destroy them in code, and unlink from them in code. You may have noticed a pattern.
For example, if you want an OnHit EventVar
for every AI spawned, there's no easy edit-time way to specify what will be interested in these events, or how they will be indexed. SmartGroups aim to fix that. More later.
Note that SmartMultis are not intended to supply this functionality - they're a convenience for cases when there will be multiple long-lived instances of something. For example, with local multiplayer, your HUD elements can all reference a single "PlayerHp" FloatMulti
asset by index. There's no good way to destroy one of the underlying FloatVar
s and no way to automatically stop listening to it.
- Namespaces are generated for each underlying type. For example,
SmartData.SmartFloat.FloatReader
. - As mentioned, you'll need
Relay
in your project to useSmartData
. - If you use Odin Inspector, you must disable it for the SmartData namespace.
- Userspace code (generated classes etc) is nice and neat. The underlying hierarchy though is crazy complex, largely due to Unity's serialization quirks. It looks horrible, but it's all there for a reason. Sorry in advance.
Developed and maintained by Luke Thompson (@six_ways | github)
SmartGraph developed by Luca Mefisto (@LucaMefisto | github) based on his UnityEventVisualizer
SmartData, as previously mentioned, owes its existence and inspiration to Ryan Hipple (see above)
Development assistance, feedback and ideas provided by Eric Provencher (@prvncher) and Dustin Chertoff (@dbchertoff)
[1] Relay is not linked as a submodule for three main reasons. Firstly, you're fairly likely to be using SmartData as a submodule itself, and sub-submodules tend to confuse lots of git clients. Secondly, if you're already using Relay (you should be, but we're biased) it could get messy. Finally, Relay is pretty stable now so you should be able to grab a zip and forget about it.
[2] The graph is not editable!
[3] but only because nothing ever can be