個人的なメモ

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

Xamarin + Cocos Sharp で iOS, Android 対応のゲームを開発する手順 (14) 当たり判定② 敵に当たった場合のアクションの作成

今回は自機が敵キャラに当たった時のアクションを作成します。

具体的には、以下の処理を行ないます。
1. 自機に爆発するアニメーションを設定する。
2. 自機を画面上から削除する。

まず、自機が爆発するアニメーションを作成します。

アニメーションを作成する場合、それぞれのコマの画像を準備します。

CocosSharpGameSample.iOS プロジェクトに爆発する画像を追加します。

f:id:hiro128:20160920210550p:plain

/Resources/Images/Character/Explosion/Explosion00.png ~ Explosion04.png

f:id:hiro128:20160920210632p:plainf:id:hiro128:20160920210633p:plainf:id:hiro128:20160920210634p:plainf:id:hiro128:20160920210635p:plainf:id:hiro128:20160920210636p:plain

CocosSharpGameSample.Android プロジェクトに爆発する画像を追加します。

f:id:hiro128:20160920210559p:plain

/Assets/Images/Character/Explosion/Explosion00.png ~ Explosion04.png

CocosSharpGameSample.Core プロジェクトに処理を作成します。

Layers/GameLayer.cs に処理を追加します。

まずは、アニメーションの状態を把握できるフィールドを追加します。

public class GameLayer : LayerBase
{

	private ExtendedCCSprite player;
	private bool enableCollisionDetection = true;
	private CCActionState playerShotDownActionState; // <-追加

次に爆発のアニメーションを作成します。

アニメーションは以下の要領で作成できます。
1. 各コマの画像の CCSprite を作成
2. CCSprite から CCSpriteFrame を作成
3. List としてアニメーションを構築
4. ループ回数、所要時間を設定し、アニメーションを適用させたい CCSprite に設定し実行
5. アニメーションの状態を返却

この手順をコードにすると以下の通りになります。

protected CCActionState PerformExplosionAnimation(CCSprite sprite)
{
	// 各コマの画像の CCSprite を作成
	var sprite00 = new CCSprite("/Resources/Images/Character/Explosion/Explosion00.png", null);
	var sprite01 = new CCSprite("/Resources/Images/Character/Explosion/Explosion01.png", null);
	var sprite02 = new CCSprite("/Resources/Images/Character/Explosion/Explosion02.png", null);
	var sprite03 = new CCSprite("/Resources/Images/Character/Explosion/Explosion03.png", null);
	var sprite04 = new CCSprite("/Resources/Images/Character/Explosion/Explosion04.png", null);

	// CCSprite から CCSpriteFrame を作成
	var frame00 = new CCSpriteFrame(sprite01.Texture, sprite01.TextureRectInPixels);
	var frame01 = new CCSpriteFrame(sprite02.Texture, sprite02.TextureRectInPixels);
	var frame02 = new CCSpriteFrame(sprite03.Texture, sprite03.TextureRectInPixels);
	var frame03 = new CCSpriteFrame(sprite04.Texture, sprite04.TextureRectInPixels);
	var frame04 = new CCSpriteFrame(sprite03.Texture, sprite03.TextureRectInPixels);
	var frame05 = new CCSpriteFrame(sprite02.Texture, sprite02.TextureRectInPixels);
	var frame06 = new CCSpriteFrame(sprite01.Texture, sprite01.TextureRectInPixels);
	var frame07 = new CCSpriteFrame(sprite00.Texture, sprite00.TextureRectInPixels);

	// List<CCSpriteFrame> としてアニメーションを構築
	var frames = new List<CCSpriteFrame>();
	frames.Add(frame00);
	frames.Add(frame01);
	frames.Add(frame02);
	frames.Add(frame03);
	frames.Add(frame03);
	frames.Add(frame04);
	frames.Add(frame05);
	frames.Add(frame06);
	frames.Add(frame07);

	// ループ回数、所要時間を設定し、アニメーションを適用させたい CCSprite に設定し実行
	var animation = new CCAnimation(frames, 0.15f);
	animation.Loops = 1;
	var animate = new CCAnimate(animation);
	var state = sprite.RunAction(animate);

	// アニメーションの状態を返却
	return state;
}

衝突を検出した場合、爆発のアニメーションを実行するように、当たり判定検出のメソッドに下記の爆発のアニメーションを実行処理を追加します。

追加する処理

this.playerShotDownActionState = this.PerformExplosionAnimation(this.player);

追加場所は下から6行目です。

private void DetectCollisions()
{
	// プレーヤーがやられてしまった後など、プレーヤーが画面上に居ない場合
	if (this.player == null)
	{
		return;
	}
	// ゲーム開始直後、リスタート直後、ステージクリア時など、当たり判定をキャンセルする場合
	if (this.EnableCollisionDetection == false)
	{
		return;
	}
	// 当たり判定領域をプレーヤー中心部分だけにする
	var rectPlayer = player.BoundingBox;
	var rectTrimedPlayer = new CCRect(
									rectPlayer.MidX - (GameSettings.PlayerWidth / 6),
									rectPlayer.MidY - (GameSettings.PlayerHeight / 3),
									GameSettings.PlayerWidth / 3,
									GameSettings.PlayerHeight / 3);
	//// 実際の当たり判定領域の確認用コード(コメントを外すと実際の当たり判定領域を可視化できます。)
	//var acutualRect = new CCSprite();
	//acutualRect.TextureRectInPixels = rectTrimedPlayer;
	//acutualRect.Color = new CCColor3B(0, 255, 0);
	//acutualRect.Position = rectTrimedPlayer.LowerLeft;
	//acutualRect.Tag = 100000000;
	//this.RemoveChildByTag(100000000);
	//this.AddChild(acutualRect);

	// 当たり判定実施
	var enemies = new List<CCNode>();
	enemies.AddRange(this.GetChildrenByTag((int)NodeTag.Enemy));
	if (enemies.Count > 0)
	{
		foreach (var node in enemies)
		{
			// 同一エリア以外は当たり判定スキップ
			if (this.player.ScreenArea != ((ExtendedCCSprite)node).ScreenArea)
			{
				continue;
			}
			var rectEnemy = node.BoundingBox;
			if (rectTrimedPlayer.IntersectsRect(rectEnemy) == true)
			{
				Debug.WriteLine("敵キャラに当たった!");
				this.EnableCollisionDetection = false;
				this.playerShotDownActionState = this.PerformExplosionAnimation(this.player); // 爆発のアニメーションを実行処理を追加 <-変更箇所
				break;
			}
		}
	}
}

爆発のアニメーションが終了した後、自機を画面上から削除します。

private void RemovePlayer()
{
	if (this.player == null)
	{
		return;
	}
	if (this.playerShotDownActionState == null)
	{
		return;
	}
	if (this.playerShotDownActionState.IsDone == true)
	{
		this.RemoveChild(this.player);
		Debug.WriteLine("プレーヤー削除!");
		this.player = null;
	}
}

自機削除を0.5秒に1回行なうようスケジューラに登録します。

private void StartScheduling()
{
	this.Schedule(t => this.UpdatePlayer(), 0.02f);
	this.Schedule(t => this.RemoveOffScreenNodes(), 1.0f);
	this.Schedule(t => this.DetectCollisions(), 0.1f);
	this.Schedule(t => this.RemovePlayer(), 0.5f);// <-自機削除をスケジューラに登録
}

既存のメソッドで自機を削除することによる、null参照エラーが発生する部分に対策を入れます。

private void UpdatePlayer()
{
	if (this.player == null)
	{
		return;
	}
private void DetectCollisions()
{
	// プレーヤーがやられてしまった後など、プレーヤーが画面上に居ない場合
	if (this.player == null)
	{
		return;
	}


以上をまとめると、CocosSharpGameSample.Core プロジェクトの Layers/GameLayer.cs は以下の通りになります。

using System.Collections.Generic;
using System.Diagnostics;
using CocosSharp;

namespace CocosSharpGameSample.Core
{
	public class GameLayer : LayerBase
	{

		private ExtendedCCSprite player;
		private bool enableCollisionDetection = true;
		private CCActionState playerShotDownActionState;

		public bool EnableCollisionDetection
		{
			get { return this.enableCollisionDetection; }
			set { this.enableCollisionDetection = value; }
		}

		public GameLayer()
			: base()
		{
			var listener = new CCEventListenerTouchOneByOne();
			listener.OnTouchBegan = this.CCEventListener_TouchBegan;
			this.AddEventListener(listener, this);
		}

		protected bool CCEventListener_TouchBegan(CCTouch touch, CCEvent touchEvent)
		{
			Debug.WriteLine(this.ChildrenCount);
			// タッチした場所に敵キャラ配置
			var touchedLocation = touch.Location;
			// 画面の上部25%なら敵キャラ配置
			if (touchedLocation.Y > GameSettings.ScreenHeight * 3 / 4)
			{
				var addingEnemy = new ExtendedCCSprite("/Resources/Images/Character/Enemy/Enemy001.png", null);
				addingEnemy.Position = touchedLocation;
				addingEnemy.Tag = (int)NodeTag.Enemy;
				this.ApplyGoStraightFromTopDownAction(addingEnemy);
				this.AddChild(addingEnemy);
				return true;
			}
			return false;
		}

		// Y軸のみの移動で画面外まで行くアクションを適用
		public void ApplyGoStraightFromTopDownAction(CCNode enemy)
		{
			var destinationPoint = new CCPoint(enemy.PositionX, -100);
			var action = new CCMoveTo(8.0f, destinationPoint);
			enemy.RunAction(action);
		}

		// 画面外のNodeを除去
		public void RemoveOffScreenNodes()
		{
			foreach (var node in this.Children)
			{
				if (node.Position.Y < 0)
				{
					this.RemoveChild(node);
				}
			}
		}

		protected override void AddedToScene()
		{
			base.AddedToScene();
			// ゲーム画面の背景画像を配置
			var gameBackground = new CCSprite("/Resources/Images/Background/GameBackground.png", null);
			gameBackground.Position = new CCPoint(this.ContentSize.Center.X, this.ContentSize.Center.Y);
			AddChild(gameBackground);

			// 自機を配置
			this.AddPlayer();
			this.StartScheduling();
		}

		private void StartScheduling()
		{
			this.Schedule(t => this.UpdatePlayer(), 0.02f);
			this.Schedule(t => this.RemoveOffScreenNodes(), 1.0f);
			this.Schedule(t => this.DetectCollisions(), 0.1f);
			this.Schedule(t => this.RemovePlayer(), 0.5f);
		}

		private void AddPlayer()
		{
			this.player = new ExtendedCCSprite("/Resources/Images/Character/Player/Player.png", null);
			this.player.Position = new CCPoint(this.ContentSize.Center.X, this.ContentSize.Center.Y);
			this.AddChild(this.player);
		}

		private void UpdatePlayer()
		{
			if (this.player == null)
			{
				return;
			}

			this.player.PositionX = this.player.PositionX;
			this.player.PositionY = this.player.PositionY;

			// 画面からはみ出ないように、位置の値の上限、下限を設定します。
			// 左
			if (this.player.PositionX < 0 + GameSettings.PlayerWidth)
			{
				this.player.PositionX = 0 + GameSettings.PlayerWidth;
			}
			// 右
			if (this.player.PositionX > GameSettings.ScreenWidth - GameSettings.PlayerWidth)
			{
				this.player.PositionX = GameSettings.ScreenWidth - GameSettings.PlayerWidth;
			}
			// 下
			if (this.player.PositionY < 0 + GameSettings.PlayerHeight)
			{
				this.player.PositionY = 0 + GameSettings.PlayerHeight;
			}
			// 上
			if (this.player.PositionY > GameSettings.ScreenHeight - GameSettings.PlayerHeight)
			{
				this.player.PositionY = GameSettings.ScreenHeight - GameSettings.PlayerHeight;
			}
		}

		private void DetectCollisions()
		{
			// プレーヤーがやられてしまった後など、プレーヤーが画面上に居ない場合
			if (this.player == null)
			{
				return;
			}
			// ゲーム開始直後、リスタート直後、ステージクリア時など、当たり判定をキャンセルする場合
			if (this.EnableCollisionDetection == false)
			{
				return;
			}
			// 当たり判定領域をプレーヤー中心部分だけにする
			var rectPlayer = player.BoundingBox;
			var rectTrimedPlayer = new CCRect(
											rectPlayer.MidX - (GameSettings.PlayerWidth / 6),
											rectPlayer.MidY - (GameSettings.PlayerHeight / 3),
											GameSettings.PlayerWidth / 3,
											GameSettings.PlayerHeight / 3);
			//// 実際の当たり判定領域の確認用コード(コメントを外すと実際の当たり判定領域を可視化できます。)
			//var acutualRect = new CCSprite();
			//acutualRect.TextureRectInPixels = rectTrimedPlayer;
			//acutualRect.Color = new CCColor3B(0, 255, 0);
			//acutualRect.Position = rectTrimedPlayer.LowerLeft;
			//acutualRect.Tag = 100000000;
			//this.RemoveChildByTag(100000000);
			//this.AddChild(acutualRect);

			// 当たり判定実施
			var enemies = new List<CCNode>();
			enemies.AddRange(this.GetChildrenByTag((int)NodeTag.Enemy));
			if (enemies.Count > 0)
			{
				foreach (var node in enemies)
				{
					// 同一エリア以外は当たり判定スキップ
					if (this.player.ScreenArea != ((ExtendedCCSprite)node).ScreenArea)
					{
						continue;
					}
					var rectEnemy = node.BoundingBox;
					if (rectTrimedPlayer.IntersectsRect(rectEnemy) == true)
					{
						Debug.WriteLine("敵キャラに当たった!");
						this.EnableCollisionDetection = false;
						this.playerShotDownActionState = this.PerformExplosionAnimation(this.player);
						break;
					}
				}
			}
		}

		private void RemovePlayer()
		{
			if (this.player == null)
			{
				return;
			}
			if (this.playerShotDownActionState == null)
			{
				return;
			}
			if (this.playerShotDownActionState.IsDone == true)
			{
				this.RemoveChild(this.player);
				Debug.WriteLine("プレーヤー削除!");
				this.player = null;
			}
		}

		protected CCActionState PerformExplosionAnimation(CCSprite sprite)
		{
			// 各コマの画像の CCSprite を作成
			var sprite00 = new CCSprite("/Resources/Images/Character/Explosion/Explosion00.png", null);
			var sprite01 = new CCSprite("/Resources/Images/Character/Explosion/Explosion01.png", null);
			var sprite02 = new CCSprite("/Resources/Images/Character/Explosion/Explosion02.png", null);
			var sprite03 = new CCSprite("/Resources/Images/Character/Explosion/Explosion03.png", null);
			var sprite04 = new CCSprite("/Resources/Images/Character/Explosion/Explosion04.png", null);

			// CCSprite から CCSpriteFrame を作成
			var frame00 = new CCSpriteFrame(sprite01.Texture, sprite01.TextureRectInPixels);
			var frame01 = new CCSpriteFrame(sprite02.Texture, sprite02.TextureRectInPixels);
			var frame02 = new CCSpriteFrame(sprite03.Texture, sprite03.TextureRectInPixels);
			var frame03 = new CCSpriteFrame(sprite04.Texture, sprite04.TextureRectInPixels);
			var frame04 = new CCSpriteFrame(sprite03.Texture, sprite03.TextureRectInPixels);
			var frame05 = new CCSpriteFrame(sprite02.Texture, sprite02.TextureRectInPixels);
			var frame06 = new CCSpriteFrame(sprite01.Texture, sprite01.TextureRectInPixels);
			var frame07 = new CCSpriteFrame(sprite00.Texture, sprite00.TextureRectInPixels);

			// List<CCSpriteFrame> としてアニメーションを構築
			var frames = new List<CCSpriteFrame>();
			frames.Add(frame00);
			frames.Add(frame01);
			frames.Add(frame02);
			frames.Add(frame03);
			frames.Add(frame03);
			frames.Add(frame04);
			frames.Add(frame05);
			frames.Add(frame06);
			frames.Add(frame07);

			// ループ回数、所要時間を設定し、アニメーションを適用させたい CCSprite に設定し実行
			var animation = new CCAnimation(frames, 0.15f);
			animation.Loops = 1;
			var animate = new CCAnimate(animation);
			var state = sprite.RunAction(animate);

			// アニメーションの状態を返却
			return state;
		}

	}
}

以上で、自機が敵キャラにぶつかった時、爆発する動きが完成しました。

f:id:hiro128:20160920210710g:plain

今回は、ここまでです。

何かご質問などございましたらコメント頂ければご回答させていただきますのでお気軽にどうぞ!

また、間違い等ございましたら、ご指摘頂ければ幸いです。