Search code examples
iossprite-kittrigonometryskphysicsbody

Replicate custom SKAction spiral movement with SKPhysicsBody methods


I recently changed some trigonometric functions to get 4 methods that move an object by creating a spiral path (as described in the code below) :

  • From right to left to the top
  • From left to right to down
  • From right to left to down
  • From left to right to the top

Everything works fine . In this picture you can see the left to right to the top. (start to the left, go to the right - clockwise - all from bottom to top)

enter image description here

Now I would like to replicate these functions using the physics engine. In the code below (//MARK: - Physic tests) I started to move the same object horizontally from left to right but I don't know for example how to warp elliptical effectively the same as seen in SKAction methods. Any advice will be appreciated.

Code:

class GameScene: SKScene {
    var node: SKShapeNode!
    var radius : CGFloat = 30
    override func didMoveToView(view: SKView) {
        node = SKShapeNode(circleOfRadius: radius)
        node.physicsBody = SKPhysicsBody(circleOfRadius: radius)
        node.physicsBody!.affectedByGravity = false
        node.fillColor = .redColor()
        self.node.position = CGPointMake(150, 150)
        let myPath = self.rightUpSpiralPathInRect(CGRectMake(node.position.x,node.position.y+100,node.position.x+300,node.position.y+100)).CGPath
        self.addChild(node)
        //node.runAction(SKAction.followPath(myPath, speed: 350))
        //MARK: - Physic tests
        let angle: Int = 90 // degrees
        let speed: Int = 150
        let degrees = Float(angle) * Float(M_PI/180)
        let xv:CGFloat = CGFloat(sinf(Float(degrees)) * Float(speed))
        let yv:CGFloat = CGFloat(cosf(Float(degrees)) * Float(speed))
        let vector : CGVector = CGVectorMake(xv, yv)
        node.physicsBody!.velocity = vector
    }
    //MARK: - Trigonometry methods
    private func convertPoint(point: CGPoint, rect: CGRect,reverseY:Bool) -> CGPoint {
        var y = rect.origin.y + rect.size.height - point.y * rect.size.height
        if reverseY {
            y = rect.origin.y + point.y * rect.size.height
        }
        return CGPoint(
            x: rect.origin.x + point.x * rect.size.width,y: y
        )
    }
    private func parametricPathInRect(rect: CGRect, count: Int? = nil, reverseY:Bool = false, function: (CGFloat) -> (CGPoint)) -> UIBezierPath {
        let numberOfPoints = count ?? max(Int(rect.size.width), Int(rect.size.height))

        let path = UIBezierPath()
        let result = function(0)
        path.moveToPoint(convertPoint(CGPoint(x: result.x, y: result.y), rect: rect,reverseY:reverseY))
        for i in 1 ..< numberOfPoints {
            let t = CGFloat(i) / CGFloat(numberOfPoints - 1)
            let result = function(t)
            path.addLineToPoint(convertPoint(CGPoint(x: result.x, y: result.y), rect: rect, reverseY:reverseY))
        }
        return path
    }
    func rightDownSpiralPathInRect(rect: CGRect) -> UIBezierPath {
        return parametricPathInRect(rect, count: 1000) { t in
            let r = sin(t * CGFloat(M_PI_2))-1.0
            return CGPoint(
                x: (r * sin(t * 10.0 * CGFloat(M_PI * 2.0)) + 1.0) / 2.0,
                y: (r * cos(t * 10.0 * CGFloat(M_PI * 2.0)) + 1.0) / 2.0
            )
        }
    }
    func rightUpSpiralPathInRect(rect: CGRect) -> UIBezierPath {
        return parametricPathInRect(rect, count: 1000,reverseY: true) { t in
            let r = sin(t * CGFloat(M_PI_2))-1.0
            return CGPoint(
                x: (r * sin(t * 10.0 * CGFloat(M_PI * 2.0)) + 1.0) / 2.0,
                y: (r * cos(t * 10.0 * CGFloat(M_PI * 2.0)) + 1.0) / 2.0
            )
        }
    }
    func leftUpSpiralPathInRect(rect: CGRect) -> UIBezierPath {
        return parametricPathInRect(rect, count: 1000) { t in
            let r = 1.0 - sin(t * CGFloat(M_PI_2))
            return CGPoint(
                x: (r * sin(t * 10.0 * CGFloat(M_PI * 2.0)) + 1.0) / 2.0,
                y: (r * cos(t * 10.0 * CGFloat(M_PI * 2.0)) + 1.0) / 2.0
            )
        }
    }
    func leftDownSpiralPathInRect(rect: CGRect) -> UIBezierPath {
        return parametricPathInRect(rect, count: 1000,reverseY: true) { t in
            let r = 1.0 - sin(t * CGFloat(M_PI_2))
            return CGPoint(
                x: (r * sin(t * 10.0 * CGFloat(M_PI * 2.0)) + 1.0) / 2.0,
                y: (r * cos(t * 10.0 * CGFloat(M_PI * 2.0)) + 1.0) / 2.0
            )
        }
    }
}

P.S. (my code is in swift, but I accept also objective-C)


Solution

  • Well, with the great help of Confused in physics laws, we have obtain a good result using this code as the example below:

    import SpriteKit    
    class GameScene: SKScene {
        var bit: SKSpriteNode?        
        override func sceneDidLoad() {
            let field = SKFieldNode.radialGravityField()
            field.falloff = -1
            field.smoothness = 1
            addChild(field)
            bit = SKSpriteNode(color: .cyan, size: CGSize(width: 2, height: 2))
            let satellite = SKShapeNode(circleOfRadius: 4)
            satellite.fillColor = .white
            satellite.physicsBody = SKPhysicsBody(circleOfRadius: 4)
            satellite.physicsBody?.mass = 1
            addChild(satellite)
            satellite.position = CGPoint(x: 400, y: 000)
            satellite.physicsBody?.applyImpulse(CGVector(dx: 0, dy: 300))
            satellite.physicsBody?.isDynamic = true
            let dropDot = SKAction.run {
                let myBit = self.bit?.copy() as? SKSpriteNode
                myBit?.position = satellite.position
                self.addChild(myBit!)
            }
            let dropper = SKAction.sequence([
                SKAction.wait(forDuration: 0.033),
                dropDot
                ])
            run(SKAction.repeatForever(dropper))
        }
    }
    

    Output:

    enter image description here