通过触摸以有限的旋转速度旋转精灵
Rotate sprite by touch with a limited rotation speed
我正在尝试让我的 spriteNode 在手指触摸时旋转。
到目前为止我可以做到,但我想要的是我的节点有一个“旋转速度”。
所以我计算角度的长度然后设置不同的时间来旋转它(如果弧长,则需要时间......)。
这是我的代码:
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")
}
}
主要问题是节点上加了几个SKAction
会阻塞移动
我想知道解决方案是什么?如果可能的话,通过使用 SKAction,因为我想避免对每一帧进行更新来计算运动(但如果这是唯一的解决方案......)
回答后备注
当我收到答案时,我再次阅读了 SpriteKit 文档并找到 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.
你不需要这样做:removeActionForKey("rotation") - 这就是你有移动阻塞的原因。看看这些方法
- speedBy:持续时间:
- 速度到:持续时间:
来自文档的 和 属性 动画:
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.
你的情况很难说,但解决方案是创建 sequence 动作,这样它们就会一个接一个地执行。
希望这有帮助
我试试看。但是我现在不能测试它。这是一个 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];
}
我已经创建了代码,无论角度如何,都以恒定的速度将 node0 精灵转向触摸点。您在问题中表示您更喜欢使用 SKAction 而不是在更新方法中使用代码。
下面是 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");
}
}
}
我个人认为使用update方法来完成这个任务会更好。与使用 SKAction 相比,它可以让您更好地控制对象的 zRotation 的逐步移动。
I have a turret which ... should target the finger position, but not
immediately, by taking time to turn.
像这样的事情,您将无法逃脱 SKActions 的惩罚。您可以尝试,但这将非常混乱且效率低下。你需要 实时运动控制 因为你的炮塔的 angular 速度需要根据触摸位置不断变化。
所以我给你写了一个快速示例项目,展示了如何计算 angular 速度。该项目还处理所有特殊情况,例如防止角度跳过目标旋转。
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
}
}
}
我正在尝试让我的 spriteNode 在手指触摸时旋转。
到目前为止我可以做到,但我想要的是我的节点有一个“旋转速度”。 所以我计算角度的长度然后设置不同的时间来旋转它(如果弧长,则需要时间......)。
这是我的代码:
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")
}
}
主要问题是节点上加了几个SKAction
会阻塞移动
我想知道解决方案是什么?如果可能的话,通过使用 SKAction,因为我想避免对每一帧进行更新来计算运动(但如果这是唯一的解决方案......)
回答后备注
当我收到答案时,我再次阅读了 SpriteKit 文档并找到 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.
你不需要这样做:removeActionForKey("rotation") - 这就是你有移动阻塞的原因。看看这些方法
- speedBy:持续时间:
- 速度到:持续时间:
和 属性 动画:
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.
你的情况很难说,但解决方案是创建 sequence 动作,这样它们就会一个接一个地执行。 希望这有帮助
我试试看。但是我现在不能测试它。这是一个 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];
}
我已经创建了代码,无论角度如何,都以恒定的速度将 node0 精灵转向触摸点。您在问题中表示您更喜欢使用 SKAction 而不是在更新方法中使用代码。
下面是 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");
}
}
}
我个人认为使用update方法来完成这个任务会更好。与使用 SKAction 相比,它可以让您更好地控制对象的 zRotation 的逐步移动。
I have a turret which ... should target the finger position, but not immediately, by taking time to turn.
像这样的事情,您将无法逃脱 SKActions 的惩罚。您可以尝试,但这将非常混乱且效率低下。你需要 实时运动控制 因为你的炮塔的 angular 速度需要根据触摸位置不断变化。
所以我给你写了一个快速示例项目,展示了如何计算 angular 速度。该项目还处理所有特殊情况,例如防止角度跳过目标旋转。
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
}
}
}