Unite Austin 2017 - Game Architecture with Scriptable Objects

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments

Really curious to hear what you guys have to say about NOT using Singletons at all, using ScriptableObjects instead. Viable for most scenarios? Better than singleton?

πŸ‘οΈŽ︎ 3 πŸ‘€οΈŽ︎ u/SampaioDias πŸ“…οΈŽ︎ Nov 22 2017 πŸ—«︎ replies

Oh god ! Haven't finished it yet, but I love his Event Architecture pattern ! Gonna use it by now I think :)

πŸ‘οΈŽ︎ 3 πŸ‘€οΈŽ︎ u/bsymon πŸ“…οΈŽ︎ Nov 22 2017 πŸ—«︎ replies

It's an interesting take that is for sure. I think the runtime set makes quite a bit of sense but I am not a huge fan of the variables being scriptable objects.

πŸ‘οΈŽ︎ 3 πŸ‘€οΈŽ︎ u/ByMayne πŸ“…οΈŽ︎ Nov 22 2017 πŸ—«︎ replies

Haven’t tried this yet, but I’m not sure how difficult it will be to debug things. Sometimes it’s just easier to have the debugger point to the place in the scripts that is causing the issue. Seems like that will be much more difficult using this method. I may give it a shot just to see how that experience works.

πŸ‘οΈŽ︎ 1 πŸ‘€οΈŽ︎ u/jlapegna πŸ“…οΈŽ︎ Nov 22 2017 πŸ—«︎ replies

I'm going to be honest, I don't really agree with that clean slate scene and prefabs containing all their functionality modularity he talks about 3 minutes in.

I feel like if we're talking straight production time, does the time it takes to set everything up like this actually pay off in time saved from dropping a prefab into a scene that requires another prefab or system to work?

I mean it's neat, but I feel like there's a lot of work arounds and duplicate code that would get put in, that a single system would be able to maintain and help out with.

πŸ‘οΈŽ︎ 1 πŸ‘€οΈŽ︎ u/SayAllenthing πŸ“…οΈŽ︎ Nov 22 2017 πŸ—«︎ replies
Captions
[Music] hello everybody thanks for coming to my talk I'm actually pretty excited I wasn't expecting a big crowd with something that's named game architecture so this is really exciting for me that it doesn't have to be a post mortem of a game you've played for people to be into it so thank you for coming to my talk my name is Ryan Hipple I'm a principal engineer at shell games shell game studio we've been around for over 10 15 years now and we at the moment are doing a lot of virtual reality and augmented reality or XR if you want to say that so it's V RNA are we've done a lot of work with theme parks stuff with location-based entertainment and stuff that will you can ride in certain theme parks in the country and we have a long history doing transformational games the studio's up to around 100 people right now probably around 110 you probably have played I expect you to die which is not an educational game but if you haven't tried it we have a demo right now you can play in the expo hall I recommend playing that I did not work on that so I can honestly say it's my favorite VR game without that being cheating I did work on this one water bears VR which would be my favorite game if I didn't work on it and we have a bunch of other games that we've worked on but these are the ones that they are the most accessible all of them available on Steam I expect you to dies on oculus and PlayStation and we have a Ryan trail which is out on almost every platform which started out as a 2d adventure game which is now in VR and it's amazing in hilarious so it's on the oculus and I believe the gear for about eight years I've been working with unity at Shell Games and as a studio we've almost fully transitioned over to doing all of our work in unity so watching that transition and being one of the first people that actually jumped in and started working with Unity a lot of common patterns have been emerging people tend to be able to find stuff online and the documentation or just from chatting with each other to understand how to do the algorithms that they need to solve this small problem they're trying to do they understand how to write c-sharp they understand how to write code and get things where they need to be but the big piece that's missing is the big picture how do you get all of these tiny little parts of your game come together and actually be a cohesive game so I'm gonna talk a lot about my approach to game architecture using scriptable objects that's kind of the glue between everything we had an updated timer count down here you keep track of my time before we talk about architecture though we do need to talk about pillars so it sounds super cheesy but it's actually kind of true I have at the studio I've been the way that I've been operating on projects I've never really actually had to sit down and plan out these are the pillars these are the decisions that I the the things that I asked myself whenever I make a decision but when I actually sat down to put this talk together I realized that they've they've been there the whole time I just have to put them in writing so these are the things that in my approach what I use to make big decisions for how a game comes together I want everything to be modular as much as possible we want everything to be editable either runtime editable or editable by designers are just to anybody on the team and we want to make sure everything is debuggable so modular editable and debuggable modularity what I mean by this is our systems are not directly dependent on each other so if you have an inventory system you want that to be able to communicate with other systems in your game but we don't want a hard reference between them because it makes things less flexible and you can't reassemble them in new cool ways also scenes as clean slates I started trying this approach about two years ago and I've been very happy with the results this is the idea that you have no transient data that exists between your scenes so you minimize you don't destroy on load objects every time you hit a scene it's a clean break it's a clean load this allows you to have scenes that have unique behavior that weren't present in other scenes without having to do a hack if I see code that says something like if scene dot name is not equal to menu scene I throw up all the time all over the place actual just it's a mess also prefer prefabs having them work on their own this means this goes very hand-in-hand with scenes being clean slates where every single prefab you drag in the scene as much as possible having that prefab have its functionality contained inside of it being a modular piece like a Lego brick this helps us a lot with source control when we have bigger teams where scenes are just a list of prefabs and then your prefabs are the individual functionality so you'll have most of your check-ins working on to the prefab level so you'll have fewer conflicts in the scene and using components unity is a component based engine and it's one of its greatest strengths as much as possible break things up into components that do one thing and only one thing and then we can talk about how we can get those communications to those components to communicate with each other editable when I say editable this should be kind of obvious people can edit things so the inspector is the number one tool that we have to do that but if we focus on data if we try to make as much of our game data-driven and our systems just be these machines that process that data as instructions we're going to be in a much better place to be able to make changes to the game even when it's running also it allows us to change the game without changing code if we're component based if we're modular it's easy to make your game editable if we have lots of options for all of the things that our game can do again we want to allow for designers and artists to change things and this leads to something that I kind of refer to as emergent design what I mean by that is the if designers are able to piece things together in your game without having to ask for an explicit feature or if you're a coder and a designer and you don't have to code up the exact feature you find yourself in a place where these tiny components that are proven to do exactly one thing and always do that thing right because it's easy to test if it's a single outcome you can rearrange those in different ways and find new game mechanics that you didn't know that your game even really accommodated and we've seen some like the coolest features and the games that we're working on come from that sort of idea and allowing it to change at runtime super important for iteration the more you can change your game at runtime the more you can balance and find values and if you're able to save your runtime State back out like script all objects do you're in a great place and things being debuggable this is kind of a subset this works really well with the other pillars that I've mentioned the more modular your game is the easier it is to test any single piece and the more editor features you have the more edit ability you have you tend to and also produce debug views for things so making sure you can view debug state in your inspector and never considering a feature done until you have some plan for how you're actually going to debug it and this is kind of unrelated but it is my number one rule that I will always state that you never fix a bug you don't under and you have to follow the bug understand it get to know its family and then kill it in front of its family so the pillars are have things be modular editable and debuggable also bonus points if anybody knows what those pillars are from yes legacy of kain and buying you a beer it's an open bar so so scriptable object basics let's we will look at some of the kind of the goals of our architecture but let's make sure we're on the same page about what we can do with scriptable objects so here's our quick primer it's a serializable class in unity it extends mono Beit work doesn't extend model behavior sorry it's similar to model behavior but without a transform and no game object and I learned from Richard finds scriptable object talk last year which everybody should check out that's kind of I had to cut most of my material because he stole it the mono behavior under the hood is actually in the script object-- are the same exact type on the c++ layer of the engine so they're very similar typically you'll save it as a dot asset file this is not necessary you can actually just leave them in memory and work with them there so you can create one with create instance at runtime and just use that as something that you reference and even more fun is you can actually save more than one scriptable object into a dot asset file and I'm not going to get too much into that I've spoken about that before about two years ago the hack spectrum talk that I gave where you can just pump a bunch of data objects into this so they maintain internal references and you can't accidentally delete something that's super important all right so some simple use cases that we have for scriptable objects some of these are great some of them these have problems but these are the things that I've noticed teams do the most using it as a massive game config file so how many people here are doing this with them where you have a file that says this is my path to this other object I need this is the the sound that I use when we click things so this is a very common use case in inventory if you think of a simple inventory it's just a list of objects I'll go over some details on taking that a little bit of a step further and making it more modular and any stats is another place also from Richard finds talk if you have a bunch of enemies say 20 prefabs for enemies that have different visual but they want to behave the same it's very easy to have them reference this enemy stats object so that's a common use case and something like an audio collection scriptable object as an asset that references 10 audio clips and then you can ask it to give you a random one so how does this relate to game architecture well before we actually start talking about where scriptable objects fit in I do want to address one of my number-one problems with the current trends with game architecture we've got natural disasters hurricanes tornados earthquakes mass extinction events the cancellation of Firefly and that tiny piece of skin that gets up under your fingernail that if you pull it it takes an entire chunk of your hand off also a nuclear war all of these things are caused by the same thing and it's probably sitting in your game right now Singleton's now how many people here are using Singleton's in their game show of hands please just be honest so I was actually expecting not applause I was expecting everybody to be very upset with me on this because it's it's kind of one of those it's almost a religious thing where the choices that you make when you build your game you have to believe that that was the right choice but it is important since we're talking about something that that's sensitive that we are responsible objective and mature about it so let's talk about the benefits why is it the people flocked to this decision why do people use Singleton's if I'm saying they're as evil as they are and also when I was looking this up I actually found there's a scotch called singleton there's a this is a real thing if you just do a google image search or you know being image search is the first thing that will come up and I thought that was kind of funny so you can access anything from anywhere this I think there's probably the number one argument that people have that why why the singleton workflow is convenient within their game if I have this low or if I have this piece of UI that wants to know what the players name is so it can display it player manager dot instance name done I'm done writing this component so that that seems like an attractive thing it's a very fast way of accessing any part of your code generally the implementation allows for this persistent state what I mean by that is the common unity implementation that I see people do is there singleton will instantiate a model behavior in this scene that they flag is don't destroy on load that creates an object that persists across all scenes even menu which is why we often see if name not equal to menu it's an easy to understand pattern or kind of consider it more of an anti pattern but it's easy to understand consistent and easy to plan so all of these things are very related it's very clear I'm going to build the enemy manager so I will just make sure it can access anything else and then I'll build the player manager so I'll just have that access the enemy manager so that makes something much more easier for a team to go in and just start building things without having to put a lot of thought into how these systems communicate with each other and just like the singleton Scotch it's probably this is when you go down in a small project in small quantities it's something that works out it has a lot of benefits it could even make your workflow faster but if you were to finish the entire bottle at night you're gonna find yourself with a lot of headaches so down the road if you do use Singleton's and larger projects you this these are the issues that we've run into though the rigid connections so that pillar of modularity that's that's broken now having rigid connections between your systems means you can't just have one of them exist and not the other they're all depending on the other ones being there you're also removing polymorphism this is if we're using an object-oriented language we expect to be able to take an instance of some known type and have that act de fer differently based on what its actual type is if I say player-manager dot instance I'm asking the player manager to give me something so I would not expect that to be able to give me the tutorial player manager or the debug player manager or the testing player manager so we're breaking that that contract that we have with an object-oriented language that we can get these specialized types back and it's not very testable because of all of these things not being modular and not allowing for us to and like inject a different type of player manager makes it more difficult to test and ultimately what most of this leads to is it's a dependency nightmare aside from we also have this global state which means that when we jump from scene to scene or we go to place to place it's not everything we want to persist but some things will persist that aren't really what we want the dependency Knightmare makes it difficult to test things and makes it difficult to track and you get race conditions that come up a lot in this sort of case and there's a single instance which shouldn't have to be spoken but yes a singleton has a single instance and that can introduce challenges if you decide player manager doesn't make sense we've got you know couch co-op now so we need to handle two players you could go in player manager and add an array or same with enemy manager but it does have this limitation that you you're going to have to go back and revisit things if your model changes so what are our solutions for them is some of these problems at a high level I'll talk about how do we reduce our needs for global managers how can we build structures that don't force us to have to have this global state tracking and having a model of inversion of control this is a software design principle the most common use case the most common example of this is dependency injection I'm not going to get into a very formal definition of a dependency dependency injection within this talk but the general concept of objects being given the things that they need to work with rather than going out and getting them is is the approach that I want to take here so instead of this piece of UI that displays the player name saying player manager instance name something in the game needs to say hey you UI piece here's the string you should display I don't know where I got it from don't ask any questions just display this string that will make that piece much more modular reusable and editable and but there's there's a there's actually a lot of hope for this pattern there are dependency injection frameworks that exist for unity which I'm not a huge fan of one core piece of a dependency injection framework is called the injector this is the thing that is responsible for passing references from system to system and in my mind that is exactly what the unity editor does that is what the the inspector allows you to do you can have a prefab that says I display something and then you can give it a reference to something that it can use to look up that string name and along the same lines of this this single responsibility principle or every class you write or component in the case of unity does only one single thing again this aids in modularity debug ability and also allows things to be edited because you can add and remove components so let's take a look at modularizing our data and as much as we can this is going to reduce the need for some global state that we can go to look data up for let's talk about an enemy prefab so if we were to have an enemy prefab and that enemy prefab needs to know what speed do I move you can dial that into the prefab directly but if you want to have 20 prefabs that look a little bit different or have some other strange behavior but all move at the same speed we don't want it's going to be in really annoying to just dial that right into the prefab one option is it says enemy a manager that instant move speed give me my move speed as I talked about that has a hard reference and that has potential that dependency means that manager needs to be loaded I have an example for an enemy prefab when we worked on the world of lexico which is a three-year iPad game that I worked on where we wanted to test our enemy prefabs in an enemy prefab testing so we could see the animations and see them respond to things and we drag and drop the enemy prefab in the scene and hit play no reference exception can't register with the enemy manager okay drag and drop the energy man any manager in the scene and this would be the same problem if it was just a lazy instantiation hit play no reference enemy manager can't find player manager okay play I put in the player manager enemy manager can find player manager player manager can't find inventory manager okay put in the inventory manager by the time we were done we had pretty much completely rebuilt our game inside of our debug scene and we had saved no time and made no progress so reducing that dependency another way that people could have solved this problem and this is something that Richard fine talked about in his talk is having a set of variables that are associated with the enemy prefabs as a scriptable object so your twenty different enemies can reference that one set of data and that's a really nice way of having that data get shared but there's a challenge there which means you're going to lump all those stats together so now if you have an enemy that is a ranged enemy and that ranged enemy needs to know what's my minimum or my maximum fire distance that's now a new variable type that can't be added to this this large definition so this is going to break some of our extensibility and modularity if we use that approach so what other options do we have well I say we break them into much smaller pieces so scriptable object variables things we've implemented on different teams that show games kind of in different ways but really at the core they're incredibly simple I'm gonna use the example here of a float variable but we can replace float with anything else our internal systems our framework that we use has a much more generic version so the codes fancier not really good for demoing so here I'm going to be defining a scriptable object to create a set menu that's something that's important to make sure you can get create an object from the right-click context public class float variable that is a scriptable object curly braces on the next line because we're not monsters donnetta standards thank you yes we're gonna define a value we're not naming this value we're not naming it HP we're not naming it range that we attack from what the variable gets named in the project view as the file on disk that's what's going to hold the name that's what's going to hold the meaning and this is something that a designer can do they can make these at will and drag and drop those references places and here's what the class actually gets really interesting this is where it gets fun so that's it if there's anybody here that can't understand or complete this coding task I'm sorry the rest of its not going to get any easier so in this case we can do hitpoints damage your mounts timing data all these things for enemies and we can have those those different components reference these variables and we'd have this some stupid enemy yeah some stupid enemy of course would be on the next line because we're not monsters and variable max HP is a float variable to this references same with move speed there's something that can be really annoying with this workflow though and that's that this can get very cumbersome we want every kind of every bit of data to be able to be grouped like this but it's kind of annoying like maybe this one we just want to we just want to type in the float value so we just made it this layer on top of it we call the float reference which is a serializable class that we have this option of saying do we want to use a constant or do we want to use a variable and we just can go through that and this way designers can say just use this data instead or actually want to use this shared data this with this pattern once we actually look up the value it's going to the one that we would expect but now we can go in and almost any float that's exposed in the inspector in our game we now make a float reference and it's made it exceptionally more powerful for our design team to use that to configure stuff and and make sure that values are in sync across each other so now our dumb enemy doesn't take a float variable it takes a float reference simple enough on top of that I love doing over the top inspector work we can make an inspector that makes this kind of fit into one line and be a lot more elegant to work with and this seems like it could be a trivial addition to this but it's it's my belief very strongly that making inspector this was actually a property drawer making property drawers to solve these problems that make it look like it's a part of the unity editor is actually a huge benefit for your design team because there's patterns that they're used to already and my goal has been to convince everybody on the team that this is actually a feature of unity and that's happened a few times where well designer on the team that I'm working on now we're on an older version of unity he said I don't think I like unity 2017 they removed float variables so I'm very proud of that so here we can this is a yep going back to the float variable now so now we know how to reference it there's actually some functionality that is hidden in here that we might not have picked up on before so we talked about having a designer specified this is max HP these are all these other things like move speed but we can do some other really cool stuff with this that value that float value can change when the game is running and anything else that's referencing that value we'll get an update so if we think about the way unity unities animator works it exposes something like move speed in this case and it uses move speed as a condition to drive the animator the animator doesn't care where move speed gets its value from again it's just I'm not going to question how this data you got to this number I'm just going to use it inside of the system your components on the outside are editing the value of move speed and again they don't care what's happening on the inside of the animator we have this nice layer of abstraction that allows us to test these things in isolation and allows people like animators to work inside the graph and programmers to work in the component a better example of this the one drawback of this system is that that's a string binding so movespeed is a string and you can use an optimized hashed version of it when you work with the animator but it's still error-prone you still have to type in a string and keep them in sync so I'd like to see unity move this over to a scriptable object that would be great for me but also there is a system in unity that does operate like this if anybody here has worked with a render texture you know that there's two main ways of doing it you can create a render texture while your game is running or you can create a render texture with a right-click context menu in your project on disk and then have your camera reference that when you do that it's obvious that the texture data isn't stored there right this is just a target for your data there's some configuration on you know the size of the render texture but that's not that doesn't own the data that's important that your you know your UI prefab wants to use instead the camera is going to write data into your render texture and your magical piece of UI is just going to read that texture data it doesn't know where it came from it doesn't care that it's coming from a camera and it just becomes this intermediate for a messaging system so we're going to do that same type of thing with the player HP so if we have a player HP scriptable object and we have a prefab called player in the scene let's say that player takes damage and then they're going to subtract some amount from player HP the player doesn't know what happens after that the player doesn't know who cares about the player HP but there's a health UI prefab also in the scene that is happens to be referencing the same variable so this can read that change in the player HP and just display a health meter that decreases and this creates a new layer of abstraction where the player doesn't have to go out and say UI manager dot instance dot health dot update my number thing similarly we can add new systems that care about the player HP our audio system somebody can just come in and say we're going to monitor this variable and use that to ramp up the music as your HP gets lower enemy AI can reference it and you can imagine any amount of different systems referencing this but there's a very clear separation that we don't have the the player doesn't it does not communicate directly with these and these things do not know about the player they're just working off of the shared variable I'm gonna do a quick demo that shows some of this off this has audio so if it doesn't work or if it's super loud I will pause it here we have let's just call this the sphere of pain it's just a Collider that if the this lovely cube collides with it it will return the amount of damage that should come off of the player so simple player has a waz D mover not a component I'm interested in getting into here but this this simple unit health if everybody yep there we are this references this HP variable and when the player collides with something it's going to subtract a chunk and when it starts up its going to say reset my HP to this Max value so here let's get these Mazda controls going if I click on the HP variable can everybody read this in the inspector it says 100 and if I bump into something very unceremoniously it's dropping to 60 80 every time I every time I hit something let's turn on the HP meter this is just a prefab that's completely isolated referencing that object so you can see the HP meters in here now and that's going to update again this HP meter is just a component that I made called an image fill setter incredibly small simple it's using the unity image class and setting its fill amount to the HP / the max HP which are all variables very simple component I can click this and drag and we can even see this update in real time if we're testing our meters you don't have to have a player in the scene you could just have an empty scene that has your UI and you can test everything out so that can save your UI designers a lot of time that's IDI whoa I don't want that let's add a warning sound so now when the players HP gets low we've got a new type of component very simple component watches an HP variable and says if it's below some threshold play a sound again the source code isn't really the focus here it's more the idea of how these things are glued together so let's drop this I'll just do it at the quick way if I drop this down we'll hear yeah the low health heartbeat so let's bring that back up and see if we can make that fancier so here this is the exact same exact same object with one additional component on it so we've added a component and the added functionality we haven't had to change anything we haven't had to delete anything this now takes the value of HP compares that against a curve and uses that to change the pitch of the mixer of the heartbeat sound so now the lower our health gets the the pitch will increase and I will show you so the proof that I'm not lying that it just works so that would be a waste of my time so here if I drag this down you can see that the eighth the speed will increase as the HP drops and if this were to be deleted or if the player wasn't in the scene all of these systems would still work and we can just add new functionality with a very minimal risk of breaking things not the seat there we go nope yeah all right so using these variables is a useful way to share data between systems and it also this form of the data has some sort of persistent state where if you load from scene to scene since it's in memory and a scriptable object in the in the in memory copy your next scene will be able to access that same data and that's very useful for something like player HP but there's a lot of cases where we don't actually care about the data that we're passing we just want to say something has happened and I would like you to respond now so now we're going to talk about event architectures also surprise I'm going to use descriptive objects and event architecture helps modular AI systems much like these float variables do and lets you reuse them in every product in other projects at our studio we have a lot of projects running at a time and we have our own unity framework so something that we can do to connect these framework modules to our project is it's always something that we need to come up with when we start a new project and using an event architecture allows you to just drag and drop something in and okay the the bindings between this and the systems in our game will make those be events it helps isolate prefabs much like those variables and a prefab can say I respond to an event and I don't care where it comes from so if you raise it I'll do whatever my prefab tells me to do and it's a little bit of an optimization over the all of the variables so an HP meter you kind of you might want to have that watching closely if your HP is a wide range but in a lot of cases you don't want to be busy checking what are you now what do you know what are you now what do you know and instead just have a one-off event it's also highly debuggable if you can invoke these events through the editor or through other systems or through a UI it's easy to identify if the events raised is the behavior correct yes well is the event being raised yes and if you go down that search flow you can kind of do a binary search to hunt down where your bugs coming from the raised side or on the response side unity has this thing called unity event that seems like the first place I should look if I want an event in unity this was introduced I think in 4.6 with the UI system so its primary focus was what happens when you click a button so it looks like this in the inspector you can pick an object and once you select an object you get a drop-down of all the functions that you can call on that object so here you can call other things set active to true this is in my mind not really an event it's a very useful tool but this is actually more of a serialized function call because you're saving out ahead of time exactly what you're going to do there's some advantages to this it's just still a very useful pattern we use it everywhere you can hook things up and out in the editor so this allows for things to be modular and you don't have to have code the sort of last meter that connects one system to another you can just do that using these unity events so your code doesn't need to assume it's bound to a button or it's bound to a collision it allows you to pass arguments I'm not going to get into depth on that too much but it's one of the things that people don't always use with them you can extend unity event with with whatever type of the argument you want to pass so unity event bool and if you declare that as serializable you can use that new class as as something that you can pass data with when you do that the arguments are either static or they are dynamic so let's take a look at an example needed with a boolean so other thing that's said active if you click you get this new section up top that is dynamic arguments so at the bottom you have arguments that you can just specify in the inspector and if you select one of these this means that it's going to wait for the event to be Dazed encode with an argument and it'll forward that along there's still some problems with this as I mentioned it isn't really an event system because it relies on rigid bindings so if you use an event for a button response that means a unity event for a button response that means that that button needs to know exactly what object responds to it so if you want to keep your UI in its own prefab which I would recommend and have some system that's not in that prefab respond you have a much you have a challenging system now you now you need to make an awkward class at the top of your prefab that responds to every single button click and when you make a new button you have to go in and make a new response so this becomes a little bit of a problem when we're trying to modularize and extend things yes the hard references in the button or the problem and there's some limited serialization with unity events the capability of a unity via event far surpasses the serialization model that they use for the unity event what I mean by that is you can it with the unity event you can define something that takes up to four arguments and those arguments can be just about anything but the only ones that you can actually edit in the inspector are if it takes one argument and the argument is an old string one of the one of the you know first-class primitives that unity treats or unity engined object so you do run into some issues when you're trying to serialize out different unity event responses and also there's a garbage allocation since it's a serialize function call it's using the string to resolve what message gets the response so every time you call that you're gonna see a little bit of garbage this doesn't make them useless that just means you don't want to call it every frame something happens every frame it's not really an event anymore so how do we extend this we're going to try just making our own events so we want to make them data-driven obviously make sure that they can be designer or Roble so again probably gonna be a scriptable object spoiler and make sure that they're debuggable it's gonna look a little bit like this create a set menu because it's the scriptable object we'll call it game event curly braces in the next line we're not monsters we'll have a list of game event listeners and all this really is is a class that I'll get into real quick what happens when we call rays on this game event it's going to loop through all of the listeners and call on event raised an interesting thing about this loop is we're looping through this listener list backwards and the reason why that's a useful pattern to use is in case in events response or a listeners response includes removing it from the list that way you're removing items behind you instead of removing items in front of you so you can iterate through the list without an out of range exception and then two functions that are not super important to really define register and unregister just add and remove it from that list there's a lot of optimizations based on the number of listeners you would have in this thing you could change to different data structures but kind of as a abstraction on the system the list is a good starting point and always end your classes so the game event listener this is the other half of that this is a model behavior so the game event is a scriptable object game event listener is a model behavior yeah next line not monsters whatever the game event is something that's referenced so this is in the inspector you can drag and drop the reference to the game of any want to respond to and then use a unity event to serialize the function call as the response so now if you have a prefab and you have an event listener at the top of your prefab you can decide what message do I listen for that's going to come from outside of my prefab and then what response do I have inside my prefab so now it's much more reliable your unity event knows exactly what or the objects that's responding to this event knows what the context of this prefab is so we can have that unity event response and not need to rely on external prefab data and in this case we'll say when the objects enabled it registers itself as a listener and when it's disabled it on registers itself and this shouldn't be a surprise when the event is raised it's going to call response dot invoke I'm gonna show a demo I'm gonna jump into the same cube exciting game here this is kind of the where we left it off before but we're gonna add a damage effect so we wouldn't want something constantly checking the HP saying did I get damage did I get damage did I get damaged so we'll make this work like an event here I made an addition to this unit health class no spinny bar when damaged when there's a collision that happens on this player object it's going to invoke this damage in event which is just here it is in our scene we can create it by saying create it's in here I swear game event just like that so player says game event dot raise that's what this unity events going to trigger then somewhere else in the scene or maybe it doesn't even have to have a presence in the scene will have something respond to that so here we have game event listener so this is just a component that all it does is what we talked about it listens for an event and calls a response that is a Unity event so listens for all player damaged and the response is emits 40 particles and play a sound so you love my programmer particles but as this happens it just gets that event as far as debug ability is concerned if you just write a simple inspector you can come in here and click on on player damaged and raise that event as much as you want without actually having to recreate whatever setup gets you to that place in your game player damage is simple to recreate but a lot of events are much more complex in our games we can keep adding to this and let's say when the player dies there's now two new prefabs death sound and death screen programmers don't even know that this stuff's happening because an artist can just make this thing that responds to an event and turns on so here when the player dies we're going to raise this on player killed and it's going to turn off the player object so let's make that happen so there we go I forgot to not have a broken bug sorry there supposed to be a bug right here so I can fix it in front of you because it's a but it's a bug that there's no bug so here I'll reintroduce the bug so now the players dead and the heartbeats still going which is very annoying and very clearly you're gonna get somebody reporting that to you in QA as a bug when you're late in late stages of QA and you can fix something in your game without having to write any code it's so much less stressful you don't have to retest all of the systems just that one thing so here's how we would fix that we have this warning sound all we need to do is add a game event listener to that that's going to listen for player killed and their response will be to turn itself off that's it so now when I play which you've all seen this before because it wasn't not broken it turns off the sound that you can see that this game object has been actually turned off so now we've fixed the bug by just adding one single component that affects only that object so scriptable objects are great for this modular ization of data making things into smaller pieces and smaller messages but there's still some things about like the singleton pattern that we haven't solved yet so one of the big things that a singleton still wants to do is they want to have in the case of an enemy manager you want to track all the enemies in the scene so every time an enemy gets placed in the scene it's going to on enable say hey I'm here and that way the manager has this high-level view of everything in the scene this still can create sort of create racing race conditions similarly an enemy manager would want to understand where the player is and it would want to take that list of enemies that it has and say hey do this thing to this player so there's more than just a collection of data there there's actual functionality that's implied and we can take a look at this and see what some of the problems are as they mentioned there's race conditions I'm sure many of you have been in a where you put an object in a scene and player or enemy manager dot instance is called and it hasn't been created yet so you get a null reference or it's checking if it's been created and if it hasn't it doesn't respond which means that enemy doesn't get added to the list also we're dealing with the same rigid singleton problems where if we have an enemy manager that does this stuff we can't do that test scene that I talked about where we placed a bunch of enemies and we needed to bring in every other system because of its rigidity and still there's only one so we can address these with this feature I call run time sets these are going to be almost exactly like the float variables except instead of a float we're going to be dealing with a list so we can keep track of a list of all the objects in the scene that's that's one of the goals with this it'll avoid race conditions because if you're referencing a scriptable object it's in memory copy is available when you reference it there is no you don't have to manually initialize this it's just there and it's gonna be more flexible than using unity tags or a singleton and more performant than saying then calling a get object find object of type so unity tags will let you say yes I'm an enemy object and there's a special list maintained of all of your special tagged objects that's also using strings so it's very likely to be error-prone and you can only have one tag on an object which to me just that's that's not really what a tag is that's more of a label so we'll take a look at how we build these also very simple here I'll just do it as an abstraction because it is a little bit fancier so we'll have a scriptable object that has a list of items in this case they could be enemies or in my example there will be cubes because that's very exciting like an ad that will add it to the list much room for optimization on the lists here I'm sure and a remove that will remove it from the list also all very simple and this is all stored on a scriptable object so you can have something in your scene reference that object and we've used this type of pattern before when you have an RTS game when you place a building in an RTS or build a unit you want something that has an understanding of what are all of the units what are all of the buildings it's also very common that when your game is fully in production you'll have maybe a designer oh but I also want to maintain a list of the flying enemies and we don't necessarily have coding resources available at the time to code in a new list of flying enemies and the rules for looking that up so we could very easily just say this enemy or here's that here's a new component that says add it to enemies list also add it to flying enemies list and then we can maintain these lists kind of just based on whatever components we add we also use this and actually this was the first time that we started it was for special effects so we had a custom renderer that are our graphics engineer Joe wrote and here we needed to reference some renderers and do some dirty stuff to them to get some really cheap and nice blur and glow on them so our rendering script needed to know about every individual renderer and since our graphics engineer isn't a full-time member of the project he shifts around and solves really hard problems for every for all the projects there wasn't a lot of time to understand how the games all put together so instead of actually having this integrate with some big part of the system it just put that into the in-memory copy of escapable object and then we were able to you know on start we would have all of these special renderers write themselves to that list and whenever the camera went to render it would just go through the list and say hey here's a new renderer and it didn't care what that data came from similarly you could do this with items on a mini-map so anything that wants to display on your mini-map can add itself to a list and your UI component can inspect that list and say for each item in here we're just going to draw whatever icon we associate with it and I have a demo that is less exciting than any of those but it demonstrates this with cubes cubes there we go so here we have a handful of things these are just the ugly cubes this thing component references a runtime set which is the scriptable object and what this is going to do is when we click on any of these things that are sorry when these things are enabled they'll add themselves to the list and we're the disable they'll remove themselves this enabled things scriptable object is still this is not a system this is still just a collection of data meanwhile we have this thing in the scene the disabler this has functionality to disable a random item in the list so if you have you can imagine this expanding to be something that's going to be your enemy target this is responsible for assigning targets to enemies so instead of just simply disabling a cube it says some amount of time has elapsed I'm going to pick an enemy in this list a random enemy and assign him to attack the player so now this is a system that doesn't need to know about specifics in the scene it's inspecting a scriptable object that means that that reference can be maintained between scene loads because this is a prefab referencing an asset it's not referencing anything else in the scene so there's also a text in this canvas here that says there are five things that's just text that's inspecting that list and then just printing out the number so you can have a lot of things that kind of just operate on this as these observers that's an observer pattern just looking at this set of data but if I click disable random it's going to do what we expect which is makes it'll send an event using our game event system to the disabler which will just disable one object from that list randomly and again you can see that this is all these are all simple components this doesn't make any assumptions as to where these objects come from it just says I I'm looking at a list somewhere and it's at a dependency that's been injected into it by the inspector okay I also wanted to talk a little bit about enums and how they relate to data and scriptable objects this is another thing that Richard fine talked about in his talk but I think it's important to bring up again so I'm going to go through this kind of quickly but yeah he stole it from me first some of the limitations with it with enums with enumerations that they're all code driven and I think that the more code you can remove from your game the less bugs you're going to have that are programmers fault so you can just assign all of your bugs to the designers and everybody's going to be happy except for the designers so if you have enums you're going to have to change these things in code so if we have elements your fire ice and wind element I can bet you somebody's going to ask for a new element at some time so using an enum for that is something that you're just you're you're know you're going to have a feature ad they work out with things that are that are relatively fixed by some law so cardinal directions north south east west that could be an enum unless you're game of course wants to go into a more gradient space we've used enums for comparison operators less than less than equals equals not equals greater than greater than equals so that's a place where there's not a lot of new comparison operators being invented every day but that's the space where enums makes sense where you have a relatively fixed list using the enums and having them all be code driven they're difficult to reorder or to add or remove things if these are serialized the enums are going to be saved out based on their index so if you delete one you're going to break every single thing that uses that I've seen games deal with this by renaming something to unused and then that way they maintain the indices of everything that comes after it and then you've got this massive list of items that have three things that are valid because you don't know where they're actually being used and then finally they can't hold additional data which is kind of obvious you end up building sort of a lookup table in a lot of cases and even if your initial definition is here is we don't need it to hold additional data this is somebody will somebody somebody want to put some data in there and when you define your when you use your initial limitations or your initial expectations to define your implementation I think that you'll realize that yeah but that's a self-fulfilling prophecy it can't do that therefore it never will so we can solve this of course with scriptable objects demo so here we'll look at elements and never look away sample data so here we have a handful of my favorite types of elements rock paper and scissor we all know how these work I didn't write a fancy mover so I'm gonna drag things around in the inspector to demonstrate what happens here these cubes each are have an elemental component which just references one of our elements this is a scriptable object that expend extends that is a talent of type of attack so here all of this lists is what are the things that I beat we know paper beats rock rock beats scissors scissors beat paper so if I grab the scissors and drag them around scissor on scissor sound good that does nothing edit that out since our on paper we know what's going to happen says your on rock that's exciting it just died so that's that's a very simple elemental system but inevitably you're gonna have a designer come in drunk at night and say I want dynamite so here we go let's add dynamite we all work with that guy and dynamite beats everything it beats rock it beats paper it beats scissors it beats dynamite which is just devastating and I'm gonna turn on these two special things cuz I knew I was going to do this and we'll drag and drop this new dynamite right here into the element so now these two at the bottom are gonna be dynamite so what I've done and I could also have done this when the game was running by the way but what I've done here is I've just added a new feature dynamite going through cleaning everything up and it's just being destructive so now we've added this new feature that you know this small example wouldn't have been too much of a drain on your programming team to add one thing to the enum but it says your game starts to get larger these things get to be much more expensive and this also wouldn't break your reference index if this ends up getting deleted or reordered okay so we've talked a lot about modularizing data and that's all well and good but there's still things that you can do with you still need more than just data points these small chunks of data in order to build your game architecture so just like we can reference a variable that only has a float we can also reference a scriptable object that contains the entire definition and all of the code that gets executed for a system I'm sure everybody here knows that you can write functions inside of scriptable objects but there's this sort of mental barrier where most people I talk to they avoid it because they look at it as this is a data bucket it holds data but you can write any method you want inside of that they don't respond to most of the Unity events so they don't have an update tick which means that they do only what you tell them to do which is kind of what you want code to do so here we can go through an example of how we make an entire system packed into an asset so if we make a descriptive object that system lives in the project and it can be referenced by other prefabs or by other scriptable objects you're gonna just build that as to that reference directly with the inspector again this makes the inspector act like a injector in a dependency injection system this makes it easy to see what system something's relying on and it makes it easy to debug if there's well also you won't have race conditions with startup here because these scriptable objects are available on demand there's also no code lookup no inventory manager dot instance so this is very flexible and you can even swap out what you're doing with your inventory manager and you don't end up with any references in your scenes that say oh yeah here's my inventory manager game object if you did it that way and this UI has a reference to that that's saved in the scene data and if you were to move any of those prefabs to a different scene they wouldn't function so let's take a look at how the audio mixer and the mixer group do this the audio mixer has an option of selecting an output mixer group so in your project view you have one and you drag and drop it in so this is this is the pattern that unity uses that's very much the type of thing that what we're looking for I also want to give an example here for specifically a an inventory system on the game that we're working on now we had an inventory where you have abilities so as an inventory of abilities and there's a sort of telekinesis ability that your player has and in our tutorial we're going to teach you how to use that but there's a problem that you can repeat the tutorials and we don't know what the state of the players inventory is at that point so in past games I've seen this be implemented by somebody saying oh we're just going to go in and modify the players inventory instead in our tutorial scene we would just drag and drop a reference to a new material or sorry a new material to a new inventory so we had a tutorial inventory that had only the item we were tΓͺte we were teaching and then that was the one that we would reference in the tutorial scene and we didn't have to make any code changes to support that right so this inventory contains a master list this is all of the items that are available it has one scriptable object per item this means that on source control changing an item doesn't change this massive asset and we have a lookup that happens on validate of the master list or you can have it happen on other asset events that will just automatically populate it with every item in the scene or in the project view and as I mentioned we can use different inventories in different scenes like the tutorial example so here we have an inventory asset that is are the hub of our system the player references that because the player says you know my equipment to display all of my equipment on me we also have maybe the equipt screen referenced that so now we need to have a prefab that's UI this can have a direct reference to that asset and that asset can then modify the inventory and we can imagine an npc wants to have access to the inventory so this is where the NPC can say I'm going to give you an item and they don't need to know that the players around the player doesn't need to be in the scene we don't need any special objects to exist the player just says I'm going to add this item to the inventory and now here's where things get interesting you can have your systems so if you have an inventory system reference other systems so in this example we can see our inventory can reference our saved system we could also have our inventory reference our localization system if it needs to do a lookup of text so these are scriptable objects the references that you tie between them are all going to be saved in in your project view so they'll be things that are guaranteed to always exist I want to leave some room for some questions so I'm gonna skip over the inventory demo guess I've just walked you through how it works that's just about it I want to remind everybody that the goals here have been to keep things modular editable and debuggable which is going to let us make sure we can focus on building game architectures that are reusable across different projects across different scenes and hopefully across different Studios so I think that the game industry and the unity community gets better if we're using similar practices so I'd love to hear some questions at the microphones and some comments and if you want to pick a fight over Singleton's I'll be at the unity party today thank you please use the microphone for your questions yes so forgot the question like one like once side-effects of objecting all of this is that true for company code you can't look like yep like references and such the guys have had problems with it like the debugging or with people that are new to the team to see like okay where's this fing being called from its public method this or something out of you know like something is calling it which is why before so I showed some very well first of all I can answer that first we the the samples that I showed were very simple examples that would that are very subject to that that limitation tracing them gets more difficult we have a much many more layers on top of that and one of them that we've added is for our events specifically anything any invocation in debug mode is logged and displayed in the inspector so it'll give you a full stack trace of where that was raised from it also maintains a list and the way that unity handles serializing in a scene versus in the assets so it uses a the instance ID rather than a good and file ID you have to do a lot of fancy work to maintain the display but we have our events our debugger for our events also let you see every single thing that's registered so in the inspector or even in the scene view you can you get little gizmos inside of our seen that show you hey I'm listening for this event I'm listening for this event so we we've built all of our tools that we've built for it kind of focused on solving that problem but that one of the costs is is that like the or you have that layer of abstraction and you're pushing a lot of that to data so the power of giving designers control is that they can make patterns that still can be challenging so there's still a lot of discipline required for it cool thanks yeah thanks yes hey Ryan thanks for the talk to those really useful gorgeous I get great collection of patterns there have you thought of polishing this is a blog post or open source in that project or something I'm hoping to you know make a cleaned-up version of that project and put it up I might put it up on github if I do that I'll probably tweet that out in a Twitter account I use once a year so if I put that up I'll definitely post that as far as blog posts shell games does do we have a shell shell games or shell labs where we have a lot of our developers do blog posts when my project wraps it's pretty busy right now it's very likely that I'll be contributing to that to put things like this up there awesome there's a lot of examples I had to cut I had two hours of stuff so great thanks hey so we had a lot of similar problems you shot here but we went to sort of a different approach on how to solve them so we're using dependency injection framework yeah so there's basically just ones inject something and it kind of solves a lot of the problems that you show them I'm just wondering did you ever think about that or what are the benefits of using dependency injection yes you're examples we've we've talked about dependency injection a lot in the studio actually we have we do a lot of client work at Shell I listed the games that we've made but we probably do 60 70 % of our work for clients so a lot of the times we inherit their processes which gives us a good view of alternatives so we have a team that's been working with Zen Jack's specifically and I think it does it definitely addresses a lot of those problems the thing that the thing that it doesn't address for me is it doesn't expose things to the design team so it's it's taking everything outside of the inspector so that's that's where it breaks down for me and it this really depends on how comfortable your team is and what your team breakdown is but like my current project it's a handful of programmers and one designer but it's a very he's a very technical designer who's good at exploiting systems so it's very nice to be able to say we've given you all of these tools if you follow if you use them they'll follow their own rules and if there's a bug it's on us and if or if there's a bug with how the systems work it's on us if there's a bug just with how all these things come together we have a big behavior tree that's on you so it's it allows us to have that big separation of responsibilities by just pushing it into the data space instead of into the scene but Zen jecht is something I've heard a lot of a lot of good things about thanks there's a great talk I'm curious it seems like this sort of approach emphasizes like see realized behavior almost it's like a lot of systems or event handling is set up via serialized references and I was curious how you guys have managed code reviews and looking at disk and source control mm-hm compared to like normal code where you can like look through references in your IDE and you can easily see like code from the code review perspective depending on the project we've have different standards for a code review and that usually is something we inherit from clients and one of the projects we're using now we're you're using git and a little bit of a git flow model so at the end of the day we're in QA so at the end of the day we have this nice review of everything that's been done if we make a change that adds an event so often our bug fixes are oh we just need an event to fix this problem we need to listen for this player died turn off the thing that we don't want on that the way that that actually serializes in the amyl is pretty readable at least for our team we're comfortable seeing okay this is very clearly an event because it says event name this response this so when the when the components are very small it's easy to pick those things up but I will say that yes the biggest the biggest drawback is that you cannot trace it all through code and that is also one of the biggest benefits that I see with a system like this there is there any cause for concern for something like memory fragmentation or something I think the way that the the way that the assets are loaded you mean memory fragmentation as it impacts performance yes so far no we haven't run into any issues for this we're running on our current project is on mobile devices we're not necessarily targeting the highest and mobile devices and we're using this extensively it depends a lot on the needs of your game so what we're doing is we have a seam that's a that's essentially a battle that's very self-contained we understand what's happening in that so for us that's been a model that works really well that hasn't been too demanding to cause memory thrashing or problems with that but it's it would be something to look out for if you have you know a larger scope have you tried doing any like profiling on where data is stored and stuff in the gamer as far as profiling is concerned there's we've nothing really shows up on the profiler for this it's all very the the profiler hits that you get that we see would be the memory allocations the garbage allocations that happen when you use the Unity events and that's where we actually have the option of using a Unity event or we can just register something with an interface that says I am an I'm an event listener through my interface therefore it doesn't have that overhead so we do have some optimal paths for things that happen more often Thanks it seems like the focus is that Singleton's like our site script ilanics primarily replaced single things I wouldn't I wouldn't say that I'd say I've run into a lot of issues that come up on on projects where the core of the architecture is connections from singleton to singleton to singleton that's not to say that some stub system especially from some API being implemented that way doesn't doesn't mean that's the end of the world it's more about maintaining those connections and the script logic can replace a lot of things and one of the things that it can replace is global it can manage help you manage global state or references between things okay another question is you could also use game object with a mana behavior right and do you like kind of all the same stuff you could so the limitation there is if it's a game object and you're not just serialize a reference oh a game object in your project yes yes if a nun instantiate a game object works exactly the same except it has the overhead of a game object and a transform so if you need the game object and transform first up for whatever reason you can do that but they do work exactly the same so it's more of a syntactic thing where you're saying this is this is what we expect this to operate like a system and not be in stanchion you cannot instantiate in your scene a scriptable object right so it's kind of more that is what's into it's intended for and you don't want somebody to accidentally instantiate your weird systems I have one other question I for some reason I remember that if you changed like at runtime the data data of objects that was like in the project but that would remain changed after a play stopped it does and that can be a good thing or a bad thing so if you're changing some balanced variable that's something you can do and we actually we have we have an index that keeps track of all of our variables that takes care of resetting them to their default state okay so like if you had a player health look like the bar and things are all looking at that player health something has to reset it to some and in that in that example it would reset it on start to the max health I'm getting the sign that we're we're out of questions so if you have more questions I'd be glad to talk to you after this thank you [Applause] [Music]
Info
Channel: Unity
Views: 337,361
Rating: undefined out of 5
Keywords: Unity3d, Unity, Unity Technologies, Games, Game Development, Game Dev, Game Engine
Id: raQ3iHhE_Kk
Channel Id: undefined
Length: 64min 29sec (3869 seconds)
Published: Mon Nov 20 2017
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.