sigtrapgames / SmartData

A designer-friendly, editor-driven Unity3D framework for connecting data and events.
MIT License
271 stars 14 forks source link

SmartData for Unity

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.

Game Architecture with Scriptable Objects

UPDATE NOTE

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.

INSTALLATION

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.

What is SmartData?

Firstly, SmartData is in beta. With that out of the way, SmartData is a framework that uses ScriptableObjects 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!

Traditional method A - Singletons

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.


Traditional method B - Find reference at runtime

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.


SmartData method

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:

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.

SmartEvents

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.

SmartComponents

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.

Imgur

Debugging

SmartData includes a visual debugger graph which shows all the current connections between SmartRefs (FloatReader, FloatWriter etc) and SmartObjects (FloatVar etc)[2]. At runtime, it will also animate to show updates and events.

SmartObjects 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.

Advanced Features

The above is just scratching the surface. SmartData has many other features which will gradually be documented in the wiki.

What is SmartData not?

What's next?

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 FloatVars and no way to automatically stop listening to it.

Notes

Credits

Developed and maintained by Luke Thompson (@six_ways | github)
SmartGraph developed by Luca Mefisto (@LucaMefisto | github) based on his UnityEventVisualizer

Acknowledgements

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)

Footnotes

[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