TinyUnityUnity

TinyUnityのサンプルを徹底解析!BasicWorkshop編

TinyUnityとは?

Unity Editorで小型で軽量のHTML5ゲームやアプリケーションを作成できるUnityのModeの1つです。

Unity 2018 3.0b以上で起動できます。

導入や概要については以前の記事に記載しました!

TinyUnity 0.13.2-preview 試して見ました!

今回はこちらのTinyUnityのサンプルプロジェクトの解析を徹底的におこなって行こうかと思います。

・対象のサンプルプロジェクトを開く

前回解説した記事通りにTinyUnityのpackageをインストールした前提で進めていきます!

Assets/TinySamples/BasicWorkshop/BasicWorkshop.utproject

を開きます。

・どんなサンプルゲーム?

宇宙を駆け巡るシューティングっぽいゲームです!

・プロジェクトの構成

・Hierarchyの構成

Hierarchy以下このようになっています!

EnemyのEntityが現在、初期で配置されているようです。

(EntityからGroupを読み込むことで各種Playerなどの他の主要なEntityを確認することができます。)

 

・プロジェクトアセット構成について

・Components

コンポーネントとは・・・?!
通常のUnityのコンポーネントとは少し違うようです!

エンティティにアタッチできるデータセット

がCompornentの定義になるかと思われます。

以下がこのプロジェクトで使われる独自に定義されたコンポーネント群です。

公式ドキュメント:カスタムコンポーネント参照

・Boundaries

4つの値(minX,maxX,minY,maxY)をもつコンポーネントです。

・ChangeOverTime

切り替え時間?に使われるコンポーネント

・EnemyTag

アタッチされたEntityが敵であることを示す為のコンポーネントっぽい

・MoveSpeed

移動時の速度を設定するためのコンポーネント

・MoveWithInput

Inputに応じて移動を行うためのコンポーネント

・PlayerTag

プレイヤーであることを示す為に使われているっぽい。

・ScrollingBackground

背景をスクロールさせるためのコンポーネント

・Spawner

敵などの生成を行うためのコンポーネント

・Entities

・EnemyGroup

敵のEntityが含まれています。

・ExplosionGroup

爆発のEntityが含まれています。

・MainGroup

CameraやPlayer、BackgroundやEnemySpawnerなどメインゲームで使われている

Entityが含まれています。

・Scripts

敵の行動パターンや各種SysytemなどのScriptがあるようです。

別途プロジェクトのコード解析の項で解説します。

・Textures

ゲーム中で使われている各種Spriteが格納されていました!

・プロジェクトのコード解析

・EnemyBehavior


export class EnemyBehaviorFilter extends ut.EntityFilter 
{
        entity: ut.Entity;
        position: ut.Core2D.TransformLocalPosition;
	tag: game.EnemyTag;
	speed: game.MoveSpeed;
	speedChange: game.ChangeOverTime;
	bounds: game.Boundaries;
}

Entityにアタッチするコンポーネントを定義しているようです。
ut.EntityFilter  を継承して作成されています。


    export class EnemyBehavior extends ut.ComponentBehaviour 
	{

        data: EnemyBehaviorFilter;

        OnEntityEnable():void 
		{
            let totalTime = ut.Time.time();
			let newSpeed = this.data.speed.speed + (this.data.speedChange.changePerSecond * totalTime);
			
			this.data.speed.speed = newSpeed;
			
			let randomX = getRandom(this.data.bounds.minX, this.data.bounds.maxX);
			let newPos = new Vector3(randomX, this.data.bounds.maxY, 0);
			
			this.data.position.position = newPos;

			console.log("enemy initialized. Speed: " + newSpeed);
        }
        
        OnEntityUpdate():void 
		{
            let localPosition = this.data.position.position;
			localPosition.y -= this.data.speed.speed * ut.Time.deltaTime();

			this.data.position.position = localPosition;

			if(localPosition.y <= this.data.bounds.minY)	
				//this.world.addComponent(this.entity, ut.Disabled);
				this.world.destroyEntity(this.data.entity);
        }
    }

ut.ComponentBehaviourを継承して定義されている。
UnityのMonoBehaviourに近いイメージ?

data: EnemyBehaviorFilter

を使い上記で定義したFilterを設定できるようにしているように見えます。

OnEntityEnable():void

をはEntityがEnableになったタイミングで呼ばれるように見えます。

let newSpeed = this.data.speed.speed + (this.data.speedChange.changePerSecond * totalTime);

で敵の加速が行われているように見えます。

console.log(“enemy initialized. Speed: ” + newSpeed);

でログ出力を

OnEntityUpdate():void

でUpdate処理が行われているように見えます。


            let localPosition = this.data.position.position;
			localPosition.y -= this.data.speed.speed * ut.Time.deltaTime();

			this.data.position.position = localPosition;

ここは敵のポジションを移動させる記述のようです。

this.world.destroyEntity(this.data.entity);

 で自身の破棄をおこなっているようです。

・GameService


        private static mainGroup: string = 'game.MainGroup';
        private static enemyGroup: string = 'game.EnemyGroup';
        private static explosionGroup: string = 'game.ExplosionGroup';

各種Staticな値が定義されている模様


        static restart(world: ut.World) 
        {
            //return; //Uncomment this line if you don't want the game to restart

            setTimeout(() => 
            { 
                this.newGame(world);
            }, 3000);
        };

worldを引数に取り、
setTimeoutで指定秒後に即時関数を発火
this.newGame(world);
で最初から開始


static newGame(world: ut.World) 
        {
            ut.Time.reset();

            ut.EntityGroup.destroyAll(world, this.mainGroup);
            ut.EntityGroup.destroyAll(world, this.enemyGroup);
            ut.EntityGroup.destroyAll(world, this.explosionGroup);

            ut.EntityGroup.instantiate(world, this.mainGroup);
        };

worldを引数に取り、
Timerをリセットし
world中で使われているEntityGroupを破棄

・InputMovementSystem


/** New System */
 @ut.executeAfter(ut.Shared.InputFence)
 export class InputMovementSystem extends ut.ComponentSystem 

ut.ComponentSystemを継承したInput受付用のクラス


OnUpdate():void {

中で各種Inputを監視し実行


if(ut.Runtime.Input.getKey(ut.Core2D.KeyCode.W))
					localPosition.y += speed.speed * dt;
				if(ut.Runtime.Input.getKey(ut.Core2D.KeyCode.S))
					localPosition.y -= speed.speed * dt;
				if(ut.Runtime.Input.getKey(ut.Core2D.KeyCode.A))
					localPosition.x -= speed.speed * dt;
				if(ut.Runtime.Input.getKey(ut.Core2D.KeyCode.D))
					localPosition.x += speed.speed * dt;

キー入力を監視して
localPostionを変更します。

あと、スマホなどのTouch可能デバイス向けに


ProcessTouchInput(position: Vector3, speed):void

からTouchの座標をVectore3で取得

・PlayerCollisionSystem


export class PlayerCollisionSystem extends ut.ComponentSystem 

例によって ut.ComponentSystemを継承


OnUpdate():void 
		{
			let isGameOver = false;

			this.world.forEach([ut.Entity, ut.Core2D.TransformLocalPosition, ut.HitBox2D.HitBoxOverlapResults, game.PlayerTag], (entity, position, contacts, tag) => 
			{
				let explosion = ut.EntityGroup.instantiate(this.world, game.PlayerCollisionSystem.explosionGroupName)[0];

				this.world.usingComponentData(explosion, [ut.Core2D.TransformLocalPosition], (explosionPos) => 
				{
                    explosionPos.position = position.position;
                });
				
				this.world.destroyEntity(entity);
				
				isGameOver = true;
				
			});

			if(isGameOver)
				game.GameService.restart(this.world);
        }

Updateで this.world中のEntity群をforEachで回して処理を実行
敵の座標とプレイヤーの座標が重なってる時にgameoverのフラグをtrueにして
game.GameService.restart(this.world);
しているっぽい。
この処理はパフォーマンス大丈夫なんだろか???

・ScrollingBackgroundSystem


    /** New System */
    export class ScrollingBackgroundSystem extends ut.ComponentSystem 
	{    
        OnUpdate():void 
		{
			let dt = ut.Time.deltaTime();

			this.world.forEach([ut.Core2D.TransformLocalPosition, game.ScrollingBackground], (position, scrolling) => 
			{
				let localPosition = position.position;
				
				localPosition.y -= scrolling.speed * dt;
				
				if (localPosition.y < scrolling.threshold) 
					localPosition.y += scrolling.distance;
				
				position.position = localPosition;
			});

        }
    }

Updateでut.Time.deltaTime()に応じて、
game.ScrollingBackground のEntityのポジションを移動させている感じ

・SpawnSystem


OnUpdate():void 
		{
			this.world.forEach([game.Spawner], (spawner) => 
			{
				if (spawner.isPaused) 
					return;

				let time = spawner.time;
				let delay = spawner.delay;
				
				time -= ut.Time.deltaTime();

				if (time <= 0) 
				{
					time += delay;
					
					ut.EntityGroup.instantiate(this.world, spawner.spawnedGroup);
				}

				spawner.time = time;
			});
        }

例によってOnUpdate中で
game.Spawnerを取得し
ut.EntityGroup.instantiate(this.world, spawner.spawnedGroup);
を使いspawner.spawnedGroup を生成しているようです。

・Time


    @ut.executeBefore(ut.Shared.UserCodeStart)
    export class Time extends ut.ComponentSystem
    {
        private static _deltaTime: number = 0;
        private static _time: number = 0;
        
        static deltaTime(): number {
            return Time._deltaTime;
        }

        static time(): number {
            return Time._time;
        }

        static reset()
        {
           Time._time = 0;
        }
        
        OnUpdate(): void {
            let dt = this.scheduler.deltaTime();
            Time._deltaTime = dt;
            Time._time += dt;
        }
    }

時間進行用のクラス


static deltaTime(): number {
            return Time._deltaTime;
        }

        static time(): number {
            return Time._time;
        }

などで時刻を取得できる。


OnUpdate(): void {
            let dt = this.scheduler.deltaTime();
            Time._deltaTime = dt;
            Time._time += dt;
        }

UpdateでdeltaTimeやTimeを更新している模様

・このサンプルプロジェクトについてのまとめ

ゲームとしての基本的な作り方がわかったような気がしました!
TypeScriptあまりわからん・・・・!!!
ざっくり覚えとかねば!!

・次にやりたいこと

もう1本サンプルゲームを解析してみます!

解析が終わったら、取り急ぎ色々調べながら簡単なカジュアルゲームを作成してみます!