Unity Tutorial: Callbacks and a (Really Awesome!) Event System

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hey folks Quilly Keene here and welcome to a unity tutorial on callbacks and events actually got an email the other day asking about how to do callbacks and things in unity and I was surprised like haven't I done a tutorial about this before and while we have done many callback key things in many of the tutorials and games that we've made together on the channel it doesn't look like I've made a video specifically oriented on that so that's what we're doing today first it's gonna be two-part video here we're gonna start with what I'm gonna call the direct callback method which is actually something that we've done basically and all the video is up to now and then we're going to look at a different way to do it which I'm gonna call it event callback scene and sort of an event manager or event queue or a message pump or a variety different ways that we can call it and I think is probably going to be a superior way to do it going forward anyway I've set up a little test environment to work with this it's very very very simple I mean hit play over here there's nothing in the scene there's two units there's an event unit spawner and a unit death listener if I hit the S key the unit spawner will spawn a unit just a cube in the middle of the screen and if I hit D all the units will kill themselves boom that's all that happens over here I'll very quickly showcase the code that we've got to make this go I've got the spawner object over here in the update it's just listening to see if the S key is pushed down if it is it calls spawn unit which just in stancy it's a unit and does nothing else and then each unit is just a prefab that has this health component on it and all it's doing right now is listening through the D key and then it dies when it call when it gets the D key it calls die dies for some reason and destroys itself you know presumably this would be from running out of health or you know falling off a cliff or whatever it doesn't matter what it is but at some point something will cause a unit to die period now we want to have something in the game wants to listen for these death events this could be all kinds of things because of course we could do something like you know play death sound the spawn explosion increase score drop loot like you know this might be a player dying this could be the other unit like a monster die this could be all kind of different things but you know there's a bunch of things you could do and you could write all that code in line over here but what we want to do is we want to void that one of the key things in game development in fact most software development is the the decoupling of various mechanics there's no reason a unit you know let's say we're I don't know where we're making a Starcraft clone and this is circling there's no reason that the code required to run as Earling needs to know anything about how the sound system works so the physics system works or how the score system works if it's a so it's a system where you score points from killing units there's no reason an enemy unit needs to know how the scoring system works at all right and that way if you make a change if you make a change to how your sound is managed if you make a change to how your point system is managed or how power-ups get dropped you don't have to change the code in every place where something dies instead what we're gonna do what's quite nice to do is to instead of doing this manually wait week I will put in some sort of note we could do these manually here but what happens if we add remove or change how these systems work instead we want to allow other systems to listen for our death and be alerted when that happens okay when we die instead of taking these actions directly we want to just let the other systems know hey by the way we dead now so do whatever it is you want to do and so our example for that is that I've got this death listener in here so this is something that is instantiate it's attached to a game object that is in the scene this death listener is sitting there and is waiting for on unit died all this death listener does is output something to debug log that's it but this could be anything this could be something that keeps track of score drops powerup etc etc etc it's just something that it's job is to probably do one thing you probably want a separate one for each one of these right so like imagine that this is just your sound does a lot of things the sound manager is playing tunes in the background it's doing whatever but one of the things that would like to do is it would like to be told whenever a unit dies because it's gonna play death sound effects and it might have some sort of code in here like what is 100 units died at the same time we don't play a hundred sounds instantaneously so maybe it will do is just play a couple of them at the same time offset slightly but at a higher volume like this way we've got we've got more control over how to mix those events over here a lot of games when a bunch of Units die like when units now you might earn points but instead of showing plus one plus one plus one plus one plus one score for every unit that died it'll sort of aggregate them together and then just tell you hey you just earned fifty seven points simultaneously like good job you right it's also really good these callbacks for achievement systems you don't want every unit in the game to be aware of what your achievements for the game is instead you have some sort of achievement listener somewhere that listens to various events and then uses that the track of achievements should be activated anyway in this case we just want this to run whenever a unit dies so how are we gonna do that well on our unit this is something we've done before our unit which actually I should probably rename this code instead of being health I should probably just call it unit logic but typically I have a component on a unit to track his health which is responsible for this it has like a function to like take damage or whatever and it's also responsible processing the death in here let's provide some sort of way to register a function basically we want this function here to be called whenever unit dies so how do we do that well there's a variety of different ways to do it in c-sharp in fact there's a variety of different studio in many languages probably what we're gonna want to do first is define in this case with the the direct the direct callback system so I'm calling it here we're gonna go and designate some sort of delegate a delegate I'm I don't like using the term in this way exactly for delegate but basically a delegate here is we're gonna define some sort of function template some sort of signature for a function that we can then refer to but let's just do an example and we'll see how it goes let's say we define a delegate so we're gonna say there's going to be some sort of function that returns void it's gonna be called something like on death delegate on death call back delegate there you go will be really worthy as I usually am in the tutorials and it's something that's gonna require a single parameter which is going to be a unit death info and we don't even need to worry about name here but let's specify something like are you there you enter there okay it's gonna have a single parameter like that so this doesn't do anything by itself this is define a template or signature for a function and then what we can have is some sort of let's say let's say list for now we're gonna change this in a second but let's say we want to have a list of on death call back delegate type functions so this is something these are my on death call back listeners I guess it would be this would be I guess on death listeners is probably mr nures lists then nerves is always the way i have to pronounce this to keep track so we're gonna have this this list right it could be an array could be all kinds of different types of structures we're gonna have a list in this case for death listeners and what we want to do is when we die let all our listeners know that we have first we'll do something like if on death listeners is not equal to null right because it's possible I mean we could set up and start or something like that make sure it exists or something but whatever for now we'll just make sure it's not a null file and then what we're gonna do is something like for each uh what I call this on death callback delegate so we're call it something like funk in this list okay let's go lower case no that's got to be upper case yeah they're excellent and then all we're going to do is call those functions now this requires a unit death info so let's go and we're gonna create a unit to death info UDI is equal to a new unit death info and this class here is just very this could be this could be a struct it could be set up for read-only it could set up some constructors or something like that I know I don't know you know what this structure should look like what this form should look like but this is some way for us to pass data to that function I mean we could have on unit died instead of having a single sort of structure we can say something like well okay what is the killer what is you know some sort of other object that like describes my death that like you know I died in fire I did you know I killed myself I whatever rather than having some sort of really complicated set of parameters here that might change a bunch of times we'll have the idea that we're waiting for a particular class particular structure and it'll have a variety of different very values in here that will be used or ignored by different death listeners because a lot of things might listen for units death not everything cares about every part of this unit death info so we can add more stuff as needed by various sub functions without hurting everything else okay so we've got this kind of set up in here so the death listeners eager for that so here we're creating a new unit death info and all we're doing in here is the one value that's in here the unit game object we're gonna set it to be our own game object so we're populating the unit death info with our yeah with this right this is the unit that died so unit death info wants to know the dead unit which is gonna be this I suppose I could call let me rename that what's a shortcut key I'm still getting used to visual Studios over here oh it is just f2 dead unit game object there we go so we're gonna rename it that way and so over here dead unit game object we passed it there and we simply call this function with our UDI over here so there could be many we could have a hundred things listen a hundred things listening to us or or nothing listening to us and tiredly possible we don't know but we're gonna loop through this entire list and call those now this works fine and is very handy but the question is okay how do we actually add something to this list so at this point what you'd have to do is generate some sort of function that's like it's has to be public I mean we could just make this list public but that would be a really bad idea I mean we could write horrible code somewhere that breaks something and what if we change the way that this works there's all sorts of reasons why we might not want to do this so we could create some sort of function that something like register death callback and all it does is it requires that you give me an all death on death callback delegate which we'll call f and then here what we we just do is do something on death listeners that add F and then we probably want to do something like you know if on death listeners is equal to know then we instantiate it and so on and so forth now this this will work however unity does provide us a very convenient keyword to make or not unity c-sharp provides us with a very very handy keyword very convenient keyword that makes her life slightly simpler instead of just having this list that we manage ourselves and having to write these little functions what we can do instead is simply declare an event and yeah well actually I could have kept this lowercase just for now give me a second we'll leave this year on death listeners no little thingy make sure it compiles there we go this event keyword here basically does exactly what we had before this makes a list of on death callbacks that we can add or remove things from okay now again I could still at this point have a a public void register yadda yadda yadda register yadda which is a non death callback funk and what we could do is we could add it to the on death listeners simply by doing you know adding it this way funk what are you complaining about and consistent accessibility that's accessible then Oh since this is this since this function is public and on death callback is required as a parameter we need to make sure there's this delegate is public we need to let the rest of the world know that this delegate exists as a function so the rest of the code can see it and know what to put in here now it's worth noting we don't have to use this anywhere else we don't have to like make our so our death listener over here this thing here we don't have to use this on death call back delegate to declare this function it's just as long as this signature here it returns a void it requires a unit death info is there we can use it as part of a registering yata over here in there and we can add it in there and that's fine then we write an unregistered yadda and so on but the the whole point of using this event is that we don't have to write these functions for us instead what we can do is this if I set this to be public then another piece of code for example our death listener it's let's assume at this point so we have we have some sort of health you know some sort of health thing right some unit health that we've picked up somewhere and we could call unit health dot on death listeners and we could register our own on unit died now we can do that we're gonna get underlined here because it's unassigned right now so it won't compile it'll complain because we haven't assigned us but otherwise the syntax is okay we can do this to add ourselves to the list of death listeners now at this point you might say whoa we don't want this to be public well it turns out it's okay first of all what I'm going to do is I'm just gonna go and rename this to have a capital because we are gonna have it public over here okay it seems bad but it turns out events are kind of special if we have it with the default is it protected I think it's protected by default if we don't do anything else with it then what we we can no longer access it from outside of that class so we couldn't add it in here if we make it public we can access it out of that outside of that class but only to do two things they're at ourselves to list or remove ourselves from the list we cannot set the list this should give us an error and it does we also are not allowed to call these functions I can't go and say haha let's try to call on deaf listeners from outside of this event makes it impossible event is like a really weird halfway point between having a public property and a protected property so it doesn't let anyone else mess with it directly the only thing you can do is add yourself the list of listeners or remove yourself from the list of listeners but it saves you from having to write the whole register and unregister functions very handy so otherwise it works exactly the same all right so we've defined the signature and then we've got this on death listeners that are waiting here to be told that this guy died and then over here we still have to check for no miss which annoys me with the events I wish we were a little smarter that it wouldn't I would automatically do that but we still have to check to see if on death listeners is known and then we can still we don't actually have to loop through it this is technically not legal apparently I think you still could do this but I guess the on deaf listeners not innumerable but instead of looping through these and calling funk once each instead what we can do is just say hey call this this will call every single function that was registered in our death listeners this will call them all with this now that's pretty cool there is something missing again if we go and we compile and we know we've got a rotten line somewhere at some point Chooka Chooka Chooka Chooka Chooka Chooka there we go we've been told we're trying to use an unassigned local variable unit health and that's over here in the death listener the death listener wants to listen to when the units die but how does it actually register itself basically what we need is every time like for every unit that exists every unit that gets spawned we have to tell that unit hey I want to listen to you I want to listen in case you die so what it means is we need another set of of events and another set of listeners over here so in our spawner we sort of need some sort of public delegate voice on unit spawned delegate delegate which takes a could be a game object let's assume it's just gonna be looking for this health thing I don't know again I should have just called this class unit but let's keep going with it so it requires some sort of some sort of that way and then we'll make a public event which is on unit spawns that'll get on unit spawned lists and nerves like that and then whenever we spawn a unit what we do is say okay um if on unit spawn listeners is not equal to null then we're gonna call this for this with the getcomponent health so we're gonna get the health component from this newly spawned game object and we're going to let everyone who wants to listen in on their units Bani know that that's happening so now our death listener changes up what we're gonna do is in the start we're going to find so our spawner okay spawner we're gonna do something like game object dot find object of type spawner like this and then we're gonna say spawner dot on unit spawned listeners we're gonna register a function here so we need another function on unit spawned and this is gonna take a health parameter over here so when a unit gets spawned please call my own unit spawned a on unit spawned apparently have to hit down arrow twice for their and then on unit spawned will simply say something like a health dot on unit on death listeners okay I'm being inconsistent with my names but then we're gonna call on unit died no died the autocomplete in here I'm still getting used to been a long time working with monodevelop I'm not saying this is worse it's clearly much better but okay so now we listen for when a unit gets spawned and then when a unit gets spawned we say hey let me know when you die please now what we probably want is we might want some on destroys to deregister ourselves or all kinds of things I don't know we'll see but at this point in theory unless I've forgotten a step compile compile compile compile that's our old error here okay if I hit play no error if I hit s and then D there we go it works so actually we should add a little bit more debug over here like debug log on unit spawn spawn notified so now every time we spawn a unit we'll get a little notified in here to confirm that our listener there you go has been notified that these units responding and then if I hit D all the units will die and our death listener has been alerted about each one of those unit deaths boom done what you can already see this is a little cumbersome right we need to make sure we register ourselves over here we don't actually care our death listener doesn't care at all about units bondings except it's forced to care about it so that it can properly register itself in the list of things to listen over there which is kind of awkward is there a way around some of this double step it is there a way we can avoid registering ourselves as a listener to every single unit because every single unit that spawns has to maintain this list of things that listen which is almost always gonna be duplicated right it's almost always gonna be the same thing over and over so what we could do is instead of having each units health have this on death call back delegates have this thing you know listen at with a list of listeners we could have it be static right we could change this and let's do this for the sake of argument if I change this to be a static public event callback delegate okay so by being static this is shared by all instances of the class in here and then um so when we die we still call the death listeners nothing nothing here will change when a unit dies it still calls all the death listeners this happens to be static that's okay and the difference becomes our death listener over here no longer needs to register itself with every single unit that ever appears instead in fact we don't even care about listening for spawn events anymore this entire function here can be calmed it out there's a key for it I don't know what it is yet you know control EC yeah this corded macro is I don't know if I'll get used to that instead what we're gonna do when the death listener starts it's gonna grab the health class right the whole static class which has this aunt which has this on death listeners thank you thing and we're going to register ourselves at that point so we've now registered ourselves on the class level so for all units for everything with a health component we want to be told when they die so now if we go and go to unity over here and hit play if I spawn a unit well no longer get the message that I've been notified because I no longer listen for spawns but when I hit deed kill all these we've been alerted about unit deaths that's a little bit more commute with the static the downside is we do get told about every single unit death and maybe death listener doesn't care maybe we only want to listen for when enemy unit dies or maybe we won't to listen to when player units die now of course we could do a filter over here all right you know because we know what our dead unit is right we could do something like do we need to check if then unit go is a unit we care to listen to for example only the player and we could do that and actually wouldn't even be that hard or that time-consuming to do but that's one way you can keep this a little bit cleaner so that's direct callbacks but what I want to do now is I want to look at an event based callback system instead and there are advantages and probably disadvantages to doing this as well so I have a separate scene set up well that was weird and laggy separate scene setup it's exactly the same just called event callback scene over here the basic bits and bobs of it are the same if we go ahead and close all these windows and open up the event callback scene version the scripts are the same it's still the inappropriately named health script instead of whatever we got the spawner over here it works exactly the same and then we've got the death listener and death info over here but what we might want to do instead instead of having the death listener be responsible for registering itself for a particular callback we might want to have a central event queue so what am I talking about let's say we have some sort of extra script in here and so this is going to be our this is gonna be our event system not to be confused with unities like gooey event system probably should name it something else but we'll just put in our name space over here just to keep things nice and isolated what did I pick for the namespace this thing here there we go anyway it's got its own little space and we don't have to worry about colliding with the name of another class all right so we got this event system is it gonna be a mano of behavior attached to a game object or is it just gonna be a static class that you can pick and choose let's go ahead and just make an empty over here and we're gonna call you event system and we're just gonna give you this thing over here boom done okay so the idea with this is the event system is gonna be responsible for handling all these these sort of callback e events basically anyone who does anything can just send an event through the event system to let whoever is interested in let them know it's nice and centralized the advantage of this is that all the various subscripts don't have to health for example won't need to have its own list of listeners in here and additionally to that the the death listener doesn't have to know the class that it has to register itself to like again right now our this class is called health what if we Nate later later renamed it to unit or we change a lot of the innate structure and all of a sudden we've broken this death listener was trying to connect up to this this health class for this unit class or whatever and it was expecting things to be named a certain way but we everything is broken because we decided to rewrite it from scratch and now none of this works so now we've got these interconnected subsystems and that's not great so instead in this event system what we're gonna have is some sort of just central list actually menu it's basically a dictionary that's gonna be organized based on the type of event so let's assume we're gonna do a very simple implementation of this but let's say we've got some sort of public enum over here and we can put it outside the class as well but it's gonna be ok this is gonna be something like event type and so we got lots of things like unit spawn bullet shot Looch dropped whatever and finally like something like unit died okay so we've got a bunch of different types of events and then what we're going to have is we're gonna have a dictionary which is going to be as a key is going to be keyed by event type and as its value is then gonna be a sub list of Delegates so we're gonna have some sort of public delegate delegate delegate which is going to be an event listener list in there and it's gonna take a single value in here and this value is going to be something like a like an event it's gonna take an event which is already something in unity so we prop I mean well we'll just do another name today's base thing so let's create a new new class in here it's like Generic whatever I'll hit this doesn't matter um this is gonna be called avenge and it's gonna be in our namespace because then the nice the thing with namespaces namespaces don't do much mechanically what they do is they allow you to define stuff and you don't have to worry about duplicating names names are you know there's no name duplication concerns with things outside of the namespace in terms of scope if our event system over here is saying like okay this event listener is going to be accepting an event e this is going to prefer the one from our namespace the event callbacks namespace so instead of the unities event or some other library we decide to add in here later on that also has its event cause this is gonna prefer this one we could also be explicit with it right I could specify listen this is the event callbacks event but it's gonna be okay undo what are you oh um you're gonna return void okay there we go perfect done done done done done we don't run this update and mostly it's complaining because we got syntax that's complete over here so this dictionary is going to be indexed by event types and it's going to contain a list of event listeners again we could use events and different things over here let's go and make an explicit sort of like list here and we'll figure stuff out that's gonna be okay so this is gonna be something like it's like a master list of event listeners there's just all event listeners so you just call it something like event listeners like that and then we are gonna need in this case a sort of registration and unregister a ssin system again we could write it in a few different ways but I think it's gonna be fine here because we're gonna have to check to make sure that all these like the dictionaries in the list are all instantiated proper than everything like that so we're gonna have something like register listener so this dinner so when you register yourself to listen for an event you're gonna have to specify what event type you're listening for and then you're gonna have to provide function which is gonna be an event listener now listener go over there boom done so if we do that okay no right online so that's good and so our job will be first of all we're gonna check I guess well we're gonna check the dictionary exists so if event listeners is no wow I mean we could make sure this happens is part of the startup but there could be a timing thing this is probably best implemented as a static class rather than a monobehaviour but anyway let's move on if this is equal to null then we need to make sure that this is instantiated as a new dictionary done okay and then what we're gonna do is if the event listener listeners of event type is equal to null then we need to make sure that this gets instantiated as a new list of event listeners dun-dun-dun and then finally we can then do something like event listeners event type dot add and then we can go and add in the listener that was requested over here I'm actually gonna make an argument that this should probably rather than a list this should probably be a like a set to ensure that we may not want to duplicate the same exact function twice we might want to do a check for that we could explicitly check for it or just use a different base type there's a bunch of things we can do but lil go ahead and add a listener okay and then we'll want public void on register rat G stir list in there which is gonna be the same signature and we'll just remove ourselves from that did you go left it's an exercise to the viewer but then finally we want some sort of public void function over here which is going to be something like fire event okay or do event or whatever I don't know what we're gonna call we're gonna call a fire event because it sounds cool so this is what happens when some other piece of code decides to launch an event so we're gonna have to specify what the event type is and then we're gonna have to pass some sort of data so we have this unit death info class over here and really this is this is some sort of event data right or event info so we could actually rename this slightly and I will do so to call this unit death event info and here's the one of the issues you're like okay we want to fire an event when we have our unit that dies let's go over to health over here so what we want to be able to do is we probably want a handy way to get the event listener I think we want some sort of public event system current get and we'll have a probably fully private event system current over here if current is equal to null o is a static public or static public whatever if current isn't set anything that what we'll do is we'll set current this also see static well set current to be equal to game object dot find object of type so we're going to find the current event callback dot this I don't think I actually have to specify it because with scope it's gonna be fine there and then put it in there and then otherwise what you're gonna do is return underscore current like that done okay so now our health over here can just do something like event system dot current dot fire event and what we're specifying here is an event of type unit died and then we have to specify info about it we can't just say a unit died we want the game to know which unit died so what we'd like to do is probably something like set a new unit death event info and really this probably should have a constructor I suppose we could just you know do something here so unit death event info you d E I is equal to this and we say you de I dot unit game object that died is equal to our current game object and so what we want to do is we want to pass this so we're firing this event and then we're giving some info about this event so again all the details about this is me this is who killed me this is I died because of this type of damage or in this circumstance or whatever whatever kind of things need to be put in there we can stuff into that game object or that structure over here as we go such a class but maybe it won't be later we don't know so we've got this so this is sort of what makes sense is we need to fire this so again if we go back to our event system over here fire event clearly needs to accept some sort of structure but I don't want to have a unit death event info because like we're gonna fire all kinds of different events that's not just unit death events and we don't know like when we call and and a lot of different sorry let me reword this so there's a lot of different types of events and different events will have all kinds of different information so what we need to do is make this a little bit more generic over here we need our unit death info over here and I'm gonna rename this file this one over here is gonna get renamed to event info like this and so one of the things this file is gonna have is public class event info this is going to be the the base event info might have some generic text for doing debug dot log hey you know I can see something like that so you'd have you know we can probably assume we'll have some sort of public string you know event description all events will have that and then specific event types like the unit death info we're gonna have this be inherited or descended from event info so unit death info is an event info it's just adding more stuff it still has the event description and then it has more stuff so now listen our event system just accepts an event info of any kind I don't care what kind of subtype we've just gonna have this so our health code doesn't have to change over here we're passing a unit death info which is a type of event info and that's okay heywhat let's go and add a dot event description units has died and we do something like game object dot name or something like that so we'll give it an event description and then we also populate the unit that is now dead and probably other stuff in it later and then we just go ahead and say hey event system you can fire this event let everyone know that I am dead and the achievement system can kick in and the sound system can kick in and the power drop system can kick in and the user interface can do something special about it and so on and so forth whoever we don't care we don't have to keep track of a bunch of listeners our only job is to let everyone know that we died and the event system will then go over here and say okay something like first of all if we could use can we use the C sharp six question mark dot method here unless I go in the beta's version let's leave that for another episode so first we need to make sure that the event listeners if the event listeners are null or event listeners for this event type is null then no one's listening so we can return right away this is not an error no one is listening listening we are deaf that's not my hair that's perfectly fine otherwise if we get here then what we have is our list of functions and so we'll go ahead and do the for each loop for this for each della delegates let me call this o event listener for each event listener al in this list of event listeners again maybe maybe a for loop is better for you know garbage collection or whatever we'll leave that to another thing we're gonna call that function and we have to pass it the event so we pass it event info done there it is clearly I lost track of what I was doing I mean delete this event file and just specify that this is an event info called AI there you go am i bad any other there's probably place in here I can compile and check for compilation errors have to learn that I'm just gonna check over here no no errors or anything like that okay so when an event gets fired we go through the list of everyone who's listening for this particular event we call that function and pass it the event info so now we come around all the way to our death listener over here so what we want to do is our death listener wants to be told whenever unit dies so in the start what we're gonna do is something like event system dot current dot register a listener we want to register on unit died as a listener for events of type unit ID so when a unit dies we want to call we want on unit died to be called now we're getting a red underline and why is that it's because the signature doesn't match the delegate that register listener is looking for has to have a signature remember the signature of this delegate has to be a function that returns void and takes a single parameter which is an event info now the DEF listener function our on unit died doesn't just take a vanilla event info it requires specifically unit death which is no good so what we're gonna do is we have to change this to actually require a generic event info like this now this will make this call legal we can now legally register ourselves to an event but we know we need this to be a unit death info because the event info the only thing it's got in it is an event description but we need to know what unit died so what we're gonna do is we're gonna do a cast over here we're gonna cast event info into a unit death event info now this is a one part of this process then I'm not super keen on re box the event info into unit death event info because it's only at runtime that you'll find out if you like are sending the wrong event somehow because what could happen as health over here could just create a generic event info and feed it in there although um I could make event info a virtual that would really avoid the problem or an abstract class rather here we'll do it just to say we're to make this a public abstract class so the idea is you can never instantiate event info directly there's an it doesn't make any sense to just make event info although maybe it does maybe you know you want for just logging purposes you want to just fire a generic event that just got a string that will then show up in the debug log or something but probably you're gonna want some sort of like instead you want to go to want it a gonna wanna a public class debug event info which is like exactly the same thing but show you it there's gonna be a logging system nothing to add here party levels maybe for like priority slash verbosity levels actually that's probably a good idea some sort of like integer public integer verbosity level which is gonna have a value so of I don't know whatever makes sense in your system from one to five so we're like five is like the most spammy kind or the most important messages like holy crap we've got a crash horrible disaster whereas like zero or one is just babble just garbage like constantly being spewed in there so you have some sort of logging system that is listening to all events or all debug events and then depending on what your debug level is set to if it's and so it's set to a 3 so all debug event infos that have a verbosity level 3 or above we actually write out the log or something like that obviously this has to descend from event info anyway so that avoids the problem of like anyone making this but in a sense there's nothing stopping health over here from registering the wrong kind of type you know we're making all of a sudden we make a debug info event or something and then feed it into the fire event so depending if you screw up your code somewhere you could end up in a situation where a death listener this line fails because well the event info is not actually a unit death info luckily unlike something like C++ or something like that it will freak out and break if that happens as opposed to just trying to force something to a box that doesn't make sense and getting memory leaks and stuff actually I don't know if C++ is vulnerable to that I'm not sure anyway so in theory have I done everything first do we compile yes if I hit play oh he was not present in the dictionary um no just double clicked on it which is probably going to load the wrong file I then system 41 as a result of death listener 31 or 13 so this is being called and then event system 41 oh right we actually have to check so this is just checking to see if the list returned by at this key exists what we actually have to do is also check does it does event listener contain a key of type event type or is it's null so or sorry if if this is false again I could put the exclamation mark but for the Torres I like to be explicit so if event listeners doesn't contain something with this key or it does contain something about this key but it's a null list for some reason which should never happen then we need to make sure to instantiate that it'll put something in under that key and make sure it's a valid list okay let's try that again I'd forgotten about that so if I hit play okay no errors have paid s we spawn some stuff and more importantly if I hit D all these these three units should all die and should all fire a death event boom they did and our listener was ready to listen to that so you have to do a slightly more work in the sense that we have to manage you know we have to have this event system ready to accept events and then broadcast them to all the listeners over here or sorry over here to broadcast them to all the listeners and there's some room for optimization in terms of how this is set up here and you also have to go and have two things you're gonna have to have a sort of enum listing all the different types of events over here and again I'd be tempted I think what I do here is cut that enum out and probably you could do is a separate file like just no new classes the item it's exactly the same just do like a blank file anyway um something like event types and just keep it in one area here move the code in there event types it's not it's on c-sharp script or anything it's actually basically all we want is a name space inside the namespace we're gonna define some event types like that and it's probably better organized if we do something like this these are all the types of events that you can fire in our system and then that's probably again there's got to be a way to get errors in here but okay you're fine you're just referring to event type which is okay it's gonna be right here because it's not event systems that event type it's just within the event callbacks namespace so we can just save ourselves a little bit of timing same typing same thing here we don't have to go a cent system not this we just say event type unit died and then you've got one list you know in in a way though event type and event info are very strongly linked so there might be some better way of organising this but these are all the types of events that you can fire and the people will listen to and some of these will be over overlapping right the thing is I guess there's nothing stopping you from having multiple events fire for the same event type here's actually an interesting what if instead of being keyed by this enum like this actually yeah I think I just had a brainwave that I should have done before let's get rid of these this enum completely okay so we no longer have in the event system over here there we go we no longer have this event type instead what this is going to respond to you is is the type of event info class that's what we're gonna filter by because it would be wouldn't it be interesting and I'm sure there's a way of doing it if the class that you specify over here is also your filter for listening so what I mean by that is in over in health instead of saying what type of event if I could just say fire event for this we're creating a unit death info event fire this event this tells you what kind of event it is and also has all the data for it so over in an urn system when we get the fire event so we no longer take an event type because this doesn't exist and instead for a dictionary what we're actually using is something like type of this we want the true class of this now I don't know if this is going to return event info or the true final type I wasn't going into this tutorial thinking about this I might have to put a cut in here and double-check the correct syntax for doing this and then you change event listener so this returns a class it's a system type so all this in our dictionary the key for tis system duck type is the key system duck type system dot type and then something like death listener instead of specifying event type unit died we say listen we want Shana listen to this and at the same time would you be able to do templated code where instead of this although we're getting there we type of yeah instead of doing this could you do something like like this I bet you can do something like that I apologize for apologies for getting off the rails right at the end here I'm just wondering if we could actually get a typed final event over here and save us from this I'm not we get we can because the fire event could have recast things at that point because it knows I'm sure I'm losing a lot of people I'm really sorry because over here it would know at this point what the final and true class of this is right true event info class is equal to something I don't think type of this works I mean Frank we're getting is a variable use like a type there's some other there's some other method to get this C sharp get class name from variable type the scripted a class name get type don't get type like this true event true event and then finally we cast this so this event info will be of type event info but at this point it should cast it that's gonna return the wrong thing isn't it yeah yet type on this is not the right one we're looking for its subclass of we can do checks can we cast from this I'm sure there's a way to do this I will probably put a cut in here this is going on way too long but you know what I'm doing it's like I'm saying and then and then we end up with a situation where our death listener can just properly it knows for sure it gets the right event type but I guess I should leave this be for now I still think this is okay I don't know about like the magic retyping of things that's probably not what we want to do we'll just call like this this is the only part I'm not quite sure about but I love the idea of getting rid of the enums because we were calving to keep two separate lists of things which overlapped but not literally so in weird ways so now we have no enums the only thing we care about when something is saying hey I want to listen to an event is you declare what type of event you listen to and when you fire an event like in health you just fire the event of the right type and anything that's trying to listen to an event of that type will then be notified you still have to do a cast here as is let me make sure we run I bet you there's a way to fix that and what I'm going to do is they'll be a link down below to download this code I will try to get that in there in the final code I'm just gonna have to do a little bit of looking up just check right now we can spawn objects of hit D to kill we get notified beautiful oh I'm so much happy I wish I'd thought of this before I started the tutorial but didn't occur to me until I was in it that hey there's probably a better way so the one thing I don't like is that we have to Reeboks event info into the true final form over here I would like this to just be this because it makes this clean code a lot cleaner we're effectively gonna do exactly the same thing as this in fire event but it'll look nicer so I want to say thank you for watching I would say I also want to say thank you and we haven't actually gotten the credits in here just realized all the the livestream videos I was doing I wasn't actually including the credits properly so well I say thank you everyone's supporting this on patreon you guys are continuing to make these videos possible I love you so much for all that thanks for watching thanks for supporting it including these mic-check supporters we've got Jeremy strong John Pavlik we've had Tiburon we've got mighty mix 1900 that Pavel's Dan off Michael McCain McClintock we've got roars Khal we've got gurko trees or dries we're done with trees I don't know there's Julie I was your that phone we got Marius field though we got speedy spot Steven stager hey Walter cool shoot probably short Thomas Overson Jason Janna T Easter Egg productions a good name and Neil Blakey Milner and once again thank you for everything and we'll be back some more tutorials real soon and again check the link in the doobly-doo so that I can resolve this that we want to cast this to the true event info cast variable Oh c-sharp clap cast variable from system type type casting using system not type yes it's two second Google the example here would be convert dot change type of event info to true event like that archer event that doesn't work and there's still a problem like don't even need to do this cuz ah yeah I definitely need to put a cut in here because if I do this and that then this doesn't become legal anymore yeah because it's the wrong type yeah it's not just a matter casting at the end it might not be possible to do it it's not just a matter of casting at the end because this would definitely need some sort of to be templated in some way oh oh god I'm supposed to put a cut in here okay well tell you what the real video is cut if those are you who want to keep listening to my sort of frustrations and things can feel free to do that so if we go oh because the delegate yeah we can't the define a delegate on the fly can we this would hold the thing yeah her feel like it's so close like I need to be able to do something like this it's probably a way to do it there's probably a way to do it and then over here like the event type is our t te t well you know what might have been easier here let's do that there's no reason you have to always use T but we need to accept a function that's got a signature that's something like this using events type um c-sharp template parameter delegates surely someone else how can I pass in a funk with a generic type parameter down casting I convertor down casting Oh doing I don't use square brackets here no can you use the func helper yeah we're just renamed the system dot funk funk which is actually I'm sorry listener listener do we do this No and type is a type which is not valid right so this actually wants to be type of that yeah okay so it because it's a system that type is what we want here so I don't know we'll just we'll rename this to event type type actually what I'll do is I will actually rename this to tea that way event type becomes type of tea and then goes here and this becomes t get rid of the type of cuz that's redundant and then right and then so comes down to we can't add this listener because we can't just say that this is gonna be a list of funks no that's okay it's um yeah okay hold on this will be a list of event listeners what we can do is we can wrap it I can make a temporary little function as a wrapper which just takes an event info and then calls the real function so our listener in this case is going to be this generic thing so like what I can do it's the event listener rapper is equal to this and what this is going to do is it's going to call this listener with a I casted to T convert type event info to tea you can if you know that this is a subclass how do you do that their event info c-sharp delegates or sorry c-sharp template subclass or something like that [Music] outside of this for a function where key that's what it is [Music] is it after before where key is a subclass of event info okay that's still fine does not take one argument doesn't it just take one in element I'm confused doesn't listener just take one element T t is equal to nu t I can't instantiate from here dozen of the new constraint I know that you won't be initialized but you're not taking one argument what do you take you take a zero how do you take zero oh because the function returns the first thing we actually want system to action so this takes one parameter now use of unassigned variable that's fine okay and then we can cast you to a tee excellent and then we add the wrapper okay so now so this dictionary just has a list of things that match the eventlistener delegate which actually no longer has to be public at all as far as I know hmm oh and then this thing can be updated to match this signature boom like this there you go register listener takes two arguments oh no overload before it does that's right because we don't have to pass this type of we have to say we're registering a listener of event of unit death info boom convert does not exist that's okay we don't need to do this mm-hmm yeah we do we stuff to figure out how to cast event info to this that's the only thing mm-hmm so my type converter I converter it's my hand your way to do this so we need cast using system type type references variable change the casting system the change type I feel like even this there's something that I'm missing here something okay no there's no way oh no we still have this hold on so I want to get the right type of thing right from there good and now you will complain cuz or is no one complaining anymore this doesn't feel right oh no it's fine because yeah yeah we're doing the we're doing the conversion here there we go this is where we're doing our casting that's right so the event listener just needs any event info and it's in here that we're wrapping wrap a type conversion around the event listener which will guarantee that death listener now has this over here conceivably one running here if we're somehow this shouldn't actually be a problem there should be no way because events register themselves based on the typed of looking to listen for like if I just change this to like event info then this will be a mismatch over here yeah so you get told right away if there's a mismatch so we specify what we're listening for this has to be what it's looking for over here and I think a compiler time we actually make sure that all the types work out we still do this one type conversion over here but I don't think there's any logical way where we up where we're trying to cast this event to something that is not actually the one that the final function is waiting for I bet you there's a way to do this without this rapper someone who's a little bit more up on c-sharp syntax can probably solve this one for me and again I'll be putting this up on on github even though it's owned by Microsoft now and so someone can put in there though I suspect there might be a someone will push some sort of change to make this cleaner I'm betting someone better at c-sharp generic syntax can find a way around this but I think the code is solid I think it's all sorry I think the code is functional let's not say solid so I'm gonna hit s to spawn some stuff i'ma hit D and there we go we get alerted about unit death so in the end we end up with a fairly kind of there's some arcane stuff going around in the event system but it's it's basically done and fully self enclosed again I make this to make this a not a model behavior just be a static class might be a way to go we actually might want on start to explicitly set current to be equal to this as well just in case because this old static data will kick around there's a possible timing issue I guess we do it like on enable which is the one of the soonest things if this were static we wouldn't have to worry about this time issue yeah I kind of like the idea of going static instead although we'd probably have to listen to something like a scene reload event to purge this list of listeners or maybe we don't want to purge list of listeners maybe you want some of this to persist between scenes I don't know so there's there things to be considered over there with that but yeah now again I do like the fact that all we do is now whenever want to listen to something we say here listen I'm I'm I want to register a listener this is the kind of info I'm looking for so anyone who fires an event with this info I want to know about it the info is the type of the event I love the fact that that's the case because I would always be the case the having setting up the and sending up the structures to separate things when they're really the one on the same so now that the structure is the type of the event I love it call this so whenever someone dies whenever any event is fired with this type of info this will get called and I think that's beautiful what if something very simple very easy to accept you can you can add a new system to your game without changing anything else in the game which is you can just add a new system simply by having a new system listen for the right kind of stuff and I think that's lovely thanks for watching credits happen previously love you all bye bye
Info
Channel: quill18creates
Views: 34,186
Rating: undefined out of 5
Keywords: Unity (game Engine), Game Engine (Software Genre), programming, tutorial, fps, howto, how to, beginner, guide, unity, unity 3d, unity 4, physics, blender, 2d, 3d, quill18, quill, multiplayer, c#, generics, callbacks, delegate, event, events
Id: 04wXkgfd9V8
Channel Id: undefined
Length: 75min 27sec (4527 seconds)
Published: Sun Jun 10 2018
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.