Native Scripting | Game Engine series

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hey what's up guys my name is echeno welcome back to the game engine series today we're actually going to start finally looking at some kind of integration with scripting but don't get too excited because we're not going to start looking at c sharp or other languages we're going to start right here within c plus last time we talked about camera systems check out that video if you haven't already and today as i mentioned we're going to be all about the c-plus plus scripting so let's start a little bit with talking how we're talking about how we're going to deal with cbos plus as a scripting language and what that even means and to demonstrate this a little bit more we're going to transition into the source code and i'm going to just show a little bit more about um what we're actually going to do here so at the moment if we take a look at our game and i might launch this it's technically an editor but we still have some kind of game related features in there i think the biggest thing that we need to uh the biggest thing that prompted me to move on from the camera stuff to the scripting stuff as well it's worth noting is the camera system and the fact that we need a way to control the camera but how do we do that without actually you know hard coding it i guess or creating like an orthographic camera control class like we've already got we have no way to move this we've got components that make this up we've got two different cameras and all of that's great we can control the two different cameras and it looks great we can switch between them however i want to be able to move the camera so with like wasd or the arrow keys how do i achieve that well currently if we take a look at our code and how it's laid out we need to probably manually do that inside the editor layer but that's not right because even though the editor layer and this is i don't want to get too much into this but technically speaking if it's an editor camera you probably would write it like say outside of the games runtime obviously and outside of the entity component system and outside of all that stuff but when it comes to the actual run time camera so say that you're making a game and you want to have a camera entity that obviously responds to some kind of behavior so for example maybe it always follows the player you want it to be part of the entity component system it has the camera component which we talked about and developed like in over the last few episodes but now we need a way to actually hook in some kind of behavior into this camera entity and that's where this whole c plus scripting is going to come in now this is something that is moderately complicated and it's actually something that i've developed over the course of a live stream i've been live streaming this stuff on twitch lately there'll be a link in the description below tuition tv for slash the channel there's also a schedule there and if you want to catch any of the live streams they are there as well as the vods so the kind of recorded videos the videos on demand that you can go back and watch so if you want to see the whole planning process of how this works check out twitch they only stay there for two weeks unfortunately just the way the twitch stores them so i will make them accessible too or buy patreons as well so if you want to really catch the whole archive of them there's like one total video at the moment then check out patreon.com and help support this series and you'll get access to all of that but basically the reason i'm saying that is because long story short you know i have this has been planned over a while with the stream so what i'm going to get into today is more or less the solution that we all kind of agreed upon and all the ideas that i gathered from stream because i was originally going to do it a different way and that's why i love you know this whole streaming thing as well i think coder2k suggested that i take things in a slightly different direction and i did and the whole stream was supportive for that and so we decided to uh basically do something a little bit better i think than my original solution so i'm really happy for that anyway bit of a streaming disclaimer and also i'm actually recording this episode live on stream as you probably saw from the chat in the beginning of the episode so that's the new way that we're going to be moving forward with this game engine series anyway without further ado let's talk about how we would want to add functionality to this camera class and i'll try and summarize all of the iteration kind of that we went through when we wrote this in the stream and i'll kind of integrate it into the current version of hazel as well so the way that you would the way that you would probably expect this to work is i want to be able to collect to create a class such as camera controller and i'm just writing it here for now but it doesn't really matter i want uh basically the standard entity kind of script functions that you would expect such as oncreate which is a function that we can call or rather a function that gets called when this entity i can't write anything when this entity actually gets created so just like unity you know pretty standard on update which is a function that takes in a time step i think we've actually got a time step so we can just take that in this is a function that gets called whenever we update uh well whenever our game scene gets updated and then all of the entities subsequently get updated as well this gets called and then you know there's there's a lot of other ones like there might be one for like the destroying of entities there might be one for like on awake or something if you want to uh maybe create them first but then actually like awake them a bit later there could be like collision callbacks there's a lot of stuff we're going to focus on these three here today so with these three functions and this is by the way exactly how you would expect this to work in c sharp as well so you can probably see some some similar some similarities here i mean it's exactly the same and that's the whole point but these are native scripts so they work a little bit differently there's no script runtime sitting apart like on top of this this is all directly integrated with the c plus one source code so it should definitely perform faster and this is always going to be an option i think it's worth mentioning that the reason why we want to actually create this whole c plus plus scripting in the first place instead of just blindly integrating or rather straightaway integrating c sharp because that's what we're going to use for our scripting language instead of just doing that which we could have done i'm not just doing a cpus first because it's easier i'm doing it because it has a function but also because people have been asking me hey is hazel going to support scripting in c plus plus i really want that i don't know c sharp or i don't use c sharp or i don't like c sharp can i still build games in c plus plus and in my opinion the answer should be yes so to make that a lot simpler than having to modify engine code to bypass the whole you have to have c-sharp scripts thing i want to also make native scripting a thing and that's why we have this whole sequel plus layer so because it is a native script it has to differ from a regular script so what we're going to do is we're going to into the second camera we're going to add a component called native script component this is different from the script component of the future which is something that will be c sharp well most likely i mean it's going it's gonna definitely be c sharp but we might also extend it with lua in the future i don't know for now it's going to be just the native one and the c sharp one so we're going to add a new component into this second camera and we need a way to provide this component with this camera controller class so i mean let's think about how we can do that what we could do is maybe like native script component is a component that takes in some kind of pointer to an instance of an object so in other words we could allocate camera controller here i guess and just pass it in like this right and then assuming that it has these functions which we would somehow need to check maybe it's like it inherits them from like uh you know some kind of entity maybe we have a scriptable entity um or something like that here and then these are virtual functions and they're inherited from that that's something that you could do that's probably a simple solution but we decided with a stream that we can do better than that because as you probably know virtual functions require a dispatch to happen so in other words every time we actually call this function it needs to look up in the v table what the over overridden function is if it's present and called that that's an indirect function call that's not the best for performance and of course since this is a native script we want it to be as close as possible to to the actual function calls we don't want any kind of indirection that's what c sharp will end up doing anyway so because of that what we're going to do is we're going to use some magic to basically determine and bind all of these functions into the native script component that we add so that the scene can then call these functions directly without having to go through the v table and that's what today's episode is going to be about so how do we achieve this stuff and we are we are still going to have it in inherit from a superclass the reason for this is there's not going to be any virtual functions but the reason we we want that is just to give access to some other useful functions so for example in the oncreate function we might want to retrieve the transform component well the thing is if if this is not a subclass of some kind of super class if this does not derive from scriptable entity how exactly are we going to be able to write this function well we would somehow first of all need this scene as well as the entity id from within the entity library or the ant library that's gonna get a little bit complicated maybe if we had the scene we could do something like this but then again we need the entity id the api becomes very messy i really like the way that it's clean inside c sharp and inside an engine such as unity we still want to keep it like this and that's why we have this to begin with okay so i'm not going to lie i do have some reference code from this that i wrote during the stream because then this is mildly complicated and a little bit finicky so i'm going to be referring to that but in general we should be good to go to start implementing this stuff so the first thing that i want to do is talk about this native script component and how we're going to be able to bind to it so to you know long story short here we're not going to be providing any kind of instance this is another important thing that we need to think about how exactly is this instance going to come to be so we don't want to allocate it here we don't want it to be allocated during the creation of you know our components whatsoever when we hit play in the editor and our game actually starts we want to start instantiating these script classes like camera controller so what we need to actually do is provide this native script component with almost like a blueprint we really just need to provide it if you think about it with a bunch of lambdas or function pointers that are going to call these appropriate functions and then let the scene handle that at the right time we don't have any concept of play yet right we don't have any concept of runtime yet really and so because of that like this is the editor layer because of that we're going to basically simulate that today but just know that there are some things here that will be done a little bit later okay so to start this off what i want to do is uh talk about the ui here and in fact let me just find the place where i wrote this stuff specifically so the way that i made this work and the api that we used was basically we had this native script component and then we added a function to it called bind and the reason is we can't really take in like another i i guess i could have made an overload or like specifically like add script here that that added the scripting component for you in fact this might be something that we do this is fun i didn't explore this during the stream but we could we could write a function on the entity class called ad native script that would make this a little bit easier but for now the way that it works is we do second camera at component so entity add component native script component which we're about to create and then once we get that script component it gets created we obviously get it back as a reference we can call dot bind and then the name of the class we want to bind with and that's it and the script component and that bind function on the script component takes care of the rest so it's really really straightforward um and that's it and so basically this is the this is the whole api of what you'll need to do as part of your game or application if you want to add a behavior add a behavior class really to a entity to an entity so i think that's a really clean api and i honestly really like it so let's make it work so let's go to the components class so what we're going to need here is a native script component and so inside this native script component what we want is first of all a pointer to the instance now this is not stuff that you should create on your own in fact the only way to use this class is really by just adding it so you go add component native script component and then use the bind function so i might make this stuff private in the future but basically what we want to have is that scriptable entity we still need to have a pointer to it we're not going to be creating it ourselves it's going to be up to the same class to create it when this actual script entity gets instantiated but we still need to have this so i'll say a scriptable entity uh instance equals null pointer we'll have to make this class in a minute in fact we'll probably take a look at making that now so this script entity and i'm not sure where to make this i think that the best place to make this is probably going to be inside the entity class um although it might be fair to let's see if we can add this somewhere else so we have seen entity because i'll add it here for now maybe we'll add it into a different class in the future so scriptable entity so this all scripts should extend from this class and so actually because we do that we'll end up repeating the api a lot i mean i am actually going to go back and add this uh as a def as a different header file so scriptable entity scriptable entity namespace hazel we will include what's going on today we can include entity because we'll need to what is going on today journal can't write anything journal is stumbling on the keyboard all right class scriptable entity um so this includes entity and the things that it will really have is first of all it needs to actually have an entity right if it doesn't have an entity then it has no way of interacting with the scene or itself so if we want to call something like get component we obviously need to have an actual hazel entity which as you should know by now is an entity handle as well as the actual scene pointer really important stuff i'm also going to make this a friend of the class scene so by doing this i'm just letting the scene class access like the private [Music] members and functions and everything of this class and then the cool thing is over here i can add any functions that i want every scriptable entity to have so stuff like the getcomponent function so let's add that one in as an example so we'll say this is basically going to be a copy of this isn't it so we're going to basically copy this it's just that instead of calling this stuff we're actually going to return m entitygetcomponent so it's really just a wrapper around that stuff and you know i mean this what's whoops what am i doing um and this should just get like you know inlined and optimized away of course by the compiler um when we actually compile this stuff so that's what it looks like and now the cool thing with that is that um let's go into hazel.h let's include scriptable entity here maybe we'll put this into like a script class folder in the future to make it a little bit better but as you can see we now have this and i can now call code like this and i should be able to get that stuff so that's pretty cool so now that we've got that if we go back to the components class we can take a look at adding the rest of this native script component so the big one of course is the bind function so what this needs to do is take in a type taking a class and then just figure out how do i call the existing function so how do i call the oncreate function how do i call the on update function how do i call the ondestroy function so of course since it needs to take in a type we'll make this a template because it needs to be needs to basically be able to bind to anything and then over here we need to set up a few functions so we're going to use lambdas for that now they're they are going to be stored inside an std function so let's just for example take the oncreate function so this is the oncreate function so to do that we basically need to say that the oncreate function is going to um to call the uncreate function it's worth noting that we obviously need the instance so if we go back to here let's let's take a look at this so how do we call on create well we need to have a camera controller instance and then if we have an instance we can just call you know instance pointer whatever arrow on create and that's it so to do that if we take a look at this if we just create a quick lambda i mean we need to need to somehow get the instance and then call on create right and that should work so instance is up here how do we capture that well we could take it in by value or by reference we're going to take it in by reference because it's really important this isn't going to be set at this point at all so we need to actually bind to this on by reference actually hold on i don't think we need to do this at all no because what's going to happen is it's actually going to be passed in as a parameter so we'll say scriptable entity pointer instance and then we're just going to call instance oncreate now hold your horses scriptable entity doesn't have an oncreate function right if we did it like we could add that and we could make it virtual and then we could make the camera controller override it but we're not trying to do that we're not trying to have any virtual functions here at all no v table none of that we want to just have the function that's on this type so how do we do that well we know this type or rather we know instance is going to be of this type right i mean we might as well take in a void pointer here it doesn't really matter because of the way that we're going to control this we're going to know how it's going to work but i'm not going to take an avoid point just to keep this a little bit more safe i guess or safe looking and then so over here what we need what we really need to do is grab instance and cast it to a t pointer so now instance is a t is a t pointer which in this case is going to be if we go back to editor layer we know it's going to be camera controller and then when we create it of course we will create it as a camera controller so that checks out and we just call on create and that's it so check this out we have oncreate we can call it amazing so first of all how does the instance get instantiated well to make that happen we actually also need a function here that sets up this instance so how do we make it actually uh create or instantiate our camera controller class or whatever our script class is so we'll say instantiate uh funk i'll call these funks just because oh maybe not you know what maybe not we'll be all detailed here so what is the instantiate function going to do well i'm going to have it be a capturing lambda here that's going to actually take in the reference of this instance thing right so we could write this out like this if we wanted to um in fact i think i will just to make sure that that's the only thing we capture um doesn't need any parameters and then it's just going to set instance equal to new t so this is worth noting that it's only going to work if our constructor here is um how should we say this i do need a semicolon here um if the constructor has no parameters if it does we'll have to forward those parameters typically you don't write constructors at all right if you look at like an example from unity when you create a new c-sharp script you don't write a constructor with a bunch of parameters or at the at the very least if you do that which is a bit weird because maybe you use it elsewhere i don't know whatever you should probably have a default constructor so that is a requirement that we do enforce here but again usually that's fine we also need need a destroy function so you need to be able to destroy this instance a little bit later so to do that we are going to add that in i didn't actually add that during the stream um but destroy instance function i don't know if there's an opposite of instantiate but basically destroy instance function will just delete this um and i guess you know if we wanted to in debug mode we can also so delete instance now instance needs to be of type t when we delete it otherwise we'll have some issues um so we just need to make sure that we're actually passing that to t um and then uh you know just make sure that the destructor of our um you know camera controller class in this case gets called just in case we allocated memory this shouldn't happen though that's the thing we're deleting this because it's heap allocated and we need to but um you know we probably want to be a little bit smarter about how we manage memory that's a discussion for another day but basically this instance gets deleted so we have uh this function as well i think that's all we need to do and again if we wanted to we could also set instance to null pointer maybe only in debug mode or something like that in the future just to make sure that that actually works okay cool on create let's create the two other functions that we have so we have ondestroy and we have on update so on destroy is also going to be a void function here um and it's just going to call so we'll do ondestroy it's going to call on destroy and then finally on update and on update is going to call on update but we also need the time step so we need to take in float ts here or rather time step ts over here and i'm also going to mark this as accepting a time step of course so these are our functions now one quick note i want to add is that std function we recently removed bind from the engine because bind actually std bind allocates memory and it's a little bit it's you shouldn't be using it std function does not allocate any memory um usually or at least in my testing it hasn't so i have yet to do additional extensive testing to make sure that it's fine or just maybe reading the source code would be a good start but as far as i'm aware this should not allocate the lambda itself should not allocate memory even if it is capturing so that's also worth noting std bind is the evil thing that we didn't want to use not a city function as far as i am aware okay so uh and we have all these functions fantastic so now if we go to the um editor layer and we uh have all this all of this stuff here first of all let's do a quick compile control f7 just to compile this one file make sure all of these templates check out of course they don't a lambda capture must be available from enclosing function scope okay oh because we need to capture this in that case sorry so to make this happen we need to kind of capture this and then do this instance but if you just leave an ampersand here i think it should work because it should figure that stuff out for you automatically all right now we have no um blah blah what's this so we're doing a right right hand of buying lambda there's no acceptable conversion from this um why is that the case it's a void function isn't it so as far as i'm aware that should oh sorry um well for this stuff it's not working obviously because we need to actually take in a scriptable entity so these functions these std functions obviously need to take in a scriptable entity as a parameter these two don't because they just affect instance um i think that's it if yeah all right that's it um [Music] yeah of course like we could take in the component if we really didn't want to capturing lambda we could definitely make it work we would just need to take in the component itself or a reference to this pointer and then we could assign to it and in this case it could even be copied so we could make this a parameter in the future if we wanted to but i think doing it this way at least for now is fine okay so now that we've got that i believe we can compile our code and it should work great so let's test it out or rather before we test it out we should talk about what this actually does because at the moment it does nothing so the point of this is we've now binded these functions these functions they're now bound to be inside the component class right so in other words this native script component now holds references to all those functions it can now call them so what we need to do is we need to go to the scene class and we need to actually make them do something so this is all about rendering code inside scene on update what we need is the um so update scripts we need some script updating code now oncreate should definitely happen this oncreate function should definitely be called when we create and then destroy needs to be calling it when we destroy but none of that stuff happens yet because we don't have a we don't have this concept of let's start the game now or the run time has begun we don't have that yet and as a result of that there's no point in making that do anything yet so because of that we're going to simulate it we're going to basically iterate through all of the entities that have that native script component and then if instance is set to null we're going to run the instantiate function which is going to create it and then when we create it that way we know that okay it exists now now we can call the uh on update function to actually update the entity and i guess as part of it we'll also call on create so to iterate through all of our update scripts via and of course we need to create a view so i'll do this in a slightly different way that's a little bit more optimized instead of actually writing auto view equals m registry view i'm going to take a view of just the native script components we can't use groups here because we're just going through a single component and then i'm actually going to write dot each and then this lets us provide a lambda that will be called for each of the entities that have a native script component so i'm going to add in the entity and i think it needs the actual native script component which we'll call nsc let's fill out the rest here and then our goal here is really just to as i mentioned see first of all if this whole like instance is null or not so if it if it's null right if that instance doesn't exist yet and thank you visual studio for ruining everything um if it doesn't exist yet then we want to call that instantiate and visual uh intellisense is just not helping at all so i'm gonna have to see what it is this is obviously a void function so it's like it doesn't have any parameters we just call it like this we should now have a valid instance and we can definitely check that to make sure it works but i don't think that's necessary um now we've got a valid instance we can call the oncreate function now notice that to call the oncreate function because i decided not to write it as a binding lambda which i i could have obviously i could have just done this and then that way we didn't need this parameter but we've decided to do it this way so i need to pass in nsc.instance like that and now the oncreate function should get called finally uh outside of this so every frame and every time like for as long as this exists obviously i mean if it doesn't it'll create it which should happen on the first frame i guess but then on the first frame after it's created and also on every subsequent frame we want to obviously call the onupdate function and we want to pass in the instance as well as the time step so instance and ts is the time step and that's it so now technically at the beginning of each scene update anything that any entity that has a native script component will have its create function and update function called and the update function being called every frame obviously so to test this out in a very easy way let's just print out the time step so time step ts and of course we'll test out the um i don't really care about this let's test out the uh let's just say on create we'll test out the oncreate function by just writing this okay let's hit f5 and let's see if this works no it doesn't because we have some oh yeah so what happened to this this completely are we did we i guess we didn't include this so scriptable entity needs to be included hopefully that will uh work out for us although yeah because i don't think we include the component header file inside the other one so we should be okay all right look at this so we have our time step being printed every frame and then hopefully the first frame we have on create okay so you can see that we're now able to essentially write some scripting code as easily as this pretty cool so let's test this a little bit more extensively let's try and do what we wanted to do last time which is basically make that camera move so how can we make it so that maybe like the wasd keys or whatever would move this stuff so first of all let's grab the transform component um we can grab this uh let's just keep this really simple this code really simple for now let's grab it inside the on update function um so dot transform i believe is what we're looking for we'll take a reference from that we now have transform and now since this is part of c plus code right it's got the whole c plus plus api that we have available to us we can just write if input is key pressed uh let's make it the um [Music] the w key for example so if uh we'll start with the a key so we'll move we'll make it move left so to move left we just need to say transform um so let's get the third column of the matrix and the first element of that column which is the x translation and we'll uh subtract it by some kind of speed so we'll write float speed maybe equals like five or something like that and we'll subtracted by speed times time step so um let's do that uh d w s so um d will make it move in the positive x direction w will make a move in the positive uh y direction which is the first element of that third column and sorry the first index which is the second element of the third column and then s will make a move down okay i think that's correct so remember we're modifying the camera's transform this is not the view matrix this transform will be inverted to create the view matrix and since we're taking a reference of this we should just be we don't need to assign it back or do anything like that this should just work so let's hit f5 and let's see if we have now added behavior to our camera class in which case that i think would be pretty cool let me unselect my console and we crashed oh it was almost perfect so apparently it's saying that we don't oh no no i forgot to do something extremely important so i'm actually glad that happened because i was almost about to end the episode um obviously right now if we call get component it's going to try and get the component from the entity but guess what the entity has never been set we need to actually set the entity so how do we do that in fact guys to be honest i don't remember how we did that oh we just we okay i see we just did it in the satchel function so to do that we're going to go back to our scene class and then inside the instantiate function over here and there's a few different places you could do this but now that we have the instance we can just basically before we call on create we can say instance m entity equals entity just like that except entity of course is the ant id so we need more than that we also need this because that's the scene pointer and we should have a an an entity and i'll try and actually uh make sure that it actually recognizes that type and this will probably make a little bit more sense to read as well take two let's hit our five and let's see what we get so now we're now we're actually telling our scriptable entity which entity belongs to all right does it work it does not i don't see any update happening here at at all this is not not going as smoothly as i was hoping it would go um i wonder why that's happening we're still binding to it it's still calling on update or at least it should be um the only thing i can think of is that this get component function might not be working correctly although i imagine it should be let's try and debug this so let's hit um [Music] f5 here so if i press this this uh transform is obviously oh right of course i think it is working i just forgot that we're modifying the second camera so if we click if we uncheck camera a and we now take a look at this you can see we are now able to move our camera false alarm everything's working fine so there we go and then of course if we wanted to add this to the first camera or something like that instead let's just grab camera entity let's add this to the camera entity instead suddenly we now should be able to have that same behavior on our first camera which should be pretty cool so yep there you go now if i uncheck that i can't move this camera but i can move my first camera and that is the basic way that we are going to be able to add scripting in c plus plus to our entities now there's one thing that i want to note and this is the kind of more advanced part that we went into during the stream and we will address this in the next episode because uh this took this took a while anyway but um essentially we have a bit of a problem and that is that what if we don't provide all of the possible functions so for example what if i don't want like you know on create on destroy do nothing i want to keep my class really simple i just want to do that is that going to work and the answer is well no because as you can see the template does not compile correctly because oncreate is not a member of this so when it tries to actually bind this function it can't because oncreate is not a function that exists on t which is a camera controller so how do we make it maybe only bind the functions that exist well there are a few ways to do this and to be honest i'm not sure exactly how many ways there are to do this but the best way to solve this is via some template meta programming where basically we can check to see the type traits of this t-class and see whether or not this function exists and then if it doesn't exist we can use a if const expert we can use a little constant expression here to at compile time decide to just not even compile this oncreate function code whatsoever if the oncreate function was not provided and then of course inside scene we need to check to see if this function exists and if it does exist we can call it and same with the on update function so we can check to see hey is on has on updated function been set and if so call it otherwise don't so that is something that we will tackle next time because it's it definitely deserves an episode of its own there's lots of fancy template code that needs to be written there but otherwise if we just leave our on create and on destroy functions like this then i think we should be pretty okay so let's check this out yep everything works alright guys hope you enjoyed this video if you did please don't forget to hit the like button uh below and if you're watching this on twitch don't forget to follow the stream you can also help support this series and me and everything that i'm doing by going to patreon.com for slash the channel where you'll get access to some fancy benefits such as as i mentioned these streams recorded in kind of an archive format that you can go through as well as the source code to hazeldev which is a much more advanced version of this with like 3d rendering and like you know c-sharp scripting and and like a real editor and all that stuff if that is something you're interested in anyway thank you guys for watching i will see you next time [Music] goodbye [Music] you
Info
Channel: The Cherno
Views: 29,638
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, scripting, c++ scripting, native scripting, c# scripting, ecs, entity component system
Id: iIUhg88MK5M
Channel Id: undefined
Length: 36min 32sec (2192 seconds)
Published: Thu Aug 13 2020
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.