個人的なメモ 〜Cocos Sharp 情報を中心に‥

Tomohiro Suzuki @hiro128_777 のブログです。Cocos Sharp の事を中心に書いています。 Microsoft MVP for Visual Studio and Development Technologies 2017- 本ブログと所属組織の公式見解は関係ございません。

Xamarin + Cocos Sharp で iOS, Android 対応のゲームを開発する手順 (2) Cocos Sharp をインストールする (Cocos Sharp 1.7 以降対応版)

はじめに

こんにちは、@hiro128_777です。
今回は、Cocos Sharp 1.7 以降に対応した Cocos Sharp をインストールする方法についてご説明します。

※これから Cocos Sharp をはじめる方は 1.7 以降をご利用ください。
※テンプレートを使用すれば手動でパッケージのインストールは不要ですが、今回はあえて手動でインストールしています。


Cocos Sharp 1.6.2 をご利用の場合以下をご覧下さい。
hiro128.hatenablog.jp

インストール方法

ソリューションを右クリックし、ソリューションの NuGet パッケージの管理をクリックします。

f:id:hiro128:20170127113159p:plain

Cocos Sharp を検索し選択すると、プロジェクトの選択画面が開きますので、
3つすべてにチェックが入っていることを確認し、インストールボタンをクリックします。
※バージョンが1.7.1以上であることを確認してください。

もし、 CocosSharpGameSample.Core が表示されない場合、本エントリ最後の[補足]を確認してください。

f:id:hiro128:20170127113208p:plain

下記ウインドウが表示されたら、OKボタンをクリックします。

f:id:hiro128:20170127113520p:plain

インストールが完了したら、インストール済みのパッケージに Cocos Sharp が表示されます。
※バージョンが1.7.1以上であることを確認してください。

f:id:hiro128:20170127113441p:plain

また、各プロジェクトの参照設定を見ると、Cocos Sharp のライブラリが追加されています。

f:id:hiro128:20170127113531p:plain

補足

プロジェクトの選択画面で CocosSharpGameSample.Core が表示されない場合、
または、Cocos Sharp のインストールが失敗する場合は、PCL ターゲットが間違っています。

修正するには、プロジェクトのプロパティを開きライブラリタブの変更ボタンをクリックします。

f:id:hiro128:20160303172853p:plain

ターゲットの変更画面が開きますので、下の画像のように、
.NET Framework 4.5, Windows 8, Xamarin.Android, Xamarin.iOS, Xamarin.iOS (Classic) にチェックを入れます。

f:id:hiro128:20160303172843p:plain

これで、プロジェクトの選択画面で CocosSharpGameSample.Core が表示されます。

今回はここまでです。

Cocos Sharp 入門 (3) DesignResolution について

はじめに

こんにちは、@hiro128_777です。
今回は、Cocos Sharp でゲームを開発する上で大事な概念であるDesignResolutionについてご説明します。

モバイルデバイスでのゲームの解像度の問題

モバイルデバイスで2Dゲームを開発する場合、問題になるのがゲームの解像度です。
PCやゲーム専用機の場合、解像度は機種によって特定の解像度だけを考慮すれば問題ありませんでしたが、モバイルデバイスでのゲーム開発の場合、多種多様の解像度が違うデバイス上で同じようにゲーム画面を表示する必要があります。

でも安心してください! Cocos Sharp には、DesignResolutionと呼ばれる、デバイスのディスプレイ上の物理的なピクセル数に関係なく、ゲーム内のオブジェクトのピクセルサイズを標準化するための便利な方法が準備されています。

DesignResolution

DesignResolutionで設定するものは以下の二つです。

DesignResolution …ゲーム内の仮想解像度。縦横の仮想ピクセル数を指定します。
 ※全てのゲームの処理はこのDesignResolutionを基準にして実行されます。

ResolutionPolicy …ゲームウィンドウを希望の解像度に調整する方法を指定します。

具体的なコードとしては以下のようになります。

using System;
using System.Collections.Generic;
using CocosSharp;
using CocosDenshion;

namespace CocosSharpSample.Core
{
	public static class GameDelegate
	{
		public static void LoadGame(object sender, EventArgs e)
		{
			var gameView = sender as CCGameView;

			// 省略

			// デザイン解像度設定
			gameView.DesignResolution = new CCSizeI(224, 380);
			gameView.ResolutionPolicy = CCViewResolutionPolicy.ShowAll;

			// 省略

		}
	}
}

とっても、簡単ですね!

CCViewResolutionPolicy

CCViewResolutionPolicyはゲームウィンドウを希望の解像度に調整する方法を指定します。

CCViewResolutionPolicy は以下の6つが用意されていますが、実際に使用するのはShowAllのみです。それ以外は画面が歪んだり切れたりするので実際に使うことはありません。

  • ShowAll … アスペクト比を維持した状態で、要求された解像度全体をデバイスの画面に最大になるように表示します。デバイスのアスペクト比とデザイン解像度のアスペクト比が一致しない場合は、上下、または左右に黒い帯ができます。
  • ExactFit … アスペクト比を維持せず、要求された解像度全体をデバイスの画面全体に表示します。デバイスのアスペクト比とデザイン解像度のアスペクト比が一致しない場合は、ゲーム画面がゆがみます。
  • FixedWidth … アスペクト比を維持した状態で、要求された幅全体を表示します。デバイスのアスペクト比とデザイン解像度のアスペクト比が一致しない場合は、画面の上部が切れるか、画面の上部に黒い帯が発生します。
  • FixedHeight … アスペクト比を維持した状態で、要求された高さ全体を表示ます。デバイスのアスペクト比とデザイン解像度のアスペクト比が一致しない場合は、画面の右部が切れるか、画面の右に黒い帯が発生します。
  • NoBorder … アスペクト比を維持したまま高さ全体または幅全体を表示します。デバイスのアスペクト比とデザイン解像度のアスペクト比が一致しない場合は、画面の上下または左右が切れます。
  • Custom-ゲーム画面をデバイスに表示する際の高さおよび幅の比率を自由に設定できます。

まとめ

DesignResolutionのおかげで、Cocos Sharp では、デバイスごとの解像度の差異をまったく気にすることなくゲーム開発ができます。

それでは、ぜひ皆様も Cocos Sharp でゲーム開発にチャレンジしてみてください!

Cocos Sharp 入門 (2) CCScene, CCLayer, CCSprite, CCLabel

はじめに

こんにちは、@hiro128_777です。
今回は、Cocos Sharp の基本的なオブジェクトである、CCScene, CCLayer, CCSprite, CCLabel についてご説明します。

Cocos Sharp の画面の階層構造

Cocos Sharp の画面の階層構造は下の図のようになっています。

f:id:hiro128:20161227192029p:plain

CCSceneの中に複数のCCLayerが存在し、それぞれのCCLayerの中に複数のCCSprite, CCLabelが存在します。

CCScene

CCSceneはキャンバスと考えて頂くとわかりやすいです。
キャンバスの上に色々なオブジェクトを配置します。

そしてCCSceneはその名の通り、ゲームのシーンごとに作成します。タイトル画面で1つ、ゲーム画面で1つ、ゲームオーバーの画面で1つといった感じで、場面場面ごとにシーンを切り替えていきます。

CCLayer

CCLayerCCScene上に配置する重ねあわせが可能な画面です。背景用に1枚、画面上を動くキャラクター用に1枚、各種情報表示用に1枚というように、用途ごとにCCLayerを準備します。

CCSprite, CCLabel

CCSpriteCCLayer上に配置するスプライトです。プレーヤーや敵キャラ、シューティングゲームの弾などが相当します。
CCLabelCCLayer上に配置する文字です。スコアやステージ情報、残機情報などが相当します。

非常にシンプルでわかりやすいですね!
Cocos Sharpは簡単です!

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

Cocos Sharp 入門 (1) Cocos Sharp の概要およびメリット・デメリット

はじめに

こんにちは、@hiro128_777です。
今回は、これから Cocos Sharp を導入しようかな~と検討している方もいらっしゃるかと思いますので、あらためて、Cocos Sharp の概要およびメリット・デメリットを整理したいと思います。

Cocos Sharp ってどんなもの

Cocos Sharp は、Xamarin 上で動作する2Dゲーム開発用クロスプラットフォームライブラリです。
Cocos Sharp は Monogame 上に構築されており、Monogameのエンジンを Cocos2d,3d の API で利用できます。


つまり…
C#ユーザーであれば、Xamarin + Cocos Sharp で最短でクロスプラットフォームでのゲーム開発が可能です!!

Cocos Sharp を使うメリットは

Xamarin.Forms と共存できる

Cocos Sharp を使う最大のメリットは、ゲーム画面を Xamarin.Forms 上のUIコントロールの一つとして扱うことができるため、一つの画面内で一部分はゲーム、一部分はリスト表示というようなことが実現できることです。今のところクロスプラットフォームでこんな芸当ができるのは Cocos Sharp のみです。

具体例として、前回の記事をご参照ください。
hiro128.hatenablog.jp

ゲーム開発未経験でも簡単にスマホゲームが作れる

Cocos Sharp は学習コストが非常に低いです。C#経験者であれば、1週間も触ればすぐゲーム開発が可能になります。
職業としてではなく、趣味としてできるだけ簡単にスマホゲームをリリースしたい場合、Cocos Sharp は非常に魅力的な選択肢となります。

処理が早い

Cocos Sharp は2Dに特化しているため、3D用のライブラリも存在せず、3D用のプロセスを起動する必要もないので、非常に軽快に動作します。

コード共有率が非常に高い

ゲームは基本的に標準のUIコントロールを使用せず、独自の描画を行うため、Xamarin.Forms のように各プラットフォームのUIコントロールの差異を調整するようなことをする必要がありません。よって、コード共有率が非常に高くなります。通常の場合90%以上のコード共有率が実現できます。プラットフォーム個別の実装が必要なのは、各種センサー周り、カメラ周り、広告関係、課金関係などに限られます。

Xamarin 環境があれば導入コストが無料

Xamarin の環境をすでに持っているのであれば、Cocos Sharp 自体は無料で使い始めることができます。MIT License なので商用利用も可能です。

Cocos Sharp のデメリットは

日本語情報がほとんどない

Cocos Sharp の日本語情報はほとんどありませんが、Cocos2d-x の情報は非常にたくさん存在しますので、それをC#に読み替えれば大丈夫です。それでも分からない場合は、twitter, teratailなどで質問すれば、何らかのアドバイスが得られると思います。また、私のblogにも色々情報がありますのでご参考にしていただければ幸いです。

職業としてゲーム開発者を目指す場合メリットが薄い

C#環境で職業としてゲーム開発者を目指すのであれば Unity が最適だと考えられます。ゲーム会社さんで Cocos Sharp を使っているという会社さんは聞いたことがありませんので…

まとめ

Cocos Sharp は「Xamarin 触ったことあるけど、ゲームも作ってみたいな~」という方には最適なソリューションです。ぜひ、皆様も Cocos Sharp でゲーム開発にチャレンジしてみてください!

CocosSharp for Xamarin.Forms で寿司を流そう!

はじめに

こんにちは、@hiro128_777です。

この記事は「Xamarin(その2) Advent Calendar 2016 - Qiita」の14日目になります。

今回やってみたこと

皆様 Xamarin.Forms というと業務アプリ的なイメージがあるかと思うのですが、なんと、CocosSharp for Xamarin.Formsを使うと、Xamarin.Formsの中にCocosSharpのゲームを同居できてしまいます。

というわけで今回は、CocosSharp for Xamarin.Forms で寿司を流すサンプルを作成してみました。
アプリの中に動きのあるコンテンツを利用したいときのご参考にでもなれば幸いです。

今回のサンプルのソースは以下をご参照ください。

https://github.com/TomohiroSuzuki128/CocosSharpFormsSample

作成方法

Xamarin Forms の新規プロジェクトを作成し、CocosSharp.Forms 1.7.1 のパッケージをインストールしてください。
※この辺りの詳細な方法については、たくさん情報がございますので割愛します。

概要

f:id:hiro128:20161213150101p:plain

画面の構成は上の図のように、画面の上半分が Cocos Sharp のゲーム画面で、寿司が流れる部分となります。
下半分が Xamarin.Forms で操作するボタンが配置されています。

ボタンを1回押すごとにゲーム画面部分に寿司が1つ流れます。

f:id:hiro128:20161213152450g:plain

XAMLの作成

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
			 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
			 xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
			 prism:ViewModelLocator.AutowireViewModel="True"
			 Title="ココ寿司ャープ"
			 x:Class="CocosSharpFormsSample.Views.MainPage">
	<Grid x:Name="MainGrid" RowSpacing="0">
		<Grid.RowDefinitions>
			<RowDefinition Height="*"/>
			<RowDefinition Height="*"/>
		</Grid.RowDefinitions>
		<StackLayout Grid.Column="0" Grid.Row="1">
			<StackLayout Padding="0,100,0,0">
				<Button Text="左へ寿司を流す" Command="{Binding LeftCommand}" />
			</StackLayout>
			<StackLayout Padding="0,20,0,0">
				<Button Text="右へ寿司を流す" Command="{Binding RightCommand}" />
			</StackLayout>
		</StackLayout>
	</Grid>
</ContentPage>

XAMLは通常の Xamarin.Forms の場合と何も変わりません。上下2段のGridが配置してあり、下のセルにボタンが2つ配置されています。
CocosSharp のコントロールは XAML 上には配置されていません。CocosSharp のコントロールは XAML 上で追加するとなぜか落ちるため、後ほどコードビハインドで追加します。

コードビハインド

using System;
using System.Collections.Generic;

using Xamarin.Forms;
using CocosSharp;
using CocosSharpFormsSample.ViewModels;

namespace CocosSharpFormsSample.Views
{
	public partial class MainPage : ContentPage
	{
		public MainPage()
		{
			InitializeComponent();

			var grid = this.FindByName<Grid>("MainGrid");

			var gameView = new CocosSharpView()
			{
				HorizontalOptions = LayoutOptions.FillAndExpand,
				VerticalOptions = LayoutOptions.FillAndExpand,
				ViewCreated = CocosSharpView_ViewCreated
			};
			grid.Children.Add(gameView, 0, 0);
		}

		private void CocosSharpView_ViewCreated(object sender, EventArgs e)
		{
			var gameView = sender as CCGameView;

			if (gameView != null)
			{
				gameView.DesignResolution = new CCSizeI(350, 300);
				gameView.ResolutionPolicy = CCViewResolutionPolicy.ExactFit;
				gameView.ContentManager.SearchPaths = new List<string> { "Images" };
				var gameScene = new GameScene(gameView);
				gameView.RunWithScene(gameScene);
				var viewModel = BindingContext as MainPageViewModel;
				viewModel.GameScene = gameScene;
			}
		}

	}
}

コードビハインドでは、InitializeComponentの後にCocosSharpViewのコントロールを作成し配置します。
また、コントロール作成時のイベントハンドラ内で以下のようにゲーム画面の初期設定を行っています。

gameView.DesignResolution = new CCSizeI(350, 300);

ゲーム内の仮想解像度を横350ユニット、縦300ユニットに設定する。

gameView.ResolutionPolicy = CCViewResolutionPolicy.ExactFit;

アスペクト比を保ったまま最大の大きさで表示する。

gameView.ContentManager.SearchPaths = new List<string> { "Images" };

アセットを探しに行くパスを設定する。

var viewModel = BindingContext as MainPageViewModel;
viewModel.GameScene = gameScene;

また、ViewModel側からゲーム操作できるように参照を渡しています。この部分本当は疎結合にしたいのですが、現状では厳しそうな感じです。

ゲームロジック

CocosSharp のゲームでは、CCSceneの中にCCLayerが存在し、その中にCCSpriteが配置されます。

最初にCCLayer上で寿司が流れるロジックを作成します。

using System;

using CocosSharp;

namespace CocosSharpFormsSample
{
	public class GameLayer : CCLayerColor
	{
		private CCTexture2D sushiTexture;

		public GameLayer(CCColor4B? color = default(CCColor4B?)) : base(color)
		{
			sushiTexture = new CCSprite("Sushi.png", null).Texture;
		}

		public void ConveyorShushiRightToLeft()
		{
			var sushi = new CCSprite(sushiTexture);
			sushi.PositionX = 401f;
			sushi.PositionY = 80f;
			var moveBy = new CCMoveBy(4f, new CCPoint(-500f, 0));
			var remove = new CCRemoveSelf();
			var sequence = new CCSequence(moveBy, remove);
			sushi.RunAction(sequence);
			AddChild(sushi);
		}

		public void ConveyorShushiLeftToRight()
		{
			var sushi = new CCSprite(sushiTexture);
			sushi.PositionX = -50f;
			sushi.PositionY = 210f;
			var moveBy = new CCMoveBy(4f, new CCPoint(500f, 0));
			var remove = new CCRemoveSelf();
			var sequence = new CCSequence(moveBy, remove);
			sushi.RunAction(sequence);
			AddChild(sushi);
		}

	}
}

ConveyorShushiRightToLeftConveyorShushiLeftToRightの各メソッドは、それぞれ「左へ寿司を流す」「右へ寿司を流す」ボタンを押したときに、画面外の領域に寿司を配置しそのまま画面上を寿司が流れ、画面外に到達したら寿司をGameLayerから削除する一連のアクションを実行します。

次に、作成したGameLayerCCSceneに配置します。

using System;

using CocosSharp;

namespace CocosSharpFormsSample
{
	public class GameScene : CCScene
	{
		GameLayer layer;

		public GameScene(CCGameView gameView) : base(gameView)
		{
			layer = new GameLayer(CCColor4B.White);
			AddLayer(layer);
		}

		public void RightToLeft()
		{
			layer.ConveyorShushiRightToLeft();
		}

		public void LeftToRight()
		{
			layer.ConveyorShushiLeftToRight();
		}

	}
}

ViewModel

ViewModelでは、Xamarin.Forms側のCommandに従いCocosSharp側に指示を与えています。

using Prism.Commands;
using Prism.Mvvm;
using Prism.Navigation;

namespace CocosSharpFormsSample.ViewModels
{
	public class MainPageViewModel : BindableBase
	{

		public GameScene GameScene { get; set; }
		public DelegateCommand LeftCommand => new DelegateCommand(Left, CanLeft);
		public DelegateCommand RightCommand => new DelegateCommand(Right, CanRight);

		INavigationService _navigationService;

		public MainPageViewModel(INavigationService navigationService)
		{
			_navigationService = navigationService;
		}

		private bool CanLeft()
		{
			return true;
		}

		private void Left()
		{
			GameScene.RightToLeft();
		}

		private bool CanRight()
		{
			return true;
		}

		private void Right()
		{
			GameScene.LeftToRight();
		}

	}
}

以上で完了です。

では早速動かしてみましょう。

ボタンを押すと左右から寿司が流れました(笑)

f:id:hiro128:20161213152450g:plain

まとめ

  • CocosSharp for Xamarin.Formsを使うと、Xamarin.Formsの中の一部分としてCocosSharpのゲームを同居できます。
  • Xamarin.Forms側からCocosSharpのゲームを操作することも可能です。
  • Xamarin.Formsでは表現しきれない動きのあるコンテンツを利用したい時に有効です。


今回のサンプルのソースは以下をご参照ください。

https://github.com/TomohiroSuzuki128/CocosSharpFormsSample

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デリゲートを実行しているのが分かります。

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

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