Event System | Game Engine series

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
Hey look guys my name is trying to welcome back to my game engine series so last time we planned an event system for hazel if you haven't seen that video make sure that you check it out I know it's planning video I know you might think that it's kind of boring and it's let's just rice and curd sure but it's really important to understand why we're making these kind of architectural decisions that we actually are making because we don't understand that then well you're not gonna be able to learn how to actually write a game engine which is what the series is all about so definitely make sure you check that out the second thing is I want to you just give a huge thank you to all the patrons that make this series possible if you guys aren't supporting the series you can go to pattern icon for safe the Cherno you'll get episodes like a week early as well as access to the source code of the hazel engine as fast as I can ride it so I mean I mean we've already got like a window and like colors on the screen and all that stuff already working correctly kind of in that development branch so if you want to kind of just surge ahead or see where this is heading or just you know get some gear hands on actual code that is way more kind of further along than what we're actually doing in these videos that's definitely best players to do that so speaking of which because we're kind of getting into large systems I thought that I would kind of change the format of this series a little bit and this is kind of the first episode in which we kind of see this this this change that is happening so in previous episodes what I would do is I'd kind of just write code in front of you and I and I still will do that to an extent but that part is actually going to change because this event system is a considerable amount of code and these videos which I originally wanted to keep between kind of ten to fifteen to twenty minutes are kind of becoming twenty to thirty minutes and I mean even thirty plus some episodes which is a little bit too much I want this series to be a little bit more accessible a little bit more consumable and I guess more so about the actual decisions and how the code works rather than actually physically writing out the code in front of you now I might I might make a video on why I made this decision and we can talk about it maybe in another video I don't want to really just we were in this event video by talking about all that kind of stuff but what's going to change now and probably going forward is that instead what I've done is I've taken code from that private development branch code that I've already written code that I've already tested code that I've been probably using for a while at this point and I've kind of merged it into this hazel public repository and then what I'm going to effectively do is explain how all of it works of course and also kind of go through the diff of before and after the last episode now because this is pushed to github you can actually look at the differential if you want to once this video goes public but even in the video I'll kind of go through each change and explain what's actually happened and not just kind of why I've done this but also how it works so I think that ultimately I mean in terms of learning it should be exactly the same it's just gonna save a lot of me typing out stuff that doesn't necessarily need to be typed out in front of you and I can't even talk about some of the reasons why like some of the different different things that I thought about while writing this code as well because this code is all 100 Center in by me so of course I know exactly why I've decided to do certain things and not other things anyway without further ado let's just jump in and take a look at this event system although also I have a few amendments from the premake video I believe that will also cover here but hopefully this new format is also kind of entertaining for you guys to watch as well I think it actually might be easier to watch because instead of just waiting for you to write a code it's more more of the episode is spent on me actually explaining the code and talking about the actual code rather than physically talking about so anyway let's just jump in and take a look so here is the event a file I'll kind of go through what I've actually added so that you guys can see essentially what I've done is I've created this folder called events inside the hazal directory and there's four files in here there's the event dot H header file which is kind of the main file for this whole event system and then we've got different types of events kind of split up into their own files so we'll kind of go through how these work first and then we'll take a look at an example of actually creating an event just to see what the API kind of looks like and what we can actually do with it okay so let's start with the with the top I guess this file is completely new so there's no need to show like a div or anything because it's 100% new so the first thing I want to kind of talk of these includes because these are something that I really don't like including into files like this because string and also functional I just kind of C++ standard library stuff that first of all should really probably be in a pre-compiled header but even above that they should just be kind of included maybe in core so one thing that we need to get to doing very very quickly because it's just gonna be the further along this engine gets the harder is committing it to integrate later is precompiled headers so we might take a look at that maybe in the next episode or sometime soon in the future okay so I've written a nice little comment here just to explain how the event system currently works and basically this just says that it's blocking right which means that the way that this is designed right now is that it's not buffered it's not kind of events aren't deferred as soon as they actually happen like as soon as the mouse is clicked that's it the whole application basically stops and then processes that event it's not kind of just like let's get the information from the event push it into some kind of queue or some kind of buffer and they kind of defer until until we actually go through the event you know pass or whatever and all kind of even propagate it through like some kind of event bus or something no it just happens immediately so this is just a little comment explaining that how the design currently works and maybe in the future we can buffer them into some kind of event bus okay cool so this is kind of the heart of everything so we have this event class but we also have an enum which basically an ATM class which describes the event type right so we have different kind of event types I've kind of split them up into separate lines some of them we might not even use we can explore using them for example these ones I'm not a hundred percent convinced that I actually want to use but everything else is totally fine and you can also see that because this code is is code that I've already written and because it's kind of written in the hazel development branch I'm able to actually like I totally forgot about Mouse scrolling for example and if I made this video I might have just forgotten about that that I had to add it later but because I've kind of gone through and been using his code for a while it's kind of matured I guess to the point where the code that I can merge in here and show you guys is actually somewhat you know final I mean obviously will tweak it in the future as need be but it's a little bit more mature than me just kind of writing what's off the top of my head or bed on some loose design that hasn't been through like you know actually usage like this card has because I've been using this code quite a bit in that hazel development branch anyway so we have different kinds of event types we've got all the window events now these actual events are implemented in their relevant kind of files so application event kind of covers all of these key events covers key pressed and released and then Mouse events cover all this stuff so protected look at application event for example you can see we have that like window resize event I don't think about yeah okay app Brenda app update app tick window close and all that stuff it's all kind of implemented here and we'll take a look at that later so this in arm basically is just like hey you know what is we have a code assigned to each event you can see it's like a number essentially from like you know none is zero but from one to like 14 and this basically just says this is what type I am and will mean that in the future because we need to be able to kind of tell which type an event is and you know we don't really don't want to use something like dynamic cast or runtime type information for something as trivial as this it's easier just to have an int ID kind of associated with everything and then we can deal with it okay so event categories the reason we have these is because we basically may want to filter certain events so in other words I am receiving all events from my application into some kind of an event class but I only care about the keyboard events right or free like here's a really simple scenario I would like to log every keyboard event now or every mouse event let's say Mouse event because there's more of them right so do I really have to go and check to see if the event type is pressed or released or moved or scrolled that's a little bit annoying so instead what I can do is I can just say hey you know what just give me all of the Mouse events and because we have kind of this some event category enum we're able to do that because it's kind of like a it's almost like a um a trait I guess that's applied to certain event types so that we can actually see what they are now this is a bit a bit field essentially um if we just go to this because I've just kind of defined it in the cord or H file this is all it is right define bit X as just one shifted by X places this just says that if you do a bit one for example you'll have essentially just a bit at position you know 1 0 would be position 0 and so on and so forth so that we can actually end up with a bit of field right and the reason we want that specifically in not just 0 1 2 3 4 is because we have the ability for an event to go into multiple categories so for example you know keyboard mouse and mouse button events are all input events a mouse button event is a mouse event right a keyboard event is a keyboard and an input event so we have kind of we want to apply multiple categories to a single event type so we need to create a bit field so that we're gonna have multiple bits set and then we can simply just mask them out and see what what kind of category and event or what categories an event actually belongs to during worried about these macros for a second we'll get to them let's just take a look at this actual event class okay so first of all I just want to give a quick pause you see how how nice this is like it is my first time doing a video in this format but imagine if I had to write out all this code this video would be like an hour long so I think that this is actually a lot better and I might not even have mentioned a lot of the stuff I did just because I would be you know stretched for time so um yeah I just wanted to make a quick comment that I'm actually enjoying this okay anyway so we have this event class which will talk about the event dispatcher later as well but essentially this is a base class for events the other thing this actually stores is they handle the bullying because we need to be able to see if an event has been handled a lot the reason we have that is because further down the line when we actually start to dispatch events to do various layers for example it's pretty common for us to decide to say that I don't want this to be propagated any further so a mouse click event for example if we have a button on our screen and we've clicked the mouse and the the mouse has fallen within the bounds of the button then that's it that event has kind of been handled we want it to consume that event so that the layer underneath it which might be our game world doesn't actually receive a click event because of course it's already been handled by the button so that's why we need this here and of course in the future when we actually get around this you'll see real examples of this in use is in Category is a very simple kind of utility function which basically just asks hey you know is this you know is this kind of is this event for example you know a mouse event or whatever is it in the given category and we can just use this quickly to filter out certain events and all it does is just an end with the actual category to see if it actually belongs to that category or not of course this will if this returns zero it means that it's not in the category at all if it returns anything other than zero it will be caught it'll be true right which means that it is at least in that category might be in other categories as well but it's at least in this one then we have a bunch of virtual functions some of them being pure virtual which means they must be implemented so such as get event type get name get category flags now I haven't decided whether or not I actually want category flags to be a debug or anything name definitely probably like will be it's probably not the biggest overhead to have but still we because it's just a constant reminder so it'll be like a pointer to constant you know kind of read-only memory anyway but mmm something to think about at the moment I've left it obviously enabled for all configurations but it's this should really only be is for debugging I don't I don't expect ever being having the necessity to actually retrieve an event name so yes but anyway that pure virtual which means every event has to implement them to string by default just returns the name of the event obviously if you have more details that you want to be able to print such as you know monitor let's go to a random event a window resize event you might want to print you know width and height like I've done here then you can override that to string function and actually say that okay this is a window resize event and I'm including this information with it okay because I've kind of you know this the to string is definitely a debugging only thing I haven't been really kind of attentive to performance or anything like that i'm just using string stream here not that the string stream is terrible for performance but obviously it has the ability to allocate memory but again we don't care about performance here at all because all this is is a debug kind of a print the event information because I'm not sure my events are working correctly or whatever right should never be used by actual game runtime for anything other than debugging okay cool moving on yeah so these have to be implemented this by default you know it the event class provides you with just a default kind of to string which is just the name of the event but of course you have the ability to override that that's it that's the whole event class so before we get into the dispatcher let's just actually look at what an event might look like so I think I might um maybe like a key event would be something better to look at so a key event is very simple obviously key events work when we kind of press things on our keyboard so if I press a key on my keyboard that's a key press event when I release the key from my keyboard that's a key released event now these these key events have certain things in common right namely the key code of what has been pressed or released right whether it's press or released that's should be its own event type but the key code is common between those events which is why I've made a base class called key event which contains the key code now key released probably shouldn't need anything else apart from just the fact that it's a key released event therefore the key has been released but a key pressed should also have whether or not the key is a repeated event what that means is that when I press a key what happens usually depending on the operating system and the drivers and all that but usually when I press a key it sends the operating system sends a key pressed event right and then waits like a little bit and then it sends a bunch of key repeat events you can see this in action if I just kind of click somewhere and I press the a key you'll see that there's going and I'm gonna press and hold the a key in a minute here you'll see there's like it'll add the letter a will appear immediately then there'll be a pause and then it will print a bunch of them right so yeah again a and then they're there the rest right so the first one is a key press event and then there's other ones are essentially key repeat events so we want to be able to kind of also take that so that's how a press event might be different from a released event other than the name but because they have that key code in common what I've actually done here is I've created this kind of key event base class as you can see which just contains a key code that's it it's also got a protected constructor because nothing else can construct it and then it's got it just together for that key code obviously we don't need to set the key code because that happens when the event gets constructed but we do want to get it and then we have a macro which implements this class category thing we will talk about the macros in a minute because there's more okay so a key pressed event so essentially this is kind of like an abstract event you shouldn't really be able to make a key event you really can't I mean it's got a protected constructor so you won't be able to make this class in anything other than a class that derives from it so keypress event is an actual event right that we'll see in our engine it's basically got a key code and a repeat count so I haven't really decided whether or not we want to keep track of the amount of times that a key has been repeated usually like essentially like this could act like a boolean so you know I mean if this is zero then it has not been it's not a repeat event it's the first time that Keys been pressed but then the kind of remaining like if it's anything other than when other than the zero which might be one if it's been repeated all like twenty if it's been repeated twenty times or it could just be one even if it has been repeated twenty times depending on how we implement it that just means it's a repeat event so that's really like this whole key repeat event stuff is really really useful because if you know if you're making something like a menu in again so you have like four options in your menu and the mouse isn't in use for example you just want to use the arrow keys to go down the menu obviously like you know for as an example you might want to only you might want to force the user to actually press the arrow key multiple times to go through something instead of being able to press and hold it traditionally if you just handle a pressed event and you say that until it's released go cycle through the menu options that'll keep looping really quickly and even if the user presses the cake quickly it might actually still send two events that's bad because the way that this is set up is as you saw with me typing Anke it will kind of just send the event wait a bit and then send a bunch of repeat events because that's how most of racism's handle this you could just you don't you don't even need to worry about repeat count for menus because you could just say that you know obviously there's that gap so you can just kind of you can just kind of wait naturally until it kind of that will give the user man enough time to release it if they don't want to go through multiple menu options and then essentially you could just say that I'll just let it naturally work which means that you use the president down or it goes down weights a bit and there goes bumble the mobile all the way down and maybe loops or whatever so that's just a use case scenario anyway um that's kind of the idea with repeats we've implemented a two string function here which overrides our two string and just essentially says that this will just print which queue has been pressed and also the amount of times and then we have this event class type macro which implements all of our functions so let's talk about these macros now we have event class category and event class type the reason these exist is because we need these three things implemented now event type name and category flags are so trivial that I mean we don't really need them now category flags obviously does need the category to be specified but name an event type I mean the name is the event type and it's just a pain to kind of type all this so instead of us having to type essentially all of this code I would I'll show you what it wouldn't look like forgot a key event you know if I wanted to employ this stuff I'd have to basically say I mean look at all this stuff we also need a static type which we'll talk about in a minute but basically what I would need to say is that I would need a static function which returns the type of this event now this is a key pressed event which if we go to our event class it's this key pressed event which would just be event type key pressed I need to have a virtual function which returns this static type and we'll see why we need that in a minute um and then I wouldn't need this name which again would probably be something like that so instead of copying and pasting this code in every single implementer I've just implemented a macro that I can use which just says event class type and then key press which is this type and that's it and then obviously what it will do is fill in the blanks it will stringify it for the name and I mean that's about it so that being said what is the idea with these static types why do we need them well we want to be able to at runtime check to see which type this key pressed event is right so obviously we need a function that returns which event type it is now this does not have to be a member function it can just be a static function because if we say key pressed event type or whatever we don't need to have an instance of the key pressed event class to actually see what type it is obviously key pressed event is always going to be a key pressed event no matter what the instance is now the reason we need a non instance based one is specifically a virtual one right this overrides a virtual function the reason we need that is because if we just have an event base class right if we're just treating it kind of this is like a polymorphic kind of thing we're just we just have a reference or a pointer to an event we want to be able to see what type it actually is so we can do event you know get event type and it will return which event type it actually is one implementation scenario for this is the actual event dispatcher you can see what it does here in the dispatch function is it checks to see which event type the current event that we're trying to dispatch is whether or not it matches this template argument and because this is a static function we can just do take call and call and get static type now there's no type safety here to make sure it's an event for example but it can already have but that aside you can see that obviously uh because it's just one compile if this is an event or anything that has get static type but anyway you can see here we're able to do this comparison between an event reference which is just which could be any event and this template argument and then if server example in this case this dispatchable dispatch it to the appropriate event or to the appropriate function essentially and we'll talk about the event dispatcher in more detail a little bit later but anyway that's why we need these two and then the get name you know just returns the name for usually for debugging purposes so all these three are easily implemented with this one macro same with the event class category instead of us kind of having to you know type out you know virtually and get category flags constant override whatever we can easily just say event class category these two okay I've ordered them together because of course a keyboard event is also an important event okay and I've implemented this in the base class because a key press and release event are both of these anyway so I don't need to implement them in their own kind of classes okay that's the key pressed event key release event is identical except Issa's and does not have that repeat count and obviously it's just got a key released event and the type is key released instead of key pressed okay that is that is the Kalyan now Mouse events exactly the same I mean you guys probably already understand how to write these a mouse moved event okay is an event it's got x and y which is the location of the mouse as it currently stands we've got getters for that we've got a two string which prints mouse moved event along with the coordinates and then we will we've implemented these two things which implement the mouse moves class type which gives us all of our type information and then also the event class category which tells us which kappa rates and that's it and then we're storing everything you quick note for the hazel kind of engine the coding style that I've kind of adopted is public stuff first and then private stuff the reason is when you kind of look at a class and you know you let's just say you're running a game with the heads London and you want to quickly see what you can actually do with the API obviously you don't care about the implementation details which are private right what you want to see first is what you can actually use right so that's why it makes more sense to me to just say all the public stuff gets written first and then the private kind of stuff that is only in that is only kind of interesting to us as developers as days people developing the hazel engine you know that's kind of let's just not talk about that because if we're actually using the hazel engine we want to actually see what we can use and not implementation details which we don't care about okay scroll event exactly the same right we have our offset which is essentially where we've kind of scrolled both x and y because there is horizontal scrolling that we can do on some mice and then this obviously prints all of the information implements all the type stuff as well but an event again you know this is an actual base class and then we have button press button released we have our button being stored here this is protected so that we can't create this event only the mouse button pressed event and release event can create that and then we have everything else implemented kind of as usual you get the drift application event has things like window resize window close event window close event is really simple has absolutely nothing it's in the application event category and all it really is is just hey I'm a window close event it doesn't need any data at all resize event has the width and height that we've been resized to and then we have also these four events which I'm not sure if I'm going to use yet but we have ticket an update event and render event this is if we would like our kind of you know update render and tick functions to actually be propagated as events which is doable but again if we were trying to be really like pedantic about our correct kind of event system usage then maybe everything should be an event and thus you know thus we should have this implemented as an event but because they're kind of you know sorry so intrinsic to an actual application I feel that maybe this should just be hard-coded that's just like you know these three are like you know actual non-modular events they're always present and you can choose to implement them or you don't have to but they're kind of already there for you you don't need to first receive them as an event and then dispatch them to a function there already called and I'm I'm leaning towards doing it that way but just in case we do have these three events over here okay so that is that that's what I've actually added these kind of four four files which are for your full event system are the dispatcher let's quickly talk about that so the dispatcher is a is a way for us to actually dispatch events based on their type really easily okay so if we're if we receive an event and we you know our own event function gets called we will we will be receiving it as an event reference which means that it could be any type of event we have no idea what it is so what we can do is we can actually write we can create an instance of this class with the event that we've just received and then we can call this dispatch function a bunch of times with with a different event function and this event function is as you can see it's an it's a standard function all right which returns bull and takes in T reference so T in this case could be any event type like window resize event so we could say bull dispatch in a window resize event and then we could implement a function somewhere in our engine usually in the same class as you've actually are handling is this event in but essentially a function that takes in you know a window resize event and then returns a boolean and then uses basically passes through and if the event that you're trying to dispatch matches the type of this function essentially then it will actually run that function it'll call that function with that event otherwise it just won't right that's really it it's really easy to use we'll see it in action probably in the future when we actually start you know receiving events from like the window class or whatever but that's kind of just a really easy way for us to actually be able to not have to say you know manually now on event function if the event type is key event called the key pressed you know on key pressed function with the in your and also cast this to it you know to the to a key pressed event because it's currently just a normal base event this kind of automates that it makes our code in the client side look really simple and then finally we have a little output stream operator this exists for our logging library so that we can easily just call to string on the event and we'll be able to log events really easily as we'll see in a minute okay that's all of the new files that I've added I'm going to take you through the diff of the kind of existing files that I've modified I've set up a very very simple example of a surveillance system feel free to just play around with it and make your own examples or just you know play just play around with it so that you can understand how it works a little bit better obviously as we add like a window class and we actually start receiving these real events like real input events will be heavily using this system but for now it's kind of just there for us to use so yeah let's just take a look at all the dips so I'm using beyond compare here I've really just run gift.if tool and I've set up the on compare as my dev tool I've done a full directory diff so this is everything that's changed so let's take a look so if you think a few things that I did in the pre-made file is from last episode you might you might have realized add a little I added a little note this wasn't in the last episode this was in the Premack episode but I added a little note saying that we don't actually have to hard code this there is a system version latest now in przemek that we can just use and our just use the latest system version so that's what we want so I've changed that for both the sandbox and the hazal projects and then I've also added into it the include directories for hazel I've also added the source directory this is just so that we can you know do stuff like because you know this event is inside the events folder this event on edge file but I don't really want to go back a directory and go into core or whatever I want to be able to just start from the source directory and always do my parts kind of relative to that source directory which contains Hazel's so if we look at what all files here you can see we have a source directory and the fact that I've added this source directory here means that everything is about everything can be relative to this which means I can just write hazel call like that and it's really clean it also means that if this file moves into a different folder I don't have to redo all my paths everything's going to be always relative to this okay so that's all the changes that I made to the pre make file obviously I have ran the generate project's batch file and I've actually regenerated all of these projects which means that that's why these two have changed so yeah okay application so I've included events here probably didn't need to do it necessarily in the application file in the header file could be done in the CPP file but all I've really done here is just and we'll run this code in a minute but I've just created a little test I've created a window resize event like that and then I've just logged it using H set trace and because we've added those output stream operators we should just be able to do that okay cool and then in quarter H I added that bit macro just so that we can define bit fields easily and then into our logging system I have added this include which is the output stream operator for SPE low for speed log so that we can actually use our kind of custom so that we can log custom types like in our events okay that's it that's all that's changed so to do a little test here what I've done is again I've got this window resize event just creates a window resize event and then we just love it if I run this code you can see that I get my window resize event being logged correctly and everything is great if I do something else you know why couldn't for example say you know is this in the is in category but in our application then we could trace it otherwise you know if it's in let's just write category that is not in event plus event category input so this should only log it once right and I can put a breakpoint here just to verify what actually happens you can see it is in that category so we actually log it and it's not in this category so we skip it and in the end it gets logged once ok so that's kind of the idea it's really quite simple and we've got a very basic test obviously as we start to implement a window class which is probably gonna be our next major thing after maybe some other things like precompiled headers will actually start using this event system properly and you'll see it fully in action all right so I hope you guys enjoyed this video let me know what you think of this new format I really like it this ended up over 30 minutes anyway imagine if I'd written all this code it were probably an hour I probably be really boring and I'm just I'm really happy and it this just makes it easier for me as well because instead of concentrating and making sure that I'm not forgetting anything and typing the right code again I'm less stressed about that and I'm actually I can actually spend more time explaining what the card actually does and like to me that's more fun and possibly more useful to you guys let me know if you notice any differences in kind of how this goes and the information you're getting out of this anyway I hope you guys enjoy this video if you didn't hit that like button you have support this series by going to patron Ocampo slash the channel as I said earlier you will get access to that private repository in which we already have a window class I just hit the microphone I'm sorry in which we already have the window class as well as all these events being used so you want to see this event system in action like today you can just go and help support the series and of what obviously does help actually support this series so you'll see more episodes and it's just a good little community we have which makes this series possible which is awesome because I love making these videos for you guys next time I really want to do precompiled headers and then after that I think we'll probably finally be able to start talking about how we can implement a window class because getting something on the screen would be quite nice I'm really happy we have this advanced system one thing that I just remembered that I didn't talk about is and it which will probably be more or less tied with the winter class is going to be an input system so we have the ability to be notified about input events but we can't actually ask as to what the current state of like a key on our keyboard is that's going to be important as well because whilst receiving events are great it's also really nice to be able to say hey you know it is the left mouse button currently pressed we can't actually do this with the system what we could do is receive a mouse event and then store that event until we get a mouse released event and in that way you know we could save mouse buttons press but instead of doing that on the client side I'd like to move that into hazel and just have it as something that the API can do so we will kind of revisit input events in the future to support that but again that's not an advances thing that's just a that's just like an input state input manager conflate anyway hope you guys enjoy this video I will see you next time goodbye [Music]
Info
Channel: The Cherno
Views: 95,294
Rating: undefined out of 5
Keywords: thecherno, thechernoproject, cherno, c++, programming, gamedev, game development, learn c++, c++ tutorial, game engine, how to make a game engine, game engine series, event system
Id: xnopUoZbMEk
Channel Id: undefined
Length: 35min 40sec (2140 seconds)
Published: Sun Dec 02 2018
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.