13

iPhoneアプリケーションのようなボードゲームで使用するフリップアニメーションを実装しようとしています。アニメーションは、回転して背中の色に変わるゲームピースのように見えるはずです(一種のリバーシピースのようなものです)。直交軸を中心にピースを反転させるアニメーションを作成できましたが、z軸を中心に回転を変更して対角軸を中心に反転しようとすると、実際の画像も回転します(当然のことながら)。代わりに、対角軸を中心に画像を「そのまま」回転させたいと思います。

私は変えようとしましlayer.sublayerTransformたが、成功しませんでした。

これが私の現在の実装です。これは、アニメーションの最後に鏡像を取得する問題を解決するためのトリックを実行することによって機能します。解決策は、実際にレイヤーを180度回転させるのではなく、レイヤーを90度回転させ、画像を変更してから元に戻すことです。

最終バージョン:個別のキー付きアニメーションを作成し、各フレームの変換行列を計算するというLorenzosの提案に基づいています。このバージョンでは、代わりに、レイヤーサイズに基づいて必要な「ガイド」フレームの数を推定し、線形キーアニメーションを使用します。このバージョンは任意の角度で回転するため、対角線を中心に回転するには45度の角度を使用します。

使用例:

[someclass flipLayer:layer image:image angle:M_PI/4]

実装:

- (void)animationDidStop:(CAAnimationGroup *)animation
                finished:(BOOL)finished {
  CALayer *layer = [animation valueForKey:@"layer"];

  if([[animation valueForKey:@"name"] isEqual:@"fadeAnimation"]) {
    /* code for another animation */
  } else if([[animation valueForKey:@"name"] isEqual:@"flipAnimation"]) {
    layer.contents = [animation valueForKey:@"image"];
  }

  [layer removeAllAnimations];
}

- (void)flipLayer:(CALayer *)layer
            image:(CGImageRef)image
            angle:(float)angle {
  const float duration = 0.5f;

  CAKeyframeAnimation *rotate = [CAKeyframeAnimation
                                 animationWithKeyPath:@"transform"];
  NSMutableArray *values = [[[NSMutableArray alloc] init] autorelease];
  NSMutableArray *times = [[[NSMutableArray alloc] init] autorelease];
  /* bigger layers need more "guiding" values */
  int frames = MAX(layer.bounds.size.width, layer.bounds.size.height) / 2;
  int i;
  for (i = 0; i < frames; i++) {
    /* create a scale value going from 1.0 to 0.1 to 1.0 */
    float scale = MAX(fabs((float)(frames-i*2)/(frames - 1)), 0.1);

    CGAffineTransform t1, t2, t3;
    t1 = CGAffineTransformMakeRotation(angle);
    t2 = CGAffineTransformScale(t1, scale, 1.0f);
    t3 = CGAffineTransformRotate(t2, -angle);
    CATransform3D trans = CATransform3DMakeAffineTransform(t3);

    [values addObject:[NSValue valueWithCATransform3D:trans]];
    [times addObject:[NSNumber numberWithFloat:(float)i/(frames - 1)]];
  }
  rotate.values = values;
  rotate.keyTimes = times;
  rotate.duration = duration;
  rotate.calculationMode = kCAAnimationLinear;

  CAKeyframeAnimation *replace = [CAKeyframeAnimation
                                  animationWithKeyPath:@"contents"];
  replace.duration = duration / 2;
  replace.beginTime = duration / 2;
  replace.values = [NSArray arrayWithObjects:(id)image, nil];
  replace.keyTimes = [NSArray arrayWithObjects:
                      [NSNumber numberWithDouble:0.0f], nil];
  replace.calculationMode = kCAAnimationDiscrete;

  CAAnimationGroup *group = [CAAnimationGroup animation];
  group.duration = duration;
  group.timingFunction = [CAMediaTimingFunction
                          functionWithName:kCAMediaTimingFunctionLinear];
  group.animations = [NSArray arrayWithObjects:rotate, replace, nil];
  group.delegate = self;
  group.removedOnCompletion = NO;
  group.fillMode = kCAFillModeForwards;
  [group setValue:@"flipAnimation" forKey:@"name"];
  [group setValue:layer forKey:@"layer"];
  [group setValue:(id)image forKey:@"image"];

  [layer addAnimation:group forKey:nil];
}

元のコード:

+ (void)flipLayer:(CALayer *)layer
          toImage:(CGImageRef)image
        withAngle:(double)angle {
  const float duration = 0.5f;

  CAKeyframeAnimation *diag = [CAKeyframeAnimation
                               animationWithKeyPath:@"transform.rotation.z"];
  diag.duration = duration;
  diag.values = [NSArray arrayWithObjects:
                 [NSNumber numberWithDouble:angle],
                 [NSNumber numberWithDouble:0.0f],
                 nil];
  diag.keyTimes = [NSArray arrayWithObjects:
                   [NSNumber numberWithDouble:0.0f],
                   [NSNumber numberWithDouble:1.0f],
                   nil];
  diag.calculationMode = kCAAnimationDiscrete;

  CAKeyframeAnimation *flip = [CAKeyframeAnimation
                               animationWithKeyPath:@"transform.rotation.y"];
  flip.duration = duration;
  flip.values = [NSArray arrayWithObjects:
                 [NSNumber numberWithDouble:0.0f],
                 [NSNumber numberWithDouble:M_PI / 2],
                 [NSNumber numberWithDouble:0.0f],
                 nil];
  flip.keyTimes = [NSArray arrayWithObjects:
                   [NSNumber numberWithDouble:0.0f],
                   [NSNumber numberWithDouble:0.5f],
                   [NSNumber numberWithDouble:1.0f],
                   nil];
  flip.calculationMode = kCAAnimationLinear;

  CAKeyframeAnimation *replace = [CAKeyframeAnimation
                                  animationWithKeyPath:@"contents"];
  replace.duration = duration / 2;
  replace.beginTime = duration / 2;
  replace.values = [NSArray arrayWithObjects:(id)image, nil];
  replace.keyTimes = [NSArray arrayWithObjects:
                      [NSNumber numberWithDouble:0.0f], nil];
  replace.calculationMode = kCAAnimationDiscrete;

  CAAnimationGroup *group = [CAAnimationGroup animation];
  group.removedOnCompletion = NO;
  group.duration = duration;
  group.timingFunction = [CAMediaTimingFunction
                          functionWithName:kCAMediaTimingFunctionLinear];
  group.animations = [NSArray arrayWithObjects:diag, flip, replace, nil];
  group.fillMode = kCAFillModeForwards;

  [layer addAnimation:group forKey:nil];
}
4

3 に答える 3

6

このように偽造できます。対角線に沿ってレイヤーを折りたたむアフィン変換を作成します。

A-----B           B
|     |          /
|     |   ->   A&D
|     |        /
C-----D       C

画像を変更し、CALayer を別のアニメーションに変換します。これにより、レイヤーが対角線を中心に回転するような錯覚が生まれます。

数学を正しく覚えていれば、その行列は次のようになります。

0.5 0.5 0
0.5 0.5 0
0   0   1

更新: OK、CA は縮退変換を使用するのがあまり好きではありませんが、次の方法で近似できます。

CGAffineTransform t1 = CGAffineTransformMakeRotation(M_PI/4.0f);
CGAffineTransform t2 = CGAffineTransformScale(t1, 0.001f, 1.0f);
CGAffineTransform t3 = CGAffineTransformRotate(t2,-M_PI/4.0f);  

シミュレーターでの私のテストでは、回転が移動よりも速く発生するため、まだ問題がありました。そのため、黒一色の正方形では効果が少し奇妙でした。周囲に透明な領域がある中央のスプライトがある場合、効果は期待に近いものになると思います。次に、 t3マトリックスの値を微調整して、より魅力的な結果が得られるかどうかを確認できます。

さらに調査した結果、トランジション自体を最大限に制御するには、キーフレームを介して独自のトランジションをアニメーション化する必要があるようです。このアニメーションを 1 秒で表示する場合、kCAAnimationDiscrete を使用して補間なしで 10 分の 1 秒ごとに 10 個のマトリックスを表示する必要があります。これらの行列は、以下のコードで生成できます。

CGAffineTransform t1 = CGAffineTransformMakeRotation(M_PI/4.0f);
CGAffineTransform t2 = CGAffineTransformScale(t1, animationStepValue, 1.0f);
CGAffineTransform t3 = CGAffineTransformRotate(t2,-M_PI/4.0f);  

keyFrame の ech の animationStepValue は、この進行から取得されます。

{1 0.7 0.5 0.3 0.1 0.3 0.5 0.7 1}

つまり、10 個の異なる変換マトリックス (実際には 9 個) を生成し、それらをキーフレームとしてプッシュして 10 分の 1 秒ごとに表示し、「補間しない」パラメーターを使用します。滑らかさとパフォーマンスのバランスをとるためにアニメーション番号を微調整できます*

*エラーの可能性があります。この最後の部分はスペルチェッカーなしで書かれています。

于 2010-03-17T11:56:39.610 に答える
4

解決しました。あなたもおそらくすでに解決策を持っていますが、ここに私が見つけたものがあります。それは本当に非常に簡単です...

CABasicAnimation を使用して斜めの回転を行うことができますが、これは 2 つのマトリックス、つまりレイヤーの既存のマトリックスと CATransform3DRotate を連結する必要があります。「トリック」は、3DRotate で、回転する座標を指定する必要があることです。

コードは次のようになります。


CATransform3DConcat(theLayer.transform, CATransform3DRotate(CATransform3DIdentity, M_PI/2, -1, 1, 0));

これにより、正方形の左上隅が軸 Y=X を中心に回転し、右下隅に移動するように見える回転が行われます。

アニメーション化するコードは次のようになります。


CABasicAnimation *ani1 = [CABasicAnimation animationWithKeyPath:@"transform"];

// set self as the delegate so you can implement (void)animationDidStop:finished: to handle anything you might want to do upon completion
[ani1 setDelegate:self];

// set the duration of the animation - a float
[ani1 setDuration:dur];

// set the animation's "toValue" which MUST be wrapped in an NSValue instance (except special cases such as colors)
ani1.toValue = [NSValue valueWithCATransform3D:CATransform3DConcat(theLayer.transform, CATransform3DRotate(CATransform3DIdentity, M_PI/2, -1, 1, 0))];

// give the animation a name so you can check it in the animationDidStop:finished: method
[ani1 setValue:@"shrink" forKey:@"name"];

// finally, apply the animation
[theLayer addAnimation:ani1 forKey@"arbitraryKey"];

それでおしまい!このコードは、正方形 (theLayer) を回転させて非表示にし、90 度移動して画面に垂直に表示します。次に、色を変更し、まったく同じアニメーションを実行して元に戻すことができます. 行列を連結しているため、同じアニメーションが機能するため、回転するたびに、これを 2 回行うか、に変更M_PI/2M_PIます。

最後に、明示的にアニメーション終了状態に設定しない限り、完了時にレイヤーが元の状態にスナップバックすることに注意してください。[theLayer addAnimation:ani1 forKey@"arbitraryKey"];つまり、追加したい行の直前に


theLayer.transform = CATransform3DConcat(v.theSquare.transform, CATransform3DRotate(CATransform3DIdentity, M_PI/2, -1, 1, 0));

アニメーションが完了した後の値を設定します。これにより、元の状態に戻るのを防ぎます。

お役に立てれば。あなたではない場合は、おそらく私たちのように壁に頭をぶつけていた誰かがいるでしょう! :)

乾杯、

クリス

于 2010-06-24T20:29:25.773 に答える