The most basic element of this system is the
ForceGenerator
. This is a virtual base class that should be extended for each specific type of force needed. It is registered with a unit and is used to calculate the correct force and apply that force to the unit. The base class is very simple, but different members may be needed for more complex forces.ForceGenerator.h
class ForceGenerator
{
float Duration;
virtual void updateForce(Unit* unit)=0;
}
updateForce(Unit* unit)
is where the force is calculated and applied to unit
and Duration
indicates an amount of time this force should be applied. If you want a force to be applied forever setting duration to FLT_MAX
and not decrementing the time if it is set to this would allow the ForceGenerator
to never be removed from the registry.One use for a
ForceGenerator
would be to create simple spring motion. With a simple ForceGenerator
you can get fairly complex and convincing behavior with very little code:Spring.h
class Spring:public ForceGenerator
{
float RestLength;
float SpringConstant;
Unit* BaseUnit;
Spring(Unit* base,float springConstant=1,float restLength=0);
void updateForce(Unit* unit);
}
Spring.cpp
Spring::Spring(Unit* base,float springConstant,float restLength)
{
BaseUnit=base;
SpringConstant=springConstant;
RestLength=restLenght;
Duration=FLT_MAX; //this force will stay forever
}
void Spring::updateForce(Unit* unit)
{
Vector3 dir = BaseUnit->Position - unit->Position;
//get the direction of the force to be applied
float dist = dir.Magnitude();
//get the distance between unit and BaseUnit
float offset = RestLength - dist;
//get the offset from the rest length for determining the magnitude of the force
dir.Normalize();
Vector3 force = dir * (-SpringConstant * offset);
//calculate the spring force
unit->addForce(force);
}
By registering this
Spring
with a Unit
you will get anchored spring motion with the anchor being the BaseUnit
. If you would like the BaseUnit
to be affected by the spring as well, you would register another Spring
force with the BaseUnit
and the BaseUnit
of the Spring
set to the Unit
that the last Spring
was registered with.You can view my code for basic gravity here ( Gravity.h Gravity.cpp )
or for planetary gravity here ( PlanetGravity.h PlanetGravity.cpp ).
This
ForceGenerator
is queued for processing with a Unit
pointer with a ForceRegistration
which is a simple struct:ForceRegistration.h
struct ForceRegistration
{
Unit* unit;
ForceGenerator* force;
}
The
unit
in the ForceRegistration
is the one that will be passed to force->updateForce()
. The Unit class at its most basic needs to contain the following:Unit.h
class Unit
{
Vector3 Position;
Vector3 Forces; //Force accumulator
Vector3 Velocity;
float Mass;
float InverseMass;
float Radius;
void update();
void draw();
void addForce(Vector3 force);
}
This
Unit
class is specifically for spherical units for simplicity, but Radius
could be replaced with some kind of dimensions variable for other shapes. Also, I am storing both the mass and the inverse mass so that they do not have to be calculated each frame.Now that we have each of the basic components involved in the force registry system we need to create the actual
ForceRegistry
. The ForceRegistry
is entirely just a ForceRegistration
holder and updater.ForceRegistry.h
class ForceRegistry
{
vector<forceregistration> ActiveForces;
void add(ForceRegistration* force);
void clear();
void remove(int index);
void updateForces();
}
ForceRegistry.cpp
void ForceRegistry::add(ForceRegistration* force)
{
ActiveForces.push_back(force);
}
void ForceRegistry::clear()
{
for(unsigned int i=0;i<ActiveForces.size();i++)
{
delete ActiveForces[i];
}
ActiveForces.clear();
}
void ForceRegistry::remove(int index)
{
delete ActiveForces[index];
ActiveForces.erase(ActiveForces.begin()+index);
}
void ForceRegistry::updateForces()
{
for(unsigned int i=0;i<ActiveForces.size();i++)
{
//remove ForceRegistrations that have run their course
if(ActiveForces[i]->Unit->Duration <= 0)
{
remove(i);
i--;
continue;
}
//update forces
ActiveForces[i]->Force->updateForce(ActiveForces[i]->Unit);
//decrement force duration
if(ActiveForces[i]->Unit->Duration < FLT_MAX)
ActiveForces[i]->Unit->Duration -= DeltaTime;
}
}
The last step is to make sure that the units process and apply their accumulated forces correctly when updated. To do this you apply the velocity to the position, calculate acceleration from the accumulated forces, apply the acceleration to the velocity and then clear out the accumulated forces.
Unit.cpp
void Unit::update();
{
//apply velocity
Position += Velocity * DeltaTime;
//calculate acceleration
Vector3 acc = Forces * InverseMass;
//apply acceleration
Velocity += acc * DeltaTime;
//clear force accumulator
Forces = Vector3(0,0,0);
}
Now all that's left to do is register a
ForceGenerator
with a Unit
.
//create the anchor and unit
Unit* anchor=new Unit();; anchor->Position=Vector3(0,0,0); anchor->Radius=1;
Unit* unit=new Unit();; unit->Position=Vector3(3,6,9); anchor->Radius=1;
//create the spring force and a force registration
Spring* spring=new Spring(anchor,1.2f,5.0f);
ForceRegistration* reg=new ForceRegistration(unit,spring);
//add the ForceRegistration to a previously made ForceRegistry
forceRegistry.add(reg);
This will result in an anchored spring object.
You can view my whole system code here.