Skip to content

Instantly share code, notes, and snippets.

@takuoka
Last active May 28, 2023 03:33
Show Gist options
  • Save takuoka/bebc65e184ea109ddef6 to your computer and use it in GitHub Desktop.
Save takuoka/bebc65e184ea109ddef6 to your computer and use it in GitHub Desktop.
This is the TimingFunction class like CAMediaTimingFunction available in AnyWhere also SpriteKit. All the cool animation curves from `CAMediaTimingFunction` but it is limited to use with CoreAnimation. See what you can do with cubic Bezier curves here: http://cubic-bezier.com
//
// SKTimingFunction.swift
// Pods
//
// Created by Takuya Okamoto on 2015/10/06.
//
//
// inspired by https://gist.github.com/raphaelschaad/6739676
import UIKit
/**
# CAMediaTimingFunction in Anywhere.
All the cool animation curves from `CAMediaTimingFunction` but it is only available to use with CoreAnimation.
This is the TimingFunction class like CAMediaTimingFunction available in AnyWhere.
This is translated by [JavaScript](http://greweb.me/2012/02/bezier-curve-based-easing-functions-from-concept-to-implementation/).
# Usage
``` swift
let move = SKAction.moveTo(point, duration:2.0)
let timingFunc = SKTimingFunction(controlPoints: 0.6, 0.0, 0.1, 0.6)
move.timingFunction = {timingFunc.get($0)}
```
*/
class SKTimingFunction {
let mX1: CGFloat
let mY1: CGFloat
let mX2: CGFloat
let mY2: CGFloat
init(controlPoints c1x: CGFloat, _ c1y: CGFloat, _ c2x: CGFloat, _ c2y: CGFloat) {
self.mX1 = c1x
self.mY1 = c1y
self.mX2 = c2x
self.mY2 = c2y
}
func get(aX: CGFloat) -> CGFloat {
if (mX1 == mY1 && mX2 == mY2) { return aX }// linear
return calcBezier(getTForX(aX), mY1, mY2)
}
func get(t: Float) -> Float {
return Float(self.get(CGFloat(t)))
}
func A(aA1: CGFloat, _ aA2: CGFloat) -> CGFloat { return 1.0 - 3.0 * aA2 + 3.0 * aA1 }
func B(aA1: CGFloat, _ aA2: CGFloat) -> CGFloat { return 3.0 * aA2 - 6.0 * aA1 }
func C(aA1: CGFloat) -> CGFloat { return 3.0 * aA1 }
// Returns x(t) given t, x1, and x2, or y(t) given t, y1, and y2.
func calcBezier(aT: CGFloat, _ aA1: CGFloat, _ aA2: CGFloat) -> CGFloat {
return ((A(aA1, aA2)*aT + B(aA1, aA2))*aT + C(aA1))*aT
}
// Returns dx/dt given t, x1, and x2, or dy/dt given t, y1, and y2.
func getSlope(aT: CGFloat, _ aA1: CGFloat, _ aA2: CGFloat) -> CGFloat {
return 3.0 * A(aA1, aA2)*aT*aT + 2.0 * B(aA1, aA2) * aT + C(aA1)
}
func getTForX(aX: CGFloat) -> CGFloat {
// Newton raphson iteration
var aGuessT = aX
for (var i = 0; i < 4; ++i) {
let currentSlope = getSlope(aGuessT, mX1, mX2)
if (currentSlope == 0.0) {return aGuessT}
let currentX = calcBezier(aGuessT, mX1, mX2) - aX
aGuessT -= currentX / currentSlope
}
return aGuessT
}
}
@raphaelschaad
Copy link

Nice! Thanks for the shout out to the Objective-C version https://gist.github.com/raphaelschaad/6739676

@dataxpress
Copy link

Note to future explorers: When a timing function returns a value outside 0...1 the action stops executing right away. If you want to achieve any effects that require timing outside of this range (eg easeInBack) then you will need to wrap your method in a SKAction.customAction(withDuration:actionBlock:) with linear timing, and then call these functions to calculate your custom timing curves

@maxgribov
Copy link

Thank you @dataxpress! Very useful information.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment