import SpriteKit class Joystick: SKNode { private let substrate: SKSpriteNode private let stick: SKSpriteNode var resetAnimationDuration: NSTimeInterval = 0.3 var velocity: CGPoint = .zero var angularVelocity: CGFloat = 0 override var zPosition: CGFloat { get { return super.zPosition } set { super.zPosition = newValue recalculateNodesZPosition() } } private var anchorPoint: CGPoint { return CGPointMake(0, 0) } var expectedSize: CGSize { return CGSizeMake(substrate.size.width + substrate.size.width, substrate.size.height + substrate.size.height) } override init(substrateSprite: SKSpriteNode, stickSprite: SKSpriteNode) { substrate = substrateSprite stick = stickSprite super.init() setup() } private func setup() { userInteractionEnabled = true setupSubtrate() setupStick(substrate.position) recalculateNodesZPosition() } private func setupSubtrate() { substrate.xScale = 0.3 substrate.yScale = 0.3 substrate.zPosition = self.zPosition addChild(substrate) } private func setupStick(position: CGPoint) { stick.xScale = 0.22 stick.yScale = 0.22 stick.zPosition = substrate.zPosition + 1 moveStick(position) addChild(stick) } private func recalculateNodesZPosition() { substrate.zPosition = zPosition stick.zPosition = substrate.zPosition + 1 } private func moveStick(position: CGPoint) { stick.position = position } private func resetStick() { let animation = SKAction.moveTo(anchorPoint, duration: resetAnimationDuration) animation.timingMode = SKActionTimingMode.EaseOut stick.runAction(animation) } private func resetVelocity() { velocity = .zero angularVelocity = 0 } override func touchesBegan(touches: Set, withEvent event: UIEvent?) { if let touch = touches.first where substrate == nodeAtPoint(touch.locationInNode(self)) { let position = touch.locationInNode(self) moveStick(position) } } override func touchesMoved(touches: Set, withEvent event: UIEvent?) { touches.forEach { t in let location = t.locationInNode(self) if sqrtf(powf((Float(location.x) - Float(stick.position.x)), 2) + powf((Float(location.y) - Float(stick.position.y)), 2)) < Float(stick.size.width) { moveStick(calculateStickPosition(forTouchPoint: location)) } self.velocity = CGPointMake(((stick.position.x - anchorPoint.x)), ((stick.position.y - anchorPoint.y))) self.angularVelocity = -atan2(stick.position.x - anchorPoint.x, stick.position.y - anchorPoint.y) } } private func calculateStickPosition(forTouchPoint touchPoint: CGPoint) -> CGPoint { if sqrtf(powf((Float(touchPoint.x) - Float(anchorPoint.x)), 2) + powf((Float(touchPoint.y) - Float(anchorPoint.y)), 2)) <= Float(stick.size.width) { let moveDifference: CGPoint = CGPointMake(touchPoint.x - anchorPoint.x, touchPoint.y - anchorPoint.y) return CGPointMake(anchorPoint.x + moveDifference.x, anchorPoint.y + moveDifference.y) } else { let vX: Double = Double(touchPoint.x) - Double(anchorPoint.x) let vY: Double = Double(touchPoint.y) - Double(anchorPoint.y) let magV: Double = sqrt(vX*vX + vY*vY) let aX: Double = Double(anchorPoint.x) + vX / magV * Double(stick.size.width) let aY: Double = Double(anchorPoint.y) + vY / magV * Double(stick.size.width) return CGPointMake(CGFloat(aX), CGFloat(aY)) } } override func touchesCancelled(touches: Set?, withEvent event: UIEvent?) { resetVelocity() resetStick() } override func touchesEnded(touches: Set, withEvent event: UIEvent?) { resetVelocity() resetStick() } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } }