Unity3Dで2Dシューティングゲームの作り方、パート3(移動する背景、敵集団、移動制限、タイトル画面、ゲームの流れ)

2Dシューティングゲームの作り方の続き、パート3です。

2Dシューティングゲームだから簡単に作り終わるだろうと思っていたら、基本的なことだけでも予想していたよりもいろいろ覚えることがあって、なかなか完成しません! がんばろう。

背景を作る

Unity Japan: 第06回 背景を作る

背景と言ってもとまっている背景じゃなくて、
シューティングゲーム見たく動いている背景です。
それを作ります。

Backgroundsという名前で空のGameObjectを作成します。

Frontを作る
GameObject → Create Other → Quad
作成されたQuadをFrontという名前に変更してBackgroundsの子要素にします。
BackgroundとFrontの位置をリセットして0にします。
FrontのScaleをX:8 Y:6 Z:1にします。

Assets → Materials
Background-FrontをドラッグしてシーンビューのFrontにドロップします。

MiddleとBackを作る
Frontと同じようにMiddleとBackを作ります。
Duplicateして画像を差し替えます。

3枚の位置を調整する
Frontの位置Z:0.1、Middleの位置Z:0.2、Backの位置Z:0.3に変更します。

背景をスクロールさせる
Backgroundスクリプトを作成して背景をスクロールさせます。

Backgroundスクリプト

using UnityEngine;

public class Background : MonoBehaviour {

	public float speed = 0.1f;

	// Update is called once per frame
	void Update () {
		float y = Mathf.Repeat (Time.time * speed, 1);

		Vector2 offset = new Vector2 (0,y);

		renderer.sharedMaterial.SetTextureOffset ("_MainTex", offset);
	}
}

BackgoundスクリプトをFront、Middle、Back全てにアタッチします。
FrontのSpeed:0.1、MiddleのSpeed:0.08、BackのSpeed:0.02に変更します。

これでPlayボタンを押すと3つのBackgroundが異なる速度でスクロールし続けます。

ここでUnity3Dの解説によると、
AssetsのTextureのbackground_back、background_middle、background_frontのWrap ModeがRepeatになっているか確認しろと言っています。ためしにClampにすると画像が途切れ途切れになりました。ただ、MaterialsにあるBackgroundとTextureにあるbackgroundの関係性がいまいちわかりません。。うーん、勝手に関係して設定されるものなんでしょうかね?

ここまでの感じ

背景をつけただけでゲームっぽくなりました。

Wave型の仕組み作り

Unity Japan: 第07回 Wave型の仕組み作り

Waveの作成
そもそもWaveとは何なのかと言うと、ここでは敵の集団の一塊のことです。Waveで敵3体をひとまとめにコントルールします。

空のGameObjectを作成してWaveという名前に変更して位置をリセットします。
EnemyオブジェクトのInspectorのPrefabのApplyを押してPrefabを更新します。
Enemyオブジェクトを3つに複製して、Waveの子要素にします。
3つのEnemyの位置を、それぞれX:-1 Y:1 Z:0とX:0 Y:0 Z:0とX:1 Y:1 Z:0に変更します。
WaveをPrefabにして、シーンビューにあるWaveは削除します。

Waveを呼び出すEmitterを作成する
Emitterというスクリプトを作成してWaveを呼び出します。

Emitterスクリプト

using UnityEngine;
using System.Collections;

public class Emitter : MonoBehaviour {

	public GameObject[] waves;
	private int currentWave;
	
	IEnumerator Start () {
		if(waves.Length == 0){
			yield break;
		}
		
		while(true){
			GameObject wave = (GameObject)Instantiate(waves[currentWave], transform.position, Quaternion.identity);
			
			wave.transform.parent = transform;
			
			while(wave.transform.childCount != 0){
				yield return new WaitForEndOfFrame();
			}
			
			Destroy(wave);
			
			
			if(waves.Length <= ++currentWave){
				currentWave = 0;
			}
		}
	}
}

空のGameObjectを作成してEmitterという名前に変更して位置をX:0 Y:3.5 Z:0に変更します。
EmitterオブジェクトにEmitterスクリプトをアタッチします。EmitterオブジェクトのWavesのSizeを1に変更して、PrefabのWaveをElement0にアタッチします。

ここまででPlayボタンを押してみます。

自機がうまいこと敵を3体とも倒すと新しいWaveが作成されて3体の敵が現れます。敵が下の画面外までいくと敵は自動的に削除され、新たにWaveが作成されるようになりました。

音をつける

Unity Japan: 第08回 音をつける

Assets → Sounds → BGM
bgmをシーンビューにドロップしてLoopにチェックします。
これでPlayボタンを押すとBGMが流れるようになります。

プレイヤーにショット音をつける
PlayerオブジェクトにAudio Sourceをアタッチします。
Assets/Sounds/SEのshootをAudio Clipにアタッチします。
Play On Awakeのチェックをはずします。
Volumeを0.3、Pitchを0.64に変更します。

Playerスクリプトを変更して弾を撃つごとに音を出すようにします。

Playerスクリプト

while(true){
	spaceship.Shot (transform);
	audio.Play (); #追加部分
	yield return new WaitForSeconds (spaceship.shotDelay);
}

これでPlayボタンを押すと自機が弾を撃つごとに弾の音がするようになります。

爆発音を付ける
PrefabのExplosionにAudio Sourceをアタッチして、Assets/Sounds/SEのboomをAudio Clipにアタッチします。これで、自機や敵機が爆発すると爆発の音が出るようになります。

以上で音のパートは完了です。

プレイヤーの移動制限と様々な修正

Unity Japan: 第09回 プレイヤーの移動制限と様々な修正

プレイヤーに移動制限つける
まずDestroyAreaの範囲を修正します。
DestroyAreaのBox Collider 2DのサイズをX:8 Y:6に変更します。

プレイヤーの移動方法を変更する
PlayerスクリプトでUpdateしたときに移動制限を付けてみます。

Playerスクリプト

void Update () {
	float x = Input.GetAxisRaw ("Horizontal");
	float y = Input.GetAxisRaw ("Vertical");

	Vector2 direction = new Vector2 (x, y).normalized;
	spaceship.Move (direction);

	Clamp ();
}

void Clamp (){
	Vector2 min = Camera.main.ViewportToWorldPoint (new Vector2 (0, 0));
	Vector2 max = Camera.main.ViewportToWorldPoint (new Vector2 (1, 1));
	Vector2 pos = transform.position;

	pos.x = Mathf.Clamp (pos.x, min.x, max.x);
	pos.y = Mathf.Clamp (pos.y, min.y, max.y);

	transform.position = pos;
}

この状態でPlayボダンを押して自機を画面外に動かすと、自機が消えてなくなります。つまりClampで移動を制限できていません。画面端に行くと、Clampで制限する前にOnTriggerEnter2Dが呼ばれてしまうためです。

これを防ぐためにはrigidbody2D.velocityで移動するのではなく、transform.positionで移動するようにします。

Playerスクリプト

void Update () {
	float x = Input.GetAxisRaw ("Horizontal");
	float y = Input.GetAxisRaw ("Vertical");

	Vector2 direction = new Vector2 (x, y).normalized;

	Move (direction);
}

void Move (Vector2 direction){
	Vector2 min = Camera.main.ViewportToWorldPoint (new Vector2 (0, 0));
	Vector2 max = Camera.main.ViewportToWorldPoint (new Vector2 (1, 1));
	Vector2 pos = transform.position;

	pos += direction * spaceship.speed * Time.deltaTime;

	pos.x = Mathf.Clamp (pos.x, min.x, max.x);
	pos.y = Mathf.Clamp (pos.y, min.y, max.y);

	transform.position = pos;
}

これで自機が画面外にいかなくなり、OnTriggerEnter2Dも呼ばれなくなりました。
ちなみにPlayerはRigidbody2Dを使わなくなったのでDestroyAreaと当たり判定を行わなくなっているらしいです。よくわかりません。敵や敵の弾とは当たり判定は引き続き行っています。

Spaceship.csとEnemy.csを修正する
PlayerでMoveを使わなくなったので、Spaceshipから共通のクラスMoveを削除して、EnemyだけにMoveを追加します。

Spaceshipスクリプトで削除するコード

public void Move (Vector2 direction) {
	rigidbody2D.velocity = direction * speed;
}

Enemyスクリプトの変更点

#spaceship.Move (transform.up * -1);
Move (transform.up * -1);

#IEnumerator Start ()の後に追加する
public void Move (Vector2 direction) {
	rigidbody2D.velocity = direction * spaceship.speed;
}

他のやり方で、Spaceship.csを抽象クラスとして扱う方法もあるそうです。よくわからないので、その存在だけ心にとめておきます。

タイトルを付ける

Unity Japan: 第10回 タイトルを付ける

タイトルの表示
空のGameObjectを作成してTitleという名前に変更して位置をリセットします。
GameObject → Create Other → GUI Textを作成してShooting Gameというタイトルに変更してTitleの子要素にします。Shooting Gameの位置をX:0.5 Y:0.65 Z:0に変更します。
Shooting GameオブジェクトのTextをShooting Game、Anchorをupper center、FontをAssetsのFontsからSAM_5C_27TRG_をドラッグしてドロップします。Font Sizeを60にします。

Shooting Gameオブジェクトを複製して、名前をPress Xに変更します。
Press XオブジェクトのTextをPress X、Positionを0.45に変更します。

「タイトル → ゲームスタート → 死んだら → タイトル」のマネージャークラスを作る
Playerオブジェクトの位置をX:0 Y:-2.5 Z:0に変更してPrefabをApplyして、Playerオブジェクトを削除します。

Managerの作成
空のGameObjectを作成してManegerという名前に変更します。
Managerスクリプトを作成します。

Managerスクリプト

using UnityEngine;

public class Manager : MonoBehaviour {

	public GameObject player;
	private GameObject title;

	void Start () {
		title = GameObject.Find ("Title");	
	}
	
	void Update () {
		if(IsPlaying() == false && Input.GetKeyDown (KeyCode.X)){
			GameStart();
		}	
	}

	void GameStart(){
		title.SetActive (false);
		Instantiate (player, player.transform.position, player.transform.rotation);
	}

	public void GameOver(){
		title.SetActive (true);
	}

	public bool IsPlaying(){
		return title.activeSelf == false;
	}
}

ManagerスクリプトをManagerオブジェクトにアタッチします。
PrefabのPlayerをManagerオブジェクトのPlayerにアタッチします。

他のスクリプトからマネージャーを呼び出す
Playerスクリプトで敵や敵の弾に当たったときにゲームオーバーになるように修正します。

Playerスクリプト

if(layerName == "Bullet(Enemy)" || layerName == "Enemy"){
	FindObjectOfType<Manager>().GameOver ();

	spaceship.Explosion ();
	Destroy (gameObject);
}

Emitterスクリプトを修正してゲームが始まるまで敵の出現を待機させます。

Emitterスクリプト

public GameObject[] waves;
private int currentWave;
private Manager manger; #この行を追加する

#Start()の変更箇所
manger= FindObjectOfType<Manager>();

while(true){
	while(manger.IsPlaying() == false){
		yield return new WaitForEndOfFrame();
	}

	#以下同じ
}

Playボタンを押すとタイトルとXを押せと言う画面が出てきます。
Xを押すとゲームが開始され、敵や敵の弾に当たると再びタイトルとXを押せと言う画面が出てきてXを押すと・・、この繰り返しになります。

敵の弾に当たったところ

以上でUnity3Dで2Dシューティングゲームの作り方、パート3の終了です。

ここがたぶん折り返し地点です!
残り半分がんばって作っていきましょう!

コメントを残す

メールアドレスが公開されることはありません。

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください