個人的なメモ

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

Xamarin + Cocos Sharp で iOS, Android 対応のゲームを開発する手順 (10) サンプルゲームの概要

ここまで、Cocos Sharp の基本をご説明してきましたが、ここから実際にサンプルゲームを作成していき、より実戦に即したゲーム作成についてご説明していきます。

ゲームの概要

ゲームプレイヤーは敵キャラを率いる超知性体となりの進撃を防ぎます。

画面にタッチで敵キャラが配置され自機への攻撃を行ないます。

自機は自動で動き、攻撃を行ない配置された敵キャラを攻撃、破壊します。

自機が3機破壊された時点での進入距離の短さがスコアとなります。

f:id:hiro128:20160809164945p:plain

これから、このサンプルゲームを作成しながら、Cocos Sharp の実践的な使い方をご説明していきます。

では、これまでに作成したサンプルコードをゲームの概要に適するように変更していきます。

まずは、CocosSharpGameSample.iOS プロジェクトの AppDelegate.cs です。

加速度センサーに関する部分を削除します。

以下を削除します。

// 加速度センサー
private CMMotionManager cMMotionManager;
// ロックキー
private object accelerometerSyncLock = new object();

FinishedLaunching から加速度センサーに関する部分を削除します。

public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
	// ここから削除
	// 加速度センサーのセットアップ
	this.cMMotionManager = new CMMotionManager();
	// センサーの更新間隔
	cMMotionManager.AccelerometerUpdateInterval = 0.1d;
	// センサー更新時のハンドラの設定
	cMMotionManager.StartAccelerometerUpdates(NSOperationQueue.CurrentQueue, (data, error) =>
	{
		// センサーの値を書き込む
		lock (this.accelerometerSyncLock)
		{
			// 横向きなのでX,Yを入れ替え
			// X,Yの感度を調整するためセンサーのX,Y軸の取得値を調整
			AccelerometerInfo.Default.AccelerationX = data.Acceleration.Y * 8d;
			// 角度のオフセット
			double angleY = Math.Atan2(data.Acceleration.Z, data.Acceleration.X);
			AccelerometerInfo.Default.AccelerationY = Math.Sin(angleY - 30.6d) * 6d;
			AccelerometerInfo.Default.AccelerationZ = data.Acceleration.Z;
		}
	});
	// ここまで削除する

	// ゲームをを起動する。
	var application = new CCApplication();
	application.ApplicationDelegate = new SampleGameApplicationDelegate();
	application.StartGame();
	return true;
}

画面の向きを Portrait に固定に変更します。

// 画面の向きを Portrait に固定
[Export("application:supportedInterfaceOrientationsForWindow:")] 
public UIInterfaceOrientationMask GetSupportedInterfaceOrientations(UIApplication application, IntPtr forWindow)
{
	return UIInterfaceOrientationMask.Portrait;
}

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

using System;
using Foundation;
using UIKit;
using CoreMotion;
using CocosSharp;
using CocosSharpGameSample.Core;

namespace CocosSharpGameSample.iOS
{
	[Register("AppDelegate")]
	public partial class AppDelegate : UIApplicationDelegate
	{

		public override bool FinishedLaunching(UIApplication app, NSDictionary options)
		{
			// ゲームをを起動する。
			var application = new CCApplication();
			application.ApplicationDelegate = new SampleGameApplicationDelegate();
			application.StartGame();
			return true;
		}

		// 画面の向きを Portrait に固定
		[Export("application:supportedInterfaceOrientationsForWindow:")] 
		public UIInterfaceOrientationMask GetSupportedInterfaceOrientations(UIApplication application, IntPtr forWindow)
		{
			return UIInterfaceOrientationMask.Portrait;
		}

	}
}


自機の画像を縦画面用に差換えます。

Resources/Images/Character/Player/Player.png

f:id:hiro128:20160809164837p:plain

※自機の画像はハムコロ様の素材を利用させていただきました。

f:id:hiro128:20160809164848p:plain

・制作者:ハムコロ様
・配付元:http://homepage2.nifty.com/hamcorossam

タイトルとゲームの背景画像を宇宙空間のものに差換えます。

Resources/Images/Background/GameBackground.png

f:id:hiro128:20160809164859p:plain

f:id:hiro128:20160809164906p:plain


次にCocosSharpGameSample.Android プロジェクトの MainActivity.cs を変更します。


加速度センサーに関する部分を削除します。

ISensorEventListener を削除します。

public class MainActivity : AndroidGameActivity/* この部分削除 , ISensorEventListener*/

以下を削除します。

// センサーマネージャ
private SensorManager sensorManager;
// ロックキー
private object accelerometerSyncLock = new object();

OnCreate からセンサーに関わる部分を削除します。

protected override void OnCreate(Bundle bundle)
{
	base.OnCreate(bundle);
	// ここから削除
	// センサーのセットアップ
	this.sensorManager = (SensorManager)GetSystemService(Context.SensorService);
	// ここまで削除
	// ゲームを起動する。
	var application = new CCApplication();
	application.ApplicationDelegate = new SampleGameApplicationDelegate();
	SetContentView(application.AndroidContentView);
	application.StartGame();
}

OnAccuracyChanged, OnSensorChanged, OnResume, OnPauseを削除します。

// センサーの精度が変更されたときのハンドラ
public void OnAccuracyChanged(Sensor sensor, SensorStatus accuracy)
{
	// 何もしない
}

// センサーの値が変更されたときのハンドラ
public void OnSensorChanged(SensorEvent e)
{
	lock (this.accelerometerSyncLock)
	{
		// 横向きなのでX,Yを入れ替え
		// X,Yの感度を調整するためセンサーのX,Y軸の取得値を調整
		AccelerometerInfo.Default.AccelerationX = e.Values[1] * 1.5f;
		// 角度のオフセット
		double angleY = Math.Atan2(e.Values[2], e.Values[0]);
		AccelerometerInfo.Default.AccelerationY = Math.Sin(angleY - 29.1d) * 12f;
		AccelerometerInfo.Default.AccelerationZ = e.Values[2];
	}
}

protected override void OnResume()
{
	base.OnResume();
	this.sensorManager.RegisterListener(this, sensorManager.GetDefaultSensor(SensorType.Accelerometer), SensorDelay.Game);
}

protected override void OnPause()
{
	base.OnPause();
	this.sensorManager.UnregisterListener(this);
}

画面の向きを Portrait に固定に変更します。

// 画面の向きを Portrait に固定
public override ScreenOrientation RequestedOrientation
{
	get
	{
		return ScreenOrientation.Portrait;
	}
	set
	{
		base.RequestedOrientation = ScreenOrientation.Portrait;
	}
}

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

using System;
using Android.App;
using Android.Content;
using Android.Content.PM;
using Android.Hardware;
using Android.OS;
using CocosSharp;
using CocosSharpGameSample.Core;
using Microsoft.Xna.Framework;

namespace CocosSharpGameSample.Android
{
	[Activity(Label = "CocosSharpGameSample.Android"
		, MainLauncher = true
		, Icon = "@drawable/icon"
		, AlwaysRetainTaskState = true
		, LaunchMode = LaunchMode.SingleInstance
		, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.Keyboard | ConfigChanges.KeyboardHidden
	)]
	public class MainActivity : AndroidGameActivity
	{

		protected override void OnCreate(Bundle bundle)
		{
			base.OnCreate(bundle);
			// ゲームを起動する。
			var application = new CCApplication();
			application.ApplicationDelegate = new SampleGameApplicationDelegate();
			SetContentView(application.AndroidContentView);
			application.StartGame();
		}

		// 画面の向きを Portrait に固定
		public override ScreenOrientation RequestedOrientation
		{
			get
			{
				return ScreenOrientation.Portrait;
			}
			set
			{
				base.RequestedOrientation = ScreenOrientation.Portrait;
			}
		}

	}
}

自機の画像を縦画面用に差換えます。

Assets/Images/Character/Player/Player.png

f:id:hiro128:20160809165052p:plain

※自機の画像はハムコロ様の素材を利用させていただきました。

f:id:hiro128:20160809165059p:plain

・制作者:ハムコロ様
・配付元:http://homepage2.nifty.com/hamcorossam

タイトルとゲームの背景画像を宇宙空間のものに差換えます。

Assets/Images/Background/GameBackground.png

f:id:hiro128:20160809165109p:plain

f:id:hiro128:20160809165116p:plain

次に、CocosSharpGameSample.Core プロジェクト、CrossCuttingConcerns/GameSettings.cs の画面サイズ設定を縦画面対応に変更します。

// 画面のサイズ
public const float ScreenWidth = 720f;
public const float ScreenHeight = 1280f;

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

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace CocosSharpGameSample.Core
{
	public class GameSettings
	{
		// 自機のサイズ
		public const float PlayerWidth = 32f;
		public const float PlayerHeight = 32f;

		// 画面のサイズ
		public const float ScreenWidth = 720f;
		public const float ScreenHeight = 1280f;

		// 加速度センサーの取得値の増幅倍率
		public const float AccelerationRatio = 3f;
	}
}

次に、CocosSharpGameSample.Core プロジェクト、Layers/GameLayer.cs からセンサーに関わる部分を削除します。

以下を削除します。

private CCLabel accelerationX;
private CCLabel accelerationY;
private CCLabel accelerationZ;

AddedToScene からセンサーに関わる部分を削除します。

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.accelerationX = new CCLabel(String.Empty, String.Empty, 40f, CCLabelFormat.SystemFont);
	this.accelerationX.HorizontalAlignment = CCTextAlignment.Left;
	this.accelerationX.VerticalAlignment = CCVerticalTextAlignment.Top;
	this.accelerationX.Color = CCColor3B.White;
	this.accelerationX.PositionX = 250f;
	this.accelerationX.PositionY = 120f;
	AddChild(this.accelerationX);

	this.accelerationY = new CCLabel(String.Empty, String.Empty, 40f, CCLabelFormat.SystemFont);
	this.accelerationY.HorizontalAlignment = CCTextAlignment.Left;
	this.accelerationY.VerticalAlignment = CCVerticalTextAlignment.Top;
	this.accelerationY.Color = CCColor3B.White;
	this.accelerationY.PositionX = 250f;
	this.accelerationY.PositionY = 80f;
	AddChild(this.accelerationY);

	this.accelerationZ = new CCLabel(String.Empty, String.Empty, 40f, CCLabelFormat.SystemFont);
	this.accelerationZ.HorizontalAlignment = CCTextAlignment.Left;
	this.accelerationZ.VerticalAlignment = CCVerticalTextAlignment.Top;
	this.accelerationZ.Color = CCColor3B.White;
	this.accelerationZ.PositionX = 250f;
	this.accelerationZ.PositionY = 40f;
	AddChild(this.accelerationZ);
	// ここまで削除

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

	this.StartScheduling();
}

StartScheduling からセンサーに関わる部分を削除します。

private void StartScheduling()
{
	// ここから削除
	this.Schedule(t => this.UpdateAccelerationLabels(), 0.2f);
	// ここまで削除
	this.Schedule(t => this.UpdatePlayer(), 0.02f);
}

UpdateAccelerationLabels を削除します。

// ここから削除
private void UpdateAccelerationLabels()
{
	this.accelerationX.Text = "X : " + (AccelerometerInfo.Default.AccelerationX >= 0 ? "+" : String.Empty) + AccelerometerInfo.Default.AccelerationX.ToString("0.00000000");
	this.accelerationY.Text = "Y : " + (AccelerometerInfo.Default.AccelerationY >= 0 ? "+" : String.Empty) + AccelerometerInfo.Default.AccelerationY.ToString("0.00000000");
	this.accelerationZ.Text = "Z : " + (AccelerometerInfo.Default.AccelerationZ >= 0 ? "+" : String.Empty) + AccelerometerInfo.Default.AccelerationZ.ToString("0.00000000");
}
// ここまで削除

UpdatePlayer からセンサーに関わる部分を削除します。

private void UpdatePlayer()
{
	// ここから削除
	float accelerationX = (float)AccelerometerInfo.Default.AccelerationX * GameSettings.AccelerationRatio;
	float accelerationY = -(float)AccelerometerInfo.Default.AccelerationY * GameSettings.AccelerationRatio;
	// ここまで削除

	// ここから変更
	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;
	}
}

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

using System;
using System.Diagnostics;
using CocosSharp;

namespace CocosSharpGameSample.Core
{
	public class GameLayer : LayerBase
	{

		private CCNode player;

		public GameLayer()
			: base()
		{
		}

		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);
		}

		private void AddPlayer()
		{
			this.player = new CCSprite("/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()
		{
			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;
			}
		}

	}
}

次に、CocosSharpGameSample.Core プロジェクト、Layers/TitleLayer.cs のタイトル背景画像を差換えます。

protected override void AddedToScene()
{
	base.AddedToScene();
	// タイトル画像を配置
	var title = new CCSprite("/Resources/Images/Background/GameBackground.png", null);// 画像を GameBackground.png に差換えます。
	title.Position = new CCPoint(this.ContentSize.Center.X, this.ContentSize.Center.Y);
	this.AddChild(title);

	// スタートボタンを配置
	var startButton = new CCSprite("/Resources/Images/Button/ButtonStart.png", null);
	startButton.Position = new CCPoint(this.ContentSize.Center.X, 100f);

	// 後から参照できるようにタグを設定
	startButton.Tag = (int)NodeTag.StartButton;
	this.AddChild(startButton);

	// ボタンタップ検出用のイベントリスナー作成
	var eventListener = new CCEventListenerTouchOneByOne();
	eventListener.OnTouchBegan = this.EventListener_TouchBegan;
	eventListener.OnTouchEnded = this.EventListener_TouchEnded;
	this.AddEventListener(eventListener, this);
}

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

using CocosSharp;

namespace CocosSharpGameSample.Core
{
	public class TitleLayer : LayerBase
	{

		public TitleLayer()
			: base()
		{
		}

		protected override void AddedToScene()
		{
			base.AddedToScene();
			// タイトル画像を配置
			var title = new CCSprite("/Resources/Images/Background/GameBackground.png", null);
			title.Position = new CCPoint(this.ContentSize.Center.X, this.ContentSize.Center.Y);
			this.AddChild(title);

			// スタートボタンを配置
			var startButton = new CCSprite("/Resources/Images/Button/ButtonStart.png", null);
			startButton.Position = new CCPoint(this.ContentSize.Center.X, 100f);

			// 後から参照できるようにタグを設定
			startButton.Tag = (int)NodeTag.StartButton;
			this.AddChild(startButton);

			// ボタンタップ検出用のイベントリスナー作成
			var eventListener = new CCEventListenerTouchOneByOne();
			eventListener.OnTouchBegan = this.EventListener_TouchBegan;
			eventListener.OnTouchEnded = this.EventListener_TouchEnded;
			this.AddEventListener(eventListener, this);
		}

		private bool EventListener_TouchBegan(CCTouch touch, CCEvent e)
		{
			// OnTouchBegan で true を返却しないと、OnTouchEndedが発動しない。
			return true;
		}

		private void EventListener_TouchEnded(CCTouch touch, CCEvent e)
		{
			// タッチした座標でボタンが押されたかを判定
			CCNode startButton = this.GetChildByTag((int)NodeTag.StartButton);
			if (startButton.BoundingBox.ContainsPoint(touch.Location))
			{
				// 移動先のシーンを作成
				var newScene = new CCScene(this.Window);
				var gameLayer = new GameLayer();
				newScene.AddChild(gameLayer);

				// シーン切り替え時の効果を設定
				CCTransitionScene cCTransitionScene = new CCTransitionFade(1.0f, newScene);

				// ゲーム画面へシーン切り替え
				this.Director.ReplaceScene(cCTransitionScene);
			}
		}

	}
}

これで、縦画面のゲーム作成の準備が整いました。

今回は、ここまでです。