個人的なメモ

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

CocosSharp for Xamarin.Forms では、Xamarin.Forms 内のコントロールとしてCocos Sharp を使えます。

こんにちは、@hiro128_777です。

実はCocos Sharpは、バージョン1.6.2までと1.7.1以降で大きな違いがあります。
以下のようにNuGetのパッケージも別パッケージとなっています。

バージョン 1.6.2まで
CocosSharp PCL ………………………………フルスクリーンでの使用が前提。同一画面内で、Xamarin Native, Xamarin.Formsとの同居は不可。

バージョン 1.7.1以降
CocosSharp ……………………………………Xamarin Native内の一つのUIパーツとしてとしてCocos Sharpを利用可能。
CocosSharp for Xamarin.Forms 1.7.1 ………Xamarin.Forms 内の一つのUIパーツとしてとしてCocos Sharpを利用可能


バージョン 1.7.1以降ではゲーム画面は、NativeではCCGameView、FormsではCocosSharpViewというUIコントロールの一つとして扱われるように変更されています。
これによって一つの画面内で一部分はゲーム、一部分はリスト表示というようなことが実現できるようになり、Cocos Sharpの利用シーンがぐっと増えた感じです!

図で表すと以下のようになります。

f:id:hiro128:20161206173158p:plain

今後は、業務系のアプリでもCocos Sharpを効果的に利用できますね。
次回は、CocosSharp for Xamarin.Formsを実際に使ってみたいと思います。

Cocos Sharp Deep Dive(2) スケジューラ

今回は、Cocos Sharp の もう一つの重要な機能であるスケジューラがどのような内部動作をしているのかを深く解析したいと思います。

スケジューラにメソッドを登録する場合には、以下のようにCCNodeScheduleメソッドを使用します。

Schedule(t => this.DetectCollisions(), 0.1f);

これは、0.1秒ごとにDetectCollisionsメソッドを実行しなさいという意味です。

まずは、Scheduleメソッドの内部動作を確認してみましょう。

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

public void Schedule (Action<float> selector, float interval)
{
	Schedule (selector, interval, CCSchedulePriority.RepeatForever, 0.0f);
}

Scheduleメソッドのオーバーロードをコールしていますので、コール先のソースコードを見てみると以下のようになっています。

public void Schedule (Action<float> selector, float interval, uint repeat, float delay)
{
	if (Scheduler != null)
		Scheduler.Schedule (selector, this, interval, repeat, delay, !IsRunning);
	else
		AddLazySchedule (selector, this, interval, repeat, delay, !IsRunning);
}

SchedulerプロパティのScheduleメソッドをコールしています。AddLazyScheduleの方も追っていくと、最終的にはScheduleメソッドをコールするようになっています。

Schedulerプロパティのを確認すると、その実体はCCScheduler.SharedSchedulerのようです。

CCScheduler Scheduler
{
	get { return CCScheduler.SharedScheduler; }
}

SharedSchedulerというプロパティ名からもわかるとおり、CCSchedulerを確認すると Singleton になっています。

さらに、Scheduleメソッドを確認すると以下のように、デリゲートと付帯情報をCCTimerのリストとして登録しています。

public void Schedule (Action<float> selector, ICCUpdatable target, float interval, uint repeat, float delay, bool paused)
{
	// 省略
	element.Timers.Add(new CCTimer(this, target, selector, interval, repeat, delay));
	// 省略
}

よって、CCNodeScheduleメソッドに登録したデリゲートは全てCCSchedulerで一括管理されるようになっていることがわかります。

では次に一括管理されたデリゲートがどのように実行されていくか見てみましょう。

デリゲートと付帯情報はCCTimerのリストとして管理されており、CCSchedulerUpdateメソッド内で、各CCTimerUpdateメソッドがコールされています。

public class CCScheduler
{
	internal void Update (float dt)
	{
		if (TimeScale != 1.0f)
		{
			dt *= TimeScale;
		}

		// 省略

		for (elt.TimerIndex = 0; elt.TimerIndex < elt.Timers.Count; ++elt.TimerIndex)
		{
			elt.CurrentTimer = elt.Timers[elt.TimerIndex];
			if(elt.CurrentTimer != null) {
				elt.CurrentTimerSalvaged = false;

				elt.CurrentTimer.Update(dt);

				elt.CurrentTimer = null;
			}
		}
		// 省略
	}
}

CCTimerUpdateメソッドを確認してみると以下のようになっています。

internal class CCTimer : ICCUpdatable
{
	public void Update(float dt)
	{
	    if (elapsed == -1)
	    {
	        elapsed = 0;
	        timesExecuted = 0;
	    }
	    else
	    {
	        if (runForever && !useDelay)
	        {
	            //standard timer usage
	            elapsed += dt;
	            if (elapsed >= Interval)
	            {
	                if (Selector != null)
	                {
	                    Selector(elapsed);
	                }
					elapsed = 0;
	            }
	        }
	        else
	        {
				// 省略
	        }
	    }
	}
}

今回のフレームの経過時間であるdtから、前回デリゲートを実行してからのトータル経過時間elapsedを計算して、与えられたIntervalを経過していたら、Selectorデリゲートを実行しているのが分かります。

これで、スケジューラがどのような内部動作をしているのかがわかりました。

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

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フレームごとの動作に細切れにされた上で実行されているのが分かります。

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

Cocos Sharp での基本的なゲームの制御の方法(5) フェードイン、フェードアウト。

今回はフェードイン、フェードアウトする方法についてご説明します。
フェードイン、フェードアウトすること自体は非常に簡単ですが、それぞれ1つずつ注意点があります。

フェードイン、フェードアウトには以下のクラスを使用します。

public CCFadeIn(float durataion);
public CCFadeOut(float durtaion);

それぞれの使用方法ですが、

フェードインを実行する。

public CCFadeIn(float durataion);

durataion で指定された所要時間でフェードインします。

注意点:CCFadeIn を実行する前に CCNodeOpacity を 0 に設定しないと、今からフェードインする CCNode が画面上に表示されてしまいます。
CCFadeInCCNodeOpacitydurataion で指定された所要時間で 0 から 255 にリニアに変化させるためです。

(例)スプライトを3秒の所要時間でフェードイン

var sprite = new CCSprite("Image.png", null);
//  Opacity を 0 に設定するのを忘れずに!
sprite.Opacity = 0;
this.AddChild(sprite);

// 3秒の所要時間でフェードイン
var fadeIn = new CCFadeIn(3f);
sprite.RunAction(fadeIn);

フェードアウトを実行する。

public CCFadeOut(float durtaion);

durataion で指定された所要時間でフェードアウトします。

注意点:CCFadeOut で画面上から見えなくなっても、Opacity = 0 で画面上にオブジェクトは残ったままですので、きちんと Remove する必要があります。


(例)スプライトを3秒の所要時間でフェードアウトし、2秒待って、スプライトを消去する

var sprite = new CCSprite("Image.png", null);

var fadeOut = new CCFadeOut(3f);
var delayTime = new CCDelayTime(2f);
var removeSelf = new CCRemoveSelf(true);

// 逐次実行アクション作成
var sequence = new CCSequence(fadeOut, delayTime, removeSelf);

sprite.RunAction(sequence);

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

Cocos Sharp での基本的なゲームの制御の方法(4) アクションにひも付けてメソッドを実行する。

はじめに

こんにちは、@hiro128_777です。

今回はアクションにひも付けてメソッドを実行する方法についてご説明します。

アクションにひも付けてメソッドを実行

アクションにひも付けてメソッドを実行するというのは具体的には、
・オブジェクトのアクションに合わせて効果音を鳴らす。
・オブジェクトのアクションにひも付けて何らかのフラグを立てる。
・オブジェクトが弾を発射するなどの、CCMoveTo CCScaleBy CCRotateBy 等では表現できない複合的なアクション。
というような場合に使います。

このような場合、以下の4つのうちいずれかを利用します。

public CCCallFunc(Action selector);
public CCCallFuncN(Action<CCNode> selector);
public CCCallFuncND(Action<CCNode, object> selector, object d);
public CCCallFuncO(Action<object> selector, object pObject);

では、それぞれの使い方を見ていきましょう。

CCCallFunc … 引数なしの callback を実行する。

public CCCallFunc(Action selector);

(例)ステージ1クリアのフラグを立てる

var isFirstStageClearToTrue = new CCCallFunc(() =>
{
	this.IsFirstStageClear = true;
}

これは結構使います。特に前回の CCSequence と組み合わせて使うことが多いです。

CCCallFuncN … アクションが実行される CCNode を引数とした callback を実行する。

public CCCallFuncN(Action<CCNode> selector);

(例)ボスを倒したアクションを発動し、BGMを停止し、ボスを倒したSEを鳴らす。

var bossDefeated = new CCCallFuncN(node =>
{
	CCMoveTo bossDefeatedAction = new CCMoveTo(3f, new CCPoint(node.PositionX, node.PositionY - 1000f));
	CCSimpleAudioEngine.SharedEngine.StopBackgroundMusic();
	CCSimpleAudioEngine.SharedEngine.PlayEffect("Audio/SE/BossDefeated.xnb");
	node.RunAction(bossDefeatedAction);
});

これも結構使います。同様に前回の CCSequence と組み合わせて使うことが多いです。

CCCallFuncND … アクションが実行される CCNode および、任意のオブジェクトを引数とした callback を実行する。

public CCCallFuncND(Action<CCNode, object> selector, object d);

(例)ボスを倒したアクションを発動し、BGMを停止し、bossDefeatedSeFile で指定されたSEを鳴らす。

var bossDefeated = new CCCallFuncND((node, fileName) =>
{
	CCMoveTo bossDefeatedAction = new CCMoveTo(3f, new CCPoint(node.PositionX, node.PositionY - 1000f));
	CCSimpleAudioEngine.SharedEngine.StopBackgroundMusic();
	CCSimpleAudioEngine.SharedEngine.PlayEffect((string)fileName);
	node.RunAction(bossDefeatedAction);
},
bossDefeatedSeFile
);

実際にはこれは使ったことがないですね(笑)

CCCallFuncO… 指定した pObject を引数とする callback を実行する。

var CCCallFuncO(Action<object> selector, object pObject);

(例)う~ん、CCCallFuncO を効果的に使用できる状況が思いつきません!

正直 CCCallFuncO は、使わないですね…

CCCallFuncCCCallFuncN を使いこなせればほぼ思い通りの動作ができると思います。

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