User Tools

Site Tools


proton_entity

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revision Previous revision
Next revision
Previous revision
proton_entity [2010/10/26 08:15]
seth
proton_entity [2012/02/07 09:38]
aki
Line 36: Line 36:
 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? 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.+Well, not much, alone. ​ It's fairly lightweight at about 200 byte memory footprint.
  
-** Instantiate an entity **+The power comes from Components. ​ Components can (but don't have to) render, think, and process input.
  
-Oktime to just cannon-ball into the pool and show you what this stuff can do.  The important thing to realize ​is we're just using entities ​and components ​to do everything.+They way they accomplish this is by connecting a component function (sayRender) ​to a function object called "​OnRender"​ (or any name) of their parent, ​and when that is triggered, the component function (and any others functions connected ​to it) is called.
  
-If you say weird function, like, BobEntity(),​ realize that it'​s ​just helper function from EntityUtils.cpp and is components to manipulate things to make any entity "​bob"​. These helpers are not that complicated and just save on repetitive code, so dig in and look at them.+There is no looping through the entities to do things, Entities don't even need to be in the same hierarchy or in list to work.  But it's a good idea as the hierarchy makes rendering/​thinking order clear.
  
-My philosophy is if it's a simple one line of code to add a fade, zoom, or bob to a visual, you are that much more likely to do it.+====Test drive===== 
 + 
 +Ok, time to just cannon-ball into the pool and show you some stuff. ​ The important thing to realize is we're just using entities and components to do everything. 
 + 
 +If you see a weird function, like, BobEntity(),​ realize that it's just a helper function from **EntityUtils.cpp** and is using components to manipulate things to make any entity "​bob"​. These helpers are not that complicated and just save on repetitive code, so dig in and look at them.  Most of them can operate on any kind of entity, without knowing anything about it.  If they try to read "​pos2d"​ but the entity doesn'​t have it set, it will be created with 0,0 instead of crash. ​ If they call a function object that no components have connected to (like, "​OnRender"​ or something) the effect is nothing happens, not an error. 
 + 
 +There are a TON of tiny helper functions in **EntityUtils.cpp**,​ check the h sometime.  ​My philosophy is if it's a simple one line of code to add a fade, zoom, or bob to a visual, you are that much more likely to do it.
  
 If you'd like to work along with me, open the RTSimpleApp project now! If you'd like to work along with me, open the RTSimpleApp project now!
Line 61: Line 67:
 {{:​entity_1.png?​|}} {{:​entity_1.png?​|}}
  
-Well, we see the logo there. ​ If you looked inside of CreateOverlayEntity,​ you'd see it creates an Entity, and adds a RenderOverlay component to it and sets some properties. (All of which are storied ​in either entity'​s VariantDB (like pos2d, color, scale2d, etc) or the components ​VariantDB if it is specific to the component. (properties like fileName, and framex, framey)+Well, we see the logo there. ​ If you looked inside of CreateOverlayEntity,​ you'd see it creates an Entity, and adds a RenderOverlay component to it and sets some properties. (All of which are stored ​in either entity'​s VariantDB (like pos2d, color, scale2d, etc) or the component'​s ​VariantDB if it is specific to the component. (properties like fileName, and framex, framey)
  
-Add a few lines of code: +Add a few more lines of code: 
-<​code>​+<​code ​cpp> 
 +//​MainMenuCreate(pGUIEnt);​ 
 +AddFocusIfNeeded(pGUIEnt);​ //so it will render, update, and draw.  Really it just adds a few components. 
 +Entity *pEnt = CreateOverlayEntity(pGUIEnt,​ "​logo",​ "​interface/​proton.rttex",​ 0,0); 
 +//Instead of drawing at 0,0, let's set the pos2d variable to 100,100 so it draws there instead. 
 +pEnt->​GetVar("​pos2d"​)->​Set(CL_Vec2f(100,​100));​
  
 +//​let'​s move to 200,200 1000ms (one second) later.
 +GetMessageManager()->​SetEntityVariable(pEnt,​ 1000, "​pos2d",​ CL_Vec2f(200,​200));​
 +//and, move back to 0,0 a second after that.
 +GetMessageManager()->​SetEntityVariable(pEnt,​ 2000, "​pos2d",​ CL_Vec2f(0,​0));​
 </​code>​ </​code>​
 +
 +So now the ball sort of jerks around the screen. ​ Any variable can be "​scheduled"​ to be set in this way.  If the entity is destroyed, any pending messages are also destroyed.
 +
 +Instead of jerkily moving, we can use an InterpolateComponent to smoothly change one variant value to another. ​ This works numbers, vectors, colors, etc.
 +
 +Instead of adding InterpolateComponent directly, we'll use some helper functions that apply it to pos2d, but just keep in mind we can do this for any variable.
 +
 +Replace with this code:
 +
 +<code cpp>
 +//​MainMenuCreate(pGUIEnt);​
 +AddFocusIfNeeded(pGUIEnt);​ //so it will render, update, and draw.  Really it just adds a few components.
 +Entity *pEnt = CreateOverlayEntity(pGUIEnt,​ "​logo",​ "​interface/​proton.rttex",​ 0,0);
 +
 +
 +CL_Vec2f imageSize = pEnt->​GetVar("​size2d"​)->​GetVector2();​
 +
 +FadeInEntity(pEnt,​ true, 1000);
 +
 +//zoom to bottom right, take 1000 ms to get there, initiate it in 1000 ms.
 +ZoomToPositionEntityMulti(pEnt,​ GetScreenSize()-imageSize,​ 1000, INTERPOLATE_SMOOTHSTEP,​ 1000);
 +//zoom back to start
 +ZoomToPositionEntityMulti(pEnt,​ CL_Vec2f(0,​0),​ 1000, INTERPOLATE_SMOOTHSTEP,​ 2000);
 +//zoom to bottom right again
 +ZoomToPositionEntityMulti(pEnt,​ GetScreenSize()-imageSize,​ 1000, INTERPOLATE_SMOOTHSTEP,​ 3000);
 +FadeOutAndKillEntity(pEnt,​ true, 1000, 4000);
 +</​code>​
 +
 +The logo now fades in, smoothly zooms around the screen a bit, then fades out. (and kills the entity completely)
 +
 +Notice that most things work by time in milliseconds. ​ This allows very exact sequencing and movement of events. ​ Internally, ZoomToPositionEntityMulti is using the MessageManager to schedule things to happen, most helper functions allow you to control the "start time"​. ​ In this way, they sort of act like a scripting language.
 +
 +Let's add a graphic trail effect to our moving logo.
 +
 +<code cpp>
 +//​MainMenuCreate(pGUIEnt);​
 +AddFocusIfNeeded(pGUIEnt);​ //so it will render, update, and draw.  Really it just adds a few components.
 +Entity *pEnt = CreateOverlayEntity(pGUIEnt,​ "​logo",​ "​interface/​proton.rttex",​ 0,0);
 +
 +//only this part is new, we're adding a component here
 +EntityComponent *pComp = pEnt->​AddComponent(new TrailRenderComponent);​
 +
 +CL_Vec2f imageSize = pEnt->​GetVar("​size2d"​)->​GetVector2();​
 +FadeInEntity(pEnt,​ true, 1000);
 +
 +//zoom to bottom right, take 1000 ms to get there, initiate it in 1000 ms.
 +ZoomToPositionEntityMulti(pEnt,​ GetScreenSize()-imageSize,​ 1000, INTERPOLATE_SMOOTHSTEP,​ 1000);
 +//zoom back to start
 +ZoomToPositionEntityMulti(pEnt,​ CL_Vec2f(0,​0),​ 1000, INTERPOLATE_SMOOTHSTEP,​ 2000);
 +//zoom to bottom right again
 +ZoomToPositionEntityMulti(pEnt,​ GetScreenSize()-imageSize,​ 1000, INTERPOLATE_SMOOTHSTEP,​ 3000);
 +FadeOutAndKillEntity(pEnt,​ true, 1000, 4000);
 +</​code>​
 +
 +{{:​entity_2.png?​|}}
 +
 +You see the same thing as before but with trail.  ​
 +
 +By simply adding the TrailRenderComponent we can add a "​trail"​ to any kind of moving visual, including buttons, text rendering, despite not knowing a thing about them.
 +
 +In fact, this will even work with visuals not invented yet because the method TrailRenderComponent is using is just recording specific variables such as "​pos2d",​ "​scale2d",​ "​rotation",​ connecting itself to the "​OnRender"​ function object of the parent and calling OnRender() multiple times while controlling those variables. ​ It puts them back to normal when done.  This is something only possible with a loosely coupled system.
 +
 +We accepted TrailRender'​s default properties but what if we wanted a fancier trail?
 +
 +First, take a look at TrailRenderComponent::​OnAdd. ​ This is where components register the properties/​functions they will allow the outside world to read and set.
 +
 +We see:
 +<code cpp>
 +//shared with the rest of the entity
 + m_pPos2d = &​GetParent()->​GetVar("​pos2d"​)->​GetVector2();​
 + m_pSize2d = &​GetParent()->​GetVar("​size2d"​)->​GetVector2();​
 + m_pScale2d = &​GetParent()->​GetShared()->​GetVarWithDefault("​scale2d",​ Variant(1.0f,​ 1.0f))->​GetVector2();​
 + m_pColor = &​GetParent()->​GetShared()->​GetVarWithDefault("​color",​ Variant(MAKE_RGBA(255,​255,​255,​255)))->​GetUINT32();​
 + m_pAlignment = &​GetParent()->​GetVar("​alignment"​)->​GetUINT32();​
 + m_pColorMod = &​GetParent()->​GetShared()->​GetVarWithDefault("​colorMod",​ Variant(MAKE_RGBA(255,​255,​255,​255)))->​GetUINT32();​
 + m_pAlpha = &​GetParent()->​GetShared()->​GetVarWithDefault("​alpha",​ Variant(1.0f))->​GetFloat();​
 + m_pRotation = &​GetParent()->​GetVar("​rotation"​)->​GetFloat(); ​ //in degrees
 + m_pTrailAlpha = &​GetParent()->​GetVarWithDefault("​trailAlpha",​ 0.5f)->​GetFloat();  ​
 + //register ourselves to render if the parent does
 + GetParent()->​GetFunction("​OnRender"​)->​sig_function.connect(1,​ boost::​bind(&​TrailRenderComponent::​OnRender,​ this, _1));
 +
 + //our own variables/​settings
 + m_pFrames = &​GetVarWithDefault("​frames",​ uint32(5))->​GetUINT32(); ​
 + m_pTimeBetweenFramesMS = &​GetVarWithDefault("​timeBetweenFramesMS",​ uint32(50))->​GetUINT32(); ​
 +</​code>​
 +
 +The first big chunk setting up pointers to variables that the parent has (or doesn'​t have). ​ You can see defaults are set in many cases, this kicks in if they don't exist yet.
 +
 +Look near the bottom, it's not using the GetParent() prefix, it's using variables from its own VariantDB. ​ A uint32 named "​frames"​ with a default of 5, and a "​timeBetweenFramesMS"​ with a default of 50.
 +
 +(It means every 50 ms it takes a "​snapshot"​ and it only keeps track of the last 5 frames when drawing the trail)
 +
 +So those are the two properties we can change!
 +
 +Add this code somewhere after the TrailRenderComponent is added:
 +
 +<code cpp>
 +pComp->​GetVar("​frames"​)->​Set(uint32(20));​
 +pComp->​GetVar("​timeBetweenFramesMS"​)->​Set(uint32(20));​
 +</​code>​
 +
 +Then compile and you'll get this:
 +
 +{{:​entity_3.png?​|}}
 +
 +Whee!
 +
 +Let's say you want the image to be clickable, and when clicked, it changes to a random tint color. ​ To do this we'll need to add a TouchHandlerComponent. ​ These will call function objects on parent called "​OnTouchStart",​ "​OnOverStart",​ "​OnOverEnd",​ "​OnTouchEnd"​. ​ It's up to us to connect them to a function to do something. (yep, sigslots again)
 +
 +Here is the new code:
 +<code cpp>
 +//first add this anywhere above the App::​Update(),​ it will get called when we "​touch"​ the logo
 +
 +void OnEntityTouched(VariantList *pVList)
 +{
 + CL_Vec2f touchPt = pVList->​Get(0).GetVector2();​
 + Entity *pEntTouched = pVList->​Get(1).GetEntity();​
 +
 + LogMsg("​Touched %s at %s!", pEntTouched->​GetName().c_str(),​ PrintVector2(touchPt).c_str());​
 +
 + //change to a new random tint
 + pEntTouched->​GetVar("​color"​)->​Set(GetBrightColor());​
 +}
 +
 +//Next, add this to your existing init code from the last section
 +
 +//add the change color when touch stuff
 +pComp = pEnt->​AddComponent(new TouchHandlerComponent);​
 +pEnt->​GetFunction("​OnTouchStart"​)->​sig_function.connect(&​OnEntityTouched);​
 +</​code>​
 +
 +{{:​entity_4.png?​|}}
 +
 +Clicking (tapping once on a touch device) it will now change the color randomly and show something like "​Touched logo at 153.00, 120.00!"​ on each touch.
 +
 +Well, we came this far, let's make a few changes so ten of these things spawn and randomly move around forever.
 +
 +First we'll need to add another separate function to wire to, in addition to the one we did.  Here they both are:
 +
 +<code cpp>
 +
 +void OnEntityTouched(VariantList *pVList)
 +{
 + CL_Vec2f touchPt = pVList->​Get(0).GetVector2();​
 + Entity *pEntTouched = pVList->​Get(1).GetEntity();​
 +
 + LogMsg("​Touched %s at %s!", pEntTouched->​GetName().c_str(),​ PrintVector2(touchPt).c_str());​
 +
 + //change to a new random tint
 + pEntTouched->​GetVar("​color"​)->​Set(GetBrightColor());​
 +}
 +
 +void MoveEntityToRandomPlace(VariantList *pVList)
 +{
 + Entity *pEnt = pVList->​Get(0).GetEntity();​
 +
 + CL_Vec2f vPosToMoveTo = CL_Vec2f(Random(GetScreenSizeX()),​ Random(GetScreenSizeY()));​
 +
 + int timeToTakeForMoveMS = RandomRange(100,​ 1000);
 + ZoomToPositionEntityMulti(pEnt,​vPosToMoveTo,​ timeToTakeForMoveMS,​ INTERPOLATE_SMOOTHSTEP,​ 0);
 +
 + //​let'​s schedule this to run again at the exact point they reach their final destination,​ we'll use the
 + //message manager
 + GetMessageManager()->​CallEntityFunction(pEnt,​ timeToTakeForMoveMS,​ "​MoveToRandomPlace",​ &​VariantList(pEnt));​
 +}
 +</​code>​
 +
 +And here is the replacement code for the init:
 +
 +<code cpp>
 + //​MainMenuCreate(pGUIEnt);​
 + AddFocusIfNeeded(pGUIEnt);​ //so it will render, update, and draw.  Really it just adds a few components.
 +
 + int logosToCreate = 10;
 +
 + while (logosToCreate--)
 + {
 +
 + //start in a random place
 + Entity *pEnt = CreateOverlayEntity(pGUIEnt,​ "​logo",​ "​interface/​proton.rttex",​ Random(GetScreenSizeX()),​Random(GetScreenSizeY()));​
 + EntityComponent *pComp = pEnt->​AddComponent(new TrailRenderComponent);​
 + pComp->​GetVar("​frames"​)->​Set(uint32(20));​
 + pComp->​GetVar("​timeBetweenFramesMS"​)->​Set(uint32(20));​
 + float scale = RandomRangeFloat(0.4f,​1.0f);​
 + pEnt->​GetVar("​scale2d"​)->​Set(CL_Vec2f(scale,​scale));​ //make it a random size
 + FadeInEntity(pEnt,​ true, Random(600)+400);​ //add a random element to the fade in time
 +
 + //add the change color when touch stuff
 + pComp = pEnt->​AddComponent(new TouchHandlerComponent);​
 + pEnt->​GetFunction("​OnTouchStart"​)->​sig_function.connect(&​OnEntityTouched);​
 +
 + //​connect/​create a function object called "​MoveToRandomPlace"​ in the entity that we can connect a real function to,
 + //then use the MessageManager to trigger it when we want
 +
 + pEnt->​GetFunction("​MoveToRandomPlace"​)->​sig_function.connect(&​MoveEntityToRandomPlace);​
 +
 + //Call it now to get things started. ​ We pass in the entity pointer so it knows who to operate on
 + pEnt->​GetFunction("​MoveToRandomPlace"​)->​sig_function(&​VariantList(pEnt));​
 + }
 +</​code>​
 +
 +{{:​entity_5.png?​|}}
 +
 +And now you get a bundle of crazy logos bouncing around. ​ Tapping them changes color. ​ Hmm, make them explode and you've almost got a crappy game here!
 +
 +So now you probably can sort of see the concept of "​wiring functions to things and calling them later" - we have only barely touched on what is possible!
 +
 +Well written components respond to property changes at any time - for instance, if you were to change the "​fileName"​ property of the OverlayRenderComponent to a new image to display, it would understand and the touch rectangle and such would all just work. (It connects to the "on changed"​ signal of the fileName Variant) This technique can be used for cheapo anims.
proton_entity.txt · Last modified: 2012/02/07 09:38 by aki