The Power of Scriptable Objects as Middle-Men
Summary
TLDRThis video explores the use of Scriptable Objects in Unity for event-based messaging, a technique that decouples code and reduces dependencies between components. The speaker illustrates how traditional health management in games can be improved by using Scriptable Objects, which act as middlemen for communication between scripts. They demonstrate how this approach simplifies game architecture, enhances flexibility, and facilitates easier testing and debugging. The video references Ryan Hipple's talk on game architecture with Scriptable Objects and highlights the benefits of using Unity Events for managing game data across scenes, ultimately leading to a more scalable and maintainable project.
Takeaways
- 📦 Scriptable Objects are data containers that persist outside the scene and maintain their values across scene reloads unless programmed otherwise, making them ideal for storing game data like health and enemy attack stats.
- 🔗 Scriptable Objects can be used for event-based messaging, acting as intermediaries between different scripts or components to decouple code and reduce dependencies, leading to easier testing, debugging, and flexibility in game design.
- 👨🏫 The video recommends watching a previous video for an introduction to Scriptable Objects and their benefits, and also suggests a Unity Austin 2017 talk by Ryan Hipple for in-depth insights on using Scriptable Objects for game architecture.
- 🎮 The video discusses the use of Scriptable Objects in managing player health, demonstrating how they can be used to avoid direct dependencies between the player and UI elements like health sliders.
- 💡 The concept of the 'single responsibility principle' is highlighted, emphasizing that each module, class, or function should handle only one part of the program's functionality to minimize dependencies.
- 🔄 The issues with using Singletons for managing health are discussed, including the potential for creating global dependencies and difficulties in testing due to static instances.
- 🔗 The video introduces the idea of using Scriptable Objects to maintain health data across different scenes without relying on Singletons or PlayerPrefs, promoting a more scalable and manageable approach.
- 🛠️ The implementation of a Scriptable Object for health management is outlined, detailing how it can be used to send events and communicate changes to subscribed scripts, such as a UI manager.
- 🎯 The benefits of using Scriptable Objects over MonoBehaviour for event communication are pointed out, noting that Scriptable Objects do not require instantiation in the scene and thus are more memory efficient.
- 🧩 The video touches on the concept of dependency injection, drawing parallels with Scriptable Objects in terms of decoupling code, but notes that Scriptable Objects are more designer-friendly due to their visual nature in the Unity Inspector.
- 🎁 The video concludes with a call to action for viewers to support the content creator and the sponsor, Mental Checkpoint, and thanks the patrons for their support, highlighting the importance of community engagement.
Q & A
What are scriptable objects in Unity and why are they useful for storing data?
-Scriptable objects in Unity are data containers that exist outside the scene and maintain their state even when the scene reloads. They are useful for storing data such as health, enemy attack values, etc., because they provide a persistent way to manage game data without being tied to specific scene instances.
Can you explain the concept of event-based messaging with scriptable objects?
-Event-based messaging with scriptable objects involves using these objects as intermediaries or middlemen between different scripts or components. This approach helps decouple the code, reducing dependencies and making the game architecture more flexible, easier to test, and simpler to debug.
What is the single responsibility principle in programming and how does it relate to game architecture?
-The single responsibility principle states that every module, class, or function in a program should have responsibility over a single part of the program's functionality and should encapsulate that part. This principle helps prevent unnecessary dependencies between different parts of a program, making it easier to manage, test, and maintain, which is particularly important in game architecture.
Why is using a slider directly in the player's script an issue in terms of game design?
-Using a slider directly in the player's script creates a dependency between the player and the UI component, which goes against the single responsibility principle. This makes the code less flexible and harder to test, as changes in one part of the game might require changes in seemingly unrelated scripts.
What are some common alternatives to scriptable objects for managing health or similar data across scenes?
-Some alternatives include using PlayerPrefs to save and load values, using a singleton pattern with a 'Do Not Destroy On Load' component to persist data across scenes, or using additive scenes to maintain a separate scene that holds the data manager.
Why are singletons generally frowned upon in the game development community?
-Singletons are often discouraged because they create global instances that can be accessed by any part of the program, leading to increased dependencies. This makes testing difficult, as it's hard to track what's referencing what, and can lead to issues like null references if the singleton is not available for some reason.
How can scriptable objects help in maintaining health data across different scenes without resetting?
-Scriptable objects can store health data independently of the scene, allowing the data to persist even when scenes are reloaded. By using a scriptable object, the health value is not tied to a specific scene or game object, ensuring that the player's health remains consistent across different levels.
What is the difference between using a scriptable object and a MonoBehaviour for event-based communication?
-A MonoBehaviour would require instantiation of a GameObject with a Transform component to act as a middleman, whereas a scriptable object does not need the extra memory of the Transform component and does not exist in the scene. Scriptable objects are also more designer-friendly, as they can be easily managed and adjusted in the Unity Inspector without writing additional code.
How does the Unity Event system relate to scriptable objects for event-based programming?
-The Unity Event system can be used in conjunction with scriptable objects to create a powerful event-based programming model. Scriptable objects can hold events that other scripts can subscribe to, allowing for a decoupled and flexible way to handle game events and changes in data.
What is the benefit of using scriptable objects over dependency injection for game design?
-While dependency injection also helps in decoupling code, it typically requires a framework and can be less intuitive for designers who may need to look into the code to understand or modify behavior. Scriptable objects offer a more visual approach, allowing designers to easily add and adjust attributes directly in the Unity Inspector.
Outlines
📚 Introduction to Scriptable Objects and Event-Based Messaging
This paragraph introduces the concept of scriptable objects as data containers that persist outside the scene and maintain their values across scene reloads unless programmed otherwise. It highlights their utility for storing game data like health and enemy attack stats. The speaker recommends a previous video for a deeper understanding of scriptable objects and discusses their use in event-based messaging, where they act as intermediaries between scripts to decouple code and enhance flexibility, ease of testing, and debugging. The talk from Unite Austin 2017 by Ryan Hipple on game architecture with scriptable objects is also recommended. The paragraph concludes with a mention of the sponsor, Mental Checkpoint, a YouTuber known for game design tips and the game 'Move or Die'.
🎮 Managing Player Health with Scriptable Objects
The second paragraph delves into the typical management of player health in games, using a player controller and a UI slider as examples. It outlines the process of setting health to maximum on awake, decreasing health with a trigger, and updating the UI slider accordingly. The paragraph then critiques this approach for creating dependencies between the player and the UI slider, which can complicate testing and scalability. It introduces the single responsibility principle, advocating for reducing dependencies to simplify testing and maintenance. The discussion leads into how scriptable objects can be used to manage health across different scenes without resetting, contrasting with the limitations of singletons and the complexities they introduce.
🔄 Persistence of Health Data Across Scenes
This paragraph explores solutions for maintaining health data across different game scenes. It mentions the use of player preferences and the 'Don't Destroy On Load' method to persist values, but also points out their limitations, such as the messiness of player prefs and the complications of singletons. The paragraph then introduces the concept of additive scenes, where multiple scenes can run simultaneously, as a potential solution for persisting data like health. However, it acknowledges that the issue of interdependencies between managers, like health and UI managers, still needs to be resolved.
📡 Using Scriptable Objects for Event-Based Communication
The fourth paragraph presents scriptable objects as a solution for the issues discussed earlier. It describes how a scriptable object can hold player health data and be used to communicate health changes between scripts, such as triggering health decrease and updating the UI. The speaker explains the implementation of a 'Health Manager Scriptable Object' that maintains health data, triggers events when health changes, and allows other scripts to subscribe to these events. This approach eliminates the need for singletons, reduces dependencies, and simplifies the game architecture. The benefits of using scriptable objects over MonoBehaviours and the concept of dependency injection are also briefly discussed, highlighting the ease of use for designers and the visual friendliness of scriptable objects.
Mindmap
Keywords
💡Scriptable Objects
💡Event-based Messaging
💡Decoupling
💡Single Responsibility Principle
💡Singleton
💡Don't Destroy On Load
💡Additive Scenes
💡UI Manager
💡Dependency Injection
💡Unity Events
Highlights
Scriptable objects are data containers that persist outside the scene and maintain their values across scene reloads unless programmed otherwise.
Scriptable objects are beneficial for storing data like health, enemy attack, etc., and can also be used for event-based messaging.
Event-based messaging with scriptable objects acts as a middleman between scripts or components, helping to decouple code and reduce dependencies.
Decoupling code makes it easier to test, debug, and architect a game to be flexible and easy to change for both coders and designers.
The video references a Unity Austin 2017 talk by Ryan Hipple on game architecture with scriptable objects for in-depth insights.
Mental Checkpoint, a game design YouTuber, is highlighted for their experience and valuable game design tips.
The single responsibility principle is discussed, emphasizing that each module, class, or function should handle only one part of the program's functionality.
Using a health manager as a singleton can help manage dependencies but is generally discouraged due to potential issues with testing and global access.
Singletons can create dependencies and make it difficult to track references, leading to potential null reference errors.
Player prefs and DontDestroyOnLoad are alternative methods to persist data across scenes but have limitations and can be messy.
Additive scenes can be used to keep certain objects persistent while reloading others, but still involve dependencies.
Scriptable objects can be used to communicate between scripts, avoiding singletons and reducing complexity.
Unity events are a powerful tool for event-based programming, as demonstrated with the health manager scriptable object.
Decoupling code with scriptable objects makes it easier for both developers and designers to work with and modify game elements.
Scriptable objects are more designer-friendly compared to dependency injection, allowing for easy adjustments in the inspector.
The video concludes with a demonstration of how scriptable objects maintain health values across scene reloads and play sessions.
Transcripts
scriptable objects are data containers
which live outside the scene and are
also persistent meaning that their
values do not change when the scene
reloads unless you programmed that
functionality so they're a great way of
storing data you may need such as the
health enemy attack etc however there is
another use for them and if you're
interested in learning more about
scriptable objects i'd recommend my
previous video before watching this one
which explains scriptable objects what
they are and how to use them and their
benefits but there's also another use
case for scriptable objects and that's
called event-based messaging and it's
where the scriptable object kind of acts
as a middleman between two different
scripts or components so we can decouple
the code which basically means to
separate or to remove dependency onto
components and when you remove
dependency it makes your life easier
makes it easier to test more flexible
and easier to debug but it is a great
way to architect your game to be
flexible and easy to change for both the
coders and designers and what i'm going
to show you is based on a unite austin
2017 talk which is actually pretty well
known it's called game architecture with
scriptable objects and it's given by
ryan hipple a principal engineer at
shell games so i definitely recommend
checking out this video because they go
more in depth into scriptable objects
and how to architect your entire project
to use scriptable objects and on the
topic of game design this video is
sponsored by mental checkpoint if you
haven't heard of mental checkpoint
they're an amazing youtuber who actually
made the game move or die which is an
awesome game on steam if you haven't
played it already and their channel is
full of super useful and secret tips on
game designs that i have not heard in
many of the other videos that i watch
and this is largely due to their
experience in the field the editing and
pacing of the videos are also amazing so
if you're interested in learning about
game design i definitely recommend you
check out mental checkpoint
alright so i'm going to show you how to
use scriptable objects for event based
messaging now what does that entail so
first let's see how we would usually
manage our player health so let's say we
have a player controller which you can
ignore all of this but the important
thing is let's say we have a health here
of a hundred and then on awake we set
the health to the max health and then we
have a function here called decrease
health which decreases the health and
you'll see that we have a reference to
the slider here which the slider is just
a ui slider on the scene and we set the
value of the slider whenever we decrease
the health so you'll see that right here
we need a reference to the slider and of
course we need to import the unity
engine ui namespace to use this slider
so what happens here is that when the
player enters this red box it loses
health and when the player enters this
green box the level reloads so if we
enter this red box you'll see that the
help decreases by 10 which is just a
simple script here health decrease
trigger that we attach to this box and
we have this on trigger enter function
if the other tag is called player then
we get a reference to the player
controller and we decrease the health
seems simple enough right and make sure
to add a rigid body disable using
gravity and also marking the object as
static if it's not moving as well as
enabling its trigger on the box collider
so this is simple enough we walk into
the green box and the level reloads and
we have our health back however there
are multiple issues with this first of
all now the player has to have a
reference to the slider so now the
player is dependent on the slider so
what happens if we have a level where we
don't want the slider but we still want
to decrease the health well in that case
you'd have to change the script around
so that you don't accidentally have any
null reference values or you might be
accessing this slider when you don't
actually have one in the scene with game
architecture you want to try to reduce
dependencies between things that don't
need them does the player really need to
depend on a slider no the player should
only be in charge of what it needs to do
and in the coding world this is called
the single responsibility principle it
states that every module class or
function in a program should have
responsibility over a single part of
that program's functionality and it
should encapsulate that part this is to
prevent dependencies between different
things that might not need them because
if you have dependencies it makes it
harder to test because now if i wanted
to put my player in a different scene to
test just the movement
now i'd need to import the slider over
or else i wouldn't be able to test this
accurately and this is just a simple
example what happens if the player
starts depending on a bunch of other
stuff on the audio what if the player
depends on the enemies themselves then
to test this we need to import all of
those variables into a new scene just to
test if the player mechanics are working
and that is not scalable so this
bad another issue is that what if we
want to keep the health over different
scenes what if we don't want it to reset
well since we're reloading the scene
somehow you'd need to pass the value
over to the new scene and i'm actually
going to explain this in the following
examples all right so let's see the
example number two so what a lot of
people do is that they have a health
manager now instead of the player having
a direct reference to the ui you have a
manager as the middleman speaking
between the two parties which helps with
the single responsibility principle
since this health manager is only
managing health and now the player
doesn't need to worry about the slider
so what's common practice is to make a
singleton of managers and a single 10
just means a single instance of this on
a scene similar to a static class so you
can only have one health manager in the
scene but that means that it's just
really easy to access for example if we
go to this singleton class you'll see
that we can access the singleton just by
typing in the class name and dot
instance and it'll return an instance of
health manager all we're doing is
putting the decrease health function
here and in the awake function we're
just setting the health back to the
maximum health so this is refreshed when
the new scene loads and now for the
health trigger instead of accessing the
player directly you can do health
manager.instance.decreasehealth so now
the trigger does not depend on the
player it just depends on a manager that
we can assume that will be in our scene
at all times alright so if we click play
here you'll see that
the health decreases we reload the level
and it refreshes the slider and the
health so there's some issues with this
the main one being the singleton class
is generally frowned upon in the gamedev
community and in general because of
multiple reasons with singletons you
have a global instance meaning that
anyone can access it it also means that
when someone accesses the singleton you
create a dependency to this singleton
and we're back at the issue with
dependencies where now if something
depends on the singleton and the
singleton is not available for some
reason many reasons can happen then you
have a null reference and tracking that
down and fixing it is just quite a
hassle with singletons since this is a
static instance it makes it hard to test
because it's hard to track exactly
what's referencing what so in general
singletons have the issue where if
you're not careful you can end up having
a bunch of dependencies so you're
singletons with your scripts and it can
make it harder to test now if you're
making a small project this isn't much
of a problem however as you scale your
project upwards and create a bigger
project with different teams that's
where the issue begins and another issue
is that the health resets when we reload
the scene and we don't want it to reset
we want to keep the health the same
throughout the different scenes like
most games do now first let's tackle the
issue of having the data persist along
different scenes so usually if you
follow this line of thinking and not use
scriptable objects there's some ways to
do that first you can use player prefs
to save your values and then load it
again when the scene reloads however i
tried doing this and it was just kind of
a mess especially when you start the
game over again because the previous
player prefs value is saved and you have
to reset it and using player prefs isn't
totally scalable if you have a ton of
values another method is for your
singleton your health manager is to use
don't destroy on load so in this case
i've changed the singleton to be
singleton persistent which if you go to
the main difference is that in the awake
function we have the stone destroy
unload which when loading a new scene
does not destroy the game object it
keeps it and with the singleton
persistent value this makes sure that
there's only one instance of the health
manager on the scene even when the scene
is reloaded so this is one way to make
your values persist over multiple scenes
by using a singleton and do not destroy
unload and then the main difference here
is that instead of accessing the slider
directly we create a ui manager instance
which is another singleton and we call
that to change the slider value with the
ui manager all it does is has this
function called change slider value and
that's what we call here from the health
manager prefs so for example we press
play and we reset the level you'll see
that now we have the scene here which is
the previous game objects and the don't
destroy unload which is the health
manager so since this was not destroyed
the reason why we made a ui manager is
so that the ui manager would have a
reference to the slider and so that the
health manager can access the slider
since the ui manager is a singleton if
you put the slider directly on the
health manager once the levels were
loaded it would lose access to the
slider since the slider would be
destroyed and a new slider is created
when the level is reloaded so now you
see this is getting a little bit
complicated we're making managers we're
making singletons you see how this can
get messy really quickly additionally
the health manager is assuming that the
ui manager exists and that the ui
manager has a reference to the slider so
if the ui manager does not exist there's
a null reference or if the ui manager
doesn't have a reference to the slider
that's another null reference and so
following this line of thinking there's
actually another way to have the value
persist over multiple scenes apart from
player preps and it's using additive
scenes usually in unity you have one
scene active the main scene and what you
can do is you can actually have multiple
scenes running at the same time and
those are called additive scenes because
they're added to the main scene and
similar to the do not destroy that lives
in its own bubble you can actually click
in the hierarchy here to add a new scene
you can delete the lights that the scene
has and you can actually add a game
object in that new scene and you can add
your health manager in that scene what
you can do is you can load in a new
active scene this is the active scene
where all your game objects are and
where your light
settings are which is important the
directional lights and so you can keep
this health manager scene running and
instead just reload this main active
scene now i'm not going to go over the
implementation for that but just know
that that's a possibility but the issue
still remains where we're using
singletons and the health manager
depends on the ui manager and vice versa
so what is the solution we can fix all
of our problems really easily using
scriptable objects as a way to
communicate between scripts similar to
sending events so what we can do is have
a scriptable object with the health of
the player and then whenever our player
enters the red box which decreases our
health that trigger will talk to the
scriptable object which will then tell
the scriptable object to decrease the
health and the scriptable object will
then send out an event which the ui
manager will subscribe to and the ui
manager will listen to that event when
the health has decreased and when it has
it will change the value of the slider
so let's go over the implementations
pretty simple we have this health
manager scriptable object here it
extends scriptable object so all we have
here is an integer for our health which
is our data and then we have max health
which is the maximum amount of health
our player can have so in the on enable
of this script which is called also when
this scriptable object is loaded in this
case when we press play and the level is
loaded this is called we set the health
equal to max health so kind of like when
the game starts we just set it to the
max health and then what we're doing
here is that we have an event a unity
event called health change event and
here we're just instantiating that event
if it equals no and so what we're going
to do here is when we decrease the
health of our player we decrease the
health as usual but now we invoke or we
just call this event and we pass in the
health so to make that easier to
understand we have our trigger here so
when the player enters our on trigger
enter we call the health manager which
in this case is just our scriptable
object so we just have a reference to
our scriptable object here and we call
decrease health and so this will call
this function and then any script that
has subscribed to this event
will get notified of this new health
value and unity events are super
powerful i definitely recommend using
event based programming that's what the
input system uses actually and side note
the input system actually uses
scriptable objects for most of its stuff
such as the assets the input actions
which i thought was pretty interesting
besides the point here we invoke this
event meaning we call it and then the ui
manager what it does is that in the
enable function we use our reference to
the same scriptable object health
manager scriptable object we do dot
health change event and we add a
listener in this case we add the change
slider value function that we made here
which just changes the value of the
slider to the new amount and on disable
we unsubscribe from this event so we
remove the listener so this script is
listening to the scriptable object and
the trigger is telling the scriptable
object what value is changing so the
scriptable object now acts as the middle
man and not only is it scene independent
so we can avoid any problems with
reloading scenes and the health will get
passed on from scene to scene but we
also now are not using any single tins
and now these two scripts only depend on
this one scriptable object and so we've
dramatically decreased the complexity of
having a bunch of scripts everywhere
reference each other all we need to do
is drag our scriptable object which you
can right click create scriptable object
health manager you can call it whatever
you want and you can just drag in your
health manager scriptable object into
your field and when you press play and
you reload the scene you'll see that the
value still stays the same because it's
still reading it from this scriptable
object you see that now the health is at
90. you see that when we keep going here
the health decreases and when we reload
the scene the health is the same since
it is reading this value from the
scriptable object and when we unclick
play and click play again you'll see
that the value is reset and that is
because of this on enable function that
is called when the scene is played
health equals max health so we've
decoupled our code we've made our lives
easier and we've also made the lives of
the designers of our games easier and so
a few other things i wanted to mention
are the differences between using a
scriptable object and a monobehaviour so
with the monobehaviour it's c
independent this is nuts independent
however you could do something similar
with a monobehaviour if you wanted to
however since the monobehaviour is in
the scene you have to instantiate a game
object and that game object has to have
a transform component just to act as a
middleman whereas the scriptable object
does not need that extra memory of the
transform component and it does not
exist in the scene which is nice and
there's also something called dependency
injection which dependency injection is
when you inject a script with the
dependency that it needs so instead of
it needing to go and fetch that
dependency itself it just waits to
receive it and then it can use it for
whatever it wants now this is kind of
similar to what we're doing with
scriptable objects but you'll need a
whole framework for dependency injection
and it's good because it decouples code
and removes hard-coded references
decoupling just means separating so it
is kind of similar to scriptable objects
in a way but scriptable objects are more
designer friendly because you can easily
drag and drop stuff in the inspector
without much code with dependency
injection the designers will have to
look into the code to see what's going
wrong to see how to change values with
scriptable objects they can easily add
in their own attributes it's visually
friendly and it's good for building
tools and holding data overall so i hope
you enjoyed the video if you did make
sure to like subscribe and sign up for
notifications by clicking the bell icon
and thank you once again to the sponsor
of this video mental checkpoint i'd also
really like to thank all of my patrons
for the support their support makes
these kind of videos possible and so i
really appreciate it if you're
interested the link is in the
description i offer source code early
access to videos sneak peeks exclusive
discord chats and more and with that i'd
like to thank my new patrons in the
supporter tier we have
thank you so much in the enthusiastic
tier we have flamed keith andre mirko
ever the journeyman nicholas jesse you
3000 banu popa
sebastian
stephen
bernard
paul knew
thank you so very much for your support
and in the dedicated tier we have brett
thank you so much for all of your
support it is really really appreciated
if you're interested again the link is
in the description and if you haven't
joined our discord yet the link is in
the description you can chat post memes
or ask for help so thank you again for
watching and i'll see you next time
[Music]
[Music]
you
Посмотреть больше похожих видео
5.0 / 5 (0 votes)