This week, I worked on rendering objects with direction and velocity. The agent is rendered as a triangular object, which points at its current heading. The green line represents the velocity of the object. The text underneath represents the speed.
I worked on additional behavior, which is the arrive behavior. Unfortunately I ran into a bug, where the velocity doesn't seem to be getting updated consistently. On further investigation, it turns out that the update loop was running so fast that the delta time between frames was turning up to be 0. What confuses me is that if the delta time is 0, then the agent shouldn't be moving at all, based on these lines:
On further debugging, I discovered that the delta time is non-zero for the first few updates (suggesting that there was perhaps some setup code that was still running when the game loop starts) - and then it becomes 0. This probably means that the agent reaches a certain initial velocity and then remains at that velocity.
Anyhow, this is how Arrive is implemented:
Arrive
I worked on additional behavior, which is the arrive behavior. Unfortunately I ran into a bug, where the velocity doesn't seem to be getting updated consistently. On further investigation, it turns out that the update loop was running so fast that the delta time between frames was turning up to be 0. What confuses me is that if the delta time is 0, then the agent shouldn't be moving at all, based on these lines:
Vec2 Acceleration = SteeringForce / mMass;
mVelocity += Acceleration * deltaTime;
mVelocity.Truncate(GetMaxSpeed());
mPosition += mVelocity;
Arrive
The problem with seek is that it maintains acceleration (or constant velocity if the max speed is reached) - all the way until it reaches the target. The problem with this is that it will tend to overshoot the target and bounce back and repeat this process. Even if the update loop is granular enough for arrive to detect reaching the target, the movement will not look graceful. Arrive allows for the agent to decelerate, or "ease" to a stop. To implement this, we define a radius inside which the deceleration will become active. If we are outside the radius, we simply Seek to the target. If we are inside it, the speed will be proportional to the distance from the target.
Vec2 Arrive::GetSteeringForce() const
{
Vec2 force;
Vec2 vecToTarget = mTargetPosition - mAgent.GetPosition();
double distance = vecToTarget.GetLength();
if (distance > DefaultBrakeRadius)
{
Vec2 direction = Vec2::Normalize(mTargetPosition - mAgent.GetPosition());
Vec2 desiredVelocity = direction * mAgent.GetMaxSpeed();
force += desiredVelocity - mAgent.GetVelocity();
}
else if (distance > 0.0f)
{
double speed = distance / mDecelerationFactor;
speed = min(speed, mAgent.GetMaxSpeed());
Vec2 desiredVelocity = Vec2::Normalize(vecToTarget) * speed;
force += desiredVelocity - mAgent.GetVelocity();
}
return force;
}
Behavior summing
A single agent can have
multiple steering forces acting upon it. All the forces acting upon it should affect the agent simultaneously.
There are many ways to implement behavior summing. The simple, straightforward (albeit naïve) approach would be to simply add all the forces.
The approach I've chosen is to do a weighted sum: Each behavior is given a weight. Based on the weight given, the percentage of force is calculated. Then all these are added together to obtain the final force.
Dynamically changing goal positions and behaviors
I also now added functionality to help move the goal when the mouse button is clicked. The position where the mouse click occurs is sent to the world, and the world will update the agents' goals. I've also mapped keys - A, S and F, and the Shift + versions of those, to enable/disable those behaviors.
Next week
This week, I was to
implement other steering behaviors such as Pursue and Evade, as well. However,
as I mentioned above, I've ran into a lot of problems with the custom
"engine" I've written - double precision floats and the game loop
don't seem to be very precise and I'm having to delve down and fix those from
time to time. I am also finding myself having to spend quality time on
rendering and Win32-specific areas. While I love exploring new
tech, and deeply enjoy new platforms to work on, it takes a lot of time and
effort to write a good core game loop, and it seems like I am spending a
majority of my time on that, rather that on the actual AI. I am seriously
considering moving to a different engine, such as OGRE or Unreal, to ramp up
the amount of time I spend on the actual AI implementation. That is, after all,
the main goal of this project.
Since most of my code is written in a platform-agnostic manner, it should not take too much time to port this over to the engine of my choice. This is what I have planned for next week:
- Pick an engine to port the code over to. (2h)
- Port the code and make sure it works (5h)
- Finish pending behaviors (Evade, pursue, wander, arrive) (5h)
- Allow keyboard input to control one agent while other agents follow the one being controlled (3h)
Comments