Advertisement

Decelerate a sprite

Started by January 08, 2015 03:45 AM
9 comments, last by CelticSir 10 years ago

Hey

I have a sprite which i want to decelerate but I am having trouble understanding how to do this correctly, i think i have over complicated it and got myself confused.

So i set my sprite path and speed like so:


obj.speed     = 100; //km/hr
obj.velocity  =  distanceToWorld(obj.speed); // converts to game speed from real unit

var vectorX   = obj.destX - obj.posX,
    vectorY   = obj.destY - obj.posY;

    obj.normal    = Math.sqrt(vectorX*vectorX + vectorY*vectorY); 
    obj.vectorX   = vectorX / obj.normal;
    obj.vectorY   = vectorY / obj.normal;
    obj.totalTime =  obj.normal / obj.velocity; //time taken to complete path

Now once my sprite has reached the destX and destY (destination point). I want to slow the sprite down to its next destination point. But this is where I think I over complicated the method to do it.

My code looks like this for moving & slowing (using a boolean) the sprite down:


function slowSettings(obj,destX,destY){
	 obj.destX   	= destX;
	 obj.destY   	= destY;
	
     var vectorX        = obj.destX - obj.posX,
         vectorY	= obj.destY - obj.posY;		
	 
         obj.normal     = Math.sqrt(vectorX*vectorX + vectorY*vectorY);
	 obj.vectorX    = vectorX / obj.normal;
         obj.vectorY    = vectorY / obj.normal;		
		
     var range     = distanceToWorld(8) - obj.velocity; //8km/hr target speed for destination point converted to pixels
	 obj.brake = range / obj.normal;			
}


function updatePosition(obj,brake){
         var delta = new Date().getTime() - obj.timer;
	 if(brake){ //set to true if we want to slow the sprite down
	      obj.velocity += obj.brake*delta;
	 }
         var distance  = delta * obj.velocity;		
	 
         obj.posX += (distance * obj.vectorX);
	 obj.posY += (distance * obj.vectorY);
	 obj.timer = new Date().getTime();		
}

The current problem is the sprite slows down far before it reaches the destination. And I am not even sure if the way I am going about this is the cleanest way to do it?

Look into Easing / Tweening / Interpolation

This doesn't necessarily pertain to sprites, this applies to anything that occurs in a gradual way over time, whether it be objects fading in and out, something moving (whether visible or not), health recharging in a non-uniform way (maybe it starts slowly at first before speeding up), or whatever else.

Interpolation is the idea of taking a number from one state (like 0%, or your sprite's starting position) to another state (like 100%, or your sprite's ending position), and following a formula to change the variable gradually as time progresses.

Basically, in your current situation, inbetween the next-to-last goal and the last goal, you're wanting the sprite to decelerate from full speed (100%) to a complete stop (0%).

Well, if the sprite is 30% of the way between previousGoal and finalGoal, then it should be moving at 70% of full speed (100 - 30%). However, this gives a perfectly evenly deceleration. Depending on what you are doing, it usually looks alot better to use a non-linear easing function to provide a more interesting deceleration.

I'm using percentages here, but in code it's much easier to use a floating point number between 0.0f and 1.0f (1.0f as 100%).

Advertisement

I can't really find a simple tutorial based around game animation for this other than an answer on GD.SE but that doesn't include distance factor. I plan to have my sprites have different deceleration/acceleration rates (stopping distances basically) so they could reach target speed before destination ideally. But for time being i'm trying to get it to reach its target speed within a set distance. But the math confuses me.

This is why i do brake = (currentSpeed - targetSpeed) / vector normal then add it to the velocity velocity += brake * delta but it doesn't come close the destination before its deceleration causes it to go in reverse. ( I can fix the reverse issue later by setting anything below 0 to 0).

The issue with using % from start to end as a linear relation to deceleration is i then can't see any easy way then to have different deceleration rates.

The issue with using % from start to end as a linear relation to deceleration is i then can't see any easy way then to have different deceleration rates.

That's what easing formulas are for. Linear Ease is a one-for-one deceleration (or acceleration), but all the other eases provide dozens of different deceleration patterns.

The common way to picture it is to have a 2D graph - one axis representing the position in time (0.0 to 1.0f, which can be multiplied against any distance or any time), and the other axis representing the value (speed, position, color, transparency, volume, etc...) you are changing over time.

So i am going to have to store the start XY position of the path, as currently I don't store it after my sprite has moved... without it I have no way to know how far along the path in % the sprite has so far traveled. Trying to work this out in my own code is tricky. Its hard to understand articles unless its written in the code im using.

Or can i do it based on % left to destination rather than % traveled.

The graph is just for visualization. You store the point in time you are along the graph (the 'x' axis), but the speed (the 'y' axis) is calculated from the 'x' using the ease equation.

  • Suppose your entity travels at a max speed of 15 units a second.
  • Suppose your entity requires 2 seconds to go from fully stopped to max speed.
  • Suppose your entity needs 10 units of space to fully decelerate (i.e. it starts decelerating when it is within 10 units of its goal).

Entity begins moving:


//"Constants":
MaxSpeed = 15 units
TimeRequiredToSpeedUp = 2 seconds
DistanceBeforeSlowdown = 10 units

//Stored variables:
currentSpeed = 0 units-per-second
elapsedMovementTime = 0.0f

.

On update:


elapsedMovementTime += elapsedTime; //Add the new time that has elapsed.

//If we are in range of our goal, continue slowing down.
if(distanceFromGoal < DistanceBeforeSlowdown)
{
     //We'll use the distance to calculate our speed.
     float easeDelta = (distanceFromGoal / DistanceBeforeSlowdown)

     //Ease formula. Calculates the current [speed/transparency/whatever] from the easeDelta (0.0 to 1.0) using the formula.
     Var newSpeed = Ease(your_chosen_slow_down_ease_formula, easeDelta, 0, MaxMovementSpeed)

     //Just incase we didn't have enough space to reach full-speed:
     currentSpeed = std::min(currentSpeed, newSpeed)
}
//Otherwise, while we haven't yet reached full speed, continue speeding up.
else if(currentSpeed < MaxMovementSpeed)
{
     //We'll use our elapsed movement-time to calculate our speed:
     float easeDelta = (elapsedMovementTime / TimeRequiredToSpeedUp)

     //Ease formula. Calculates the current [speed/transparency/whatever] from the easeDelta (0.0 to 1.0) using the formula.
     currentSpeed = Ease(your_chosen_speed_up_ease_formula, easeDelta, 0.0, MaxMovementSpeed) 
}
/*else if(state = fullSpeed) //Not actually needed.
{
    currentSpeed = MaxSpeed
}*/

.

Something like that.

Advertisement

Okay so i looked up some easing formulas for javascript but i cannot find one which quite matches the ones i need see this link for reference: https://gist.github.com/gre/1650294

There does not seem to be easing formulas for decelerating to a specific velocity or accelerating from a set velocity (which is above zero) to a new velocity.

Easing formulas are specifically designed to work for any change you want. They aren't hard-coded with start values and end values, you pass in your desired values as parameters, or you multiply the returned values (from 0.0 to 1.0) against the values you want.

For example, if the easing function returns 0.4, and you want something in the range of 50 and 75, you do:


range = (75 - 50) //25
adjustedValue = (range * easeValue) //10
finalValue = 50 + adjustedValue; //60
 
40% between [50 and 75] is 60.

This is why we work with 0.0 and 1.0. It is very easy to take that and apply it to any range. You can wrap general-purpose helper functions around the ease formulas if you want - many libraries do.

Look at my example earlier - I'm applying a range of [0 to MaxMovementSpeed] in one part of it. Though I'm passing it into the ease function, it's really just multiplying against the returned easing value [0.0 to 1.0].

They are intentionally generalized, because they work in loads of different situations and you don't want to get it fixed in your head that they are only for movement. But they are simple enough to use, once you're comfortable with them, that more specific functions aren't actually needed.

So i got this far:


function ease(easeDelta,targetSpeed,velocity){
	var range = velocity - finalSpeed;
	var adjustedValue = range * ease Delta;
	var finalValue = //confused here
}
		
var vectorX 	 = obj.destX - obj.posX,
    vectorY      = obj.destY - obj.posY,
    normal       = Math.sqrt(vectorX*vectorX + vectorY*vectorY); //distance to destination

var easeDelta 	 = (normal / obj.stopDist);
var newSpeed 	 = ease(easeDelta, 20, obj.velocity); //call the above ease function

But i didn't quite understand the final value element of the calculation. Why are you adding 50? And where does the 40% come from ?

Many functions work from 0.0 to 1.0, or from 0 to MaxValue.
If you want from minValue to maxValue, and "minValue" isn't zero, you need to calculate the range between minValue and maxValue, generate a value within that range, and then add minValue back in.

For example, if you want a random number between 10 and 15, how do you make that?


range = (15 - 10) //Get the range (5)
valueInRange = Random(0, range /* 5 */); //Generate a value within the range
finalValue = valueInRange + 10 /* min */; //Add the minimum back in

If an ease function returns something in the range 0.0 to 1.0, and you want it to convert to to between 50 and 75, you do:


easeValue = Ease(delta)
minValue = 50
maxValue = 75

range = maxValue - minValue //25
valueInRange = (easeValue * range)
finalValue = valueInRange + minValue //Add the minimum back in


In your case, you are speeding up from 0 to MaxSpeed, so because 'minValue' is zero, we can actually just remove it from the code.

But your ease function isn't actually using any ease formula; or rather, right now (range * easeDelta) is just a linear ease. Linear ease is good for testing it to make sure your code works fine, but then you'll want to actually research ease functions and implement a standard one.

A simple ease equation is the cubic ease:


float CubicEase(float easeDelta)
{
     return (easeDelta * easeDelta * easeDelta)
}


This will start off slowly and then speed up.

For convenience, you can wrap your ease functions like this:


float Ease(FuncPtr EaseFormula, float currentTime, float timeDuration, float start, float end)
{
     float easeValue = EaseFormula(currentTime / timeDuration);
 
     float range = (end - start);
     float valueInRange = (range * easeValue);
 
     float finalValue = (valueInRange + start);
     return finalValue;
}


I don't know if your chosen language has function pointers. If not, you can write your helper function like this:


float CubicEase(float easeDelta)
{
     return (easeDelta * easeDelta * easeDelta)
}
 
float InRange(float delta, float minValue, float maxValue)
{
     float range = (maxValue - minValue);
     float valueInRange = (range * delta);
 
     float finalValue = (valueInRange + minValue);
     return finalValue;
}


And use it like this:


float finalValue = InRange(CubicEase(currentTime / timeDuration), 50, 75);


There are loads of information about Easing / Tweening / Interpolation online, so I recommend you read a few tutorials about them. If you are using JavaScript, there are collections of Ease formulas in common libraries like jQuery, though I don't use Javascript myself, so you'll need to read the docs.

This topic is closed to new replies.

Advertisement