Search code examples
sprite-kitskaction

SpriteKit: Rotate a ship clockwise around a circle


In SpriteKit, I want to position a spaceship sprite so that:

(a) Rotates clockwise, or counterclockwise.

(b) The sprite rotates and it's nose always pointing to the centre, regardless of where it is on the circle.

Currently I have this output:

current output

...

My ideal output is as shown

concept

My code now follows:

class GameScene: SKScene {

lazy var circleNode:SKShapeNode = {
    var circle = SKShapeNode(circleOfRadius: 200)
    circle.position = CGPoint(x: frame.midX, y: frame.midY)
    circle.strokeColor = SKColor.purple
    circle.lineWidth = 10.0
    circle.glowWidth = 2.0
    circle.physicsBody?.isDynamic = false
    return circle
}()

lazy var shipNode: SKSpriteNode = {
    var sprite = SKSpriteNode(imageNamed: "ship")
    sprite.position = CGPoint(x: circleNode.frame.minX, y: circleNode.frame.minY)
    sprite.xScale = 2
    sprite.yScale = 2
    sprite.zPosition = 1
    return sprite
}()

class func newGameScene() -> GameScene {
    // Load 'GameScene.sks' as an SKScene.
    guard let scene = SKScene(fileNamed: "GameScene") as? GameScene else {
        print("Failed to load GameScene.sks")
        abort()
    }

    // Set the scale mode to scale to fit the window
    scene.scaleMode = .aspectFill

    return scene
}    

func setUpScene() {
    self.addChild(circleNode)
    self.addChild(shipNode)
}

override func didMove(to view: SKView) {
    self.setUpScene()
}

override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {

    let touch = touches.first
    if let location = touch?.location(in: self){
        if location.x > shipNode.position.x {
            let moveToRight = SKAction.moveBy(x: 1, y: 0, duration: 5)
            let forever = SKAction.repeatForever(moveToRight)
            shipNode.run(forever, withKey: "move")
        }else{
            let moveToLeft = SKAction.moveBy(x: -1, y: 0, duration: 5)
            let forever = SKAction.repeatForever(moveToLeft)
            shipNode.run(forever, withKey: "move")
        }
    }

}

override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
    shipNode.removeAction(forKey: "move")
}

}

I tried to add a SKAction.follow

   func setUpScene() {
        self.addChild(circleNode)
        self.addChild(shipNode)


        let circularMove = SKAction.follow(circleNode.path!, asOffset: false, orientToPath: true, duration: 5)
        shipNode.run(SKAction.repeat(circularMove,count: 2))

    }

But the problem here is it is animating by itself around the path, which is not what I'm after. Instead, only user touches should make the ship move clockwise or counterclockwise.

How do I ensure:

(a) The sprite sits on the circular path (b) The sprite's nose is always pointing to center of circle

Many thanks


Solution

  • You can achieve this by adding your ship to a container (an SKNode), positioning the ship on a point on the circle (at zero degrees), pointing the ship toward the center of the circle, and then rotating the container. Here's an example...

    let sprite = SKSpriteNode(imageNamed:"your_ship_name")
    let container = SKNode()
    
    override func didMove(to view:SKView) {
        let numRotations:CGFloat = 100
        let circleRadius:CGFloat = 200
    
        addChild(container)
        // Rotate so the ship initially points to the center
        sprite.zRotation = CGFloat.pi/2
        sprite.position = CGPoint(x: circleRadius, y: 0)
        container.addChild(sprite)
    
        let rotate = SKAction.rotate(byAngle: 2 * CGFloat.pi * numRotations, duration: 5 * TimeInterval(numRotations))
    
        container.run(rotate.reversed())
    }