Unite 2016 - Overthrowing the MonoBehaviour Tyranny in a Glorious Scriptable Object Revolution

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
world made entirely of mono behaviors large projects become chaotic developers suffer one man has the answer good morning you night my name is Richard fine I work on the sustain engineering team at Unity we deal with all the versions of unity that have been released to the public we deal with patch releases and we deal with 11th our Hail Mary plays to help ship stopped projects get unblocked and get out to the world I want to talk to you today about some of the the horrible situations I've seen from bad decisions made in projects I want to show you an important tool that will help you avoid some of these horrible horrible things so what am I going to talk about today exactly firstly I'm going to talk about some of the problems with monobehaviour as a class I'm going to talk about scriptable object as an alternative to mana behavior another way of doing things another important element in your architectural toolbox when you're working in unity and I'm going to walk through a bunch of examples okay we've got kind of examples just on the slides with like abstract things and I've got like a nice live demo as well applying some of this stuff to one of the projects so let's start by looking at mana behavior why is it bad well let's start with exactly what we're talking about when we're saying the use of model behavior so we're talking about projects where we've got a bunch of scripts they're all written as mono behaviors that will attach to game objects they all live in the scene or they live on prefab files they store all of your state so all the variables that you're changing a so on you're keeping all of them in whatever have is all you know the vast majority of them and you get callbacks and things like that update late update start awake on enable on disable on became visible on application quit what about about okay so hopefully this is a familiar situation to most of you this is how most unity projects are the very start out is how a lot of them end up but there's a lot of problems that happen when you build a project kind of just in this way when you just apply this this one technique over and over and over you build everything up in this way so the first problem comes when you're dealing with shared versus non shared state and what I mean by that exactly so here's a little example of like an enemy component and you can see a few different variables their current health time until next attack maximum health an array of different pain sounds to use we're going to take damage or something like that but the thing is that most projects you don't have like a unique maximum health for every single enemy you don't have a unique set of pain sounds for every enemy all of your goblins have the same set of sounds at the same set of health and so on you don't want it to be different because that's confusing to players you know you want a sort of a consistent shared bit of information for a bunch of objects and some per instance information as well and the way that most people write mono behaviors combines all this information together into one place so that's what I'm talking about when I say shared versus non shared state you've got some information that you want to be kind of using across multiple objects multiple instances and you've got some information that you don't you know the current health for enemy is definitely something you want to be on that enemy only the second problem that people run into with mono behavior is that any changes you've made in play mode get lost when you exit play mode this is obviously pretty important that we don't just like you know keep the fact that you move two objects around and killed all the enemies and ghosts the player got the end the level and sort of the beginning the level we you want to restore a lot of that state but there are times when you're changing things in the middle of gameplay trying to tweak trying to iterate and it's a bit of a problem that you then lose all that information we're next in play mode so that's another thing that's that's that's bad the next problem is that model behaviors have sub file granularity and what I mean by that I mean that when you have a mono behavior in a file and I mean I've seen file or a prefab file it's not the only thing in the file in the case of a scene file you've got a ton of other objects in there as well in the case of a prefab file you've got other components you've got game objects and transforms you've got a lot of other potential information in there and this is pretty bad when it comes to collaboration if you want to edit a model behavior in one scene file then you're having to potentially lock the whole scene file or the very least you're making changes that other people in your team may be changing other objects in the same file and then you're having to kind of risk having merge conflicts when that all comes together afterwards it will be much nicer from a collaboration point of view if one object meant one file so you could lock things and operate at the same level that the VCS expects you to to operate and then the other problem that happens is callback chaos and this is where you have mono behaviors throughout the place subscribing to update and fixed update and so on and and in any given frame you find it really hard to keep track of what's actually happening you know you can't easily keep track of which code is present in the scene which code is being executed you can kind of look through and sort of find where your callbacks are but when something is changing and you don't know what's changing it it potentially any component somewhere has had an update method show up on it that you you didn't see about so one of the solutions people have applied to this for quite a while is to use uninsured prefabs and what I mean by that is having a prefab that has mono behaviors on it but you don't actually put it in the scene because it's not in the scene it doesn't get any callbacks and it's kind of it can sit in the project and it can be your kind of a place to store shared State so you have your your non shared state in the scene your shared state on prefabs kind of kept in the project and yeah this this does kind of work more or less but it's kind of not really what a prefab is I mean having having shown this to some of the founders of unity there's a sort of a people really do Wow that is that is not what we made those for so yeah you can you can do it but it's not what prefabs are for really and I mean that might seem like a conceptual objection but it's also you know it's confusing them to artists and designers that they have like some prefabs that they should be dropping into the scene and other prefabs that they're supposed to not drop in the scene and things like that it makes it slightly more confusing to understand the way your project is set up that you have some of these special prefabs in some places and the second problem then is a result of that is that they're kind of accident-prone it's quite easy to accidentally drag and drop one of these things into the scene and then suddenly you've got the shared object in the project and also the instance in the scene and that can become very confusing very quickly it's also possible to accidentally add extra components to these things as well so suddenly your your prefab that stores your your your enemy maximum health information also has a rigidbody why because nobody knows it just sort of happened at one point and someone checked it in it wasn't supposed to be like that but now especially on platforms where we're stripping is in effect now you're accidentally bringing in the physics system or something like that when you're building for WebGL and it's just it allows this sort of accumulation of mistakes and things that it'd be better to just avoid having that the first place and the other thing about using prefab services they still have subpar granularity even if you have a prefab that just has a single mana behavior on it you keep in the project it still has a game object and a transform in that file as well and maybe you never really touched those but it still means that kind of when you're looking in the file there's this extra data in there there's the extra overhead when you're loading it it's kind of it's it's extra stuff and as game programmers we like to be efficient with things we don't like having redundant extra objects kind of just hanging around for no reason the other approach that people will sometimes take to dealing with shared state in particular is to use c-sharp statics so to have a bunch of static variables and you store your shared information in that and that also works you can you can do that but it's very very DIY so the first problem is that there's no civilization support for that at all if you want to kind of store or maximum enemy health and like a static int max enemy health variable or something like that is going to be completely up to you how you fill that variable out and loading and saving that information there's also no way to visualize that in the inspector you know you have to write custom editors that will read your statics and kind of show you in the in the inspector what those look like and they all get lost whenever you reload the domain as well so if you try and do support for recompiling in play mode all your static data gets wiped out so you can do these things but you have to kind of roll a lot of support around it yourself and you're using an engine like unity because you don't want to roll your own support for all of these basic fundamental things so let's talk about sculptable object it's about a behavior gem but not a component it's actually quite literally what it is on the engine side we use the same class to represent script object a mono behavior except that a scriptable object isn't attached to a game object that's literally all it is so you can have your own custom class your own variables your own functions make an instance of it it doesn't have to be attached to anything it doesn't have to have a game object in a transform it doesn't have to live in the scene be wherever so it can't be attached to game objects prefabs at all it does not get callbacks and this might seem limiting at first but when you consider the callback chaos situation people could end up in this is also kind of a blessing you know that if you have a lot of your objects built as these things they're not participating in what's happening from a frame to frame basis so you don't have to kind of reason about them if something's going wrong you can kind of just rule them out pretty quickly you know it's not descriptive objects that are doing something every frame because they don't do anything and they can be serialized and inspected just like mono behavior that includes writing custom editors using property drawers etc it's a fully understood object within the unity ecosystem so we'll talk about now how script or word it solves some of the problems that we were talking about the beginning with mana behavior so firstly in the matter of shared versus non shared state kind of mixing together these things having scriptable object really encourages a strong separation of this stuff so usually people end up using it for shared state only so you have a mono behavior that stores your non shared state your per instance state that might then reference or scriptable object that holds all the all the shared state kind of help kind of left over you again you can do this with mono behaviors or with prefabs and so on but script object really kind of encourages this way of working as a pattern so it helps guide you into a best practice when it comes to resetting objects when you exit play mode you can have scripted objects that don't live in the scene but live in the project instead and so they don't get reset only the things in the scene get reset and this allows you to tweak all the values in a scriptable object during play mode exit play mode and they're all capped so if you're keeping all of your timing and gameplay tweaking tweaking parameters in description object you can tweak on the fly during play mode exit play mode and none of your work is lost it's all it's all about it's all kept the matter of sub file granularity how descriptive objects live in files well you have total control over that you can have a dot asset file in your project that contains exactly one object a scriptable object you don't have to have extra game objects and transforms hanging around you can pack several scriptural objects into one asset file if you want to it's up to you we have the api's to let you kind of do all of that and I'll show you how some of that works later on and lastly callback chaos as I said they get basically no callbacks they get on enable on disable them destroy and that's it so because they don't do anything it's a much lower cognitive load for you when you're trying to debug what you're trying to understand what is happening you can just ignore these things they don't do stuff unless you're making them do stuff so we'll look quickly then just what the what it looks like to kind of declare a script pull objects and work with it from code so that's what it looks like to create one it's very very similar to modern behavior in fact it's almost identical you can see the only difference is the base class there instead of it being mono behavior Unity engine dot mana behavior its Unity engine dual scriptable object that's the only difference beyond that you're declaring variables exactly the same way as you would with any one or behavior and then referencing one also looks like referencing on mana behavior you just have a variable of your class type and it will show up in the inspector it will let you drag and drop in instances of scriptable object it serializes it DC realises it does the works does everything that's great so you get kind of the same way of working with objects and assets that you're used to when dealing with textures or materials and mono behaviors or game objects or anything else any kind of opportunity it's just another one of those units he treats it like just another one of those so how do you create these things then if you don't attach them to game objects there's no add component there's no kind of going to the components menu and pulling it from there how do you actually get one of these objects made to work with so there's a couple of ways so firstly you can create these things kind of just in memory using Create instance on scriptable object as a static method so this is kind of like your add component but there's nothing to add it to so you just create one of these things it's free-floating in memory it's an object that exists it's the same as doing new new material or something like that in theory we could have done this as a constructor I don't know why we didn't historical reasons I guess once you've made one of these things you can also bind them to an asset file in the editor all the asset files that you create all the custom assets you create they have to have the dot asset extension but having made one of these things either using as a database that create asset or using us a database add object to asset you'll been able to kind of pack these things in so this is how you have the control over how these things are but you get that you can just have one object in a file or you can kind of keep on packing extra objects into one asset file whatever suits this kind of a situation you're working whatever suits the way of thinking about the problem that most make sense to you and you can automate all this as well in 5.1 I think we added this create a set menu attribute and this makes it super super easy you stick this on your script roll object class and then it will show up in assets create and you pick that and it will make an instance of the object bind it to a whole new asset file and then that you're done simple as that and you can customize the file name and the menu item name and so on with parameters on the attribute but simple as adding that one string and you've got instant kind of custom asset support in your project so the callbacks the script object gets this is going to be a very short slide firstly we get on enable and we get this when the script will object is first created so before create instance returns or we get it when the object is loaded we also get it after a domain reload so when script to recompile in the editor at the end of that process when we finish reloading everything any scriptable objects that exist in memory get on enabled to kind of warm them back up again we get on disable which is called when a script object is being destroyed or before it's being destroyed and also before we are shutting down the app domain for assembly reload amia on destroy which happens when it's destroyed the name is pretty good then and that's it that's all you actually get on a script of l'objet by default so there's not a lot to learn and you can see that most of the things that these things might do on their own is going to happen around you know startup time reloading the app domains and so on the rest of the time per frame bases frame to frame they just don't do anything unless you tell them to you can add your own functions to a script object of course just as you can add your own functions to a mano behavior and you could call those every frame if you wanted to but there's no kind of magical oh I put this thing in and it happened to be called update and that's and so unity is calling it every frame and stuff like that there's no magic methods beyond these three so that's that so let's look a little bit about the life cycle of these things I've shown you how they are created what about after that just pretty much the same as any other asset you know once you've made a texture or a material or something like that the life cycle is the same as those as long as you're as long as you still have it you're continuing to use it it's going to stay alive the point at which it usually will go away is when you call resources unload unused assets and this is where we're going to scan through all of your mana behaviors and and objects in the scene and see which things are referencing which other things don't work out this script of logic that you you created it's no longer being referenced by anything so we're going to get rid of it you have some control over that you can stop that from happening using hide flags and we'll talk a little bit about a pattern that you can create with that later on just a little note then on destroying and destroying immediate because this is the other thing that people will sometimes do with riffle objects to get to get rid of them just as you can do with a material or texture or whatever it's important to remember unity is not a dotnet engine this is something that sometimes takes people little while to get the head around it's not a dotnet engine it's a C++ engine with a dotnet layer and what this means is that a lot of the objects were pretty much all the objects in unity they have this kind of dual personality thing going on they have a C++ representation and they have a dotnet representation so I've got my scriptable object that exists in memory right now and there's part of it that exists in managed land and that's the c-sharp class that you declared okay and there's also a part of it that exists in C++ and for all scripted object instances that you ever create we have the same class on the C++ side it's actually the same class as we use for mono behavior and the C++ part takes care of the identity of the object it provides things like the instance ID it takes care of referencing it takes care of sterilization it takes care of making it available in the inspector things about Patton now when you call destroy or destroy mediate people assume that that kills off the whole thing that's not what happens okay yeah so you've got C sharp art it has a reference to it from some other field so when you destroy the scriptable object what actually happens is we kill the c++ part we cannot kill the C sharp part there is no mechanism for immediately destroying an object in C sharp closest you get is something like AI disposable but even that doesn't actually destroy the C sharp object it tells it they're going to clean up its stuff but the object itself is still kept around in disposed state until all the references to it are gone so this is a common problem people will run into sometimes not just with scriptable object but with any kind of asset that has this scripting component and native component which is all of them having destroyed it the scripting wrapper the the managed part can be still kept around by that reference from some other field the only way to actually get rid of that c-sharp part is to make sure nothing is referencing it anymore and then let the GC run and the GC will eventually clean it up at that point so when you're destroying things it's a good idea to also null out the references that you've got and don't be mistaken into thinking that if it appears to be null than it is now because that's another thing that we do in unity is if you've destroyed the C++ part of the object we will pretend it's null and this can make for some nice things but it also leads to some horrible problems so be aware of that one so that's kind of all their walls about script object itself it's a pretty simple class I think there's one page about it in the documentation and this is why a lot of people don't know about it they kind of just sort of scan through and miss it in it's like you can make an object and just like have it set there okay I'm going to make rigid bodies now just kind of sort of forget about this thing because it's so you know you blink and you miss it it's so simple but simplicity is a good thing in our architectures we want simplicity we want to use really basic objects that don't do very much so there's less to think about so let's look at some patterns that we can do with this stuff so the first most common one is to use these things as completely pure inert data objects so we're talking about having a class bound to it out to a dot asset file maybe that just holds data holds shared State usually so I've got an example here of like a drop table for killing a goblin in an RPG or something so we've got like no three potential objects it could drop and the chances of dropping these and so on so we can share all this information between every goblin in the game we say whenever you kill a goblin these are always the drops that you get from that and we could for one particular goblin maybe say okay we're going to make him use a different drop table but we don't have to kind of set up the whole drop table on each one individually Ren have to copy it all whenever we instantiate a goblin this is also a demonstration of using property drawers and custom editors to produce a slightly nicer user experience you can save what sliders for that chance there that's just sticking the arranged attribute on a float because actually what it's storing is just a float between 0 & 1 but putting the slider in there means that this is slightly more nice for a designer to go in and just tweak what the drops are for my standard goblin one of the approaches people could use to this as well is to use like a real database to go and use sequel Lite or something like that and that's that's a valid approach but using this using script object instead it's got a bit less overhead than that you know you don't have to deal with kind of setting up the sequel Lite database and loading it and kind of getting it all in place and to smoother UX as well you know your designers your artists they can edit all this stuff and exactly the same ways to edit any other object in unity and it's using familiar UX paradigms it's you know completely completely standard and referencing these things works very smoothly as well with with a standard database you have to get some way of identifying kind of which row in the database to work from I haven't yet seen anybody come up with like a nice solution to just be able to drag and drop you know this row of the database into this object here please descriptive objects will let you reference them just like any other object so the next pattern the one that I quite like because it's sort of counterintuitive is to have empty scriptable objects but you're bound to asset files and you might think well what's the point of an empty object you know what can you do with an empty object you can't store any data or not what you can do with an empty object is you can compare it you can test equality on an empty object so that means you can use these things as dictionary keys for example this is very similar to what you do with an enum right in principle when you have any numb you declare your different values you're not supposed to do arithmetic on them I mean you can but it's kind of not what items are about the only things you can really do with enum values are compare them and say is this equal to this enum value I'm expecting or is it something else and you could do exactly the same thing with these but the key advantage here is that your designers can add new values to the enum without changing any code once you decide that actually it would be helpful to have some information associated with these things there's also very natural extension to go from these empty scripts of objects to a non-empty scriptable object so maybe having created with these different damage types that exist in my game from different weapons and different attacks and so on maybe I want to add a bull for kind of which ones the player is vulnerable to or something like that so I could add that in one place and then just go through all these damage types and just check the ball on on or off instead of happy to have a separate lookup somewhere from each of these to the corresponding bull values it's usually faster to kind of put the data on these things directly and that's something you can't do with the numbers at all you can't really kind of add extra data to each enum entry very easily the other thing you get with this is null support so with an enum you might need to introduce like I sort of a nothing entry or something at the beginning with this nothing is just no reference and that ends our working really nicely for a lot of situations okay so let's look at one's quite dear to my heart dual serialization so scriptable objects are fully supported by json utility which came in 5.3 so you can you can make a scriptable objects and you can use JSON utility to json to get instantly very quickly a string with all the serializable data in that scriptable object that you can then save to a file or things like that and this means that oh yeah and you can do serialize them as well of course I think you have to use from JSON over right rather than from JSON but you can make an object and then use use over write to fill it with data that you got from Jason and what this means is that having built a system that works with scriptable objects you can mix objects that you created a design time that you saved into asset files in your project with objects that you've created by deserializing Jason what might you do with that well pretty good example would be something like user made levels so given a scriptable object that stores all the information I mean so suppose I have a simple platformer I've got some platforms I've got some enemies I've got some coins to collect and I've got to play a start position and you know the game is you collect all the coins you avoid the enemies and once you've got all the coins then the level is done you go to the next one now I don't want to ship my game with no levels I want to make a bunch of levels in the editor ideally using using the scene view using the standard tools I'm used to so I do that and I write a very simple editor script to kind of collect up all the things in the scene and pack them into a scriptable object for me so I can have a bunch of object assets just Sattar my project that I'm going to ship in the game built in but I also want to ship a level editor I just want to ship something on device you can go in to go into level edit mode drag drop replay your platforms in and so on serialize the J so maybe share it online what if you like so when it comes to loading these levels in then I can load in the built-in ones maybe for an asset bundle simple as that but I can also get them from Jason so I make an empty level layout object i read all my json from a file somewhere alright downloaded from a server or whatever and then i overwrite the object with the json data and after that i can use that object i don't have to care whether it's a level that i ship built in came from nasa bundle i level it came from jason it's a mixture of the two even one of the fun things you can do with json override is actually patch part of an object so i could even have like a level that i ship built in that a user has like customized only to start position and i load that over the top or something like that but after this is done this just abstracted away all of that kind of concern over whether it's built in or kind of user provided or things like that let's look at some reload proof Singleton's so coming back to the domain reload issue whenever you recompile code in unity we serialize everything throw away all the manage code loading the new assemblies that you built and bring it all back again so I've got my scriptable object with a sushi art partner c++ part and i've maybe got a static reference to the c-sharp part so i'm using this as a singleton maybe i'm using this to store you know the current scores of all the players in the game or something like that in principle if i then reload the domain in the editor if i recompile during play mode i'm going to lose all out later i'm going to lose the static field reference i'm going to lose the c sharp object so we've got some data that's on that now when it comes to domain reload the data gets c realized through and then the main real it happens all the manage stuff is gone the data is still safe on the c++ side because this is a scriptable object it's being serialized so we then restore the manage part bring the data back again how do we get that back static field well we can use find objects of type to then locate that singleton object just like searching for a material or a texture or anything else we can look by type for all the objects that are active find the one of the typing question dope that we want to get back so final bit type we'll then restore that static field for us it won't it automatically we have to call it but show you love code example how this might look in practice so we've got our singleton class deriving from scriptable object we have our static field that's going to point to the instance of the singleton that we want to work with it i've made it private because what we're going to do is we're gonna access it via a property and the property is going to make sure that all of this stuff is kind of implemented so we have the actual instance property bear with just a getter the first thing it does if we don't already have that instance field populated then we'll go and try and find it we're going to try and find an instance of our singleton type if that fails then we'll make one we didn't create one of these yet we didn't have to do it by create instance I mean maybe maybe we wanted to do this by going and loading an asset or something like that maybe once a singleton that's kind of ready-made for an asset bundle off from the asset database and there's something that I think I didn't include on this slide is we'd also want to set the hide flags on it to say it shouldn't be picked up by the garbage collector we want this thing to kind of live for the whole duration of the session work in unity but once that's done you know either we found the existing one or we made one at that point it should you know it should exist we should have one so we can just return it at that point this is a super simple pattern that will kind of get you these objects that will survive a domain reload that you can kind of keep just referring to and then after the main reload you go to access the instance and it's as if it never went away okay one more closing brace another pattern that might be familiar to anyone who's done Mac OS or iOS development is the use of delegates and this is where I remind you that scriptable objects like mana behaviors they can contain methods not just code and this means that you can use them as sort of pluggable implementation or a strategy pattern so you have something in the scene that needs to do some work you plug in a scriptable object that's going to actually do that work on the object in the scene calls the scriptable object to implement to do whatever needs to do and the scene object maybe passes in itself as like this is the thing that's trying to get the work done so the scripts of logic kind of knows what to operate on because if you're storing a scriptable object in an asset it can't reference things in the scene it has to kind of receive those dynamically and this is true again with you know with anything else you store in the project a prefab that that you have in a project it can't reference things in a scene you can't guarantee that scene is loaded it has to be shared so you can have a scriptable object that operates on things in scenes as long as they're passed in kind of on the fly so thus implements of strategy patent that's the same so an example of this suppose I have a game bullet-hell scrolling shooter or something like that and I'm picking up power-ups as I as I fly through I have a base class for my powerup effect oh that should have been scriptable object actually I forgot the proper base class then but it's an abstract class on its own it doesn't do anything and it has this abstract void apply method the idea is that whenever anything picks up a powerup it's going to call apply so might actually have up neo ontriggerenter or something like that for when I hit one of these power-ups I'm going to destroy the powerup I've picked up and I'm going to call apply on its effect I think that finger touched in so that's the kind of abstract base of it to look at an actual example then how we would derive from that so here's something like a like a health powerup so we're deriving from powerup effect we're implementing this apply method and we have a tunable amount value so I can have two or three different health powerup effects in my project you know a small buff big buff ultra buff and oh and even something that removes health as well I could just have a negative amount value so I pass in the player object or the NPC or let me picked up the powerup as the collector parameter and I been inside the scriptable object I'm getting the health component I'm adjusting the value accordingly so I'm delegating the the behavior of what should actually happen in that powerup after the script of Lodger on the side and again you could do this using a prefab as well that will be fine but this lets you kind of separate out the actual kind of gameplay consequences of this powerup from all the other details of you know the collider and the visual aspects and so on a lot of the value in all of this stuff is in separation of concerns and being able to say okay like I want to pull out the audio properties of the game people please to be able to keep them somewhere where my designer or model you know my audio guy is just going to work on those without having to kind of go and trawl through this prefab and find one component somewhere in the middle of it that they want to work on so here's a second one insta-kill it's kind of a harsh powerup I guess so you get this one and we'll make an explosion and we'll destroy whatever picks it up so yeah you don't you don't want to get this one but again you know we can have a whole bunch of different instils of different explosions you want a nice burst fireball you know whatever it's the same same code same object type just different instances configured in different ways and then plugged into different power-ups so let's move on to the live demo so this is based on the demo project that was used at the Boston training day last year it's called tanks it's pretty simple like two-player you know you drive a tank around you try and fire shells at the other player and you try and be the last man standing so there's a few things I'd like to do to that project so firstly I'm going to see if I can improve the audio a little bit because they've got a few audio clips playing kind of repeatedly but there's some stuff we can do to very very quickly actually make the audio a little bit more impressive second thing is I'm going to add destructible buildings because who doesn't love destructible buildings so today I'm going to add pluggable AI so the demo that they made the training day was purely human controls one player on WASD the other player on the arrow keys I'm going to add a light driven players as well I'm going to make it kind of configurable you know so do you want to play you know a live versus AI even or you know two two humans versus six six AI sore or whatever you like and we're going to look very briefly as well at kind of configurable game settings with load save so this is where everything is going to get horribly horribly wrong so let us show you to start with the original original project so we've got two players I've got so the blue tank on wsd I've got the red tank on the arrow keys and I can fire shells you hear how repetitive that sounds that's that's pretty dull right I can find the take on the red tank my aim is usually terrible in this so yeah there we go one ray thank you thank you so let's look then first and improving the audio and using script or bullets to do it so I'll step forward to some stuff I made earlier so what do we got here now so we've got some audio clips I I went to archive dad I found some creative commons different explosion effects so I've got a few of these different will be loud okay right and I've got this audio event scripted object type so a bit bigger so super super simple right it's a basic scriptable object type it's abstract and it has this play method so you pass it in an audio source and it will play itself on that audio source somehow so this is an example of the delegate baton right so to go with this I also then have a simple audio event so this derived from audio event this is one particular implementation of this so this is yeah so similar to the power up thing so a simple audio event has some audio clips that it can choose from it has the volume it should play at and I mean I've introduced this ranged float type that it's like a min and Max float pair so that every single time I play on pick a random value in that range just to vary the volume a little bit and similarly for pitch I've also got this ranged float so I can vary the pitch slightly I mean burning volume a pitch like for the bread and butter of beginning to get like audio variation super super simple so when it comes to actually playing this if this audio event on an audio source I've had the source passed in I pick a clip at random between 0 and the number of clips I've got I pick a volume value between the marrow max volume I pick a range value of a pitch value between a minute max pitch having to figure the source I then play and that's it right so what I've managed to do here is I've grouped together all the parameters that my audio design is going to care about and I put them on an asset file that can live in audio events somewhere so he just focuses on that folder he doesn't have to worry even about where this is going to be used you can just focus on like you know he's been told make a really cool explosion sample when the shells go off he just makes this and then B gameplay designer maybe later on we'll go and wire this up we also have create a set menu being applied to this in which means that I can go now into unity and I'll show you I can go into unity I'll go to the assets create simple audio event and it will just be there and it will just make an asset from you straight away so don't that so I come in here and create audio events simple okay so it's popped up straight away I call that explosion alright so a lot the inspector put all three of these in here alright now let's get some some volume in there it was pretty loud before salsa flowing quite low now pitch of one would be kind of normal speed so we'll start around there maybe a little little little power actually now I can also I've written a custom editor for this which lets me actually kind of simulate doing this in edit mode so I can hit preview yeah about but I can hit it a bunch of times suddenly I've got a lot of variety in this event so it's one event from the gameplay codes point of view it's just going to trigger this thing and it's going to get all this different variety at what's going on and I can tweak this pretty easily as well you know make me a bit longer okay one of those is kind of loud ice predict it out so I've been the whole wing down a bit more now you can imagine as well it would be quite easy to have other subclasses of audio event but have different approaches to how they pick the sounds I mean it's quite easy to end up with picking the same sample twice in a row and similar volume valleys and similar pitch values so in that instance you'd end up with two cells that do sound pretty similar and maybe you want something a bit more elaborate that very deliberately kind of you know doesn't use the same sample twice in a row or things like that you could implement different strategies for this stuff you could do something that used weighted random as well to kind of control the probability of picking these things so I'll show you as well the the editor code the one with this so yeah by the way this project is on bit bucket you can go and get old code pair so don't worry about memorizing everything so yup so inside the custom editor for audio event again as applying to every single audio event I don't have to write a separate custom editor for each subclass to do this when I enable the editor so when I'm when creating those I make a hidden game object with an audio source on it that I'll use to kind of be my my audition source when I disable or clean it up again because I'm you know a good citizen and then in the inspector I go through and I had this preview button and all I do for the preview button as I say play the audio event on my fake audio source okay so that's auditioning support in ooh 32 lines of code I probably have some unnecessary usings as well yeah I probably don't need to answer yeah okay 31 lines of code okay so I'll move forward a little bit to having kind of set this up already right so I've got my shall explode event again similar similar things that I've already had is my prefab forgot my complete shell unlike the inspector and yet there we go so I've added in a variable just a reference to an audio event that should be played when the shell is exploding so plug in shell explode yes so it's gonna be a lot more interesting now with a little bit variety in the sound but I still think it's a bit loud I'm going to tweak it here and I say okay the point is quite low already but I'll bring it down a bit further water made some other changes hit the high pitch okay high-pitched time I know Alex the play mode has kept all the values right so I just did kind of live mixing an audio balancing in play mode okay and that's all saved and I can check that into version control and share that with rest of my team very easily cool I even had notes on how was going to do this laughing neat it was brilliant cool okay so let's look at destructible buildings then step forward of it the same okay so again because this is a scriptable object talk I've made some trips of objects but I've also made a mana behavior so the idea is that we're going to have the scriptable object again act as kind of a delegate we're going to have a mana behaviour in the scene that interacts with the physic system and collision and triggers and things like that in order to kind of make this stuff happen so to show you them very briefly here so it's the model behavior so I'll attach this to any object in the scene that I want to be destructible so all the buildings anything else the tanks already have their own kind of custom stuff so I'm not gonna not not gonna mess with that but for basic objects that need to be in the scene have some health and when you run into them enough times to blow them up with type with shells and half times they want to do something I'll put this on it so we're going to store a health value per building obviously that's not shared state the damage per tank collision yeah in principle that could have been shared in practice the buildings are kind of diverse enough that I'm going to set one of these separately for each building and I have a reference to a destruction sequence and that's going to be my scriptable object so there's a take damage function and I've changed the shell scripts in here as well that when it impacts with something it sends a take damage message to it with like you know 10 points of damage per shell or something like that so this is this is how it's going to kind of receive that so if it's already been destroyed health is less than zero to do anything otherwise knock off some health and if it's now less than zero and we've plugged in a destruction sequence we're going to start the co-routine that is in the scriptable object and this is a fun thing about co-routines as well they're just code right they're just a function that returns an enumerator and they can live anywhere you don't have to define a co-routine on the same mana behavior you run it on so the mana behavior is going to be responsible for kind of holding that enumerator stepping it every frame and kind of making the expert happen but the code is going to be in a different class entirely okay and yeah so we also have on collision adjust if you drive into one of the buildings as well we'll also take some damage from being run over by tank which seems fair enough so to stretch the sequence then again it's an abstract class driving from scriptable object so it's a it's another delegate pattern and you see this time so I'm passing in the model behavior that it's going to run on and it's going to return an ienumerable co-routine instance that can be step through so if I step forward then two slightly more completed version right so now I've got a couple of actual destruction sequences made so we have a controlled demolition and this I've given a bunch of parameters so we spent a while tweaking this because it's really fun to watch buildings fall down so we've got you know is it gonna you know is going to shake around is it going to kind of slide into the ground or just like scaled down is it going to have like dust effects kick up and all this stuff so I have this car routine that may actually implement the earthly effects I'm passing in again the mana behavior from from the building that that kicked this off and so I'm able to go through I'm able you know grab grab the transform so I can set up things like the position of my dust effects are based on the object that's kind of executing this yep so I've got my particles morph if we're collapsing it then play some claps audio events whole bunch of stuff it's it's not worth kind of going through it in a little detail it's good even camera shaking that because I'm good and then there's like a slightly more simple one which has just spawned some kind of big particle effect and hide the building behind the effect so you get a big dust cloud and then when the dust clears the building is mysteriously gone so much more simple make the effect wait for a certain amount of time and then destroy the original thing pretty easy if I go in and I want to start actually using some of these things I've got instances of them set up already so I've got big building small building the radar towers always a great one I'll do that one so I put the destructible scripts on here already and I'll just plug in that radar tower effect so when you look at the real Italians it's going to take five seconds to come down it's going to slide into the ground and stuff so give that a try my aim is terrible I told you yeah there we go yeah that's good and I can maybe grab all the small buildings as well and say well they should all have the small building destruction sequence but maybe that one should just have a like a big explosion etc so I can have like a whole library of different collapse things that maybe my effects artist has gone through and kind of tuned and tweaked and then made lovely and then I go through and kind of just decide okay I'm just going to throw these things wherever and it will be great so that's destructible buildings now let's move forward to the AI brains so initially we've got a human controlled characters we've got two tanks we've got player 1 player 2 controls our main difference between them is which which input axes they read from right so I've got a couple of cuddlers scripts in here so we have this tank brain and again we're doing this as a kind of delegate object pattern you can tell I quite like that pattern and so initially when the game first starts up when the round first up starts up and the tank is made will initialize the brain so that's why that's that's why I'm pulling these things different different brains so the ideas that will plug in like a player brain or an idiot brain or a sniper brain or a commander brain or whatever and they'll they'll behave differently so we can initialize when the tank is first created the brain is first plugged in and then every frame we're going to get a think call back on our scriptable object that makes the decision about what the tank should actually do and in the case of the player controlled stuff it's very very simple this has pretty much just been taking the code for moving the tank that was already in there and kind of just refactoring it out extracting it into a scriptable object so the think step steers the tank according to the input axis so checking horizontal and turn and then if it's shooting it starts charging the shot and if you've let go the button that it passed the shots to fight them so we've provided these two components tank movement and tank shooting that allow the brain to kind of control the mechanisms of the tank movements charging a shot releasing the shot etc the brain makes the decisions of when they do these things and how I wear and so on so if I step forward now okay so now I've got two more two more brains added to the project a simple sniper who's going to pick a target turn towards it charge a shot based on some guess about how much it you know some proportion about how far away it is and then and then fire and we got a random Walker who's just going to like turn for some period of time move for some period of time and then fire some arbitrary distance so having done that I can go into my game manager here so I got I've got I've got my two tanks and there's no brains plugged in right now so I got instances of these brains so human one of human two using human troll brains and then play a number just to kind of control which set of input axes it reads from I have the idiot brain which is the random Walker and again I can I can tweak you know how long does he sit still for how long does he move for and then how long does each are the shots for at the sniper who again is going to you know this is the I can control you know how how often does he change targets how accurate is this is he you know stuff like that and how frequently does he fire when I first made this thing he was firing like every 0.1 seconds it was very very hard to win at all I can probably show you that actually so I'll put in human 1 still so I can still play but I'll play vs. the idiot and maximize okay so once it starts away he goes you can see why I call them idiot right although I'm not a much better there we go come on sit still okay okay well I'm not I'm not guys help yeah so you can see and now if I may be swapping for sniper I can stop myself the sniper even will be here a while there okay but once this nightmare is just gonna keep on yeah he doesn't really understand obstacles or movement so yeah you know potentially lethal if he has a clear line of fire yeah we could we could watch the powers I'm not not gonna stop yeah okay right and I'll show you one last thing we're running out of time so I already got to go quickly so one of the last things then is expanding this kind of single scene that just plays forever and ever and ever into something that's maybe a little bit more kind of beginning middle and end a full presentation so I've got sir my main menu scene and I have to set up something quickly because it broke and I don't know why so my prefabs and stuff the name goes in there color goes in there brain goes in that okay good yep right so I've made a little kind of front end menu fire this up so I'm not gonna maybe it's not I told you we'll all get wholly wrong okay well the idea here is that we've got game States so we're setting up the the name of the player the color that we're going to happen which brain they're using and that all actually can get saved out to to a JSON file now that should be loading from there see does it work or do it a second time it doesn't okay I can we control the number rounds as well so we'll say two rounds in the game and play now when I hit play one of the things that's just done is it save those settings to a JSON file I kind of open that up quickly with sublime and so you can see yeah okay even though wasn't displaying the names of the players and stuff it did actually kind of have it in the data model so I've been able to save through then all the information and in principle if I fire it up again if the UI work incorrectly at least it would load all this stuff from JSON instead of loading it from in the project so instant load save of kind of game settings and C number of rounds so I did set that down to two and it's got that there correctly okay yeah so that's kind of a little example of dual serialization so I've got like the default settings you might expect when starting a game or it can load it from JSON when you start the game so cool right I will very very quickly finish so in conclusion scriptable object is a vital tool in your toolbox it is only a tool I mean you know I'm not saying give up a model behavior entirely you will need it sometimes but it's definitely something you should know you should understand what you can do with it and consider it when you're kind of designing your architectures it is very simple but it's pretty flexible and you can build really nice architecture and some really really nice workflows for working in your team working with your artists and designers thank you
Info
Channel: Unity
Views: 186,483
Rating: undefined out of 5
Keywords: Unity Scripting, ScriptableObject, Unity (Software), Unite LA, Unite 16, Unite 2016 Los Angeles
Id: 6vmRwLYWNRo
Channel Id: undefined
Length: 57min 33sec (3453 seconds)
Published: Thu Dec 01 2016
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.