2014年9月24日水曜日

3-4. Unity でユニティちゃんアクションゲームを作る - 敵の生成と武器の追加

ソースはこちらで公開しています。
https://github.com/matsushima-terunao/unity-chan

実際のゲームはこちら
http://ter.dip.jp/blog_unity/unity-chan/build/web/web.html

敵の生成

敵を Prefab 化します。
 Hierarchy 上の bear を Assets に移動
 Hierarchy 上の bear を削除

MainScript

敵を生成を行う処理を追加します。
...
/** プレイヤー */
var player: GameObject;
/** 敵 */
var enemyPrefab: GameObject;
/** 敵 */
var enemies: GameObject[] = new GameObject[10];
...
function Start () {
	player = GameObject.Find("unitychan_dynamic_locomotion");
}

function Update () {
	// 敵生成
	for (var i: int = 0; i < enemies.length; ++ i) {
		if (null == enemies[i]) {
			var position: Vector3 = Vector3(
				player.transform.position.x + 10 * Mathf.Sin(player.transform.eulerAngles.y * Mathf.PI / 180) + 10 * Random.value - 5,
				0,
				player.transform.position.z + 10 * Mathf.Cos(player.transform.eulerAngles.y * Mathf.PI / 180) + 10 * Random.value - 5);
			var angle: Quaternion = Quaternion(0, Vector3.Angle(position, player.transform.position), 0, 1);
			enemies[i] = Instantiate(enemyPrefab, position, angle);
			break;
		}
	}
}
...

武器の追加

武器を追加します。Asset Store の Modern Weapons Pack を利用しました。今までと同様、Assets に Import します。
好きな武器を選んで、プレイヤーの右手(Character1_RightHand)にドロップします。



弾丸の追加

Sphere で弾丸を作成します。敵と同様に Prefab 化します。



UnityChanScript

弾丸を発射する処理を追加します。
...
/** 攻撃時刻 */
var attackTime: float = 0;
/** 弾丸時刻 */
var bulletTime: float = 0;
/** 弾丸 */
var bullets: GameObject[] = new GameObject[2];
/** 弾丸 */
var bulletPrefab: GameObject;
...
function Update () {
	var currentTime: float = Time.time;
	// 攻撃
	if (Input.GetButtonDown ("Fire1")) {
		animator.SetTrigger("Attack");
		transform.Find("Attack").gameObject.SetActive(true);
		attackTime = currentTime;
	}
	// 攻撃終了
	if (Time.time >= attackTime + 1) {
		transform.Find("Attack").gameObject.SetActive(false);
	}
	// 弾丸発射
	MainScript.Log("cou", bullets.length);
	if (currentTime - bulletTime >= 0.1) {
		for (var i: int = 0; i < bullets.length; ++ i) {
			if (null == bullets[i]) {
				bullets[i] = Instantiate(bulletPrefab, transform.position + transform.forward + transform.right * 0.2, transform.rotation);
				bullets[i].rigidbody.velocity = transform.forward * 10;
				Destroy(bullets[i], 1);
				bulletTime = currentTime;
				break;
			}
		}
	}
}
...

EnemyScript

敵が弾丸に当たったときの処理を追加します。
function OnCollisionEnter (collision: Collision) {
	MainScript.Log("enemy", collision.gameObject.name);
	if ("Attack" == collision.gameObject.name) {
		Damage();
	} else if ("Bullet(Clone)" == collision.gameObject.name) {
		Damage();
		Destroy(collision.gameObject);
	}
}

ここまでで再生してみます。
弾丸が敵にヒットして消えれば OK です。



ユニティちゃんライセンス

このアセットは、『ユニティちゃんライセンス』で提供されています。このアセットをご利用される場合は、『キャラクター利用のガイドライン』も併せてご確認ください。


← 3-3. Unity でユニティちゃんアクションゲームを作る - 当たり判定の追加
↑ 一覧

2014年9月16日火曜日

3-3. Unity でユニティちゃんアクションゲームを作る - 当たり判定の追加

ソースはこちらで公開しています。
https://github.com/matsushima-terunao/unity-chan

実際のゲームはこちら
http://ter.dip.jp/blog_unity/unity-chan/build/web/web.html

前準備

視点の変更


引いた状態でプレイヤーに追従する視点に変更します。
Main Camera を unitychan_dynamic_locomotion 配下に移動
Scene 上の視点を調整し、Main Camera を選択した状態で GameObject メニュー -> Align With View
 Inspector タブ -> Third Person Camera (Script) のチェックを外す



Main Camera に MainScript を付ける

/**
 * メインスクリプト。
 *
 * @author 2014/08/30 matsushima
 */

#pragma strict

/** デバッグテキスト */
static var debugTexts:Hashtable = new Hashtable();

function Start () {

}

function Update () {

}

function OnGUI() {
	// デバッグテキスト表示
	var y:int = 0;
	for (var k:Object in debugTexts.Keys) {
		GUI.Label(Rect(0, y * 20, 400, 20), k + ":" + debugTexts[k].ToString());
		++ y;
	}
}

static function Log(key:String, text:Object) {
	// デバッグテキスト表示登録
	debugTexts[key] = text;
	// デバッグテキストログ出力
	print(key + ":" + text);
}

敵キャラに当たり判定を追加

bear を選択し、Inspector タブ -> Add Component で Rigidbody と Capsule Collider を追加します。パラメータを写真の通りに修正します。
また、Tag に Enemy を追加し、bear に設定します。



前回と同様の方法でプレイヤーに Damage アクションを追加します。
State
 Damage
  Motion: DAMAGED00
Parameters
 Damage Trigger
Transition
 Any State -> Damage
  Conditions: Damage
 Damage -> Idle
  Conditions: Exit Time 0.82



UnityChanScript に衝突時のコードを追加します。
function OnCollisionEnter (collision: Collision) {
	MainScript.Log("player", collision.gameObject.name);
	if (collision.gameObject.CompareTag("Enemy")) {
		animator.SetTrigger("Damage");
	}
}
ここまでで再生してきます。プレイヤーが敵と接触時にアクションを起こせば OK です。



攻撃に当たり判定を付ける

ダミーのオブジェクトを作って、攻撃中に衝突したら当たりとします。

オブジェクト作成


 Cube オブジェクトを作成し、名前を Attack とし、unitychan_dynamic_locomotion の子に移動
Position: 0 0.5 1, Scale: 1 1 1
 画面に描画されないように、Mesh Renderer のチェックを外す
 Inspector に Rigidbody を追加し、移動しないように Constraints にチェックを入れる



UnityChanScript


Ctrl を押して1秒間だけ有効にするようにします。
/**
 * ユニティちゃんスクリプト。
 *
 * @author 2014/08/31 matsushima
 */

#pragma strict

/** アニメーター */
var animator: Animator;
/** 攻撃時刻 */
var attackTime: float = 0;

function Start () {
	animator = gameObject.GetComponent(Animator); // アニメーター
	transform.Find("Attack").gameObject.SetActive(false); // 攻撃オブジェクト
}

function Update () {
	// 攻撃
	if (Input.GetButtonDown ("Fire1")) {
		animator.SetTrigger("Attack");
		transform.Find("Attack").gameObject.SetActive(true);
		attackTime = Time.time;
	}
	// 攻撃終了
	if (Time.time >= attackTime + 1) {
		transform.Find("Attack").gameObject.SetActive(false);
	}
}

function OnCollisionEnter (collision: Collision) {
	MainScript.Log("player", collision.gameObject.name);
	// 敵と衝突	
	if (collision.gameObject.CompareTag("Enemy")) {
		animator.SetTrigger("Damage");
	}
}

EnemyScript


攻撃オブジェクトに衝突したときの処理を追加します。
function OnCollisionEnter (collision: Collision) {
	MainScript.Log("enemy", collision.gameObject.name);
	if ("Attack" == collision.gameObject.name) {
		Damage();
	}
}

function Damage() {
	animator.SetTrigger("Damage");
	Destroy(gameObject, 0.5f);
}

この状態で再生してみます。
攻撃が敵にヒットして消えれば OK です。



ユニティちゃんライセンス

このアセットは、『ユニティちゃんライセンス』で提供されています。このアセットをご利用される場合は、『キャラクター利用のガイドライン』も併せてご確認ください。


→ 3-4. Unity でユニティちゃんアクションゲームを作る - 敵の生成と武器の追加
← 3-2. Unity でユニティちゃんアクションゲームを作る - アニメーションの追加
↑ 一覧

3-2. Unity でユニティちゃんアクションゲームを作る - アニメーションの追加

ソースはこちらで公開しています。
https://github.com/matsushima-terunao/unity-chan

実際のゲームはこちら
http://ter.dip.jp/blog_unity/unity-chan/build/web/web.html

ユニティちゃんに攻撃アクションの追加

Animator 修正


既存の Animator に攻撃アクションの State を追加します。
 Hierarchy タブ -> unitychan_dynamic_locomotion を選択
 Animator タブ -> パネル内右クリック -> Create State -> Empty
 Inspector タブ -> New State を Attack に変更
 Motion 右端の○をクリック
 Select Motion ダイアログで Assets タブ -> SLIDE00 をダブルクリック
さらに State 間の遷移(Transition)を作成します。
 Any State で右クリック -> Make Transition -> Attack をクリック
 左下の Parameters 右の + をクリック -> Trigger
 New Trigger を Attack に変更
 先ほど追加した Trasition(矢印) をクリック
 Inspector タブ -> Conditions を Attack に変更
同様に Attack -> Idle への Transition を作成します。Conditions は Exit Time 0.50 にします。
のちに動きを確認したとき、反応が遅ければ、Transition のスライダを左に動かして遷移時間を短くしてみてください。



スクリプト作成


Trigger をセットするスクリプトを作成します。
 Assets 内に Javascript で UnityChanScript を作成
 スクリプトを Hierarchy タブ -> unitychan_dynamic_locomotion にドロップ
 スクリプトを編集
/**
 * ユニティちゃんスクリプト。
 *
 * @author 2014/08/31 matsushima
 */

#pragma strict

var animator: Animator;

function Start () {
	animator = gameObject.GetComponent(Animator);
}

function Update () {
	if (Input.GetButtonDown ("Fire1")) {
		animator.SetTrigger("Attack");
	}
}
元のスクリプト Fire1 のコードをコメントアウトしておきます。
 Main Camera の TirdPersionGamera の 41-50 行目をコメントアウト
		void FixedUpdate ()	// このカメラ切り替えはFixedUpdate()内でないと正常に動かない
		{
/*		
			if (Input.GetButton ("Fire1")) {	// left Ctlr	
				// Change Front Camera
				setCameraPositionFrontView ();
			} else if (Input.GetButton ("Fire2")) {	//Alt	
				// Change Jump Camera
				setCameraPositionJumpView ();
			} else {	
				// return the camera to standard position and direction
				setCameraPositionNormalView ();
			}
*/		}
ついでに画面のテキストも消します。
 unitychan_dynamic_locomotion の FaceUpdate の 18-30 行目の OnGUI() をコメントアウト
 unitychan_dynamic_locomotion の UnityChanControlScriptWithRgidBody の 181-190 行目の OnGUI() をコメントアウト
 unitychan_dynamic_locomotion の RandomWind の 38-42 行目の OnGUI() をコメントアウト

この状態で再生してみます。
左 Ctrl キーでスライディングすれば OK です。



敵キャラにアクションの追加

敵キャラも同様に Animator を修正します。
State
 Idle
  Motion: Assets/TinyMonsterPack/Animations/idle.anim
 Walk
  Motion: Assets/TinyMonsterPack/Animations/walk.anim
 Attack
  Motion: Assets/TinyMonsterPack/Animations/attack.anim
 Damage
  Motion: Assets/TinyMonsterPack/Animations/jump.anim
Parameters
 Damage Trigger
 Attack Trigger
 Speed Float 0.0
Transition
 Idle -> Walk
  Conditions: Speed Greater 0
 Walk -> Idle
  Conditions: Speed Less 0.1
 Any State -> Attack
  Conditions: Attack
 Attack -> Idle
  Conditions: Exit Time 0.90
 Any State -> Damage
  Conditions: Damage
 Damage -> Idle
  Conditions: Exit Time 0.82



さらに EnemyScript という名前でスクリプトを作成し、bear にドロップします。
/**
 * 敵スクリプト。
 *
 * @author 2014/09/01 matsushima
 */

#pragma strict

var animator: Animator;

var STATE_IDLE: int = 0;
var STATE_WALK: int = 1;

var state: int = STATE_IDLE;

var stateTime: float;

var speed: float = 0;

var player: GameObject;

function Start () {
	animator = gameObject.GetComponent(Animator);
	stateTime = Time.time;
	/*for (var o: GameObject in GameObject.FindGameObjectsWithTag("Player")) {
		Debug.Log(o.name);
	}*/
	//player = GameObject.FindGameObjectWithTag("Player");
	player = GameObject.Find("unitychan_dynamic_locomotion");
}

function Update () {
	var timeNow: float = Time.time;
	switch (state) {
	case STATE_IDLE:
		if (timeNow - stateTime >= 5) {
			state = STATE_WALK;
			stateTime = timeNow;
		} else {
			speed = Mathf.Max(0, speed - Time.deltaTime);
		}
		break;
	case STATE_WALK:
		if (timeNow - stateTime >= 5) {
			state = STATE_IDLE;
			stateTime = timeNow;
		} else {
			speed = Mathf.Min(1, speed + Time.deltaTime);
		}
		break;
	}
	// 攻撃
	var distance: Vector3 = player.transform.position - transform.position;
	if (distance.magnitude <= 1.0f) {
		animator.SetTrigger("Attack");
	}
	// 移動
	animator.SetFloat("Speed", speed);
	if (speed > 0) {
		var direction: float = Mathf.Atan2(distance.x, distance.z);
		// プレイヤー方向に回転
		transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.Euler(0, direction * 180 / Mathf.PI, 0), Time.deltaTime);
		// プレイヤー方向に移動
		transform.position.x += Mathf.Sin(direction) * Time.deltaTime * speed;
		transform.position.z += Mathf.Cos(direction) * Time.deltaTime * speed;
	}
}
この状態で再生してみます。
敵が近づいてきて攻撃してくれば OK です。



ユニティちゃんライセンス

このアセットは、『ユニティちゃんライセンス』で提供されています。このアセットをご利用される場合は、『キャラクター利用のガイドライン』も併せてご確認ください。


→ 3-3. Unity でユニティちゃんアクションゲームを作る - 当たり判定の追加
← 3-1. Unity でユニティちゃんアクションゲームを作る - キャラクターの配置
↑ 一覧

2014年9月14日日曜日

3-1. Unity でユニティちゃんアクションゲームを作る - キャラクターの配置

ソースはこちらで公開しています。
https://github.com/matsushima-terunao/unity-chan

実際のゲームはこちら
http://ter.dip.jp/blog_unity/unity-chan/build/web/web.html

キャラクターの配置

データのインポート

まずプロジェクトを作成します。プロジェクトは unity-chan という名前で作成し、unity-chan.unity という名前でシーンを保存しておきます。
ユニティちゃんのサイト
http://unity-chan.com/
からユニティちゃん データをダウンロードします。
 Project タブ -> Assets -> Import Package -> Custom Package...
 Import Package ... ダイアログでダウンロードした UnityChan.unitypackage ファイルを選択して開くをクリック
 Importing package ダイアログで Import をクリック

キャラクターの配置

インポートが終わったらキャラクターをシーンにコピーします。
 Assets -> UnityChan -> Scenes -> Locomotion を開く
 Hierarchy タブの Main Camera と unitychan_dynamic_locomotion と Directional light for UnityChan を選択して右クリック -> Copy
 再度 Assets -> unity-chan を開く
 Hierarchy パネル内の Main Camera を削除
 Hierarchy パネル内で右クリック -> Paste
これでキャラクターとライトが Scene 内に追加されます。

地面の作成

さらに地面を作成します。
 Hierarchy タブ -> Create -> Terrain
 Inspector タブ -> Position -> X: -500, Z: -500
 その下の筆ボタンをクリック -> Edit Textures... -> Add Texture... -> Texture -> Select でテクスチャを選択

ここまで終わったところで再生してみます。方向キーで移動できれば OK です。




敵キャラクターの配置

敵キャラクターとして Asset Store の Tiny Monster Pack を利用します。
 Window メニュー -> Asset Store
 右上の検索ボックスから tiny monster pack で検索
 Tiny Monster Pack #1 をクリック
 Import をクリック
 Importing package ダイアログで Import をクリック
 Project タブ -> Assets -> TinyMonsterPack -> Scenes -> Test を開く
 Hierarchy タブ -> bear 右クリック -> Copy
 再度 Assets -> unity-chan を開く
 Hierarchy パネル内で右クリック -> Paste
これで敵キャラクターが Scene 内に追加されます。



ユニティちゃんライセンス

このアセットは、『ユニティちゃんライセンス』で提供されています。このアセットをご利用される場合は、『キャラクター利用のガイドライン』も併せてご確認ください。


→ 3-2. Unity でユニティちゃんアクションゲームを作る - アニメーションの追加
← 2-3. Unity で砲台ゲームを作る - 改良
↑ 一覧

2014年8月30日土曜日

2-3. Unity で砲台ゲームを作る - 改良


ソースはこちらで公開しています。
改良版: https://github.com/matsushima-terunao/unity_toride

実際のゲームはこちら
PC 改良版: http://ter.dip.jp/blog/unity_toride/build/build.html
Android 改良版: https://github.com/matsushima-terunao/unity_toride/raw/master/build/toride.apk

爆破エフェクトの変更

爆破エフェクトは Particle System から作成しましたが、用意されている素材を利用するように修正します。
 Project タブ内の Assets を右クリック -> Import New Package -> Particles
 Importing package ダイアログで Import をクリック
 Assets -> Standard Assets -> Particles -> Fire -> Fire1 を Hierarchy にドロップ、さらに Project -> Assets にドロップして Prefab 化、Hierarchy -> Fire1 は削除
 Fire1 配下の OuterCore, InnerCore, smoke に対して、Inspector -> Ellipsoid Particle Emitter に Min Size, Max Size, Ellipsoid の値を3倍にし、大きくする



爆破効果音の追加

爆破エフェクト発生時に効果音を鳴らすようにします。
 wav ファイルを用意し、Project -> Assets にドロップ
 Fire1 に Inspector 上で Add ComponentAudio Source, Audio Listener を追加
 Audio Source -> Audio Clip に Assets に追加した効果音をドロップ



地形の自動生成

スクリプトで、ゲームが始まるたびに地形を自動生成するようにします。
ソースはこちら
https://github.com/matsushima-terunao/unity_toride/blob/master/Assets/terrainScript.js

スクリプトは Hierarchy -> Terrain に関連付けます。Inspector タブ上に表示されるスクリプト内変数のうち以下のものを関連付けます。
 Particle Prefab: Project -> Assets -> Fire1
 Player: Hierarchy -> cannon(自分側)
 Other: Hierarchy -> cannon(相手側)

スクリプトで地形の高さデータを変更するには、高さデータの配列を作成し、TerrainData.SetHeights() に渡します。
高さデータの配列は Start() で TerrainData.GetHeights() て取得しています。
地形データはフラクタルアルゴリズムで作成しています。プログラムは Oh!X 1995年5月号の記事を参考にしました。
/** 変更前の高さマップ */
var heightsOrg: float[,];
/** 高さマップ */
var heights: float[,]; // 高さマップ

function Start () {
	var terrain: Terrain = gameObject.GetComponent(Terrain);
	var w: int = terrain.terrainData.heightmapWidth;
	var h: int = terrain.terrainData.heightmapHeight;
	Debug.Log("w=" + w + ",h=" + h);
	heightsOrg = terrain.terrainData.GetHeights(0, 0, w, h); // 変更前の高さマップ
	heights = terrain.terrainData.GetHeights(0, 0, w, h); // 高さマップ
}

/**
 * 初期化。
 */
function Init() {
	var terrain: Terrain = gameObject.GetComponent(Terrain);
	var w: int = terrain.terrainData.heightmapWidth;
	var h: int = terrain.terrainData.heightmapHeight;
	Debug.Log("w=" + w + ",h="  + h);
	fractal(512, 1.5, 0.3, heights);
	terrain.terrainData.SetHeights(0, 0, heights);
}

地形を削る

砲弾が地形に当たったときに地形を削るようにします。
衝突イベント内で、衝突地点を中心に高さ配列のデータを変更します。なお、Inspector 上で Pixel Error を 1 にしておかないと、周辺の地形にも影響が出てしまいます。
また、範囲内に砲台があったときにヒットするようにします。
function OnCollisionEnter(collision: Collision) {
	MainScript.Log("terrain collision", "terrain collision: " + collision.gameObject.name + "," + collision.contacts[0].point);
	if ("ball(Clone)" == collision.gameObject.name) {
		Destroy(collision.gameObject);
		// 破壊エフェクト。
		var particle: GameObject = Instantiate(particlePrefab, collision.transform.position - Vector3(0, 5, 0), Quaternion.identity); // インスタンスの生成
		particle.tag = "Respawn";
		Destroy(particle, 3);
		// 穴を掘る
		var terrain: Terrain = gameObject.GetComponent(Terrain);
		var R: int = 5; // 穴の半径
		var D: float = 0.002; // 穴の深さ
		var w: int = terrain.terrainData.heightmapWidth;
		var h: int = terrain.terrainData.heightmapHeight;
		var mapX: int = collision.contacts[0].point.z * w / terrain.terrainData.size.x; // heightmap 上の座標
		var mapZ: int = collision.contacts[0].point.x * h / terrain.terrainData.size.z; // heightmap 上の座標
		var mapR: int = R * w / terrain.terrainData.size.z; // heightmap 上の座標
		// z -> x
		var z1: int = Mathf.Max(-mapR, -mapZ);
		var z2: int = Mathf.Min(mapR, -mapZ + h - 1);
		for (var z: int = z1; z <= z2; ++ z) {
			// x -> y
			var mapW: int = Mathf.Sqrt(mapR * mapR - z * z);
			var x1: int = Mathf.Max(-mapW, -mapX);
			var x2: int = Mathf.Min(mapW, -mapX + w - 1);
			for (var x: int = x1; x <= x2; ++ x) {
				heights[x + mapX, z + mapZ] = Mathf.Max(0, heights[x + mapX, z + mapZ] - D * Mathf.Sqrt(mapW * mapW - x * x));
			}
		}
		terrain.terrainData.SetHeights(0, 0, heights);
		// ヒット。
		for (var cannon: GameObject in [player, other]) {
			if ((cannon.transform.position - collision.transform.position).magnitude
					< cannon.transform.FindChild("Sphere").transform.localScale.x / 2 + R) {
				cannon.GetComponent(cannonScript).Hit();
			}
		}
	}
}

視点の変更

マウス、タッチ操作で視点を変えられるようにします。
赤枠内で、クリック、タッチ位置が Player 砲台上かを判定します。砲台上のときは従来の砲台操作、それ以外の場合は視点操作にします。
青枠内で、ホイール、ピンチ量をズーム値とします。
緑枠内で実際の視点の変更を行います。
 地形の中心(50, 0, 50)からカメラへのベクトルを求める
 横方向の角度、縦方向の角度、長さの極座標に変換
 横移動量、縦移動量、ズーム値をそれぞれ加算
 再度直交座標に変換し、カメラの位置を更新
 カメラを地形の中心(50, 0, 50)に向ける
	var inputResolution: float = 90.0 / Mathf.Min(Screen.width, Screen.height); // 操作回転量: 画面短辺 -> 90度
	var inputDistance: Vector3 = Vector3.zero; // 入力移動量
	var fire: boolean = false; // 発射
	if (gameObject.CompareTag("Player")) {
		// 自分
		// mouse drag: 移動量を求める
		var mousePosPrev: Vector3 = mousePos;
		mousePos = Input.mousePosition;
		if (mouseDown) {
			inputDistance = mousePos - mousePosPrev;
		}
// mouse down if (Input.GetMouseButtonDown(0)) { mouseDown = true; // player 上判定 var hit: RaycastHit; var ray: Ray = Camera.main.ScreenPointToRay(mousePos); mouseOnPlayer = (Physics.Raycast(ray, hit, 100) && (gameObject == hit.collider.gameObject || hit.collider.transform.IsChildOf(transform))); }
// mouse up: 発射 if (Input.GetMouseButtonUp(0)) { fire = mouseOnPlayer; mouseDown = false; }
// mouse wheel: ズーム inputDistance.z = Input.GetAxis("Mouse ScrollWheel") * 10;
// touch var touchPosPrev: Vector3 = touchPos; if (1 == Input.touchCount) { var touch:Touch = Input.GetTouch(0); switch (touch.phase) {
// touch down case TouchPhase.Began: touchDown = true; touchPos = touch.position; // player 上判定 ray = Camera.main.ScreenPointToRay(mousePos); touchOnPlayer = (Physics.Raycast(ray, hit, 100) && (gameObject == hit.collider.gameObject || hit.collider.transform.IsChildOf(transform))); break;
// touch swipe: 移動量を求める case TouchPhase.Moved: touchPos = touch.position; inputDistance = touchPos - touchPosPrev; break; // touch up: 発射 case TouchPhase.Ended: fire = touchDown; touchDown = false; touchPos = touch.position; if (fire) { inputDistance = touchPos - touchPosPrev; } break; }
} else if (Input.touchCount >= 2) { // touch pinch: ズーム touchDown = false; var touch0:Touch = Input.GetTouch(0); var touch1:Touch = Input.GetTouch(1); if (TouchPhase.Began == touch1.phase) { touchPos.z = Vector2.Distance(touch0.position, touch1.position); } else if (TouchPhase.Moved == touch0.phase || TouchPhase.Moved == touch1.phase) { touchPos.z = Vector2.Distance(touch0.position, touch1.position); inputDistance = Vector3(0, 0, -(touchPos.z - touchPosPrev.z) / 10); } }
// mouse or touch 移動量 -> 回転 // 縦方向: x軸回転 // 横方向: y軸回転 if (Vector3.zero != inputDistance) { if (mouseOnPlayer || touchOnPlayer) { var angle: Vector3 = transform.eulerAngles - Vector3(inputDistance.y, inputDistance.x, 0) * inputResolution; angle.x = Mathf.Max(0, Mathf.Min(80, angle.x)); angle.y = Mathf.Max(80, Mathf.Min(190, (angle.y + 180) % 360)); // -100..10 angle.y = (angle.y + 180) % 360; transform.eulerAngles = angle; MainScript.Log("eulerAngles", "eulerAngles=" + angle + "," + transform.eulerAngles); } else {
// 地形の中心からカメラへのベクトル var terrainPos: Vector3 = Vector3(terrain.terrainData.size.x / 2, 0, terrain.terrainData.size.z / 2); var pos: Vector3 = Camera.main.transform.position - terrainPos; // 極座標に変換 var a: float = Mathf.Atan2(pos.z, pos.x); var b: float = Mathf.Atan2(pos.y, Mathf.Sqrt(pos.x * pos.x + pos.z * pos.z)); var c: float = Mathf.Sqrt(pos.x * pos.x + pos.y * pos.y + pos.z * pos.z); // 回転、ズーム a -= inputDistance.x * inputResolution / 30; b = Mathf.Max(0, Mathf.Min(Mathf.PI / 2.1, b - inputDistance.y * inputResolution / 30)); c = Mathf.Max(terrainPos.x, c + inputDistance.z); // 直交座標に変換 pos.x = c * Mathf.Cos(b) * Mathf.Cos(a); pos.z = c * Mathf.Cos(b) * Mathf.Sin(a); pos.y = c * Mathf.Sin(b); Camera.main.transform.position = pos + terrainPos; Camera.main.transform.LookAt(terrainPos);
} }

→ 3-1. Unity でユニティちゃんアクションゲームを作る - キャラクターの配置
← 2-2. Unity で砲台ゲームを作る - スクリプトの作成
↑ 一覧

2014年8月22日金曜日

2-2. Unity で砲台ゲームを作る - スクリプトの作成

一通り画面を作成したら、スクリプトを作成します。

ソースはこちらで公開しています。
本ページ: https://github.com/matsushima-terunao/unity_toride_old
改良版: https://github.com/matsushima-terunao/unity_toride

実際のゲームはこちら
PC 本ページ: http://ter.dip.jp/blog/unity_toride_old/build/web/web.html
PC 改良版: http://ter.dip.jp/blog/unity_toride/build/build.html
Android 本ページ: https://github.com/matsushima-terunao/unity_toride_old/raw/master/build/toride.apk
Android 改良版: https://github.com/matsushima-terunao/unity_toride/raw/master/build/toride.apk

MainScript

ゲーム全体を管理するスクリプトです。MainScript は特にオブジェクトに関連したスクリプトではありませんが、オブジェクトに関連付けないとスクリプトが動作しないので、ここでは Main Camera に関連付けます。
 Project タブ -> Create -> Javascript -> 名前を MainScript に変更
MainScript をダブルクリック -> MonoDevelop が起動するのでスクリプトを編集
MainScript を Hierarchy タブ -> Main Camera へドロップして関連付ける
スクリプト内に宣言した変数が Inspector タブ -> Game Main (Script) パネル内に表示されます。これをオブジェクトに関連付けます。(スクリプト中の青枠の部分)
 Hierarchy タブ -> canon(Player)Player の右の枠 へドロップ
 Hierarchy タブ -> canon(相手側)Other の右の枠 へドロップ
 Hierarchy タブ -> GUI TextLabel の右の枠 へドロップ



Update() 内で、ゲーム中以外ではラベルを表示し、マウスクリックまたは画面タッチでゲームをスタートさせます。

/**
 * ゲームメイン。
 * 
 * @author 2014/08 matsushima
 */

#pragma strict

/** ゲーム状態: 開始 */
static var STATE_START:int = 0;
/** ゲーム状態: ゲーム中 */
static var STATE_RUNNING:int = 1;
/** ゲーム状態: 勝ち */
static var STATE_WIN:int = 2;
/** ゲーム状態: 負け */
static var STATE_LOSE:int = 3;

/** ゲーム状態 */
static var state:int = STATE_START; // ゲーム状態: 開始
/** デバッグテキスト */
static var debugTexts:Hashtable = new Hashtable();
/** プレーヤー */ var player:Transform; /** 対戦相手 */ var other:Transform; /** テキストラベル */ var label:Transform;
function Start () { } function Update () { switch (state) { case STATE_START: // ゲーム状態: 開始 label.guiText.text = "START"; label.guiText.enabled = true; break; case STATE_RUNNING: // ゲーム状態: ゲーム中 break; case STATE_WIN: // ゲーム状態: 勝ち label.guiText.text = "YOU WIN"; label.guiText.enabled = true; break; case STATE_LOSE: // ゲーム状態: 負け label.guiText.text = "WIN LOSE"; label.guiText.enabled = true; break; } // click or touch start if (label.guiText.enabled) { if (Input.GetMouseButtonUp(0) || (Input.touchCount > 0) && (TouchPhase.Ended == Input.GetTouch(0).phase)) { state = STATE_RUNNING; // ゲーム状態: ゲーム中 label.guiText.enabled = false; player.GetComponent(cannonScript).Init(); other.GetComponent(cannonScript).Init(); } } } function OnGUI() { // デバッグテキスト表示 var y:int = 0; for (var k:Object in debugTexts.Keys) { GUI.Label(Rect(0, y * 20, 400, 20), debugTexts[k].ToString()); ++ y; } } static function Log(key:String, text:Object) { // // デバッグテキスト表示登録 // debugTexts[key] = text; // // デバッグテキストログ出力 // print(text); }

cannonScript

砲台を制御するスクリプトです。MainScript と同様の方法でスクリプトを Hierarchy タブの cannon(Player, 対戦相手両方)に関連付けます。同様に Inspector タブ上に表示されるスクリプト内変数のうち以下のものを関連付けます。
 Ball Prefab: Project -> Assets -> ball
 Particle Prefab: Project -> Assets -> Particle System
 Terrain: Hierarchy -> Terrain
Update() はフレームごとに定期的に呼び出されますが、Player と相手砲台それぞれで呼ばれます。それで、Inspector タブの Tag に Player が設定されているかを
	if (gameObject.CompareTag("Player")) {
で識別しています。
赤枠内は Player の入力判定処理です。前回押下状態なら移動量を求め、今回離されたなら発射フラグを立てます。
緑枠内の処理で移動量から Player の砲台の回転量を決め、回転させます。
青枠内は相手側砲台の処理です。相手側砲台と前回砲弾着点との角度と、Player 砲台と前回砲弾着地点との角度の差から、Y軸方向の回転量を決めます。また、相手側砲台と前回砲弾着点との距離と、Player 砲台と前回砲弾着地点との距離の差から、X軸方向の回転量を決めます。
オレンジ枠内は砲弾の発射処理です。まず、砲台の角度から砲弾の発射角度を求めておきます。そして、Instantiate() で砲弾の Prefab から砲弾を実体化します。
Object.Instantiate

static function Instantiate(original: Object, position: Vector3, rotation: Quaternion): Object;
static function Instantiate(original: Object): Object;
パラメータ
 original: 複製したい既存オブジェクト
 position: 新規オブジェクトの位置
 rotation: 新規オブジェクトの回転
戻り値
 original のオブジェクトをクローンしたうえで返り値とします

オブジェクトをエディタ上の Duplicate コマンドと同様の方法で複製します。
AddForce() で砲弾に発射角度の力を加えて移動させます。
Rigidbody.AddForce

AddForce(force: Vector3, mode: ForceMode = ForceMode.Force): void;
パラメータ
 force: 加える力のベクトル
 mode: どのように力を適用するか

AddForce(x: float, y: float, z: float, mode: ForceMode = ForceMode.Force): void;
パラメータ
 x: 加える力のベクトルの X 成分
 y: 加える力のベクトルの Y 成分
 z: 加える力のベクトルの Z 成分
 mode: どのように力を適用するか

Rigidbodyオブジェクトに力を加える。加えることでオブジェクトを動かすことができる。
数フレームに渡って力を加えるには Update でなく FixedUpdate の中で適用すべきです。
砲弾側のスクリプトの変数に砲台を代入します。「オブジェクト.GetComponent(スクリプト名).変数名または関数名」で別スクリプトの変数・関数にアクセスできます。
GameObject.GetComponent

GetComponent(type: Type): Component;
パラメータ
 コンポーネントの型

GetComponent(type: string): Component;
パラメータ
 コンポーネントの型の名前

コンポーネントを取得します。コンポーネントを取得することで、Rigidbodyクラスやスクリプトなどを参照したりすることが可能になります。
OnCollisionEnter() 内は砲台がほかのオブジェクトと衝突したときの処理です。砲弾と衝突した場合、破壊エフェクトを生成し、砲台が Player か相手かによって勝ち負けを判断します。
MonoBehaviour.OnCollisionEnter(Collision)

OnCollisionEnter(collision : Collision)
パラメータ
 collision: 接触地点、衝突強さなどの情報

collider/rigidbody がほかの collider/rigidbody に接触し始めたときに呼ばれる。
Init() は対戦開始時に MainScript から呼ばれる砲台初期化ルーチンです。

/**
 * 砲台。
 * 
 * @author 2014/08 matsushima
 */

#pragma strict

var BALL_FORCE:float = 1800;

/** 砲弾 */
var ballPrefab:Transform;
/** 爆発 */
var particlePrefab:ParticleSystem;
/** 爆発 */
var particle:ParticleSystem = null;
/** 地形 */
var terrain:Terrain;

/** 砲弾存在 */
var ballExists:boolean = false;
/** 砲弾位置 */
var ballPosition:Vector3;

/** マウスボタン */
var mouseDown:boolean = false;
/** マウス位置 */
var mousePos:Vector3;
/** タッチ */
var touchDown:boolean = false;
/** タッチ位置 */
var touchPos:Vector3;

function Start () {

}

function Update () {
	if (MainScript.STATE_RUNNING != MainScript.state) { // not ゲーム状態: ゲーム中
		return;
	}
	var angleResolution:float = 180.0 / Mathf.Min(Screen.width, Screen.height); // 操作回転量: 画面短辺 -> 180度
	var distance:Vector3 = Vector3.zero; // 入力移動量
	var fire:boolean = false; // 発射
	if (gameObject.CompareTag("Player")) {
		// 自分
// mouse drag: 移動量を求める var mousePosPrev:Vector3 = mousePos; mousePos = Input.mousePosition; if (mouseDown) { distance = mousePos - mousePosPrev; } // mouse down if (Input.GetMouseButtonDown(0)) { mouseDown = true; } // mouse up: 発射 if (Input.GetMouseButtonUp(0)) { fire = mouseDown; mouseDown = false; } // touch swipe var touchPosPrev:Vector3 = touchPos; if (Input.touchCount > 0) { var touch = Input.GetTouch(0); switch (touch.phase) { // touch down case TouchPhase.Began: touchDown = true; touchPos = touch.position; break; // touch swipe: 移動量を求める case TouchPhase.Moved: touchPos = touch.position; distance = touchPos - touchPosPrev; break; // touch up: 発射 case TouchPhase.Ended: fire = touchDown; touchDown = false; touchPos = touch.position; distance = touchPos - touchPosPrev; break; } }
// mouse or touch 移動量 -> 回転 // 縦方向: x軸回転 // 横方向: y軸回転 if (Vector3.zero != distance) { var angle:Vector3 = transform.eulerAngles - Vector3(distance.y, distance.x, 0) * angleResolution; angle.x = Mathf.Max(0, Mathf.Min(80, angle.x)); angle.y = Mathf.Max(80, Mathf.Min(190, (angle.y + 180) % 360)); // -100..10 angle.y = (angle.y + 180) % 360; transform.eulerAngles = angle; MainScript.Log("eulerAngles", "eulerAngles=" + angle + "," + transform.eulerAngles); }
} else {
// 相手 if (!ballExists) { // not 砲弾存在 var player:GameObject = GameObject.FindGameObjectWithTag("Player"); // 横回転量 = (プレイヤーとの角度 - 砲弾との角度) / 2 + ランダム var playerAngle = Mathf.Atan2( player.transform.position.z - transform.position.z, player.transform.position.x - transform.position.x) * 180 / Mathf.PI; var ballAngle = Mathf.Atan2( ballPosition.z - transform.position.z, ballPosition.x - transform.position.x) * 180 / Mathf.PI; distance.y -= (playerAngle - ballAngle) / 2 + Random.value * 2 - 1; // 縦回転量 = (プレイヤーとの距離 - 砲弾との距離) / 10 + ランダム var magnitude:float = (player.transform.position - transform.position).magnitude - (ballPosition - transform.position).magnitude; distance.x += magnitude / 10 + Random.value * 2 - 1; // 回転 transform.eulerAngles += Vector3(distance.x, distance.y, 0); // 発射。 fire = true; }
}
// 発射。 if (fire && !ballExists) { // 発射角度 var y:float = Mathf.Cos(2 * Mathf.PI * transform.eulerAngles.x / 360); var r:float = Mathf.Sin(2 * Mathf.PI * transform.eulerAngles.x / 360); var x:float = r * Mathf.Sin(2 * Mathf.PI * transform.eulerAngles.y / 360); var z:float = r * Mathf.Cos(2 * Mathf.PI * transform.eulerAngles.y / 360); angle = Vector3(x, y, z); // インスタンスの生成 var ball:Transform = Instantiate(ballPrefab, transform.FindChild("Cylinder").position + angle * 5, Quaternion.identity); ballExists = true; // 力を加える ball.rigidbody.AddForce(angle * BALL_FORCE); MainScript.Log("AddForce", "AddForce=" + angle * BALL_FORCE + "," + (angle * BALL_FORCE).magnitude); // 発射元の砲台 ball.GetComponent(ballScript).cannon = transform; }
} function OnCollisionEnter(collision: Collision) { MainScript.Log("cannon collision", "cannon collision: " + collision.gameObject.name + "," + collision.contacts[0].point); if ("ball(Clone)" == collision.gameObject.name) { // 砲台非表示 gameObject.SetActive(false); // 破壊エフェクト。 // インスタンスの生成 particle = Instantiate(particlePrefab, transform.position, Quaternion.identity); // 勝ち負け if (MainScript.STATE_RUNNING == MainScript.state) { MainScript.state = (gameObject.CompareTag("Player") ? MainScript.STATE_LOSE : MainScript.STATE_WIN); } } } /** * 初期化。 */ function Init() { ballExists = false; mouseDown = false; touchDown = false; // 破壊エフェクト破棄 if (null != particle) { Destroy(particle); particle = null; } // 砲台角度・位置 if (gameObject.CompareTag("Player")) { // プレイヤー transform.eulerAngles = Vector3(45, 315, 0); transform.position.x = 100 - 20 * Random.value; transform.position.z = 20 * Random.value; } else { // 対戦相手 transform.eulerAngles = Vector3(45, 135, 0); ballPosition = Vector3(100 * Random.value, 0, 100 * Random.value); transform.position.x = 20 * Random.value; transform.position.z = 100 - 20 * Random.value; } // 砲台の地形の高さ transform.position.y = terrain.terrainData.GetHeight( transform.position.x * terrain.terrainData.heightmapWidth / terrain.terrainData.size.x, transform.position.z * terrain.terrainData.heightmapHeight / terrain.terrainData.size.z) - transform.localScale.x; MainScript.Log("height", "height=" + transform.position.y); // 砲台表示 gameObject.SetActive(true); }

ballScript

砲弾を制御するスクリプトです。MainScript と同様の方法でスクリプトを Project タブの Assets 内の ball に関連付けます。
/**
 * 砲弾。
 * 
 * @author 2014/08 matsushima
 */

#pragma strict

/** 発射元の砲台 */
var cannon:Transform;

function Start () {

}

function Update () {
	// 高さ < 0 なら破棄
	cannon.GetComponent(cannonScript).ballPosition = transform.position;
	if (transform.position.y < 0) {
		Destroy(gameObject);
	}
}

function OnCollisionEnter(collision: Collision) {
	MainScript.Log("ball collision", "ball collision: " + collision.gameObject.name + "," + collision.contacts[0].point);
	// 地形なら破棄
	if ("Terrain" == collision.gameObject.name) {
		Destroy(gameObject, 3); // 3秒後に破棄
	}
}

function OnDestroy() {
	// 破棄されたら砲台側の砲弾生存フラグをクリア
	if (null != cannon) {
		cannon.GetComponent(cannonScript).ballExists = false; // 砲弾存在
	}
}

→ 2-3. Unity で砲台ゲームを作る - 改良
← 2-1. Unity で砲台ゲームを作る
↑ 一覧

2014年8月21日木曜日

2-1. Unity で砲台ゲームを作る

昔、ベーマガを読んでいた人には懐かしい、砦の攻防という砲台ゲームを作ってみます。

ソースはこちらで公開しています。
本ページ: https://github.com/matsushima-terunao/unity_toride_old
改良版: https://github.com/matsushima-terunao/unity_toride

実際のゲームはこちら
PC 本ページ: http://ter.dip.jp/blog/unity_toride_old/build/web/web.html
PC 改良版: http://ter.dip.jp/blog/unity_toride/build/build.html
Android 本ページ: https://github.com/matsushima-terunao/unity_toride_old/raw/master/build/toride.apk
Android 改良版: https://github.com/matsushima-terunao/unity_toride/raw/master/build/toride.apk

プロジェクトの作成

Unity 起動後、Unity - Project Wizard のダイアログ
(表示されない場合は、メニューの File -> New Project...)
Create New Project タブ -> Browse...
 Choose location for new project のダイアログで、
 プロジェクトを保存したいフォルダーに移動
 新しいフォルダ(ここでは toride)を作成
 このフォルダーを選択
 フォルダーの選択をクリック
Create をクリック
ここで Scene を保存しておきます。
 メニューの File -> Save Scene
 Save Scene ダイアログで、ファイル名(ここでは toride)を入力し、保存

地形を作る

Terrain 追加

Hierarchy タブ -> Create -> Terrain で Scene に地形が追加されます。
これだと地形が大きすぎるのでサイズを小さくします。
 Inspector タブ内の Terrain (Script) パネルの中のボタンが並んでいるツールバーの右端の歯車ボタンをクリック
Terrain Width, Length, Height をそれぞれ 100 に変更

視点変更

編集しやすいように、Scnene 内でマウスホィールや、手のひらボタン+ドラッグで Scene 内に収まるよう調整します。
ここで一度再生してみます。すると、横から見た視点になっているので、Main Camera の位置と方向を変更します。
このとき、Hierarchy や Scnene 上で Main Camera を選択した状態で、メニューの GameObject -> Align With View を選んでみてください。そうすると Scene で表示されている視点になるように、Main Camera が自動的に設定されます。
再度再生して確認してみてください。



ライトの追加

Hierarchy タブ -> Create -> Directional light
Scene にライトが追加されます。左側から光が当たるようにドラッグして移動します。



山を作る

 Hierarchy や Scnene 上で Terrain を選択
 Inspector タブ内の Terrain (Script) パネルの中のボタンが並んでいるツールバーの左端の山ボタンをクリック
Brushes から好きなブラシを選択
こうして Scene 内の Terrain 上でドラッグすると地形を盛り上げることができます。



Shift を押しながらドラッグすると逆に盛り下げることができます。しかし 0 の高さ未満には盛り下げることはできません。そのときは、あらかじめ
 Inspector タブ内の Terrain (Script) パネルの中のボタンが並んでいるツールバーの左から2番目の山ボタンをクリック
Height に好きな数値(ここでは 10)を入力して、Flatten をクリック
こうすることで地形全体をかさ上げすることができます。



Texture を貼る

あらかじめ素材をインポートしておきます。
 Project タブ内の Assets を右クリック -> Import New Package -> Terrain Assets
 Importing package ダイアログで Import をクリック
実際に地形に Texture を貼ります。
 Inspector タブ内の Terrain (Script) パネルの中のボタンが並んでいるツールバーの真ん中の筆ボタンをクリック
Edit Textures... -> Add Texture...
 Add Terrain Texture ダイアログで Texture -> Select
  Select Texture2D ダイアログで好きな Texture(ここでは Grass (Hill))をダブルクリック
Add



Texture で塗る

同様に Edit Textures... -> Add Texture... で Texture を追加し、Textures から Texture を選択し、ブラシで塗ることができます。

木を植える

 Inspector タブ内の Terrain (Script) パネルの中のボタンが並んでいるツールバーの木ボタンをクリック
Edit Trees... -> Add Tree...
 Add Tree ダイアログで Tree の右端の○ボタンをクリック
  Select GameObject ダイアログで好きな Texture(ここでは Palm)をダブルクリック
Add
これで地形をクリックしていくことで木を植えることができます。ただ、この状態だと木が大きすぎると思います。その場合は Inspector タブの Settings の値を調節してください。



砲台を作る

階層化されたオブジェクトを作る

球と筒を組み合わせて砲台を作ります。このように、複数のオブジェクトを組み合わせてキャラクターを作る場合は、オブジェクトを階層化すると扱い易くなります。この場合、空のオブジェクトを親にして球や筒を子にする方法や、球を親にして筒を子にする方法がありますが、ここでは前者の方法で作ってみます。
 メニューの GameObject -> Create Empty
 Hierachy タブ -> GameObject を右クリック -> Rename -> cannon に変更
 Hierachy タブ -> Create -> Sphere
 Hierachy タブ -> Create -> Cylinder
Sphere と Cylinder を cannon にドロップ
 Sphere を選択して大きさを変更
  Scale: X:10 Y:10 Z:10
 Sphere を選択して位置と大きさを変更
  Position: X:0 Y:6 Z:0
  Scale: X:2 Y:2 Z:2



当たり判定をつける

スクリプトでの当たり判定のために canon に Ragidbody をつけます。ただし、重力で下に移動しないように Use Gravity を off、地形などに接触したときにはじかれないように Is Kinematic を on にします。
 Hierarchy タブ -> cannon を選択
 Inspector タブ -> Add Component -> Physics -> Rigidbody
 Rigidbody -> Use Gravity のチェックを外す
 Rigidbody -> Is Kinematic のチェックを入れる

敵砲台を作る

砲台をコピーして敵砲台を作ります。スクリプトで自分と敵との判別のために、自分の砲台に Player の Tag をつけます。
 Hierarchy タブ -> cannon を右クリック -> Duplicate
 Hierarchy タブ -> cannon(コピー元) を選択
 Inspector タブ -> Tag -> Player



砲弾を作る

Sphere で砲弾を作ります。
 Hierachy タブ -> Create -> Sphere
Sphere を右クリック -> Rename -> ball に変更
 大きさを変更
  Scale: X:2 Y:2 Z:2

重力をつける

 Inspector タブ -> Add Component -> Physics -> Rigidbody
 Rigidbody -> Use Gravity にチェックが入っていることを確認

Prefab 化する

砲弾はゲーム中にスクリプトで実体化するので、Prefab 化し、Scene から外します。
 Hierachy タブ -> ball を Project タブ -> Assets にドロップ
 Hierachy タブ -> ball を右クリック -> Delete



炎エフェクトを作る

Particle System で砲台を破壊したときの炎エフェクトを作ります。
 Hierachy タブ -> Create -> Particle System
Particle の大きさを変更します。
 Inspector タブ -> Particle System -> Start Size: 10
色を変更します。
 Inspector タブ -> Particle System -> Color over Lifetime をクリックしてパネルを広げ、さらにチェックをつける
 Color 右のカラーバーをクリック
 Gradient Editor ダイアログで
  カラーバー下の矢印をクリックして選択
  (矢印はスライドでき、矢印横のスペースをクリックして矢印を追加できる)
  Color 右のカラーバーをクリック
 Color ダイアログで Colors 下のをクリック
 ダイアログ右上の×ボタンで閉じる

Prefab 化する

Particle System はゲーム中にスクリプトで実体化するので、Prefab 化し、Scene から外します。
 Hierachy タブ -> Particle System を Project タブ -> Assets にドロップ
 Hierachy タブ -> Particle System を右クリック -> Delete



テキストラベルを作る

ゲーム開始、終了時に表示するテキストのラベルを作ります。
 Hierachy タブ -> Create -> GUI Text
中央に大きく表示するように設定を変更します。
 Inspector タブ
  Position: X:0.5 Y:0.5 Z:0
  Anchor: middle center
  Alignment: center
  Font Size: 50
  Color: 好きな色



→ 2-2. Unity で砲台ゲームを作る - スクリプトの作成
← 1-1. Unity のインストールから簡単な使い方まで
↑ 一覧