SpriteKit的相机抖动效果

15

有没有人知道一些开箱即用的库,可以提供类似于相机抖动的效果给SKNode

如果没有,是否有一种简单的方法使用动作来实现相机抖动?

谢谢

7个回答

16
我找到了一种优雅的方法,使用SKAction来使你的节点抖动。例如,使用水平抖动:
-(void)shake:(NSInteger)times {
    CGPoint initialPoint = self.position;
    NSInteger amplitudeX = 32;
    NSInteger amplitudeY = 2;
    NSMutableArray * randomActions = [NSMutableArray array];
    for (int i=0; i<times; i++) {
        NSInteger randX = self.position.x+arc4random() % amplitudeX - amplitudeX/2;
        NSInteger randY = self.position.y+arc4random() % amplitudeY - amplitudeY/2;
        SKAction *action = [SKAction moveTo:CGPointMake(randX, randY) duration:0.01];
        [randomActions addObject:action];
    }
    
    SKAction *rep = [SKAction sequence:randomActions];
    
    [self runAction:rep completion:^{
        self.position = initialPoint;
    }];
}

编辑:

差不多10年后,我遇到了这个请求,并找到了自己的答案。但是现在我们有了一种新的语言,让我们翻译它吧!

extension SKNode {
    func shake(times: Int, amplitude: CGSize, duration: TimeInterval, completion: @escaping () -> Void) {
        guard times > 0 else { return }
        let initialPosition = self.position
        var randomActions: [SKAction] = []
        let stepDuration = duration / Double(times)
        for _ in 0..<times {
            let randX = self.position.x + CGFloat(arc4random_uniform(UInt32(amplitude.width))) - amplitude.width/2
            let randY = self.position.y + CGFloat(arc4random_uniform(UInt32(amplitude.height))) - amplitude.height/2
            randomActions.append(
                SKAction.move(to: CGPoint(x: randX, y: randY), duration: stepDuration)
            )
        }
        self.run(SKAction.sequence(randomActions)) {
            self.position = initialPosition
            completion()
        }
    }
}

很酷。我会尝试一下,看看它比我目前所做的要好多少。 - Krzemienski
哇,这真的很有效,希望我可以点赞两次,因为它还帮助解决了一些FPS问题,因为我可以清理更新语句中的混乱。 - Krzemienski
1
这对我不起作用,我收到错误信息:SKScene的位置动画没有效果。 - 有什么想法? - user1216855
和 user1216855 一样的结果,我再也看不到这段代码的抖动效果了。我认为 iOS7.1 对其进行了更新,因此它不再起作用。如何修复? - GeneCode
@Rocotilos 我现在不在代码面前,但我想你尝试给场景添加动画了吧。你是否尝试过在一个简单的子节点上进行操作? - Martin
是的,我终于成功了,我添加了一个虚拟SKNode,并将所有的背景精灵都添加到其中,并对虚拟节点进行了动画处理。太棒了!:D - GeneCode

14

被接受答案中的摇晃方法有一个注意事项-它只适用于没有其x、y坐标被其他正在运行的SKActions修改的节点。这是因为我们在摇晃完成后将节点位置重置为初始位置。

如果您希望使用其他在顺序/并行中修改摇晃节点位置(x或y坐标)的SKAction,您需要添加一种随机摇晃的反向动作。

以下是用于参考的Swift扩展:

extension SKAction {
class func shake(duration:CGFloat, amplitudeX:Int = 3, amplitudeY:Int = 3) -> SKAction {
    let numberOfShakes = duration / 0.015 / 2.0
    var actionsArray:[SKAction] = []
    for index in 1...Int(numberOfShakes) {
        let dx = CGFloat(arc4random_uniform(UInt32(amplitudeX))) - CGFloat(amplitudeX / 2)
        let dy = CGFloat(arc4random_uniform(UInt32(amplitudeY))) - CGFloat(amplitudeY / 2)
        let forward = SKAction.moveByX(dx, y:dy, duration: 0.015)
        let reverse = forward.reversedAction()
        actionsArray.append(forward)
        actionsArray.append(reverse)
    }
    return SKAction.sequence(actionsArray)
}
}

请注意,这个修改不需要将节点作为参数传递进来。


7
如果您已经在使用Swift,可以使用以下代码...
let shake = SKAction.shake(node.position, duration: 0.3)
node.runAction(shake)

或者,您可以配置振幅以获得更强烈的震动。

let shake = SKAction.shake(node.position, duration: 0.3, amplitudeX: 12, amplitudeY: 3)
node.runAction(shake)

这是一个扩展...

extension SKAction {
    class func shake(initialPosition:CGPoint, duration:Float, amplitudeX:Int = 12, amplitudeY:Int = 3) -> SKAction {
        let startingX = initialPosition.x
        let startingY = initialPosition.y
        let numberOfShakes = duration / 0.015
        var actionsArray:[SKAction] = []
        for index in 1...Int(numberOfShakes) {
            let newXPos = startingX + CGFloat(arc4random_uniform(UInt32(amplitudeX))) - CGFloat(amplitudeX / 2)
            let newYPos = startingY + CGFloat(arc4random_uniform(UInt32(amplitudeY))) - CGFloat(amplitudeY / 2)
            actionsArray.append(SKAction.moveTo(CGPointMake(newXPos, newYPos), duration: 0.015))
        }
        actionsArray.append(SKAction.moveTo(initialPosition, duration: 0.015))
        return SKAction.sequence(actionsArray)
    }
}

感谢Stephen Haney的实现,我只是将其移植为扩展。


1
谢谢你做到了!我的代码是基于这个帖子中标记的答案,所以我们正式地完成了一个循环;) - Stephen

5
如果你想要颠覆整个场景,这里有一份用Swift编写的Rob答案的版本:
    func sceneShake(shakeCount: Int, intensity: CGVector, shakeDuration: Double) {
        let sceneView = self.scene!.view! as UIView
        let shakeAnimation = CABasicAnimation(keyPath: "position")

        shakeAnimation.duration = shakeDuration / Double(shakeCount)
        shakeAnimation.repeatCount = Float(shakeCount)
        shakeAnimation.autoreverses = true
        shakeAnimation.fromValue = NSValue(cgPoint: CGPoint(x: sceneView.center.x - intensity.dx, y: sceneView.center.y - intensity.dy))
        shakeAnimation.toValue = NSValue(cgPoint: CGPoint(x: sceneView.center.x + intensity.dx, y: sceneView.center.y + intensity.dy))

        sceneView.layer.add(shakeAnimation, forKey: "position")
      }

我将此函数包含在一个SKScene子类中。以下是一个示例调用:
    sceneShake(shakeCount: 3, intensity: CGVector(dx: shakeIntensity, dy: shakeIntensity), shakeDuration: shakeDuration)

3

这是我使用的方法,希望能够帮助您。根据您的需要进行修改。

-(void)shakeScreenFor:(int)numberOfShakes withIntensity:(CGVector)intensity andDuration:(CGFloat)duration{
    UIView *sceneView = self.scene.view;
    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
    [animation setDuration:(duration/numberOfShakes)];
    [animation setRepeatCount:numberOfShakes];
    [animation setAutoreverses:YES];
    [animation setFromValue:[NSValue valueWithCGPoint: CGPointMake([sceneView center].x - intensity.dx, [sceneView center].y - intensity.dy)]];
    [animation setToValue:[NSValue valueWithCGPoint: CGPointMake([sceneView center].x + intensity.dx, [sceneView center].y + intensity.dy)]];
    [[sceneView layer] addAnimation:animation forKey:@"position"];
}

1
这个在我的游戏中非常好用,可以通过以下数值[self shakeScreenFor:2 withIntensity:CGVectorMake(2, 2) andDuration:0.1];将某物猛烈撞击到屏幕上。 - GilesDMiddleton
1
这个可行!我喜欢这个。投了赞成票。谢谢伙计。 - GeneCode

1

@Benzi的Swift 4版本答案:

func shake(duration: Double, amplitudeX: CGFloat = 3, amplitudeY: CGFloat = 3) -> SKAction {
    let numberOfShakes = duration / 0.015 / 2.0
    var actionsArray:[SKAction] = []
    for _ in 1...Int(numberOfShakes) {
        let dx = CGFloat(arc4random_uniform(UInt32(amplitudeX))) - CGFloat(amplitudeX / 2)
        let dy = CGFloat(arc4random_uniform(UInt32(amplitudeY))) - CGFloat(amplitudeY / 2)
        let forward = SKAction.moveBy(x: dx, y:dy, duration: 0.015)
        let reverse = forward.reversed()
        actionsArray.append(forward)
        actionsArray.append(reverse)
    }
    return SKAction.sequence(actionsArray)
}

请注意不要在场景上使用此功能,否则会出现“无法对SKScene的位置进行动画处理”的错误。您需要在包含所有其他节点的父节点上运行该操作。 - Reefwing

0
-(void)screen_shake
 {
   if (shake)
   {
    current_number_of_shakes++;

    float randx = (float)[self randomValueBetween:1 and:90]/50.0f;

    if ([self randomValueBetween:0 and:1] == 0)
        randx = -randx;

    float randy = (float)[self randomValueBetween:1 and:90]/50.0f;

    if ([self randomValueBetween:0 and:1] == 0)
        randy = -randy;


    camera.position = CGPointMake(camera.position.x + randx, camera.position.y + randy);

    if (current_number_of_shakes >= total_amount_of_shakes)
    {

        current_number_of_shakes = 0;
        move_back_from_shake = YES;
        [camera runAction:[SKAction sequence:@[[SKAction moveTo:start_point_before_shake duration:.7], [SKAction performSelector:@selector(shake_done) onTarget:self]]]];

    }




   }

}

经过一些工作和参考人们在Cocoa2D中的做法,我想出了以下方法。在您的更新方法中,您需要调用此方法。有一些常量可以填写,但我希望这对所有需要它的人都有所帮助。另外,shake_done方法所做的所有工作就是将Bool shake更新为false。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接