I'm trying to make my spriteNode rotate over finger touch.
我正试图让我的spriteNode旋转手指触摸。
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.
主要问题是向节点添加几个SKAction会阻止移动。
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...)
我想知道什么是解决方案?如果可能的话,使用SKAction,因为我想避免对每个帧进行更新以计算移动(但如果它是唯一的解决方案......)
NOTE AFTER ANSWER
回答后的注意事项
As I received answers, I read again the SpriteKit documentation and found this clear note :
当我收到答案时,我再次阅读了SpriteKit文档并发现了这个清晰的注释:
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.
尽管操作有效,但创建和执行操作会有成本。如果要在每个动画帧中更改节点的属性,并且需要在每个帧中重新计算这些更改,则最好直接对节点进行更改,而不是使用操作来执行此操作。有关在游戏中可能执行此操作的详细信息,请参阅高级场景处理。
4 个解决方案
#1
7
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.
对于像这样的事情你将无法逃脱SKActions。你可以尝试,但它会非常混乱和低效。您需要对此类物体进行实时运动控制,因为您的炮塔的角速度需要根据触摸位置不断变化。
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.zPosition = targetZRotation
}
}
}
#2
1
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
你不需要这样做:removeActionForKey(“rotation”) - 这就是你有动作阻塞的原因。看看这些方法吧
- 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.
速度 - 速度因子可调整动作动画的运行速度。例如,速度因子为2.0意味着动画的运行速度是原来的两倍。
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
在您的情况下很难说,但解决方案是创建序列操作,因此它们将逐个执行。希望这可以帮助
#3
1
I'll give it a try. But I cann't test it now. this is an Objective-C (pseudo)code.
我试一试。但我现在无法测试它。这是一个Objective-C(伪)代码。
- (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];
}
#4
1
I have created code to turn the node0 sprite towards the touch point at a constant speed regardless of angle. You had indicated in your question that you prefer using SKAction instead of having code in your update method.
我创建了代码,无论角度如何,都将node0精灵以恒定速度转向触摸点。您在问题中表明您更喜欢使用SKAction,而不是在更新方法中使用代码。
Below is the sample project code for the GameScene.m file:
以下是GameScene.m文件的示例项目代码:
#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 accomplish this task. It would give you greater control in the step by step movement of the object's zRotation rather than using a SKAction.
我个人认为最好使用update方法来完成这项任务。它可以让您更好地控制对象的zRotation的逐步移动,而不是使用SKAction。
#1
7
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.
对于像这样的事情你将无法逃脱SKActions。你可以尝试,但它会非常混乱和低效。您需要对此类物体进行实时运动控制,因为您的炮塔的角速度需要根据触摸位置不断变化。
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.zPosition = targetZRotation
}
}
}
#2
1
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
你不需要这样做:removeActionForKey(“rotation”) - 这就是你有动作阻塞的原因。看看这些方法吧
- 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.
速度 - 速度因子可调整动作动画的运行速度。例如,速度因子为2.0意味着动画的运行速度是原来的两倍。
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
在您的情况下很难说,但解决方案是创建序列操作,因此它们将逐个执行。希望这可以帮助
#3
1
I'll give it a try. But I cann't test it now. this is an Objective-C (pseudo)code.
我试一试。但我现在无法测试它。这是一个Objective-C(伪)代码。
- (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];
}
#4
1
I have created code to turn the node0 sprite towards the touch point at a constant speed regardless of angle. You had indicated in your question that you prefer using SKAction instead of having code in your update method.
我创建了代码,无论角度如何,都将node0精灵以恒定速度转向触摸点。您在问题中表明您更喜欢使用SKAction,而不是在更新方法中使用代码。
Below is the sample project code for the GameScene.m file:
以下是GameScene.m文件的示例项目代码:
#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 accomplish this task. It would give you greater control in the step by step movement of the object's zRotation rather than using a SKAction.
我个人认为最好使用update方法来完成这项任务。它可以让您更好地控制对象的zRotation的逐步移动,而不是使用SKAction。