Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rotate sprite by touch with a limited rotation speed

I'm trying to make my spriteNode rotate over finger touch.

So far I can do it, but what I want is that my node have a "rotation speed". So I calculate the length of the angle then set a different timing to rotate with it (if the arc is long, it will take time...).

Here's my code :

override func touchesMoved(touches: NSSet, withEvent event: UIEvent) {
    _isTouched = true
    for touch in touches {
        let location:CGVector = touch.locationInNode(self) - miner.position
        
        miner.weaponRotation = location.angle() - CGFloat(M_PI_2)
    }
}

var wantedRotation: CGFloat {
    get { return _wantedRotation }
    set(rotation) {
        if rotation > CGFloat(M_PI) || rotation < -CGFloat(M_PI) {
            _wantedRotation = CGFloat(M_PI) + rotation % CGFloat(M_PI)
        }
        else {
            _wantedRotation = rotation
        }
        
        removeActionForKey("rotation")
        let arc: CGFloat = CGFloat(abs(_wantedRotation - zRotation))
        let shortestArc: CGFloat = min(arc, CGFloat(M_PI * 2.0) - arc)
        runAction(SKAction.rotateToAngle(_wantedRotation, duration: NSTimeInterval(shortestArc / CGFloat(M_PI) * rotationSpeed), shortestUnitArc: true), withKey: "rotation")
    }
}

The main problem is that adding several SKAction to the node block the movement.

I would like to know what could be the solution ? If possible by using SKAction, since I would like to avoid doing an update on each frame to calculate the movement (but if it's the only solution...)

NOTE AFTER ANSWER

As I received answers, I read again the SpriteKit documentation and found this clear note :

When You Shouldn’t Use Actions

Although actions are efficient, there is a cost to creating and executing them. If you are making changes to a node’s properties in every frame of animation and those changes need to be recomputed in each frame, you are better off making the changes to the node directly and not using actions to do so. For more information on where you might do this in your game, see Advanced Scene Processing.

like image 836
Tancrede Chazallet Avatar asked Mar 29 '15 14:03

Tancrede Chazallet


3 Answers

I have a turret which ... should target the finger position, but not immediately, by taking time to turn.

You won't be able to get away with SKActions for something like this. You can try but it will be really messy and inefficient. You need real-time motion control for something like this because the angular velocity of your turret needs to change constantly depending on the touch position.

So I wrote you a quick example project showing how to calculate the angular velocity. The project handles all special cases as well, such as preventing the angle from jumping over your target rotation.

import SpriteKit

class GameScene: SKScene {
    let turret = SKSpriteNode(imageNamed: "Spaceship")
    let rotationSpeed: CGFloat = CGFloat(M_PI) //Speed turret rotates.
    let rotationOffset: CGFloat = -CGFloat(M_PI/2.0) //Controls which side of the sprite faces the touch point. I offset the angle by -90 degrees so that the top of my image faces the touch point.

    private var touchPosition: CGFloat = 0
    private var targetZRotation: CGFloat = 0

    override func didMoveToView(view: SKView) {
        turret.physicsBody = SKPhysicsBody(rectangleOfSize: turret.size)
        turret.physicsBody!.affectedByGravity = false
        turret.position = CGPoint(x: self.size.width/2.0, y: self.size.height/2.0)
        self.addChild(turret)
    }

    override func touchesMoved(touches: NSSet, withEvent event: UIEvent) {
        calculateAngleToTouch(touches.anyObject() as UITouch)
    }

    override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
        calculateAngleToTouch(touches.anyObject() as UITouch)
    }

    func calculateAngleToTouch(touch: UITouch) {
        let position = touch.locationInNode(self)
        let angle = atan2(position.y-turret.position.y, position.x-turret.position.x)

        targetZRotation = angle + rotationOffset
    }

    override func update(currentTime: NSTimeInterval) {
        var angularDisplacement = targetZRotation - turret.zRotation
        if angularDisplacement > CGFloat(M_PI) {
            angularDisplacement = (angularDisplacement - CGFloat(M_PI)*2)
        } else if angularDisplacement < -CGFloat(M_PI) {
            angularDisplacement = (angularDisplacement + CGFloat(M_PI)*2)
        }

        if abs(angularDisplacement) > rotationSpeed*(1.0/60.0) {
            let angularVelocity = angularDisplacement < 0 ? -rotationSpeed : rotationSpeed
            turret.physicsBody!.angularVelocity = angularVelocity
        } else {
            turret.physicsBody!.angularVelocity = 0
            turret.zRotation = targetZRotation
        }

    }


}

enter image description here

like image 104
Epic Byte Avatar answered Oct 22 '22 14:10

Epic Byte


You do not need to do that: removeActionForKey("rotation") - it is the reason, that you have movement blocking. Just take a look at these methods

  • speedBy:duration:
  • speedTo:duration:

and property of animation from documentation:

speed - The speed factor adjusts how fast an action’s animation runs. For example, a speed factor of 2.0 means the animation runs twice as fast.

It is hard to say in your case, but also solution would be to create sequence actions, so they will perform one by one. Hope this helps

like image 41
Kateryna Gridina Avatar answered Oct 22 '22 15:10

Kateryna Gridina


I'll give it a try. But I cann't test it now. this is an Objective-C (pseudo)code.

- (void)rotateSprite:(SKSpriteNode *)sprite toAngle:(float)angle inDuration:(NSTimeInterval)duration
{
    SKAction *rotation = [SKAction rotateToAngle:angle duration:duration]; 
    [sprite runAction:rotation completion:^{
        [sprite removeAllActions];
    }];
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
   .
   .
   .
   // your sprite
   [node removeAllActions];                
   float angle = [self calcAngle]; // your angle
   NSTimeInterval duration = [self calcDuration]; // your duration
   [self rotateSprite:(SKSpriteNode *)node toAngle:angle inDuration:duration];
}
like image 1
suyama Avatar answered Oct 22 '22 16:10

suyama