2

Is there a relatively simple way to rotate SKSpriteNode so that it is always facing the direction it is moving? My class Gamescene has a system of objects with their relative physicsbodies that apply impulses on each other when they collide. This makes it relatively difficult to keep track of exactly what direction an object is moving in. Some of the objects are also not regularly shaped, which makes things a bit more chaotic. I have tried using trigonometry (e.g. arctangent), but it only works as intended in very special circumstances. Might there be a way to find out the angle of an SKSpriteNode's trajectory, so that I can rotate the object to that angle? Thanks for any help you can provide!

EDIT:

I created an extension for CGVector and tried to rotate the SKSpriteNode in the direction of its velocity (Thanks to 0x141E for the idea) once two objects collided, but it doesn't seem to be working properly

func didBeginContact(contact: SKPhysicsContact) {
    var firstBody : SKPhysicsBody = contact.bodyA
    var secondBody : SKPhysicsBody = contact.bodyB
    if ((firstBody.categoryBitMask == PhysicsCatagory.Blade) && (secondBody.categoryBitMask == PhysicsCatagory.Laser))
    {
        if (secondBody.node != nil)
        {
            if (saberAngle <= 0 && LCR == 1) // Blade pointing right
            {
                let rebound = CGVectorMake(-reboundStrength*sin(1.5707 + saberAngle), reboundStrength*cos(1.5707 + saberAngle))
                secondBody.applyImpulse(rebound)
            }
            else if (saberAngle > 0 && LCR == -1) // Blade pointing left
            {
                let rebound = CGVectorMake(reboundStrength*sin(1.5707 - saberAngle), reboundStrength*cos(1.5707 - saberAngle))
                secondBody.applyImpulse(rebound)
            }
            if (secondBody.velocity.speed() > 0.01){
                (secondBody.node as! SKSpriteNode).zRotation = secondBody.velocity.angle() - offset
            }
        }
    }
}

I didn't do it in the didSimulatePhysics() method because there would be times when the node I was trying to rotate would not exist, and I would get errors like "Use of unresolved identifier 'sprite'"

EDIT:

I got it to work by simply creating and running an action called rotate that rotates the sprite to secondBody.velocity.angle() - offset, but I'm still curious as to why my code wasn't working before ^^

Lahav
  • 781
  • 10
  • 22
  • Could you post the code you use currently? – ZeMoon Jul 15 '15 at 08:00
  • The answer by 0x141e is perfect if you want instantaneous rotation (If you are looking to rotate over a period of time check out my answer here http://stackoverflow.com/a/29443170/2158465). Note you may need to separate the drawing of your sprite from the physics body when using this approach though. If you keep them the same what can happen is you will end up rotating the physics body as it is already rotating from collisions and end up with crazy physics. If your physics bodies don't rotate then you don't need to worry about this effect. – Epic Byte Jul 15 '15 at 16:59

1 Answers1

13

It's fairly straightforward to rotate a sprite in the direction of its motion. You can do this by converting the x and y components of the sprite's velocity into an angle with the atan2 function. You should rotate the sprite only when its speed is greater than some nominal value to prevent the sprite from resetting to zero degrees when the sprite's speed is (nearly) zero.

If we extend CGVector to compute speed and angle from the velocity's components by

extension CGVector {
    func speed() -> CGFloat {
        return sqrt(dx*dx+dy*dy)
    }
    func angle() -> CGFloat {
        return atan2(dy, dx)
    }
}

we can rotate a sprite to face in the direction of its motion with

override func didSimulatePhysics() {
    if let body = sprite.physicsBody {
        if (body.velocity.speed() > 0.01) {
            sprite.zRotation = body.velocity.angle() - offset
        }
    }
}

where offset = CGFloat(M_PI_2) if your sprite faces up when zRotation is zero, and offset = 0 if your sprite faces to the right when zRotation is zero.

0x141E
  • 12,613
  • 2
  • 41
  • 54
  • Note that any extension must be declared outside of a class. You can place it above the GameScene class definition. – 0x141E Jul 15 '15 at 16:12
  • This algorithm doesn't seem to work for me. I have a body bouncing around the screen in 90-degree angles. However this algorithm turns the body 45 or 135 degrees. Am I doing something wrong or does this formula produce wrong results? – user594883 Apr 25 '16 at 04:52
  • @user594883 which direction does your sprite face when `zRotation = 0`? – 0x141E Apr 25 '16 at 08:48
  • It's facing up at zRotation = 0. It's worth mentioning that I use `let action = SKAction.rotateByAngle(body.velocity.angle() - offset, duration:0.3)` `nodeToTurn?.runAction(action)` to turn my sprite. – user594883 Apr 25 '16 at 14:13
  • 2
    You can't use an `SKAction` to rotate a sprite with this method because `didSimulatePhysics` is called ~60 times a second. An action needs time to execute, but the next action overrides the current action. – 0x141E Apr 25 '16 at 20:55
  • Ok, thanks! That's helpful! However that's not the cause of my current problem as the sprite is misaligned with direction even though the action completes ok. Basically the problem is that when turning CCW the sprite is rotated 45° and when turning CW 135°. I don't see how offset could fix that. What can I be doing wrong... – user594883 Apr 26 '16 at 04:05
  • Oh, what I failed to mention was that I'm calling `SKAction` at `didEndCollision` after detecting that a collisions has happened. Could the reason be that contact delegate's velocity vector is still what it was before the collision? – user594883 Apr 27 '16 at 09:42