Unite 2016 - MonoBehaviour Vs Scriptable Object by Richard Fine
Unite 2016 - MonoBehaviour Vs Scriptable Object by Richard Fine
-
Overthrowing the MonoBehaviour Tyranny in a Glorious
ScriptableObject Revolution
Richard Fine
Sustain Enginering @ Unity
2
THE MONOBEHAVIOUR TYRANY (Why is it bad)
3
THE MONOBEHAVIOUR TYRANY - the first problem(1)
4
THE MONOBEHAVIOUR TYRANY - the first problem(2)
5
THE MONOBEHAVIOUR TYRANY - the first problem(3)
● Shared
Information that you want to
using across multiple objects,
multiple instance.
● Non-shared
Information that you don't.
6
THE MONOBEHAVIOUR TYRANY - the first problem(3)
7
THE MONOBEHAVIOUR TYRANY - the second problem
8
THE MONOBEHAVIOUR TYRANY - the third problem
● Bad in collaboration
● Sub-file granularity (bad in collaboration)
When you have a monobehavior
in a file.
● Scene file
A ton of other objects
● Prefab file
transforms
components
potential information
9
THE MONOBEHAVIOUR TYRANY - the third problem (Why)
10
THE MONOBEHAVIOUR TYRANY - the third problem (Solution)
● Using ScriptableObject
11
THE MONOBEHAVIOUR TYRANY - the third problem (Solution)
12
THE MONOBEHAVIOUR TYRANY - the fourth problem
● Callback chaos
When you have severals monobehaviors on different objects,
you can’t make sure which “Start()” function run first.
● Accident-prone
13
Uninstantiated prefabs help a bit, but…
● Accident-prone (1)
It's quite easy to accidentally drag and drop
one of these things into the scene
and then suddenly you've got the shared object in the project
and also the instance in the scene
and that can become very confusing
14
Using prefabs might cause these problems
● Accident-prone (2)
It allows this sort of accumulation of mistakes
and things that it'd be better to just avoid having at the first place
15
Using prefabs might cause these problems
16
Using prefabs might cause these problems
17
C# statics are very DIY - 1
18
C# statics are very DIY - 2
19
C# statics are very DIY - 3
20
C# statics are very DIY - 4
● There's also no way to visualize that(static variable not showing by default) in the
inspector
● You have to write custom editors that will read your statics
and they(static variables) all get lost whenever you reload the domain
● So if you try and do support for recompiling in play mode all your static
data gets wiped out.
● You can do these things but you have to roll a lot of support around it yourself.
21
C# statics are very DIY - 5
22
ScriptableObject
23
ScriptableObject
24
ScriptableObject
25
PropertyDrawer
26
How ScriptableObject saves us pain
27
How ScriptableObject saves us pain
● sub-file granularity
● Total control over granularity
How scriptableobject live in files
You have total control over that
you can have a dot asset file in your project
that contains exactly one object - a ScriptableObject
you don't have to have extra game objects and transforms hanging around.
28
How ScriptableObject saves us pain
29
How ScriptableObject saves us pain
● sub-file granularity
● Total control over granularity
● Callback chaos
SO get basically no callbacks
SO have
1. OnEnable()
2. OnDisable()
3. OnDestory()
30
How ScriptableObject saves us pain
● Callback chaos
31
How to declare + reference ScriptableObject
32
Which part can use ScriptableObject
33
How to create ScriptableObject(Create instances in-memory with)
34
How to create ScriptableObject
35
How to create ScriptableObject
● Create instances in-memory with >... It will make an instance of the object bind
ScriptableObject.CreateInstance<MySO>()
it to a whole new asset file and then that
you're done simple as that.
● Bind them to .asset files with
AssetDatabase.CreateAsset() or and you can customize the file name and
AssetDatabase.AddObjectToAsset() the menu item name and so on
with parameters on the attribute
● Use [CraeteAssetMenu] to automate this
but simple as adding that one string
This makes it super easy
and you've got instant kind of custom asset
you stick this on your scriptableObject class
support in your project.
and then it will show up in “ Assets ” .>. “ Create”>...
36
ScriptableObject Callbacks (1)
● OnEnable()
we get this when the scripableobject is first created
before create instance returns or
we get it when the object is loaded
we also get it after a domain reload
when script to recompile in the editor at the end of that process when we finish reloading
everything any scriptableobjects that exist in memory get OnEnable( ) to kind of warm
them back up again
● OnDisable()
37
ScriptableObject Callbacks (2)
● OnEnable()
● OnDisable()
we get OnDisable() which is called when a scriptobject
is being destroyed or
before it's being destroyed and also
before we are shutting down the app domain for assembly reload
● OnDestory()
which happens when it's destroyed
and that's it that's all you actually get on a scriptableobjet by default.
38
ScriptableObject Callbacks (3)
● To sum up :
There's not a lot to learn and you can see that
most of the these things that might do on their own
is going to happen around
1. startup time
2. reloading the app domains and so on
3. rest of the time per frame bases frame to frame they just don't do anything
unless you tell them, you can add your own functions to a scriptableobject
just as you can add your own functions to a monobehavior
(No Update() in scriptableObject )
39
ScriptableObject Life Cycle (1)
● Let's look a little bit about the life cycle of these things.
I've shown you how they are created what about after that
just pretty much the same as any other asset.
You know once you've made a texture or a material or something like that
the life cycle is the same as those.
As long as you're as long as you still have it ,you're continuing to use it,
it's going to stay alive.
The point at which it usually will go away is when you call
“Resources.UnloadUnusedAssets() ”
40
ScriptableObject Life Cycle (2)
● Resources.UnloadUnusedAssets()
this is where we're going to scan through
all of your monobehaviors and objects in the scene
and see which things are referencing
which other things don't work out.
This scriptableobject that you created it's no longer being referenced by anything
so we're going to get rid of it
you have some control over that
you can stop that from happening using hide flags and
we'll talk a little bit about a pattern that you can create with
41
A note on Destory() / DestoryImmediate() [1]
42
A note on Destory() / DestoryImmediate() [2]
Ma
na
ge
Na ds
tiv ide
es
ide
C++ part
instance of standard engine
object for SOs
48
Patterns
49
Patterns(1)
50
Patterns(2)
51
Patterns(3) Data Objects and Tables
52
Patterns(3) Data Objects and Tables
53
Patterns(3) Data Objects and Tables
54
Patterns(3) Data Objects and Tables Less overhead + smoother UX than a “real” database(1)
One of the approaches people could use to use this as well is to use
like a real database - to go and use sequel Lite or something like that.
That's a valid approach but using scriptableobject instead it's got a bit
less overhead than that you know you don't have to deal with kind of
setting up the sequel Lite database and loading it and kind of getting
it all in place.
55
Patterns(3) Data Objects and Tables Less overhead + smoother UX than a “real” database(2)
Smoother UX as well you know your designers your artists they can edit
all this stuff and exactly the same ways to edit any other object in unity
and it's using familiar UX paradigms it's you know completely standard. And
referencing these things works very smoothly as well.
With a standard database you have to get some way of identifying kind of
which row in the database to work from.
Just drag and drop scriptableobjects then you can reference them.
56
Extendable Enums
Empty SOs bound to asset files
Check equality use as dictionary key
This is very similar to what you do
with an enum.
In principle, when you have any
enum, you declare your different
values.
You're not supposed to do
arithmetic on them.
57
Enums
The only things you can really do with enum values
are compare them and say is this equal to this enum value I'm expecting or
is it something else.
with these(SOs).
But the key advantage here is that (by using SO)your designers can
add new values to the enum without changing any code.
58
Extendable Enums
Netural progression to data objects
59
Extendable Enums Example(1)
Example:
60
Extendable Enums Example(1)
>> So I could add that in one place, and then just go through all
these damage types and just check the bull on or off instead of
heavy to have a separate lookup somewhere from each of these
to corresponding bull values.
It's usually faster to kind of put the data on these things directly.
You can't really add extra data to each enum entry very easily.
61
Extendable Enums
Empty SOs bound to asset files
Supports : null
>>
62
Supports : null
>> With an enum you might need to
and that ends out working really nicely for a lot of situations.
63
Dual Serialisation
● ScriptableObjects are supported by JasonUtility
● Mix in-bult SOs with deserialised JSON SOs.
Using SO & JSON Example - Level creator(user can edit level in game) [1]
I think you have to use FromJsonOverwrite() rather than FromJson() but you
can make an object and then use over write to fill it with data that you got from
Json.
What this means is that having built a system that works with
ScriptableObjects you can mix objects that you created a design time that you
saved into asset files in your project with objects that you've created by
deserializing Json.
What might you do with that a pretty good example would be something like :
66
Dual Serialisation - User Made Level
Using SO & JSON Example - Level creator(user can edit level in game) [2]
For example I have a simple platformer.
I've got some platforms, enemies , coins to collect and I've got to play a
start position. The game is to avoid the enemies and once you've got all
the coins then the level is done. You go to the next one now.
I don't want to ship my game with no levels.
I want to make a bunch of levels in the editor ideally using the scene
view using the standard tools I'm used to.
67
Dual Serialisation - User Made Level
Using SO & JSON Example - Level creator(user can edit level in game) [3]
So I write a very simple editor script to collect up all the things in the
scene and pack them into a ScriptableObject for me, so I can have a
bunch of object assets just shatter my project(Divided into modules)
that I'm going to ship in the game, built in.
But I also want to ship a level editor. I just want to ship something on
device that you can go to level edit mode, drag drop replay your
platforms in, serialize the Json and share it online.
68
Dual Serialisation Example - User Made Level
// Load built-in level from an AssetBundle
level = lvlsBundle.LoadAsset<LevelLayout>(“lvl1.asset”);
69
Dual Serialisation Example - User Made Level
// Load level from JSON
level = CreateInstance<LevelLayout>();
var json = File.ReadAllText(“customlevel.json”);
JsonUtility.FromJsonOverWrite(json, level);
I make an empty level layout object.
I read all my json from a file somewhere maybe downloaded from a server.
After that i can use that object i don't have to care whether it's a level that i ship built in came from asset bundle or level
came from JSON, over even mix of two(Load from AssetBundle and JSON ,mix use)
70
Dual Serialisation
// Load built-in level from an AssetBundle
level = lvlsBundle.LoadAsset<LevelLayout>(“lvl1.asset”);
71
Reload-Proof Singletons [1]
C# part
Let's look at some reload proof
Singletons.
C# part
I've got my ScriptableObject with a C#
part and C++part.
C# part
>> If I reload domain in the editor
If I recompile during play mode,
C++ part
Data 75
Reload-Proof Singletons example
class MySingleton : ScriptableObject{
private static MySingleton _inst;
}
We've got our singleton class deriving from ScriptableObject.
We have our static field that points to the instanc of
singleton that we want to work with.
I’ve made it private because what we’re going to do is we’re
gonna acces it via a property and the property is going to
make sure that all of this stuff is kind of implemented.
So we have the actual instance property bear with a getter.
76
Reload-Proof Singletons example
class MySingleton : ScriptableObject{
private static MySingleton _inst;
public static MySingleton Instance{get{
if(!_inst)
_inst = Resources.FindObjectOfType<MySIngleton>();
if(!_inst)
_inst = CreateInstance<MySingleton>();
}}
}
First, if we don't already have that instance field populated then we'll go and try
and find it(an instance of our singleton type).
If that fails then we'll make one.
77
Reload-Proof Singletons example
class MySingleton : ScriptableObject{
private static MySingleton _inst;
public static MySingleton Instance{get{
if(!_inst)
_inst = Resources.FindObjectOfType<MySIngleton>();
if(!_inst)
_inst = CreateInstance<MySingleton>();
Sometimes, we didn't have to do it because we want to doing this by laoding an
asset or ready-made for an asset bundle.
We'd also want to set the hide flags on it to make sure it shouldn't be picked up
by the garbage collector.
We want this thing live for the whole duration of the session work in Unity.
78
Reload-Proof Singletons example
class MySingleton : ScriptableObject{
private static MySingleton _inst;
public static MySingleton Instance{get{
if(!_inst)
_inst = Resources.FindObjectOfType<MySIngleton>();
if(!_inst)
_inst = CreateInstance<MySingleton>();
return _inst;
))
}
Once that's done you know,
either we found the existing one or
we made one at that point
it should exist we should have one
so we can just return it at that point
79
Reload-Proof Singletons Conclusion
This is a super simple pattern that will get you these
objects that will survive a domain reload that you can
keep just referring to.
And then after the main reload you go to access the
instance and it's as if it never went away.
80
Delegate objects
Another pattern that might be familiar to anyone who's
done Mac OS or iOS development is the use of delegates.
● ScriptableObjects can contain methods, not only data
This means that you can use them as sort of pluggable
implementation or a strategy pattern .
81
Delegate objects
● ScriptableObjects can contain methods, not only data
● Pass in scene objects to work on
Because if you're storing a scriptable object in an asset it
can't reference things in the scene it has receive those
dynamically.
82
Delegate objects
● ScriptableObjects can contain methods, not only data
● Pass in scene objects to work on
This is true again with you know with anything else you store in the project
a prefab that that you have in a project it can't reference things in a scene.
You can't guarantee that scene is loaded it has to be shared so you can
have a scriptableobject that operates on things in scenes.
As long as they're passed in on the fly.
So the same as implements of strategy pattern.
83
Delegate objects
● ScriptableObjects can contain methods, not only data
● Pass in scene objects to work on
● Strategy pattern
84
Delegate objects example
abstract class PowerupEffect{
public abstract void Apply(GameObject collector);
}
So an example of this suppose I have a game bullet-hell scrolling
shooter and I'm picking up power-ups as I as I fly through.
I have a abstarct class for my powerup effect
that should have been scriptable object.
85
Abstract class
abstract class PowerupEffect{
public abstract void Apply(GameObject collector);
}
It(Abstract class) doesn't do anything and it has abstract void apply
method.
The idea is that whenever anything picks up a powerup it's going to call
Apply()
86
Delegate objects example [1]
abstract class PowerupEffect{
public abstract void Apply(GameObject collector);
}
public void OnTriggerEnter(GameObject toucher)
{
Destory(gameobject);
powerupEffect.Apply(toucher);
}
87
Delegate objects example [2]
Look at an actual example thenhow we would derive from that.
Here's something like a like a health powerup.
So we're deriving from powerup effect.
We're implementing this apply method and we have a tunable
amount value so I can have two or three different health
powerup effects in my project.
88
Delegate objects example [3]
You know a small buff, big buff, ultra buff, and oh and even
something that removes health as well.
I could just have a negative amount value so I pass in the
player object or the NPC or let me picked up the powerup as
the collector parameter and I been inside the scriptable
object I'm getting the health component I'm adjusting the
value accordingly.
89
Delegate objects example [4]
So I'm delegating the behavior of what should actually
happen in that powerup after the scriptableobject on the
side.
Again, you could do this using a prefab as well,
But this(delagate in SO) lets you separate out the actual
gameplay consequences of this powerup from all the other
details like collider, visual aspects and so on.
90
Delegate objects example- code - Pull out Audio properties [5]
public class HealthBuff : PowerupEffect{
public float amount;
public override void Apply(GameObject collector){
collector.GetComponent<Health>().value += amount;
}
}
A lot of the value in all of this stuff is in separation of concerns.
Like Pull out Audio properties.
91
Delegate objects example - Pull out Audio properties [6]
I want to pull out the audio properties of the gameobjects.
People please to be able to keep them somewhere
where my designer or my audio guy is just going to work on those
without having to kind of go and trawl(find) through this prefab and
find one component somewhere in the middle of it that they want to
work on.
92
Delegate objects example- code - InstaKill [7]
public class InstaKill : PowerupEffect{
public GameObject explosionFX;
public override void Apply(GameObject collector){
Instantiate(explosionFX, collector.position, collector.rotation;
Destory(collector);
}
}
Here's a second one, insta-kill.
It's kind of a harsh powerup.
You get this one and will make an explosion and will destroy whatever picks it up.
93
Delegate objects example - Pull out Audio properties [8]
InstaKill (A collectable object but will destroy whatever picks it up. )
So yeah you don't want to get this one but again you know we can
have a whole bunch of different instantKill of different explosions.
You want a nice burst, fireball etc.
It's the same code, same object type.
Just different instances configured in different ways, and then plugged
into different power-ups.
94
Delegate objects example- code - HealthBuffn & InstaKill [9]
public class HealthBuff : PowerupEffect{
public float amount;
public override void Apply(GameObject collector){
collector.GetComponent<Health>().value += amount;
}
}
public class InstaKill : PowerupEffect{
public GameObject explosionFX;
public override void Apply(GameObject collector){
Instantiate(explosionFX, collector.position, collector.rotation;
Destory(collector);
}
}
95
Practice
96
TANKS! Demo
● Unite 2015 Training Day project
● Audio events
● Destructable buildings
● Pluggable AIs
● Configurable game settings with load/save
97
TANKS! Demo
It’s a pretty simple game.
You drive a tank around, fire shells to the other player and try to be the last
man standing.
There's a few things I'd like to do to that project.
Firstly I'm going to see if I can improve the audio a little bit because
they've got a few audio clips playing repeatedly.
But there's some stuff we can do to very quickly
actually make the audio a little bit more impressive.
98
TANKS! Demo
Second thing is I'm going to add destructible buildings,
because who doesn't love destructible buildings.
99