Search code examples
iosswiftsprite-kit2dgame-physics

Adding ease in and out to moving platforms with spritekit for 2d platformer


Well Hello,

I'm making a 2d platformer for iOS using spritekit. I have moving platforms to allow my characters to move with the platform.

I can't just use skactions to move my platforms because the character will not move with the platform.

question:
How would I add an ease in and out function in order to have the platforms??? simulate: SKactionTimeMode.easeInEaseOut

Current Solution:
I don't have the code in front of me, but for a left/right moving platform this is pretty much what I'm doing. This would be running within the platforms update() method.

If platform.position.x < xPositionIWantNodeToStopGoingLeft {
    velAmount = -velAmount
}
else if platform.position.x > xPositionIWantNodeToStopGoingRight {
    velAmount = -velAmount
}
platform.physicsBody?.velocity = SKVector(dx: velAmount, dy: velAmount
platform.position.y = staticYPosition

Just to clarify, this works great. If there is a better way to do this I'm all ears. But this creates a jagged stop and turn kind of feel. I want that ease in and out feel so that the platform feels more natural.

Thanks for any help!!!


Solution

  • Ease in out function

    If we consider the time for the platform to move from one side to the other as one unit ( it might be 10 seconds, or 17 frames, it does not matter, we work in units for now).

    We do the same with the distance. The platform must move one unit distance in one unit of time.

    For this answer time is t and the position is a function of time written as f(t) is the platform position at time t.

    For simple linear movement then the function is simply f(t)=t. So at time t=0 the distance moved is 0, at time 0.5 (half way) the distance is 0.5 (half way), and so on.

    So lets put that into something a little more practical.

    Please excuse my swift I have never used it befor (I am sure you can correct any syntax I get wrong).

    // First normalise the distance and time (make them one unit long)
    // get the distance
    let distance = Double(xPositionStopGoingLeft - xPositionStopGoingRight);
    
    // use that and the velocity to get the time to travel
    let timeToTravel = distance / Double(velAmountX);  
    
    // first we have a frame ticker
    gameTick += 1; // that ticks for every frame
    
    // We can assume that the platform is always moving back and forth
    
    // Now is the unit time where at now = 2 the platform has move there and back 
    // at 3 it has move across again and at 4 back again.
    let now = Double(gameTick) / timeToTravel; // normalize time.
    
    // get the remainder of 2 as from 0-1 is moving forward and 1-2 is back
    let phase = now % 2.0;
    
    // We also need the unit time for the function f(t)=t
    let t = abs(phase - 1);
    if phase >= 1 { t = 1 - t } // reverse for return 
    
    // implement the function f(t) = t where f(t) is dist
    let dist = t
    
    // and convert back to pixel distance
    platform.position.x = Int(dist * distance + Double(xPositionStopGoingLeft));
     
    

    So that is the linear platform. To make the movement change all we need to do is change the function f(t)=?, in the above its the line let dist = t

    For a ease in out there is a handy function that is used in most ease applications f(t) = t * t / ((t * t) + (1 - t) * ( 1 - t))

    There are some t*t which are powers, t to the power of 2 or t^2 . In swift its pow(t,2) so rewriting the above as code

    let dist = pow(t,2) / (pow(t,2) + pow((1-t),2);
    

    This gives a nice ease at the start and end As the distance and time traveled is constant the speed at the middle point t = 0.5 must be greater to catch up with the slow start and end. (Side note, Get the derivative of the above function lets you workout the speed at every point in time f'(t) = speed(t) = 2(-(t-1)t)^(2-1) /(t^2+(1-t)^2)^2)

    This function is so nice, the speed at time 0.5 is 2, the same as the power (for the linear journey it would be 1). A handy property of the function is that the speed at the mid way point is always the same as the power. If you want it to move really fast at the midpoint say 4 times as fast then you use the power of 4

    let dist = pow(t,4) / (pow(t,4) + pow((1-t),4);

    If you want it only to speed up a little say 1.2 times the speed at the center then the power is 1.2

    let dist = pow(t,1.2) / (pow(t,1.2) + pow((1-t),1.2);   
    

    So now we can introduce another term, maxSpeed which is the normalised maxSpeed (Side note more precisely it is the speed at t=0.5 as it can be the slower than 1, but for our need max speed will do)

    let maxSpeed = Double(velAmountX + 3) / Double(velAmountX); // 3 pixels per frame faster
    

    and the function f(t) = t^m / (t^m + (1-t)^m) where m is maxSpeed.

    and as code

    let dist = pow(t,maxSpeed ) / (pow(t,maxSpeed ) + pow((1-t),maxSpeed);

    So put that all together

    // the next 3 lines can be constats
    let distance = Double(xPositionStopGoingLeft - xPositionStopGoingRight);
    let timeToTravel = distance / Double(velAmountX);  
    let maxSpeed = Double(velAmountX + 3) / Double(velAmountX);
    
    gameTick += 1; // that ticks for every frame
    
    let now = Double(gameTick) / timeToTravel; // normalize time.    
    let phase = now % 2.0;
    let t = abs(phase - 1);
    if phase >= 1 { t = 1 - t } // reverse for return 
    
    // the next line is the ease function
    let dist = pow(t, maxSpeed) / (pow(t, maxSpeed) + pow((1-t) ,maxSpeed);
    
    // position the platform
    platform.position.x = Int(dist * distance + Double(xPositionStopGoingLeft));
    

    Now you can at any tick calculate the position of the platform. If you want to slow the whole game down and step frames at half ticks it still will work. if you speed the game up gameTick += 2 it still works.

    Also the max speed can be lower than the linear speed. If you want the platform to be half the normal speed at the center t=0.5 the set maxSpeed = 0.5 and at the halfway point the speed will be half. To keep everything working the ease at the start and end will be quicker a rush in and rush out. (and works for reverse as well)

    To help maybe a visual representation

    enter image description here

    Image shows the movement of the platform back and forth over time. The distance is about 60 pixels and the time can be 1 minute. So at 1 min it will be one the right 2min on the left, and so on.

    Then we normalise the movement and time by looking only at one section of movement.

    enter image description here

    The graph represents the movement from left to right side, the distance is 1, and the time is 1. It has just been scaled to fit the unit box (1 by 1 box).

    The red line represent the linear movement f(t)=t (constant speed). At any point of time you move across hit the line move down and you can find the distance traveled.

    The green line represents the ease function f(t)=t*t/(t*t+(1-t)*(1-t)) and it works the same. At any point of time scan across to find the green line and move down to get the distance. the function f(t) does that for you.

    With the maxSpeed the steepness of the line at dist 0.5 is changed, with steeper slope representing faster travel.