学校へ急ごう！（2Dスクロールアクションゲーム）

# はじめに
こんにちは。もっちゃそです。
[Weekend Engineer(WE)](#weekend-engineerとは)の10月分の開発として、2Dスクロールアクションゲームを制作しました。
WEの開発プロダクトは、今回で2個目となります。

# デモ動画
以下のような流れで撮影しました。

* 敵をファイアボールで撃破しつつ普通にクリアー
* 敵に4回(ライフ0の状態で)ぶつかってゲームオーバー
* ステージから落下してゲームオーバー

![Qiita掲載データ.gif](https://qiita-image-store.s3.amazonaws.com/0/259787/954f486e-f78d-b353-9c3f-8b7d53bb10e1.gif)

# きっかけ
（[前回の記事](https://qiita.com/Mocchaso/items/ebccd2a98ceb50cfee20)と重複しますがご容赦ください・・・）
[当コミュニティ](#weekend-engineerとは)では以下のステップに従って経験を積んでいきます。

1. アイデア出し：渡辺さん、開発：メンバー(@Mocchaso)
2. アイデア出し・開発：メンバー + 渡辺さんのヘルプ
3. アイデア出し・開発：メンバー + 基本ヘルプ無し

ステップ2まで進み、いよいよ自分でアイデア出しをする段階に入りました。
来年度からゲーム制作の仕事をさせてもらえることになり、その経験を積もうと考えました。

「ゲーム制作といえばUnityが多いよな」→「前に3～4か月間触っただけだし実装しやすいゲームにするか」ということで、2Dスクロールアクションゲームを選択しました。
それだけだとオリジナリティが無いので、完全に苦し紛れですが、ストーリーを設定しました。

# 仕組み
### ストーリー
主人公が寝坊したので、授業が始まるまでに学校へ急ぐというもの。
画像と音楽の追加まで間に合わなかったため、ゲームやっただけだと全く分かりません・・・。

### ゲームのルール
ステージ右端の赤い物体に触れればクリアー、ステージから落下する or 時間切れになる or プレイヤーのライフが無くなるとゲームオーバーとなります。

操作方法は以下の通りです。

* 移動：左右の矢印キー
* ジャンプ：Spaceキー
* ファイアボール(攻撃)：fキー

### システム図
![システム図.jpg](https://qiita-image-store.s3.amazonaws.com/0/259787/3a14ed0c-9f90-0f12-71fb-3b9d36b8bed6.jpeg)

# 実装
### タイトル画面
ゲーム画面だけだとゲームっぽくないと思い、作成しました。
Spaceキーを押すとゲーム画面へ移るようにしました。

```c#:TitleManager.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;

public class TitleManager : MonoBehaviour {

	// Use this for initialization
	void Start () {
		
	}
	
	// Update is called once per frame
	void Update () {
		// Spaceキー押下でゲーム画面に遷移
		if (Input.GetKey(KeyCode.Space)) {
			SceneManager.LoadScene("Main");
		}
	}
}
```

### ゲーム画面
#### ステージ上の物体（プレイヤー、敵、床など）
Unityの各ビューを使い、よしなに配置しました。
![Unityエディタ.jpg](https://qiita-image-store.s3.amazonaws.com/0/259787/515f8f58-964e-7461-d933-d722abbc1c8f.jpeg)

#### ゲームクリアー
ステージ右端に赤いCubeオブジェクトGoalを配置し、`"Goal"`タグを付けました。
PlayerController.csでプレイヤーがGoalに触れたかどうかを検出し、GameManager.cs内のクリアーフラグを更新しています。
PlayerController.csをPlayerオブジェクトにアタッチすることで正常に動作しています。

* void OnTriggerEnter2D(Collider2D obj)  
別のオブジェクトに接触した際、objにそのオブジェクトの情報が格納されます。  
このメソッドで検出できるのは接触のみで、衝突などの物理現象は`void OnCollisionEnter2D(Collision2D collision)`を使うことで検出することができます。

```c#:PlayerController.cs
// 略
	void OnTriggerEnter2D(Collider2D obj) {
		if (obj.gameObject.tag == "Goal") { // ぶつかったオブジェクトの判別
			GameManager.isGoaled = true;
		}
	}
}
```

#### ゲームオーバー
ステージから落下する or 時間切れになる or プレイヤーのライフが無くなる状態を、以下のように検出しました。

ステージの下部に適当なオブジェクトを配置し、プレイヤーがそのオブジェクトに衝突したら落下していると見なします。

```c#:PlayerController.cs
/*
 * ステージから落下する
*/
// 略
	void OnCollisionEnter2D(Collision2D collision) {
		if (collision.gameObject.tag == "Enemy") { // ぶつかったオブジェクトの判別
			GameManager.damaged++;
		} else if (collision.gameObject.tag == "GameOver") {
			// 落下したらゲームオーバー
			GameManager.isGameOver = true;
		}
	}
// 略
```

時間切れに関しまして、次の前半部分で1秒ごとに残り時間の表示を更新しています。
それが0秒になったら時間切れとしています。
プレイヤーのライフ切れに関しましては、`PlayerController.cs`の`OnCollisionEnter2D`（上記参照）で敵との衝突を検出し、その都度`GameManager.cs`内のダメージ変数を更新しています。
ライフの最大値 - ダメージ変数の値 >= 0となったらライフ切れとしています。

```c#:GameManager.cs
/*
 * 時間切れになる、プレイヤーのライフが無くなる
*/
	void Update () {
		// 残り時間のカウントダウン
		countdown_timer -= Time.deltaTime;
		if (countdown_timer <= 0) {
			tmp_time -= 1; // 残り時間を1秒減らす
			time_text.text = "TIME: " + tmp_time + " s"; // 残り時間の表示を更新
			if (tmp_time == 0) {
				// 残り時間が0になったらゲームオーバー
				isGameOver = true;
			}
			countdown_timer = 1f; // タイマーのリセット
		}

		// 残りライフの表示を更新
		if (lifemax - damaged >= 0) {
			life_text.text = "LIFE: " + (lifemax - damaged);
		} else {
			// 残りライフが0になったらゲームオーバー
			isGameOver = true;
		}

		if (isGameOver || isGoaled) {
			// ゲームオーバー or ゲームクリア時にリザルト画面に遷移
			SceneManager.LoadScene("Result");
		}
	}
```

#### ファイアボールを撃つ
発射のキー入力の実装は、[タイトル画面](#タイトル画面)のSpaceキー押下部分と概ね同じです。
fキーが押されたら、Prefab（プレハブ）化したファイアボールのゲームオブジェクトをインスタンス化します。

* Instantiate(gameObject, transform.position, transform.rotation)  
`transform.position`の位置、`transform.rotation`の回転で、プレハブ化しているgameObjectをインスタンス化する

```c#:PlayerController.cs
// このメソッドをUpdate()で呼び出している
	void ShootFireBall() {
		// fキー押下でファイアボールを撃つ
		if (Input.GetKeyDown("f")) {
			Instantiate(fireball, transform.position, transform.rotation);
		}
	}
```

#### プレイヤーのテクスチャの向きを対応させる（+α）
現在のプレイヤーの向きと1フレーム前のプレイヤーの向きを比較し（横方向のみ）、
向きが変わっていたらx軸方向のスケールを-1倍します。
これにより、プレイヤーが向いている向きに応じてテクスチャの向きも反転します。
※向きが変わったと判定するのは、設置している時のみに限定しています

```c#:PlayerController.cs
// このメソッドをUpdate()で呼び出している
	void ReversePlayerSprite() {
		if (isLeft != prev_isLeft) { // 現在のプレイヤーの向きが1フレーム前と異なっていたら
			Vector3 now_scale = transform.localScale;
			now_scale.x *= -1;
			transform.localScale = now_scale;
		}
	}
```

### リザルト画面
タイトル画面と同様の経緯で作成しました。
Spaceキーを押すとタイトル画面へ移るようにしました。
実装コードは[タイトル画面](#タイトル画面)と同じなので割愛させていただきます。

### 参考にしたサイト
調べたサイトが多いため、1つのセクションとしてまとめたいと思います。

#### ゲームの全体像  
* [【第７回、８回】2Dスクロールゲームの例とゲームの作り方 ](https://openbook4.me/projects/161/sections/1077)  
  こちらのプロジェクトをベースにしました。
* [【Unity】2D横スクロールアクションゲームのキャラクター移動方法](https://tama-lab.net/2017/09/%E3%80%90unity%E3%80%912d%E6%A8%AA%E3%82%B9%E3%82%AF%E3%83%AD%E3%83%BC%E3%83%AB%E3%82%A2%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3%E3%82%B2%E3%83%BC%E3%83%A0%E3%81%AE%E3%82%AD%E3%83%A3%E3%83%A9%E3%82%AF/)  
  コードを綺麗にするため、こちらのコーディングスタイルを使わせていただきました。

#### ゲーム画面(メインシーン)
* [Unityでマリオっぽいゲームを作るのに必要な５つのこと](http://nn-hokuson.hatenablog.com/entry/2017/11/29/192514)  
  Spaceキー長押しでより高くジャンプさせるための実装にて(結局できなかった)。
* [Input.GetButtonとInput.GetButtonDownの入力の違い](http://inter-high-blog.unity3d.jp/2017/07/22/inputget/)   
  Spaceキー長押しでより高くジャンプさせるための実装にて(結局できなかった)。
* [Unityで特定のオブジェクトと衝突したときだけisTriggerをtrueにしたい](https://teratail.com/questions/113089)  
  ファイアボールを撃った時、プレイヤーをすり抜けるように実装しました。
* [【Unity】Unityで「時間」を扱うときに注意すべきこと](https://blog.mohyo.net/2017/12/2229/)  
  残り時間のカウントダウンの実装にて。

#### タイトル画面、リザルト画面
* [タイトル画面を作り、タイトルからゲーム画面に移る「シーン遷移」を作ってみよう](http://inter-high-blog.unity3d.jp/2017/08/02/scenetransition/)  
シーン遷移の実装を復習するため拝見しました。
* [スカイボックス（Skybox）でゲームの雰囲気をガラッと変えよう](http://inter-high-blog.unity3d.jp/2017/07/06/skybox/)  
シーンの背景の初期状態を、真っ青な淡色からSkyboxに変えたかったので拝見しました。
* [Canvas Scaler（キャンバススケーラー）で解像度を変えてもUIが崩れないようにしよう＆ビルドしてチェックしよう](http://inter-high-blog.unity3d.jp/2017/08/25/canvasscaler/)   
解像度の違いで、Canvas上のテキストUIの表示揺れが起こらないようにするため拝見しました。

# 最後に
### 感想
簡単なゲームでも1人でつくるのは大変でしたが、ゲーム制作には何が必要かを入念に考えることができました。ゲーム内容の企画、デザイン、サウンドの導入、コーディング、テストプレイなど。
前回よりも時間配分に気を付けることはできましたが、研究を優先していたためビジュアルやサウンドの導入、アイテムの設置までは至りませんでした・・・。
ただ、全ての工程を1人でこなす必要があったので、力はついたような気がします。

※コード全体はこちらをご参照ください
→ [Unity 2Dスクロールアクションゲーム：学校へ急ごう！](https://github.com/Mocchaso/2d_scrollgame)

### Weekend Engineerとは
渡辺大智さん (@ahpjop) 率いる開発コミュニティ。
「どんなアイデアもカタチにする」ことに焦点を置いて活動しています。
<b>[ホームページはこちら。](https://yutonakano.github.io/)</b>このホームページも、WEのメンバーが制作しました。