简体   繁体   English

以有限的旋转速度触摸旋转精灵

[英]Rotate sprite by touch with a limited rotation speed

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. 有关在游戏中可能执行此操作的详细信息,请参阅高级场景处理。

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. 对于像这样的事情你将无法逃脱SKActions。 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
        }

    }


}

在此输入图像描述

You do not need to do that: removeActionForKey("rotation") - it is the reason, that you have movement blocking. 你不需要这样做:removeActionForKey(“rotation”) - 这就是你有动作阻塞的原因。 Just take a look at these methods 看看这些方法吧

  • speedBy:duration: speedBy:时间:
  • speedTo:duration: speedTo:时间:

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 希望这可以帮助

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];
}

I have created code to turn the node0 sprite towards the touch point at a constant speed regardless of angle. 我创建了代码,无论角度如何,都将node0精灵以恒定速度转向触摸点。 You had indicated in your question that you prefer using SKAction instead of having code in your update method. 您在问题中表明您更喜欢使用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. 我个人认为最好使用update方法来完成这项任务。 It would give you greater control in the step by step movement of the object's zRotation rather than using a SKAction. 它可以让您更好地控制对象的zRotation的逐步移动,而不是使用SKAction。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM