This is an old revision of the document!
“Update and Render functions? Where we're going we don't need hardcoded Update and Render functions”
First of all, a disclaimer: You don't really *HAVE* to use the Proton Entity System to use the framework. If you already have a system you like, or are porting existing code, you can safely ignore most of this section.
I've made a lot of engines and a lot of game frameworks and for some reason I'm constantly trying new methods, mostly because it keeps things fun and interesting.
Here past methods I've tried when creating games:
Using all of these, this is how it went:
(Ladies, look at your game development cycle)
(Now look at mine)
What if we could trade some initial complexity in the beginning for a more sane experience over all?
Aka, my version of “my interpretation of how Unity internals sort of work after I used it for a few hours”.
If you take a look at Entity.h you'll see some functions but nothing about thinking/updating or drawing. So what can an entity do?
Well, not much, alone. It's fairly lightweight at about 200 bytes.
Instantiate an entity
Let's do some real code to give you an idea of how to use these things, feel free to type along. (you could write it in App::Update() from the RTSimpleApp example)
Entity ent; //instantiate it //Ok, now we've got one. We can name it if want. ent.SetName("DeLorean"); LogMsg("The entity is called %s", ent.GetName().c_str()); //Shortcut: we could've just done Entity ent("DoLorean"); Check the .h to see what else it's got. //Yes, I like printf formatting, so sue me. I also like std::strings. So you'll see a lot of those .c_str() conversions around.
Is it an entity or a database?! This “Delorean” has something powerful under its hood - a Proton VariantDB object. This is a tiny but powerful name/key database that internally uses a stl hashmap for quick look ups of not only variables, but also dynamic functions. Let's store some arbitrary data in our entity.
ent.GetVar("gigawatts")->Set(1.21f); ent.GetVar("trivia")->Set("Doc pronounced giggawatt wrong in the movie"); //retrieve the data like this: LogMsg("Gigawatts: %.2f, trivia: %s", ent.GetVar("gigawatts")->GetFloat(), ent.GetVar("trivia")->GetString().c_str());
Great. Notice that to retrieve the value, you have to already know what it is. This type safety is important to keep your game bug free. If you try GetFloat() but the data inside is a string, it will assert to let you know.
Types the database understands are float, string, uint32, CL_Vector2, CL_Vector3, CL_Rect2f, *Entity, and *EntityComponent.
VariantDB's can Save()/Load() to disk as well, I use one for my app preferences in my games.
Adding a component
Well, we'd better get a visual on the screen before you fall asleep. Now, there are a ton of helper functions in EntityUtils.h but we'll do things “manually” a bit so you can get an idea of how things work.
We're going to use an “OverlayRender” component to load and draw a picture on the screen. You can see all the available components in proton/shared/entity. Projects like RTBareBones don't include them so do it yourself if needed.
Each entity can hold an unlimited number of components, or child entities. Each component also has its own VariantDB inside. Components check their parent entity for some things (color, position) and their own database for things that the rest of the entity/components wouldn't care about.
Entity ent("logo"); EntityComponent *pComp = new OverlayRenderComponent(); ent.AddComponent(pComp);
Wait, we forgot to actually load the image.
Great, so we're ready to render. If you look at OverlayRenderComponent.cpp, you'll see it registers a Render function like this:
//register ourselves to render if the parent does GetParent()->GetFunction("OnRender")->sig_function.connect(1, boost::bind(&OverlayRenderComponent::OnRender, this, _1));
Ok, don't panic. What it's doing is saying “When someone calls “Render” dynamically on our parent entity, we'll also run our own render function”. It doesn't matter that the Parent doesn't have a “Render” in its function database, by asking for it, it is created, same as variables work.
So, to get this component to run its “Render” (you'd have to do this per frame) you could do:
That would do it. Or, you could call it on the component itself:
This would render the image from a reference point of 0,0.
Now, let's say you had an entity that had 50 child entities, some of which had additional child entities. Wow, that's a lot of OnRender's to call!
No worries, you can recursively call the function so it hits all of them:
ent. CallFunctionRecursively("OnRender", &VariantList(Variant(0,0))); //Now, the above would work, but a better way would be to pass on each entities frame of reference, so the positions are relative to their parents xy position. ent.CallFunctionRecursivelyWithUpdatedVar("OnRender", pVList, string("pos2d"), 0, Entity::RECURSIVE_VAR_OP_ADDITION_PLUS_ALIGNMENT_OFFSET); //The above says "modify the reference point by the whatever is in the variable 'pos2d'". Now when you move an entity, its children will respect that move when rendering.
Now, what if instead of calling it, we build a component that called the above and we named it FocusRenderComponent?
(FocusRenderComponent attaches its OnRender to GetBaseApp()→m_sig_render, a signal that is triggered once per frame when the game is supposed to render. There are other ones there as well, anyone can attach to them if they care if certain events happen. Instead of polling, we wait for things to push data when needed. This method is sometimes called “signals and slots”.
So, to sum up, this means you can store entities anywhere, you aren't limited to a single hierarchy. The engine doesn't care, as long as you add a FocusRenderComponent, it will be rendered at the correct time.
Similarly, FocusInputComponent is used to catch touches and keystrokes in OnInput, and FocusUpdateComponent is used for recursively run OnUpdate() on all entities. You can also setup filters to not pass recursive events down to children if needed.
That said, there is a handy place for you to store your entities, GetBaseApp()→GetEntityRoot(). (It's an entity you can add to, think of an empty entity as functioning like a folder if you add more entities as children to it)
Ok, take everything you've learned and understand that you can “schedule” those things to happen as well.
Here is an example, and now we're going to use helper functions. Feel free to look at the helper function source, they are just doing easily understood stuff, it's easy to write your own.
one time init Entity *pEnt = CreateOverlayEntity(GetBaseApp()→GetEntityRoot(), “logo”, “game/test.bmp”,0,0); AddFocussIfNeeded(pEnt); so it will get render, update, and input calls GetMessageManager()→