Intro to EnTT (ECS) | Game Engine series

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hey what's up guys my name is china welcome back to my game engine series so today we're going to be taking a look more of like an introductory look at an nt component system and how we can basically integrate that into our game engine and what the big picture of that is going to look like if you don't know what an entity component system is then please watch last week's video where i actually just drew a diagram and we talked about what entity component systems were why we want them why they're better than just doing things a different way um i think that's really going to help you understand why we're going down this route at all uh and i mean there's a lot to say there so just watch that video first and then you'll this will make a lot more sense so what we're going to be doing today specifically is we're going to be taking a look at an antique component system called entity or ant this is an entity component system that has been around for a while it's used by a few major projects you can actually look at the github repository linked in the description below to take a look at what that's all about i've chosen to use an anti-component system that is not something that i've written from scratch for a few reasons and i've talked about them i've talked about those reasons in prior videos but to keep it short the reason is because an anti-component system is or at least a good entity component system is something that's largely complicated and will take a long time to write properly i know this from experience and it's it's not it's not really something i mean you can write a very basic entity component system probably in a week or maybe a few days or however many hours you want to spend on it but i've chosen to take something uh else for hazel because first of all um i i don't really like i i know that an entity component system is something that is like we need to have a good one for hazel that's just like there's no way around it because otherwise we're going to be quite limited with how we use entities and my ideas for how we're going to be using entities and components means that we really need to have a good entity component system but also because um out of all the things that i'm interested in entity component systems are not at the moment at least not my biggest focus i'm really interested in graphics and rendering and all that stuff which is why we're doing that stuff ourselves but an entity component system is not is not like my highest priority and of course as pretty much a one-man show riding this engine it would be very unrealistic for me to dive deep into every uh component that makes up a game engine so maybe in the future if people want and if that's like a thing that that i get around to i might make a tutorial series on just writing an empty component system standalone just so that um you people understand better how they're put together and that might be a fun little educational thing but i just know that end is going to be better than anything i'm going to write in the next few episodes if i spend that on if i spend that amount of time on it and it's it's it's just a really good proven entity component system so i don't see any reason for why we shouldn't use it speaking of which let's take a look at it so this is ant um i'll have a link in the description below to the github page here you can see that it is mit licensed which basically means that we need to disclose that we're using it which is totally fine um so license and copyright notice is basically the condition here uh there's you know i mean it's used by minecraft i think the windows 10 edition uh it's it's it's quite proven and it's also quite simple i've been using it in hazeldev for quite a while now um so uh for those of you who don't know hazeldev is like the patreon only kind of uh version of hazel that is like pretty much a full game engine at this point um that's been using ant for a while or entity for a while just to be clear i know that it i think it's technically it's supposed to be pronounced entity but i'm going to call it ant and the reason i'm going to call it m is just because it's a little bit ambiguous if i say the word entity it's like you might not know what i'm talking about i might be talking about the entities in the entity community system or i might be talking about this library so in the future i'll just say ent for this library and that way it'll be less ambiguous anyway i suggest that you read the wiki if you're new to this if you never used enter before read the wiki because if you specifically read the crash course of the core functionalities this um and there's a lot of like i mean you should read this whole thing really but this stuff will kind of it's not even this there's i think there's another maybe it's this empty component system yeah this is what i'm talking about if you read this this actually um explains a few of a few of the design decisions pay-per-use meaning that you don't have to um like you know as i mentioned i think last time things that you don't use even though it's a very big library things that you don't use aren't necessarily going to cost you in terms of performance um there's so much stuff going on here you should read through this whole thing i did this was basically my first point of contact with the library um it's it's written quite well and it has a lot of really nice examples so if you read this you're going to be very familiar with this and everything that i want to say in this video is probably not going to be completely completely redundant maybe because i'm going to talk about how it's going to work in hazel but it's it's going to be you're going to know a lot um so all we really need from this we could uh get this library as a sub module and add that and you can see it like it it gets uploaded it gets updated fairly recently uh fairly frequently like this was yesterday um you know the single header file was uploaded um updated two months ago i believe that it probably gets uh updated with like releases and stuff like that my favorite way to use a library like this because there's a lot of stuff here there's a lot of stuff that we don't care about is to simply take the single include so we have a single include here which is the end file now i don't know if this has the license inside it um no it doesn't so we'll need to obviously take the license as well so this is the license um so what i'm going to do is basically grab this end.hpp file and if you click on raw over here you'll just get the file right and then you can save that as and we're going to put this into if i go back to hazel and open the uh the hazel folder in the file explorer and if i go into vendor and then i'll make a folder called ent i'm just going to pop that into there so let's copy this path pop it over here and here we go we'll save it as just and dot hpp okay that's it um and in fact i'll make a folder here called include and i'll put it into there that way we can just include that directory and we should be good uh the reason is we also need to include license files i mentioned and the license file and copyright notice and all that stuff um i don't know if the license file is included in that probably not um there's a i don't really see the copyright notice anywhere um maybe that maybe just the license is fine um so if we grab this license file save it in the same folder rather up here i'll just leave it as a text file because that makes sense um that's it right so this includes the copyright that's it okay so that's all we really need to kind of use ant it's really just this file it's really easy that's the beauty of the single kind of file includes and it's going to enable us to have an entity component system in our engine now how do we set this up well a few different ways um what i'm going to do is i'm going to go back up to the root directory and i'm going to open the premake file and inside the primag file i'm going to add a directory up here because just because we like we tend to keep them up here because it's just a little bit more um it's a little bit better organized if we keep them here i'm going to type in end entity like this um in terms of like how we want this i'm going to put this last just because like all this is pretty much to do with graphics and windowing and stuff whereas this is like the entity component system i haven't given much thought to this clearly um this is all i'm doing all this live so uh it doesn't yeah like whatever maybe i'll change it um and we'll just do n include okay and then if we go down to and you can like you can treat this however you like you can put it into a folder called ant inside include so you do end slash include when you do the or and sorry and slash n.hp that's your include path um when you write code i'm going to leave it as is honestly uh and then in the include directories we're going to so we're going to have to do two things we're actually going to have to include it in the engine like this right but we're also going to have to include it in the sandbox and in well in hazelnut as well the reason is it's kind of like for the same reason as glm maybe the reason is that um and again it's whether or not you need you 100 need to do this i can't tell you well the answer is no you don't 100 have to do this but if you choose not to include ant in your client source files then things become very difficult because and run and is a header library or rather a templated library so by basically by saying that you don't want to include it in the client you're saying that much of the library's templated features cannot be used by the client because obviously if you're only including it in the hazel library then only code that has has been you only code that uses that those templates from and inside the actual hazel library that is the only code that will be compiled from end which means that if i come in from the client from my game or from my editor and decide to add a component or get a component or create a new component or something like that i can't do any of that because to get a component from ent i have to have a function to link to and if that's never been done in the engine for example then that function will simply never be compiled and it won't exist and nothing's going to work so by including and in here we're basically saying that even though we're never going like ideally we would never interact with end directly i don't want to be using the ant api from my game we should be going through hazel for that for a number of reasons mostly because we might be doing extra stuff on top of ant that you know relies on on those proper functions being called rather than internal functions which are what which is what ant is going to be but largely this is kind of uh you know this enables us to use the full power of ant um through you know wrappers and other things without having to worry about what has been compiled what has been used what templates have been instantiated and which have not so that's kind of the reason for having that in here now um that's probably it let's try and compile let's try and run that pre-mag file and then we're going to see if if that helps and by helps i mean if we get so in editor layer we should already have access to this now i don't think we have seen or an entity class um we're going to create a scene class here if we go into core um we're actually going to make a new folder inside hazel and make sure that show all files is checked and i'm going to make a new folder here called scene now this is going to contain everything to do with scenes and component systems components all of that stuff right entities everything will be inside this this scene folder so this is not just going to be the scene it's going to be like all the ecs stuff as well so inside this scene um namespace hazel class scene uh this will be something that is uh well it'll be reference counted but that's not really necessary to talk about now because we're not really doing that um h said p c h and scene just some boilerplate code here okay so we've got our scene class i'm going to basically use this scene class to talk a little bit about how ant is going to work and to be honest it's very very simple um there's not even that much to talk about but this is going to function as kind of an introductory video this is just going to be a little bit of an introduction to end in general so if we go back out here um when we create a scene uh you know i'll have just a default constructor and a destructor as well which we will fill up with a few different things and then ultimately what we want here and we'll include ends over here this is another reason why we need to include it in client because we're going to be including ant in the header file and really what we want to be using is something called the registry now the registry is basically a container for all of these components and entities so entities the way that entities work are entities are simply ids as i mentioned in the kind of what an anti-component system is and all that in the previous video um an st is basically an identifier that we use to determine which components kind of belong together in the entity and a registry contains the actual component data as well as the entity ids that's what a registry is there's a few other things going on there as well but that's basically all you need to know now inside this registry so picture the registry as like our entity context it's the container that contains our entities even though entities by themselves don't really contain much data but that is what the registry is so in other words the way that i like to do this is to just just to have a registry per scene now if you need to uh you know have an entity possessed from one thing to the other you can copy the entity into there um i know that ant has some like serialization snapshot mechanics i haven't actually been using them um i basically just rolled my own and in hazeldev and the reason for that is because honestly i like reading the documentation i just could not like i spent about 10 minutes on it and i was like no this doesn't make sense to me like i don't know how this works there aren't any clear examples i'm not even sure if this is the right path um so i just decided whatever at that point in hazel devs like development i just wanted this thing up and running and serializing as soon as possible so i didn't really care too much about that but i know it has that so if you guys want to do more research and figure out whether or not that's something we should be using then be my be might be my guest um but for now i haven't done that so registry is like lots of features there um i think there's even like a signaling system with events so for example you can cache events when components get created or removed um from entities and you can process them that way there's a lot of stuff going on um so just keep that in mind so that's the registry and that's the basis for all of our kind of entities in our scene so if i create this stuff let's uh we'll do a little bit of a test here as i mentioned so um upon destruction the end the registry will be destroyed and everything will get released but uh we don't have to worry about that because it's a stack allocated registry object here so it'll just get cleaned up automatically so what i'm interested in is how do we add stuff well it's pretty simple we just create we just do registry create what this does is create it creates an entity and we have uh an entity type that it returns so there's a few different functions here but basically we can just literally do this and i think this will return an entity right and this will be our entity so we've created an entity inside and great so what is entity and this is again important to understand i think if we look at what entity is entity is an opec type well i don't even know what this macro does you know class class type blah blah this is probably not what i wanted to look at um here it is so enum class entity id type and then you can see the type here is an id type and the id type is an unsigned int a 32-bit unsigned int okay so that is what an entity is right it's it's it's called an entity right but ultimately it is it's this this is what we're dealing with if we were to break this down and we can actually cast between them if we want but that's basically it okay so again understand that and because of that you can't do things like entity.whatever because it's an integer um instead everything goes through the registry so in other words if i want to suddenly add a component to it i just do registry dot um uh i think you can do insert there's a few different ways insert and honestly like i'm just going to reference some hazel depth because it's been a while since i actually used nsapi because i have my own wrapper in place so it places what we've been using in place um and then we obviously specify the actual name of the component the entity that we're placing into and then any constructor arguments so let's just say we have a transform component um how do we define components like this that's it it's just a struct there's no inheritance like it's not like there's like a base class that we're inheriting from none of that whatsoever because we don't want any of that that's just extra noise what we want is basically a structure of data that's our component data so transform component would have a glm map for you could use glm map for itself but that's a little bit messy it doesn't say that it's a transform component you could define different types there's a billion ways you could do this you honestly could do it that way i don't like to do it that way i like to define a component called transform component which has data inside it so let's include uh let's include glm this is what we have here okay a transform component a mesh component would be the same thing it would have mesh now you should probably make sure that you you know have a copy constructor maybe a um a default constructor as well so i would have a default constructor i'd have a constructor that you know we'd have a copy constructor that kind of is just a shallow copy i don't even think we need this we might need this um we could probably leave this as default though um like that is that a thing yes it is um um i need to brush up on my seatbelt class clearly uh transform component const glm matt for transform and then this obviously is just going to set transform to transform so this is like a basically a little tutorial of how to write a uh component class right and then destructor obviously we have a default one we don't care about that um so copying and copying components in my experience i've used that a little bit in hazel dev i think it's a good idea to have and then finally what you could do if you really wanted to is basically there are a lot of scenarios in which you might want to use the transform component as a matrix for example so what i mean is like maybe we have static void dude maths and then um yes i'm australian so i have a s in the end um sumi so we have khan's glm matt for uh transform right and then obviously this is going to do something with the transform um now if we want to call this function it's a little bit annoying if you have a transform component uh so maybe this is your transform um you know whatever and then you want to call do you want to call that and by the way just about that little joke when i write code i typically do actually write it in american so you might see me use color for example instead of that even though if i was writing a word document or some kind of handwritten note to someone i would write color like that so when i program when i write code i usually write i almost always write this like this so you'll never see me write this which is why it's not even realistic it's not even a realistic joke so for that i apologize um so if we're doing some kind of math here um then uh it's really nice for me to be able to provide a transform like that it's kind of annoying in my experience to write code like this which is the only way that this will compile at this point so what i tend to do which some people might frown upon is i basically have a an operator that is my like operator glm mat four right and by doing this and returning the transform of course right what this does and you can do whatever you want with this whatever right the point is this will return your transform um which means that suddenly instead of doing this i can actually write code like that right because i've written this is like an implicit cast operator i have no idea what it's actually called but that's basically what it is it just lets you implicitly do that cast and in fact it probably lets you do it explicitly because i don't think that this will compile if i do glm at four transform maybe it will oh oops i've got this yeah i didn't expect that to work because that's not like obviously you could type on it where you basically you know do this and that will in fact work but that's obviously very messy and some people will really frown up on that so what i like to do is add an operator like this to my components just so that you know it's just that i don't need that extra kind of level of interaction like in terms of my code i actually can just write code like this and it will work because anything that accepts a um because since a transform component really is just a map four wrapped in this transform component type um it's essentially uh it's you know you can use it in every way imaginable where you can use a map for a glm map for so i like to do that conversion just because it cleans up the code a little bit so just keep that in mind and you can obviously add this is like a const version you can obviously add a non-const version as well um like this and visual studio likes to uh correct it like this all right um so uh and sorry i meant that oops sorry microphone um there okay so now you have a non-constant accounts version you can do whatever you want with that so that is kind of how i roll as far as all that stuff goes great so how do we get that into the entity well it's pretty easy as i said registry in place we put in the type transform component this is created on the fly you don't need to register your components ahead of time which i find is really really nice and one of um and great features um then we provide the entity where we want to basically place the component into so where what entities this component belong to then we um provide the um the arguments right so in other words you know you don't have to do anything that's going to just use the default constructor but you can also provide your glm.4 transform here so i can do glm math for one right for the identity matrix and that's it i've been placed a component it's inside my entity now and it's a transform component which is initialized to ident the edit the identity matrix okay so that's done that's really all you have to do um now uh you know removing components um you know there's registry uh remove um remove as you can see will remove any like a list of components from an entity so you can do like transform component entity like that it's gonna remove it from the entity there's registry um i think you can clear the registry which is just going to remove everything i think you can um what else is useful registry get of course so i want to retrieve the component now i want i have my entity this is my entity all right pretend this doesn't exist it's transformed component well i mean it doesn't there's no way i think in place okay a place returns it a few different things going on here um so you can actually when you replace it you can also return it also returns it so you can immediately start writing to it so i can say transform equals glm map for something else right now i'm changing the matrix um so that's something that you can do if it was like a mesh you can you know do something to the mesh like get its materials whatever you guys get the point it returns a reference to it which you can do whatever you want with however if it if you're in a different function you just have the entity how do you get how do you get my entities transform um you just say get transform component and then entity uh and that's it so this will return a reference to the transform component um so now you have i'll just write the type so we're more clear now you have a reference to transform component you can reassign it you can reassign the transform within it um you can do whatever you want you have that reference um now how do you know if it has a transform component you can do registry.has transform component again passing in the entity and then that's something going to trigger if it actually has that component and then finally one thing that's particularly useful is not just um checking to see if a particular entity has a component because that's this is all kind of doesn't really need an anti-compressor system but the the big juicy part of this empty component system which is why we want one in the in the first place is what if i want to iterate through all of my entities that have transformed components so to do that there's a lot of different ways honestly there are so many different ways the best one probably i think is to use a view now view and there's also a runtime view which is based on runtime types if you don't know the type at compile time so in other words you can't use templates then you can use a runtime view but i've never had to use that yet um so you do you just do a registry view transform component uh and that's it this gives you a view that you just use like this and then you can actually iterate through all of the entities in that view so this view consists of all these different entities so now i have an iterator going through all of the entities in my scene in my registry that contain a transform component so here's a scenario i want to render all of my meshes you do this with your mesh component and then you get a view of all the entities that actually contain mesh components and you just render them simple as that so in other words if i actually wanted to get it out here i would basically just do the same thing here where i do um transform component transform and then i have my actual entity here okay simple as that um so uh that's how that works now what if you want multiple components so going back to my mesh component example we need more than just the mesh component to render a mesh usually we also want the transform component so to do that you can use a group so auto group equals registry.group and this lets you group multiple components so so basically you just do transform component and then in here um and uh and i'm going to pretend that i remember this but i don't so everything's falling apart now and i'm looking at hazel dev code to see how exactly i've been using groups okay get that was close get um mesh component now we don't have a mesh component so i'll quickly add it struct mesh component oh we don't even have meshes do we this is a 2d engine whatever um it could be a sprite that's it right so you basically uh once you have that um then uh you can and i think for the view yeah okay um i think you might be able to is there a there's also a view get function by the way um which i think does the same thing so you can actually do this does this work yeah so you can do this instead of getting getting the it from the registry which i suspect is the proper way to do it just because it's probably more optimized since you're only dealing with this current kind of narrow view not the whole registry but realistically i don't like i have no idea what i'm talking about but i can't i can't imagine it being much better because of the way that an ant is built anyway um and then yeah for the group we basically just uh iterate through the group the same way as a view so we go through the group here and then just do group get and then whatever you want and you can actually um you can actually uh like you know stack these like that so you get your transform and mesh component um and then the cool thing is this works with c plus 17s stuff so you can just do auto reference and then transform component so transform comma mesh right um and we'll get rid of this junk right and by doing this we can just get both of them immediately and that's it we have a reference to the transform component it's called transform and we have a reference to the mesh component it's called mesh we can do whatever we want we can be like you know renderer submit mesh mesh transform or whatever and that's basically our our basic render loop for a 3d renderer done okay so that's how it works it's really simple um and really powerful and really fast and really good so that's a rough overview of entity i don't know if there's anything else that i need to cover um i'm just looking through some hazel dev code but i think that that's pretty much it next time we'll obviously continue on with this because there's a lot more stuff to cover but in terms of a basic introduction to end i think that's about it um keep in mind that uh as i mentioned you can of course um chain events and like signals and stuff so you can do registry um on construct on destroy on replace on update for certain uh for certain components so in other words if we construct a transform component um we can literally do dot connect to um you know like a function so we can have a function here called on transform construct that maybe prints hey you you constructed a transform component or whatever i mean realistically you wouldn't do that but then you just give it a pointer to that and i think oh sorry that obviously needs to be uh like that and then that's it right so that's now going to every time you create a transform component this function is going to get called and of course i wrote this with nothing but really it's supposed to be something that takes in a reference to the registry as well as the entity that the component was created on so you basically end up with this um and then that way you can of course you know do other stuff in the registry if you want you can see what entity the transform component has been created on so many ways to do this for example like so many uses from this i mean for example you might be you might want to catch script events so whenever a script component gets created you might want to take this opportunity to into like initially initialize or instantiate the entity inside your script runtime now that you know the descript component has been added to it um so lots of stuff to do there and plenty of fun to be had anyway i hope you guys enjoyed this video if you did please don't forget to hit the like button you can also help support the development of hazel and everything i'm doing here on youtube by going to patreon.com the channel huge thank you to everyone who is keeping this series going as mentioned earlier if you want to get access to all of this in action in like an editor inside hazel inside a 3d world with with physics now even um then uh 2d physics don't get too excited um then check out the patreon and you'll also be able to get access to live streams of me developing fun stuff like this thank you guys for watching next time we're going to be uh probably making like an entity class of our own and seeing how that integrates with ants and how i've been using that inside hazeldev as well thank you guys for watching i'll see you next time goodbye [Music] you
Info
Channel: The Cherno
Views: 38,606
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, ecs, entt, entity component system
Id: D4hz0wEB978
Channel Id: undefined
Length: 31min 10sec (1870 seconds)
Published: Tue Jul 14 2020
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.