Take the 2-minute tour ×
Game Development Stack Exchange is a question and answer site for professional and independent game developers. It's 100% free, no registration required.

Assume you have a target that changes. Example:

// on average every 1000 frames, move the target
if (random(1000) == 0) {
    // pick a value between 0 and 999
    target = random(1000);
}

Now, I want to chase that value with both ease in and ease out. With just ease out a common solution is this

const kApproachFudge = 0.03;
value += (target - value) * kApproachFudge;

That only gives ease out though. Another might be to use a kind of arrive behavior with acceleration and deceleration but that seems like overkill and is often fiddly meaning if your deceleration is not fast enough you'll overshoot the target. If you max speed is too high you might also overshoot the target. For example

http://jsfiddle.net/Crx9R/2/

I can sit there and just max force and max velocity and mass and deceleration but then it's so damn fiddley. If I want it to go faster I have to first adjust max vel, then maybe I have to adjust max force to it started quicker but then it will over shoot the target and I have to adjust other factors. I have to imagine this is a solved problem.

Is there a simple algorithm that ALWAYS gives good results?

note: I'm not looking for an ease in / ease out function between 2 fixed values. I have that in my toolbox and there are several answers here as well. There's probably an answer for this too but I didn't find it.

share|improve this question
add comment

2 Answers

You should have a look at the animations functions in jQuery. I use them to get the math right in my own easing functions.

Here is an example of them in action... http://jqueryui.com/demos/effect/easing.html

I should add that each of these returns a value used like this..

currentValue = startValue + easeFunction(x, t, b, c, d) * (endValue - startValue)

 // jQuery Ease Functions
 x = 0 - 1 as float of completion
 t = elapsed time in ms
 b = 0
 c = 1
 d = duration in ms


    easeInQuad: function (x, t, b, c, d) {
        return c*(t/=d)*t + b;
    },
    easeOutQuad: function (x, t, b, c, d) {
        return -c *(t/=d)*(t-2) + b;
    },
    easeInOutQuad: function (x, t, b, c, d) {
        if ((t/=d/2) < 1) return c/2*t*t + b;
        return -c/2 * ((--t)*(t-2) - 1) + b;
    },
    easeInCubic: function (x, t, b, c, d) {
        return c*(t/=d)*t*t + b;
    },
    easeOutCubic: function (x, t, b, c, d) {
        return c*((t=t/d-1)*t*t + 1) + b;
    },
    easeInOutCubic: function (x, t, b, c, d) {
        if ((t/=d/2) < 1) return c/2*t*t*t + b;
        return c/2*((t-=2)*t*t + 2) + b;
    },
    easeInQuart: function (x, t, b, c, d) {
        return c*(t/=d)*t*t*t + b;
    },
    easeOutQuart: function (x, t, b, c, d) {
        return -c * ((t=t/d-1)*t*t*t - 1) + b;
    },
    easeInOutQuart: function (x, t, b, c, d) {
        if ((t/=d/2) < 1) return c/2*t*t*t*t + b;
        return -c/2 * ((t-=2)*t*t*t - 2) + b;
    },
    easeInQuint: function (x, t, b, c, d) {
        return c*(t/=d)*t*t*t*t + b;
    },
    easeOutQuint: function (x, t, b, c, d) {
        return c*((t=t/d-1)*t*t*t*t + 1) + b;
    },
    easeInOutQuint: function (x, t, b, c, d) {
        if ((t/=d/2) < 1) return c/2*t*t*t*t*t + b;
        return c/2*((t-=2)*t*t*t*t + 2) + b;
    },
    easeInSine: function (x, t, b, c, d) {
        return -c * Math.cos(t/d * (Math.PI/2)) + c + b;
    },
    easeOutSine: function (x, t, b, c, d) {
        return c * Math.sin(t/d * (Math.PI/2)) + b;
    },
    easeInOutSine: function (x, t, b, c, d) {
        return -c/2 * (Math.cos(Math.PI*t/d) - 1) + b;
    },
    easeInExpo: function (x, t, b, c, d) {
        return (t==0) ? b : c * Math.pow(2, 10 * (t/d - 1)) + b;
    },
    easeOutExpo: function (x, t, b, c, d) {
        return (t==d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b;
    },
    easeInOutExpo: function (x, t, b, c, d) {
        if (t==0) return b;
        if (t==d) return b+c;
        if ((t/=d/2) < 1) return c/2 * Math.pow(2, 10 * (t - 1)) + b;
        return c/2 * (-Math.pow(2, -10 * --t) + 2) + b;
    },
share|improve this answer
1  
That's a great answer! –  Petr Abdulin May 15 '12 at 8:12
    
...except that the poster explicitly stated that they already had a number of ease-in/ease-out functions and that that wasn't what they were after? –  Steven Stadnicki May 15 '12 at 19:37
    
oh right, I not sure I understand what gman needs exactly... if you want your object to ease in and out towards the target, you just restart the animation when the target changes. Perhaps instead gman is looking for an object that retains its velocity in the direction it was traveling before the target changed. In that case, I would treat it more like a physics sim, where the object has a velocity, and apply an amount of force to the object in the direction of the target based on how far away from the target it is. something like that. –  Jay Kyburz May 15 '12 at 22:11
    
This is explicitly NOT the answer I was looking for as I tried to make clear in the original post. Those JQuery answers are for fixed values. I need an answer for moving values. Nice examples though. –  gman May 16 '12 at 6:30
    
Yes, a physics sim is more what I was looking for. What I don't like about the physics sim approach though is that it has settings which if not set correctly for a given situation end up making bad behavior. For example if a max velocity is too high or a force too small then a simple A -> B movement will overshoot B. What I want is an example that never fails. Maybe it's the physics example except with some automatic setting of maxVel, maxForce, acceleration, etc so that they always work for any given value and target. –  gman May 16 '12 at 6:38
add comment

I think your behaviorally-based approach is right on the money - and I'm not sure that overshooting your target is the problem that you're making it out to be. What you're describing is essentially a 1d steering behavior, and overshoot is an inevitable element of that behavior. Think of it this way: suppose you started at x=0 and you're after a current target of target_x=1000, with your cur_x=800 when a new target is chosen 'behind you' at target_x=700. You presumably wouldn't want your 'chase' to stop on a dime and flip velocity instantaneously, because that would be an implausible acceleration and a discontinuous velocity — basically, a 'kink' in your curve. Instead, you'd keep incrementing cur_x a little until you were able to change your velocity to the point of starting to decrement it towards target_x. The same behavior should be true if target_x=750, or if target_x=799 — and it stands to reason by 'continuity of behavior' that it should still be true if target_x is just ahead of you, say target_x=850.

As far as the specifics on either end of the curve, I think the best way to handle 'ease out' from a stopped position would be your suggested approach of putting a limit on acceleration; for symmetry purposes, then, you'd probably want your ease-in to be symmetric, with just enough lookahead to know when it should start decelerating in order to hit the target position 'on the nose'. Alternately, if you don't care about precisely hitting your target when it ultimately 'holds still', you could use an exponentially damped approach where you have a target velocity v given by v=dx/dt = C*|x_target-x| (with C the constant controlling the damping behavior) and you clamp your acceleration attempting to reach that target velocity; this will give asymmetric ease-in and ease-out (since ease-out from a stop will be quadratic, but ease-in to a stop will be exponential) but makes the updates slightly easier since there's no real lookahead involved.

share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.