【问题标题】:Rotate sprite by touch with a limited rotation speed以有限的旋转速度通过触摸旋转精灵
【发布时间】:2015-06-02 12:49:31
【问题描述】:

我正在尝试让我的 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

何时不应使用操作

虽然动作是有效的,但创建和执行它们是有成本的。如果您在动画的每一帧中都对节点的属性进行更改,并且这些更改需要在每一帧中重新计算,那么您最好直接对节点进行更改,而不是使用操作来这样做。有关您可以在游戏中的何处执行此操作的更多信息,请参阅高级场景处理。

【问题讨论】:

  • 我对您要完成的工作感到有些困惑。您是否希望更改中间移动的角度旋转以防玩家四处移动手指(使用 thouchesMoved),或者您是否希望在移动完成之前阻止任何进一步的动作?
  • 两者都不是。我有一个炮塔,它有一个“最大旋转速度”,应该通过花时间转动来瞄准手指位置,但不是立即。我已经这样做了,但是由于 touchesMoved 被称为 60time by seconds ,因此按秒添加 60 个动作会阻止移动(动作不会运行......)
  • 那么您是否正在寻找炮塔以跟踪触摸位置,即使它在视图中移动?
  • 它只是自动开启,但是,它以手指为目标,但“以它的速度”

标签: ios swift rotation sprite-kit skaction


【解决方案1】:

您不需要这样做:removeActionForKey("rotation") - 这是因为您有移动阻塞。看看这些方法就知道了

  • speedBy:duration:
  • speedTo:duration:

和文档中的动画属性:

速度 - 速度因子调整动作动画的运行速度。例如,速度因子 2.0 意味着动画运行速度是原来的两倍。

在您的情况下很难说,但解决方案是创建 sequence 动作,因此它们将一一执行。 希望这会有所帮助

【讨论】:

  • 我尝试使用或不使用removeActionForKey("rotation") 它不会改变任何东西。而且速度因素并没有真正的帮助,因为旋转可能会在任何帧中发生变化。
  • 当用户在游戏中保持手指移动时,miner.weaponRotation 每秒会被调用 20 次,但每次调用的角度会有所不同。
  • 你试过序列吗?您可以将动作收集到一个序列中并每秒运行一次。例如,创建一个 NSTimer,然后每秒执行一次 runAction(seq),其中 seq 是您的操作序列。每个动作的持续时间为 1(sec)/seq.Count()
  • var actions = Array(); func addAction(){ actions.append(SKAction.rotateToAngle(_wantedRotation, duration: 1/20); } func performAction(){ if (timeInterval = 1){ let sequence = SKAction.sequence(actions); runAction(sequence); } timeInterval = 0; actions.removeAll() }
  • 我已经尝试在动作的执行中加入“空格”,使用序列和SKAction.waitForDuration 效果更好,但仍然显示反应不佳。所以这不是一个解决方案。
【解决方案2】:

我会试一试的。但我现在无法测试它。这是一个 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];
}

【讨论】:

  • Obj-C 没问题。但是您的代码与我的并没有太大变化,您只需对每个 touchesMoved 调用运行一个操作,这就是我所做的。
【解决方案3】:

我创建了代码,以恒定的速度将 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");
        }
    }
}

我个人认为使用更新方法来完成这个任务会更好。它可以让您更好地控制对象的 zRotation 的逐步移动,而不是使用 SKAction。

【讨论】:

  • 我已经这样做了,我的意思不是只在touchesBegan 上这样做,而是在touchesMoved 上这样做,重点是炮塔在移动时跟随手指。只需尝试将您的代码也放入touchesMoved,您会看到我的问题;)
  • @AncAinu - 您是否绝对只使用 SKAction 来执行此操作?
  • 绝对不是,我更愿意,但对于这种情况,SKAction 可能根本不可能。我只想知道是否有办法用 SKAction 来做到这一点(因为它似乎在 SpriteKit 中完成了)。如果不可能,我想知道为什么(如果有人知道)。如果没有人能说出来,那么我会得出结论这是不可能的(或者我没有找到对 SpriteKit 足够好的人)。这个问题超出了我的问题,看看这是否是SpriteKit的限制:)
【解决方案4】:

我有一个炮塔......应该瞄准手指位置,但不是 立即,花时间转身。

这样的事情你将无法摆脱 SKActions。您可以尝试,但这会非常混乱且效率低下。你需要实时运动控制来实现这样的事情,因为你的炮塔的角速度需要根据触摸位置不断变化。

所以我给你写了一个快速示例项目,展示如何计算角速度。该项目还处理所有特殊情况,例如防止角度跳过目标旋转。

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
        }

    }


}

【讨论】:

  • 我的想法完全正确。 SKAction 不是这样做的正确方法。 update 方法是逐步处理轮换变化的唯一方法。我今天打算为此添加代码,但你打败了我:) +1 以获得“史诗”解决方案。
  • 这是正确的答案。我再次仔细阅读了 SpriteKit 文档,发现:developer.apple.com/library/ios/documentation/GraphicsAnimation/… 再清楚不过了,感谢您的详细回答。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-06-21
  • 1970-01-01
相关资源
最近更新 更多