Sprite rotation with limited rotation speed

I am trying to make my spriteNode rotate over my finger.

For now I can do this, but I want my node to have a "rotation speed". So I calculate the length of the angle, then set another time to rotate with it (if the arc is long, it will take time ...).

Here is 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 multiple SKAction to a node blocks the movement.

I would like to know what could be the solution? If possible, using SKAction, since I would like to avoid updating each frame to calculate the motion (but if this is the only solution ...)

NOTE AFTER ANSWER

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

When you should not use actions

Although actions are effective, there are costs to create and execute them. If you make changes to the properties of node s in each frame of the animation, and these changes should be recounted in each frame, you better make changes to the node directly, rather than using actions to do this. For more information on where you can do this in your game, see the Advanced Scene Processing section.

+6
source share
4 answers

I have a turret, which ... should focus on the position of the finger, but not immediately, spending time on the turn.

You cannot escape with SKActions for something like this. You can try, but it will be really dirty and inefficient. You need real-time motion control for something like that, because the angular velocity of your tower must constantly change depending on the position of the touch.

So, I wrote you a quick example project showing how to calculate the angular velocity. The project also handles all special cases, such as preventing the corner from jumping over the 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

+7
source

You do not need to do this: removeActionForKey ("rotation") is the reason that you have a motion lock. Just take a look at these methods.

  • speedBy: duration:
  • speedTo: duration:

and the animation property from the documentation:

speed - Speed โ€‹โ€‹factor adjusts the speed of the action animation. For example, a speed factor of 2.0 means that the animation is twice as fast.

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

+1
source

I'll try. But I canโ€™t check it now. this is 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]; } 
+1
source

I created a code to translate the node0 sprite to the touch point at a constant speed regardless of the angle. You indicated in your question that you prefer to use SKAction instead of code in your update method.

The following is an example project code for the GameScene.m file:

 #import "GameScene.h" @implementation GameScene { SKAction *objectAnimation; SKSpriteNode *node0; } -(void)didMoveToView:(SKView *)view { self.backgroundColor = [SKColor blackColor]; node0 = [SKSpriteNode spriteNodeWithColor:[SKColor redColor] size:CGSizeMake(100, 10)]; node0.position = CGPointMake(300, 300); node0.zRotation = 0.0; [self addChild:node0]; } -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { for (UITouch *touch in touches) { CGPoint touchLocation = [touch locationInNode:self]; // calculate angle between object and touch float deltaY = touchLocation.y - node0.position.y; float deltaX = touchLocation.x - node0.position.x; float moveBydegrees = atan2(deltaY, deltaX) * 180 / 3.14159265359; // convert degrees to radians float moveByRadians = moveBydegrees * M_PI / 180; float myFloat0 = 0; if(moveByRadians < 0) { myFloat0 = fabs(moveByRadians); } else { myFloat0 = moveByRadians; } NSLog(@"myFloat0 = %f",myFloat0); float myFloat1 = 0; if(node0.zRotation < 0) { myFloat1 = fabs(node0.zRotation); } else { myFloat1 = node0.zRotation; } NSLog(@"myFloat1 = %f",myFloat1); float durationTime = fabs(myFloat0 - myFloat1); NSLog(@"durationTime = %f",durationTime); objectAnimation = [SKAction rotateToAngle:moveByRadians duration:durationTime shortestUnitArc:YES]; [self startObjectAnimation]; } } -(void)startObjectAnimation { [node0 removeActionForKey:@"animation"]; if (![node0 actionForKey:@"animation"]) { if(objectAnimation != nil) { [node0 runAction:objectAnimation withKey:@"animation"]; NSLog(@"runnign animation"); } else { NSLog(@"enemy node animation is nil"); } } } 

Personally, I think it would be better to use the update method to complete this task. This will give you more control when moving the zRotation object step by step instead of using SKAction.

+1
source

Source: https://habr.com/ru/post/984386/


All Articles