Search code examples
iosrotationsprite-kitgame-physicsskphysicsbody

Balancing a sprite


So my problem regards the rotation of a sprite. I want it look like it's balancing. When you tap left - the sprite moves left, when you tap right - the sprite moves right. To get a better idea, put a pencil on it's end and try balance it... Yeah... that (but only in x axis).

Currently I am rotating a sprite either clockwise or anti-clockwise dependent upon whether I tap the left or right side of the screen. This is all done using SKActions.

The problem with this is that it results in a very 'jerky' and not particularly realistic motion.

I assume that I want to use physics body and something similar to velocity but my questions are:

  • the use of velocity is right... isn't it?
  • would i better off with a volume based sprite (and get it to take into account it's volume and mass) or just a simple edge based sprite?

Thanks in advance!

-- Code - This is how I'm currently rotating and moving my sprite:

import SpriteKit
enum rotationDirection{
case clockwise
case counterClockwise
case none
}

   // Creates GameScene and initialises Sprites in Scene //
  class GameScene: SKScene, SKPhysicsContactDelegate {

   // Rotation direction variable for ship motion (rotation and movement) //
var currentRotationDirection = rotationDirection.none
var xVelocity: CGFloat = 0

var sprite = SKSpriteNode()



 // Setup Scene here //
     override func didMoveToView(view: SKView) {

    // Background colour //
    self.backgroundColor = SKColor.whiteColor()

    // sprite Physics + add's sprite //
    sprite.physicsBody = SKPhysicsBody(rectangleOfSize: ship.size)
    sprite.physicsBody?.affectedByGravity = true
    sprite.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame))
    self.addSprite()


  // Initialises sprite node and it's properties //
func addSprite() {

    // Sprite dimension properities //
    sprite.name = "sprite"
    sprite = SKSpriteNode(imageNamed: "sprite")
    sprite.setScale(0.5)
    sprite.position = CGPointMake(frame.midX, 220)
    sprite.anchorPoint = CGPoint(x: 0.5, y: 0.25)
    sprite.zPosition = 1;
    // sprite Physics properties //
    sprite.physicsBody = SKPhysicsBody(rectangleOfSize: ship.size)
    sprite.physicsBody?.categoryBitMask = UInt32(shipCategory)
    sprite.physicsBody?.dynamic = true
    sprite.physicsBody?.contactTestBitMask = UInt32(obstacleCategory)
    sprite.physicsBody?.collisionBitMask = 0
    self.addChild(sprite)





 override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {

    // Defines UI Touch //
    let touch = touches.first as UITouch!
    let touchPosition = touch.locationInNode(self)

    // Sets up Inital Rotation Direction //
    let newRotationDirection : rotationDirection = touchPosition.x < CGRectGetMidX(self.frame) ? .clockwise : .counterClockwise


    // Left or Right movement based on touch //
    if touchPosition.x < CGRectGetMidX(self.frame) {xVelocity = -75}
    else {xVelocity = 75}


    // Clockwise or anticlockwise rotation based on touch //
    if currentRotationDirection != newRotationDirection && currentRotationDirection != .none {
        reverseRotation()
        currentRotationDirection = newRotationDirection
    }
    else if (currentRotationDirection == .none) {
        setupRotationWith(direction: newRotationDirection)
        currentRotationDirection = newRotationDirection
    }
}

func reverseRotation() {
    let oldRotateAction = sprite.actionForKey("rotate")
    let newRotateAction = SKAction.reversedAction(oldRotateAction!)
    sprite.runAction(newRotateAction(), withKey: "rotate")
}


func stopRotation() {
    sprite.removeActionForKey("rotate")
}

 func setupRotationWith(direction direction: rotationDirection){
    let angle : CGFloat = (direction == .clockwise) ? CGFloat(M_PI) : -CGFloat(M_PI)
    let rotate = SKAction.rotateByAngle(angle, duration: 2)
    let repeatAction = SKAction.repeatActionForever(rotate)
    sprite.runAction(repeatAction, withKey: "rotate")
}

override func update(currentTime: CFTimeInterval) {
    let rate: CGFloat = 0.3; //Controls rate of motion. 1.0 instantaneous, 0.0 none.
    let relativeVelocity: CGVector = CGVector(dx:xVelocity-sprite.physicsBody!.velocity.dx, dy:0);
        sprite.physicsBody!.velocity=CGVector(dx:sprite.physicsBody!.velocity.dx+relativeVelocity.dx*rate, dy:0);
    }

Solution

  • I think what your are going to want to look into is acceleration. Right now you are adjusting verlocityX with a static amount using:

    if touchPosition.x < CGRectGetMidX(self.frame) {
        xVelocity = -75
    }
    else {
        xVelocity = 75
    }
    

    Doing this will result in your jerky motion. Which is actually just linear. Using acceleration you can avoid this.

    For this you'll need two more variables for maxVelocity and acceleration. You want a maxVelocity probably to limit the velocity. Each frame you'll need to increase the velocity with the acceleration amount.

    I suggest you try something like this:

    velocity     = 0  // No movement
    acceleration = 2  // Increase velocity with 2 each frame
    maxVelocity  = 75 // The maximum velocity
    
    if touchPosition.x < CGRectGetMidX(self.frame) {
        xVelocity += acceleration
        if xVelocity <= -maxVelocity {
            xVelocity = -maxVelocity 
        }
    }
    else {
        xVelocity -= acceleration
        if xVelocity >= maxSpeed {
            xVelocity = maxSpeed
        }
    }
    

    Increasing your velocity like this will result with a rotation speed of 2 in the first frame, than 4 in the second and 6 in the thirds etc. So it will have an increasing effect. While at the same time, when trying to reverse it you wont have a jerky effect either. Say the velocity is at 50, you first decrease it to 48, 46, 44, etc. Not until the 0 point you will actually start rotating the other way.