User Tools

Site Tools


proton_entity_part2

The most powerful component in the world

What is the most useful component, one that we've already used several times at once on each entity in the last tutorial?

The answer may surprise you… we'll use an extremely simple example to contrast the “old way” with the “new way”.

Traditionally in an entity system, you may have said “Hey mom, I want to be able to fade out my entity by doing a simple Entity::FadeOut(int fadeTimeMS); Or possibly a more fancy Entity::FadeOut(int fadeTimeMS, int delayBeforeStartingFadeMS);

And you'd need to add a timer to keep track of the fade which would manipulate the alpha component when rendering. You may end up writing it more than once for different entity types. You might end up with every entity in the game having this fade timer that most of them don't use.

What's the Proton way to do it?

You: “I'll just write a FadeOutComponent”. No, my good sir, that would be overkill. Simply modifying values over time doesn't need a custom component.

First, here is a brute force dumb way it could be done by overusing the message manager: (but notice it does save us from needing to add any code inside the entity or any component classes, we want to keep them as pure as fresh snow)

//We'll make a function that will fade down that has a component that renders visually.  All visual entities respect the property "alpha", it's not hardcoded, it's just an agreed upon standard along with "pos2d", "color" and others.
 
void FadeOutEntity(Entity *pEnt)
{
  float alpha = 1.0f;
 
  int timeMS = 10;
  while (alpha >= 0)
  {
    alpha -= 0.01f;
    GetMessageManager()->SetEntityVariable(pEnt, timeMS, "alpha", Variant(alpha));
 
    timeMS += 20; //add a bit to the time so the next message will be delivered a little later
  }
}

Ok, the above would work - over a one second period the fade would be adjusted 100 times, and finally hit 0. This is probably enough to do the job, but inside you should be uncomfortable with sending 100 messages to accomplish this. There is something “eww” about that, right? We'll do it a better way.

Enter the InterpolateComponent, nectar of the Gods

This component can efficiently interpolate any variant variable inside an entity or component to a new value.

This is what drives the “ZoomTo” helper functions we were using for entity visual movement - zooming around the screen or fading alpha is merely adjusting values.

Here is our earlier function re-done with the InterpolateComponent:

void FadeOutEntity(Entity* pEnt)
{
     EntityComponent * pComp = pEnt->AddComponent( new InterpolateComponent); //add our all purpose tool here
     pComp->GetVar("var_name")->Set("alpha"); //name of the var we'll change, doesn't have to be a float
     pComp->GetVar("target")->Set(0.0f); //where we want alpha to end up as
     pComp->GetVar("duration_ms")->Set(uint32(1000)); //fade to 0 in 1000 ms, setting this also tells it to start
 
    //remove the component when done. Other options are ON_FINISH_BOUNCE, ON_FINISH_REPEAT, and ON_FINISH_STOP
     pComp->GetVar("on_finish")->Set(uint32(InterpolateComponent::ON_FINISH_DIE)); 
}

Ok, that does about the same thing, except InterpolateComponent is going to update the variable every frame, instead of specific ticks. It makes sense and works but.. man, that's a lot of code.

What if we wrote a more generic function that morphs any named variant float to a value? You see how easy that would be to do, right? Actually there already is one in EntityUtils.cpp, so let's remake the function using that.

void FadeOutEntity(Entity* pEnt)
{
    MorphToFloatEntity(pEnt, "alpha", 0.0f, 1000);
}

Yeah, it's almost so simple we don't even need the FadeOutEntity() wrapper at all.

Getting fancy: eInterpolateType and scheduling

The real header to MorphToFloatEntity is this though:

EntityComponent * MorphToFloatEntity(Entity *pEnt, string targetVar, float target, unsigned int speedMS, eInterpolateType interpolateType = INTERPOLATE_SMOOTHSTEP,  int delayBeforeActionMS = 0);

What's the extra stuff? These are optional parameters it's feeding into the InterpolateComponent.

Interpolate type is an optional setting that allows you to control how the interpolation works. The enum gives you these options:

enum eInterpolateType
{
	INTERPOLATE_LINEAR = 0,
	INTERPOLATE_SMOOTHSTEP,
	INTERPOLATE_EASE_TO,
	INTERPOLATE_EASE_FROM,
	INTERPOLATE_SMOOTHSTEP_AS_COLOR,
	INTERPOLATE_LINEAR_AS_COLOR
 
};

If you are an animator you'll shrug and know what most of these mean already. If not, go visit Sol_HSA's excellent page about interpolation here. INTERPOLATE_SMOOTHSTEP is the one you'll use the most, it's just so.. smooooooth.

The last two tell the engine that the uint32 should be treated as a color. (it will break it up into four bytes and interpolate all four separately)

The delayBeforeActionMS parm is self explanatory, it's useful to schedule things to happen in advance at specific times. So you can play a sound effect, fade out, and then kill the entity step by step instead of all at once.

So, finally, let's take a look at the actual FadeEntity() function in EntityUtils.cpp:

void FadeEntity(Entity *pEnt, bool bRecursive, float alpha, int timeMS, int delayBeforeFadingMS)
{
	pEnt->RemoveComponentByName("ic_fade");
	EntityComponent * pComp = pEnt->AddComponent( new InterpolateComponent);
	pComp->SetName("ic_fade");
	pComp->GetVar("var_name")->Set("alpha");
	pComp->GetVar("target")->Set(alpha);
	pComp->GetVar("interpolation")->Set(uint32(INTERPOLATE_SMOOTHSTEP));
	pComp->GetVar("on_finish")->Set(uint32(InterpolateComponent::ON_FINISH_DIE));
 
	if (delayBeforeFadingMS == 0)
	{
		//do it now
		pComp->GetVar("duration_ms")->Set(uint32(timeMS));
	} else
	{
		//trigger it to start later
		GetMessageManager()->SetComponentVariable(pComp, delayBeforeFadingMS, "duration_ms", Variant(uint32(timeMS)));
	}
 
	if (!bRecursive) return;
 
	//also run this on all children
	EntityList *pChildren = pEnt->GetChildren();
 
	EntityList::iterator itor = pChildren->begin();
	while (itor != pChildren->end())
	{
		FadeEntity( *itor, bRecursive, alpha, timeMS, delayBeforeFadingMS);
		itor++;
	}
}

It has some extras our original didn't:

  • It removes any existing fade command before applying itself (it names itself “ic_fade” and looks for that to know if it already has one)
  • It supports recursive fades - you can fade a whole tree of entities at once
  • It allows you to specify the interpolation type
  • It allows you to specify a delay before starting the fade
  • It let's you set what to fade to, up or down

This is a great example of how you can use basic components to do interesting things. All of the helper functions in EntityUtils.cpp work this way.

proton_entity_part2.txt · Last modified: 2012/02/07 09:45 by aki