Having my first look at seek steering behaviour. After watching a lecture on Youtube and given this formula, where scale is a float anywhere between 0.0 and 1.0 that affects the sharpness of the seek curve:
I tried giving it a go but I get odd results. It seems to work well enough when the scale is set to 1.0 but anything below 0.5 the agent won't seek the target that is positioned in it's opposite direction. I create an agent from a struct and give it an initial, normalised, velocity and I set the target to the mouse cursor position.
nortski said: After watching a lecture on Youtube and given this formula
A problem with the formula is it's terminology. What is ‘actual’ or ‘steering’? Those are no physical terms, which would be helpful to relate the method to other alternatives.
A physical object has a velocity describing it's movement. Changing the movement means to apply acceleration ('steering'). After that change, the velocity becomes a different value, but neither the formula nor your code ever change velocity. Seemingly it remains constant to the value you gave it in the constructor, so i wonder it works at all. Instead velocity, you use ‘actual’, which does change but is simply the wrong term imo, causing confusion.
Now you may say you're not much interested in math or physics eventually, you just want to make games. Nothing wrong with that if so, but moving objects is physics, and using related terminology helps a lot with learning from multiple sources, which will for a majority agree on those established terms. It also helps with communication - asking for help or exchanging ideas, etc.
I've written a small the demo to show some basic control methods. Yours is probably similar to the first method (mode 0) with smoothing close to one.
The next adds a speed limit.
The next uses a limit on acceleration, and it's similar to the Super Mario moons / flying turtles. (iirc you are the guy talking about that some time ago)
The last uses 'critical damping' from a code snippet i've found on the internet. For it's simplicity this works pretty well for many applications. It's function parameters use physics terms, so using it becomes easier if we do this too. ; )
This is really just simple examples. All of this can be combined as needed, but it's not really intelligent behavior ofc., just physics. But often it's what we want, because it's easy to predict for the player.
To simplify the physics math, it helps to use a timestep of one. Then it is as simple as your example and initially easier to learn. But using correct timesteps matching realtime helps to have consistent results across different hardware. (I'm just assuming you're not super experienced with physics yet ; )
static bool visSteeringGame = 1; ImGui::Checkbox("visSteeringGame", &visSteeringGame);
if (visSteeringGame)
{
// timing and mouse
std::this_thread::sleep_for(std::chrono::milliseconds(16)); // throttle for 60 fps
static auto prevTime = std::chrono::system_clock::now();
auto curTime = std::chrono::system_clock::now();
std::chrono::duration<float> deltaTime = curTime - prevTime;
prevTime = curTime;
float timestep = deltaTime.count(); // measured from real time
vec2 target (application.mousePosX - 1000.f, -application.mousePosY + 1000.f);
// settings
static int mode = 2; ImGui::SliderInt("mode", &mode, 0, 3);
static float maxVel = 500; ImGui::DragFloat("maxVel", &maxVel, 0.1f);
static float maxAcc = 2000; ImGui::DragFloat("maxAcc", &maxAcc, 0.1f);
static float maxTargtetDist = 50.f; ImGui::DragFloat("maxTargtetDist", &maxTargtetDist, 0.1f);
static float smoothing = 0.5f; ImGui::SliderFloat("smoothing", &smoothing, 0.f, 1.f); // smooths the target - set to zero to see most difference between the methods
static float dampingTime = 0.5f; ImGui::DragFloat("dampingTime", &dampingTime, 0.1f);
struct Agent
{
vec2 pos = vec2(0.f);
vec2 vel = vec2(0.f);
void Integrate (float timestep)
{
pos += vel * timestep;
}
};
static Agent agent;
// simple steering behavior
vec2 smoothTarget = agent.pos * smoothing + target * (1.f-smoothing);
vec2 diff = smoothTarget - agent.pos;
float dist = diff.Length();
if (dist > maxTargtetDist) diff *= maxTargtetDist / dist;
smoothTarget = agent.pos + diff;
if (mode == 0) // hit the target immideately
{
vec2 velToHitTargetOnNextFrame = diff / timestep;
agent.vel = velToHitTargetOnNextFrame;
}
if (mode == 1) // some maximum speed, causing motion with mostly constant velocity
{
vec2 velToHitTargetOnNextFrame = diff / timestep;
agent.vel = velToHitTargetOnNextFrame;
float mag = agent.vel.Length();
if (mag > maxVel) agent.vel *= maxVel / mag;
}
if (mode == 2) // some maximum acceleration, realistic motion but overshooting the target in a springy way
{
vec2 velToHitTargetOnNextFrame = diff / timestep;
vec2 accel = (velToHitTargetOnNextFrame - agent.vel) / timestep;
float mag = accel.Length();
if (mag > maxAcc) accel *= maxAcc / mag;
agent.vel += accel * timestep;
}
if (mode == 3) // critical damping, prevents the overshoot
{
auto SmoothCD = [](vec2 from, vec2 to, vec2 &vel, float smoothTime, float deltaTime)
{
// from Programming Gems 4
float omega = 2.f/smoothTime;
float x = omega*deltaTime;
float exp = 1.f/(1.f+x+0.48f*x*x+0.235f*x*x*x);
vec2 change = from - to;
vec2 temp = (vel+omega*change)*deltaTime;
vel = (vel - omega*temp)*exp;
return to + (change+temp)*exp;
};
SmoothCD (agent.pos, target, agent.vel, dampingTime, timestep);
float mag = agent.vel.Length();
if (mag > maxVel) agent.vel *= maxVel / mag;
}
agent.Integrate(timestep);
// visualize
RenderPoint (target, 1,1,1);
RenderPoint (agent.pos, 1,0,0);
RenderLine (agent.pos, smoothTarget, 1,0,0);
static std::vector<vec2> trajectory;
if (trajectory.size() > 1000) trajectory.erase(trajectory.begin());
trajectory.push_back(agent.pos);
for (size_t i=1; i<trajectory.size(); i++)
RenderLine(trajectory[i-1], trajectory[i], 0,0.5f,1);
vec2 topLeft (-640, -320); vec2 botRight (640, 320);
RenderAABBox(topLeft, botRight, 1,1,1);
}
nortski said: I shall read through it, several times, and try to understand what is happening.
Uhh - this would not have worked for me.
I suggest you copy the code into your project and make it work with your mouse driven target. Try different settings, etc. This way you can feel what it does. That's the only way to understand simulations imo. Math text books do not help much. ; )