Game Architecture Tips - Unity

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
okay so hello and welcome back to another unity tutorial a while ago I made a short I think five-part miniseries on the solid principles I made one video for each of the different principals giving you examples explaining why you would use it and how it benefits you and I've not really done any video about best practices in a while I don't think so I'm gonna do this video here showing you how I've been coding in my own time as I've been working on DAPA tools which is down below and my girl Paige alright I've just been working in unity unity in general I've been designing my classes a bit differently rather than just jumping in making a monobehaviour and shoving all my code in there I've kind of been separating out the code a bit more so we've got like the actual core logic in a normal class and we've got monobehaviour which is used essentially for dependency injection and just doing unity API related stuff and then I can easily write unit tests and it's so easy to expand upon I'm going to show you guys that I hope you look forward to it let's get to the video but of course first I've got to thank my patrons a special thanks to some hobo 101 flow state games average morning Luke Lapham Hades Orko Rene Evgeny art ferrule butter Ray Emery Baldwin if anyone else is able to help support the channel monetarily the link to my patrons down below if not I'd be greatly appreciate if you could go check out our website and actually create an account on there it's completely free you get access to all the stuff we're working on on our light learning platform you know asking for help in tickets requesting Commission's now there's playable things on there go go look at it we're obviously actively developing it so if you keep looking every week or so or every two weeks you might notice some new features I'll be doing videos on those new features as well join our discord server to you know hear about those features and just check out all the links down below a big great appreciate now let's get to the video okay so here I am inside the dapper tools project now I'm not actually gonna be you know developing anything for it I'm actually gonna be rewriting some code I've already written in the project just to show you the actual process of writing this code and my fault process and once it's done I can show you the unit tests I wrote for it and just you know keep on explaining why you might want to design in this way and if you've got any questions about at the end feel free to ask below but yeah let's get to it now so the quick example I'm going to show you first is you might have in your game something that you want to happen after time there's a very common thing you want timers in your game right so you might think okay I'm gonna go write a timer monobehaviour right so you make that timer and you put all the logic in there and you're done right it works but then the problem is you might want to you know be able to reuse this timer not in a monobehaviour sense right mono behaviours are limited to the scene it's I mean you technically can it's very awkward to it mana behaviors if you're not on about something in the scene and components attached to things so if you want to use code just as code and not seen related code you to use normal unity classes or sorry normal c-sharp classes but then you've already rental your logic and a monobehaviour so then you'd have to rewrite it in a non mono baby class but then you'd have duplicate code it's just a mess that's what I'm gonna mention this video so if you look here if I search timer I have the timer behavior and then it has a duration and on timer and the on timer end is a unity event which I can for example when the timer ends what can I do I don't know I can change the name of this object to be hello so it's currently game object and after one second it's going to be hello and if I press play what I see my timer work right timer it changed it to low and the timer goes away now you know I might want different things to happen maybe I don't want this timer to actually go away when it ends maybe I want it to kind of tick again and just keep going every one second called this function it's up to you right it's your implementation it doesn't really matter so the sacred ice I want to show you what I've done so far okay let's go to the code so obviously that code I just showed you I've already written so I'm gonna instead of just showing you the code as it is I'm gonna actually write it out with you and explain how it works okay so I've made a timer behavior and the reason I'm not just calling it timer is because I actually want to have a separate class called timer that I use inside her so this timer behavior is just the mono behavior cannot wrap her around the timer right the timer is not necessarily anything to do with unity it's just a timer and then the behavior absord the unity like implementation itself so what kind of stuff do we want to set in it well if you remember correctly the way we did it we had a duration and a unity event for when it ends right so this is just unity stuff right now the reason we have this float duration that we pass in is because obviously we need to set the timer's duration and we could expose this in the class and make the class serializable but it's actually makes more sense to keep the timer when we make it in a second just completely private in here and we just inject essentially just like dependency injection for used to using di frameworks this is essentially the dependency injection we inject it in the inspector we set all our dependencies in the inspector and those in through the constructor so for example we also have the unity event the timer itself the time I shouldn't know about unity events unity events should be kept in the behavior about unity right mano behaviour unity this is the unity kind of version so on timer and okay and then on start what what you want to happen well we want to actually start the timer right now a timer it's not necessarily something that you start at least the way I've done it it's more of a case of you tick the time it right you take it every frame so we want to actually have a timer class to actually have all a logic rather than doing in here okay so the way we're going to do that is we're gonna create in another class so if I go up to here and I'm just going to create a new okay I'm not using my unit Explorer so instead I'm just gonna copy paste this and just call this the timer okay so we'll call this the timer I'm all to get rid of these things that we wrote so now this timer is nothing more than just a normal sea shark class okay all it is get rid of all the whoops it's reloading okay just a normal unity class and essentially this should do logic that has nothing to do with unity this should be just c-sharp code shouldn't have any using unity anything's now sometimes you will need to have specific references to unity related things in here but if you can avoid it try to and you know it helps a lot so for example we're gonna need a constructor to make a new timer right so public timer that's a constructor currently does nothing but obviously we want to when we make a new timer pass in the duration of the timer like you know how long does it take to donate so we'll pass in a float duration like so and in the constructor we want to set a variable basically to be the duration so we're gonna need some way to store you know how long is the timer got left so let's make a float now we might want it to be private at the start but then again other things might want to read how long's left on the timer right that seems like a very common thing you might want to do so we'll make it public a public float remaining time or remaining seconds right and then this is gonna be a getter but because we need to change it as well like each frame we also want to make it a private setter so you can't set it externally you can only read it but internally we can set it and so here we'll say remaining seconds is equal to the duration so as soon as we make a new timer it sets the time make sense and then we also want to have an event to raise when it's done because we want something to happen now we don't use a unity event because we know maybe we're not using the unity maybe this is just a another application you've got right some kind of timer and some of a game maybe you're making a c-sharp game without unity this code can still be reused we just want to use normal C sharp events so I'll make a public event action on timer and okay and now we need some way to actually tick down the timer so we'll make a function for it public void tick now we need to know the delta time normally you'd use time but the other time but maybe you're not using this in unity obviously you are but the point is you want this kind of stuff to be passed in you don't want to have the dependency on unity for this code there's no reason to well let the amount of behavior handle that so which one we passed in a delta time this also makes it easier to test because when unit testing you can pass in the delta time to be whatever you want right and then over here we're gonna basically say well if the remaining seconds is zero return because if you're implementing it differently to how I did earlier you might not want the component to go away when it's on zero so no matter what the timer will never tick down if it's at zero right it will just stop we also want to then say all right well reduce the remaining seconds by the delta time because that's how much time has passed since the last time you call tick and then we might as well have another function for checking if the time is finished so we'll call that check for time at end oops sorry and then we'll do control dot enter make the function it's a private void check for timer and and this will just have as you know the logic for doing that so we want to say well if the remaining seconds is greater than 0 f return so we don't want to do anything else if we still got time left but if there is no time left then instead we want to set the remaining time to zero because it might have gone past zero right when you have the Delta time being bigger than the actual remaining time so let's say there's 0.1 seconds left but Delta time is point 2 then you take off point 2 and you'll end up being at minus 0.1 so this just sets it back to zero it's useful if you want to display something on the UI from a timer if you go negative health or negative time or negative anything you always want it to stop at zero if it makes sense to so just gonna set that back to zero and then we're also gonna raise the event because at this point we are done if we you know reach this point so on timer and question mark don't invoke so the question mark basically says if it's not null if there if there are any listeners then we'll do it otherwise we won't okay and that's it for this that's the timer done we just take the timer down when it's done it raises an event that's all the timer is now we need some way to tick this using the game loop and then unity the game loop is the update function really so first of all we're gonna need a timer to actually tick so we're gonna say private timer timer okay so here's our timer it's not at the start though we just have a way to store it and we're just going to say when we start the game we'll set the timer equal to a new timer with the duration duration we're setting it in the inspector so obviously the timer class doesn't know anything about unity or the inspector but we're making use of this layer of abstraction using the monobehaviour to pass the data in and then we're also gonna subscribe on start for the timer on end so when the timer ends what do you want to do we want to for example handle timer and okay we can then generate a method for that so it's a private void jet handle time rent and here what we want to do well when a timer ends we want to raise the unity event for it sorry my throat's a bit sore so when the C sharp event is raised are actually raising a unity event now some people might complain about the performance of this saying oh well events you know whatever whatever but to be honest it's really not a big deal you know it's definitely worth the payoff for this slight I mean you it won't even you even notice it really unless you're doing every frame unity event some people seem to be scared of unity events because of you know the performance but yeah unless you're using them for every little thing every frame it's not a big deal because the fact it allows you to be so much more customizable customizable I don't know you know the problem with using normal events like c-sharp offences you can't dynamically like design with them in the inspector it's very hard to do that that's what unity events are for you can design event callbacks whereas normally if you want to use c-sharp events you have to do it in code you don't want to have to go in code to change stuff when you can just do it needs vector it makes life so much easier right like just there when I showed you earlier in the example on timer and change the name whatever if we actually wanted to do that in code we'd have to write an extra class to handle that subscription to that specific thing and it's just a mess right use unity events where they're meant to be used and it'll save you a lot of time and a lot of typing and a lot of everything so yeah we're gonna invoke that event and then the way I set it up is that we destroyed this we destroyed this now you don't have to but I might as well go ahead and do it right so destroyed this which destroys this component not this game object right so this component goes away but the thing it's on doesn't it's up to you what happens whatever else happens and then all we need now is to actually tick the timer so private void update on update we want to say time add optic so we're gonna tick the timer it wants a delta time so we are the unity layer we are allowed to know about time to double time the unity class so we can just pass that in okay and there you go so now we got this timer behavior and we've got the timer which actually has the logic for a timer we just do the extra layer stuff on top of it right so if I go back to unity there's not even any point showing it really cuz I showed you earlier and it's probably gonna have some problem because now there's two classes is the same name it'll probably just come up twice yeah timer behavior they're both the same really so yeah it works okay now you might think okay yeah that's it right that's nice and all it works but you know another huge benefit is if your unit testing which you should be doing this makes it so much easier to unit tests because some people will say yeah you can just create a new instance of a monobehaviour you can like in your unit test make a new game object and add a component but there's no need you don't want to worry about any unity stuff in these kind of unit tests you just want to test you know you you assume unity have unit tested their own code and sorted all that out you just need to unit test your own code you don't need to test if any unity stuff works just if your stuff works so if I go over to the unit tests real quick so they go it's the runtime tests opponents timer tests you'll see here I'm over screen when I bring it over here are my timer tests okay so for example when I here we make a new timer right nothing to do unity just a new timer we pass in a duration and we just make sure that it's the remaining time you know it is a pretty rare test it's not necessary but I just find do it it's a test where we're making sure that when we pass in the starting duration it's the same as the remaining time okay probably not necessary to do this test but I've done it so whatever right now we're checking does it stop at zero when we take below zero so because I said we pass in the Delta time rather than it getting it from unity we can now kind of fake pass in fake time just to see if it works right so we're saying okay here's our timer and it's got one second left and we're gonna pretend that two seconds have passed now you would expect maybe it would go to minus one but you don't want it to happen you want it stopped at zero so we're saying is it 0 if it is 0 then it certs true meaning the test passes so this is a way to make sure that we can't go below zero then when the timer ends is the event race just making sure that I still works so we're saying well here is a timer with one second on it we have a boolean that's false but if the TAT when the timer end is raised we will set the of this boolean to truth so it's false but it becomes true when the timer ends then we say ticket for the 1 second and because it's a 1 second timer this should get set to true and then we say if it's true and then over here we want to say let's just make sure you know it's nice and all saying the event is raised when the time it ends but for all you know the event could be just raised anyway so let's do a check to see if the timer isn't sorry the event isn't raised if the time it doesn't end so we've made a one second timer and we take it for half a second it should not have been raised they should it's false it should be raised false right so these are my tests for the timer and obviousiy if I go to my test Runner and run whatever you'll see it passes I mean component timer test the head they're all here yeah I'll just close this down here at the time of test and they all pass okay most of them take zero seconds or 0.001 you know super fast to do obviously there's all these different versions of this one I've done but yeah that's basically it right so we've got unit test for my code I know this timer code works it's very simple code the point is it's very easy to expand on this now I've done this same logic elsewhere right I've got other places in my code where I have a class and a behavior version of the class and if I go to for example movement I've got a movement class and a movement behavior and the movement behavior has this now this is a quite common thing I've done as well if I want to ever reference the class from another behavior right let's say I want to reference the timer from somewhere else what I'll do is I'll make the timer have a private field but a public getter and what happens is when I request the movement we check if this like private variable if the pro field if it's not null we return it but if it is null we create it so it's called like lazy loading rather than loading on start we load it when we need it which most the time will be right at the start so it's fine anyway but what it means is if you always refer to this variable rather than this one then it means it'll never be null or at least if it's null it'll create a new instance and then give you that one right so when we say take a movement we don't have a start function in here saying set it up we just untick we say it refer to movement so we're going to get movement we're gonna say okay movement if this is not normal return it but because it isn't all the first time you request it we set it equal to a new instance now the movement class only cares about the character controller dependency that's the dependencies I need to know about our character controller to move so this the monobehaviour passes it in right that's the point the mana behavior is to kind of wrap this around and the mana behavior handles the ticking and the update right this isn't a common thing if you need to tick in your normal class you pass that in through the mana behavior with Delta time this is quite a common thing and it makes sense then inside the movement class we set our thing here we have movement modifiers this is some of a I don't need to explain how the code all works the point is all this code you see I can add modifiers here now the way you can even call this function is by referring to the movement instance in here through this getter right so another class can refer to the movement behavior so we can have a serialize filled movement behavior and then from movement behavior you can say moving behaviour dot movement dot add modifier and you can modify the movement and stuff so I hope you enjoyed this video of C it was kind of short just explaining a design pattern if you guys have any questions about it feel free to ask below feel free to give suggestions and improvements some people might not see the use of this if their game scope is really smaller than new but believe me as you design you know bigger games and more complex games this kind of design can actually help a lot because when you for example start using interfaces one problem of unities interfaces in the inspector so you can actually still make use of interfaces you'll have to make different monobehaviour instances but it's actually a much better trade-off than just not using interfaces at all because it allows you to avoid writing awkward place like awkward code away you've got like switching and casting so you've got to say like oh if it's the type of this then do this oh if it's this type do this or if it's this type do this you don't want that in your code really the only time you ever should use enums and switches is when you've got like code that won't be expanded upon when you can like be a 100% certain or like 99% certain you'll never come back and change it if it's the kind of stuff that will change based on the content of your game let's say you have a a damage types enum for like physical and magical and whatever well the thing is you never know down the line you might for sake of content want to add new damage type so that's when you should make it not necessary an interface but you might want it to be a scriptable object because then what you can do is you can create a new scriptable object instance and put the data there without having to go to an enum file add a new enum instance and then go to some other code and then add a new switch case for the enum you know it makes much more sense to use interfaces and stuff like that so if you want more videos on design patterns for your free to let me know that as well if you want more videos and interfaces and just how to write better code you know ask those as well that includes not just unity but other areas like every area object-oriented programming should use solid and because that's one of the main points in object-oriented programming is you know how to write it well using the tools you've got like interfaces and abstraction and in unity say sense its components which will then lead into ECS which is a completely different ballgame but anyway I hope you enjoyed this video you know you can get access to this code in my go repository for DAPA tools down below I hope you enjoyed the video I'll see you guys next time thanks for watching and good bye
Info
Channel: Dapper Dino
Views: 60,897
Rating: undefined out of 5
Keywords: Unity, How to, Architecture, Game design, Best practices, SOLID, Interfaces, Abstraction, C#, Composition, Design, Software, Game, Video game, How to make a video game
Id: pRjTM3pzqDw
Channel Id: undefined
Length: 19min 52sec (1192 seconds)
Published: Mon Dec 02 2019
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.