個人的なメモ

Tomohiro Suzuki @hiro128_777 のブログです。Microsoft MVP for Developer Technologies 2017- 本ブログと所属組織の公式見解は関係ございません。

Cocos Sharp Deep Dive(1) CCAction

これまでCCMoveToや、CCRotateToなど個々のアクションの使い方をご説明しましたが、今回は、実際にこれらのアクションがどのような内部動作をしているのかを深く解析したいと思います。

例えば、CCMoveToをはじめとする全てのアクションを利用する場合には以下のようにCCNodeRunActionメソッドを使用します。

var moveToAction = new CCMoveTo(3f, new CCPoint(50f, 100f));
node.RunAction(moveToAction);

まずは、RunActionしたアクションがどのように管理されているかを確認してみましょう。

CCNodeRunActionメソッドのソースコードを見てみると以下のようになっています。

public CCActionState RunAction(CCAction action)
{
	return ActionManager != null ? ActionManager.AddAction(action, this, !IsRunning) : AddLazyAction(action, this, !IsRunning);
}

RunActionしたアクションをActionManagerに登録しています。AddLazyActionの方も追っていくと、最終的にはActionManagerに登録するようになっています。

よって、RunActionしたアクションは全てActionManagerで一括管理されるようになっています。

また、ActionManagerAddActionを確認すると、追加されたアクションのStartActionをコールし、CCActionStateを返すようになっています。

public CCActionState AddAction(CCAction action, CCNode target, bool paused = false)
{
	// 省略
	var state = action.StartAction(target);
	// 省略
	return state;
}

次に、実際のアクションの動きがどのように動作しているのかを確認します。
基本的な動作原理として、1フレームごとに、ActionManagerUpdateメソッドがコールされます。
引数のdtは deltaTime つまり、前のフレームから現在のフレームまでの経過時間です。

public void Update(float dt)
{
	// 省略
	for (int i = 0; i < count; i++)
	{
		// 省略
		currentTarget.CurrentActionState.Step(dt);
		// 省略
	}
}

この中で、登録されているCCActionStateStepメソッドをコールしています。

Stepメソッドは、CCMoveToや、CCRotateToStartActionしたときに生成される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フレームごとの動作に細切れにされた上で実行されているのが分かります。

というわけで、今回はここまでです。