これまでCCMoveToや、CCRotateToなど個々のアクションの使い方をご説明しましたが、今回は、実際にこれらのアクションがどのような内部動作をしているのかを深く解析したいと思います。
例えば、CCMoveToをはじめとする全てのアクションを利用する場合には以下のようにCCNodeのRunActionメソッドを使用します。
var moveToAction = new CCMoveTo(3f, new CCPoint(50f, 100f)); node.RunAction(moveToAction);
まずは、RunActionしたアクションがどのように管理されているかを確認してみましょう。
CCNodeのRunActionメソッドのソースコードを見てみると以下のようになっています。
public CCActionState RunAction(CCAction action) { return ActionManager != null ? ActionManager.AddAction(action, this, !IsRunning) : AddLazyAction(action, this, !IsRunning); }
RunActionしたアクションをActionManagerに登録しています。AddLazyActionの方も追っていくと、最終的にはActionManagerに登録するようになっています。
よって、RunActionしたアクションは全てActionManagerで一括管理されるようになっています。
また、ActionManagerのAddActionを確認すると、追加されたアクションのStartActionをコールし、CCActionStateを返すようになっています。
public CCActionState AddAction(CCAction action, CCNode target, bool paused = false) { // 省略 var state = action.StartAction(target); // 省略 return state; }
次に、実際のアクションの動きがどのように動作しているのかを確認します。
基本的な動作原理として、1フレームごとに、ActionManagerのUpdateメソッドがコールされます。
引数のdtは deltaTime つまり、前のフレームから現在のフレームまでの経過時間です。
public void Update(float dt) { // 省略 for (int i = 0; i < count; i++) { // 省略 currentTarget.CurrentActionState.Step(dt); // 省略 } }
この中で、登録されているCCActionStateのStepメソッドをコールしています。
Stepメソッドは、CCMoveToや、CCRotateToをStartActionしたときに生成されるCCMoveToStateおよび、CCRotateToStateの継承元であるCCFiniteTimeActionStateに実装されています。
Stepメソッドでは、当該のアクションのすでに完了した部分の時間を分子に、アクション全体の所要時間を分母にし、アクションが何%進んだかを示す値を引数にしUpdateメソッドをコールしています。
protected internal override void Step(float dt) { if (firstTick) { firstTick = false; Elapsed = 0f; } else { Elapsed += dt; } Update (Math.Max (0f, Math.Min (1, Elapsed / Math.Max (Duration, float.Epsilon) ) ) ); }
これを、例としてCCMoveToStateで確認すると、以下のようになっています。
残りの未完了のアクションであるPositionDeltaのうち、今回のフレームで消化すべき量を求めてその分だけアクションするようになっています。
public class CCMoveToState : CCMoveByState { public CCMoveToState (CCMoveTo action, CCNode target) : base (action, target) { StartPosition = target.Position; PositionDelta = action.PositionEnd - target.Position; } public override void Update (float time) { if (Target != null) { CCPoint currentPos = Target.Position; CCPoint newPos = StartPosition + PositionDelta * time; Target.Position = newPos; PreviousPosition = newPos; } } }
つまりアクションは一見するとスケジューラのようなフレームで管理された動作とは違うように見えますが、詳しく見てみると、スケジューラと同じように1フレームごとの動作に細切れにされた上で実行されているのが分かります。
というわけで、今回はここまでです。