見出し画像

ゼロから始めるUnity開発 Part4~一筆書きゲームを作ってみる!【完成編】~

(ごめんなさい。うっかり公開するの忘れてました。去年10月に公開していたつもりでした。今更公開します。)

こんにちはハナミズキです。これまでPart1Part2を通して理解したUnityの知識を元に簡単なゲームアプリを1つ作ってみたのでPart3,Part4に区切ってチュートリアルとして紹介します。今回の記事はPart4です。

環境

Unity Version 2020.3.18f1 Personal

概要

Part3にも記載しましたが、今回作成するゲームアプリは「一筆書き」です。

一筆書きとは、最初から最後まで同じ所を2度と通る事なく全ての線を続けて書く作業のことです。

画像38

これをゲームにする場合、以下の仕様が重要になります。
 ・プレイヤーが歩いた所がわかる事
 ・同じところを2度通ったら、ゲームオーバー
 ・ゲーム性としての障害物(一筆で書くのに考える必要ある)
 ・最後まで描き切ったらゲームクリア

実装の流れ

 1. プロジェクトを新規作成する
 2. 床を自動生成させる
 3. 死の壁を自動生成させる
 4. プレイヤーを設置する
 5. プレイヤーを動かす
 6. メインカメラを設定する
 〜〜 今回の記事(Part4)にて解説 〜〜
 7. 床の塗り潰しでゲームクリアを実装する
 8. ゲームオーバー条件を実装する
 9. 次のゲーム開始(next game)を実装する
 10. ゲームの終了(Exit game)を実装する
 11. ビルドする

前回(1~6)の振り返りはPart3を参照

7. 床の塗り潰しでゲームクリアを実装する

まず猫が歩いたら床を塗り潰しできるように実装します。

[Assets]-[Script]を右クリックして、[Create]-[C# Script]を選択します。この時、空のC# Scriptが生成されるため、名前を”GroundController”に変更します。

画像1

GroundControllerをダブルクリックして、VSCodeを開き以下のように中身を書き換えます。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GroundController : MonoBehaviour
{
   void OnCollisionEnter(Collision collision)
   {
     if (collision.gameObject.tag == "Player") {
       // プレイヤーと接触した床の色を変更する
       // Debug.Log("Playerとの衝突2");
       GetComponent<Renderer>().material.color = Color.blue;
     }
   }
}

次に作成した[C# Script]のGroundControllerをGroundプレハブに関連づけます。

[Assets]-[Ground]をクリックして、[Inspector]に表示し、[Add Component]をクリックします。

画像2

次に[Scripts]を選択します。

画像3

先ほど生成したGroundControllerを選択します。

画像5

この状態で実行すると猫が歩くと床が青色になることが確認できます。

画像5

次に全てを塗りつぶしたら、ゲームクリアが表示されるようにします。

[GameObject]-[UI]-[Canvas]を選択してください。するとCanvasとEventSystemが追加されます。

次に追加された[Canvas]を右クリックして、[UI]-[Text]を選択します。

画像6

Canvasの下にTextが追加されるため名前をGameClearLabelに変更します。そのほかゲームクリア時の文字の見た目を[Inspector]を利用して調整していきます。

全体サイズを600x400に変更します。

画像7

テキスト文字列を以下のように変更します。

Game CLEAR!

Next Game >>

FontやFontStyle、Font Size、Colorを好みの見た目に変更します。

画像8

今回はゲームクリア時に以下のような見た目がポップアップされるようにしました。

画像9

ゲームクリア時にのみ表示されて欲しいため、名前横のチェックボックスを外して、状態を非表示にします。

画像10

次に全ての床を塗りつぶしたら、ゲームクリアのテキストが表示されるように実装します。

まず初めに[Assets]-[Ground]プレハブを選択し、[Inspector]でTagを”Ground”に変更します。

初期値では”Ground”タグは存在しないため、[Add Tag...]を利用して追加します。

画像14

+ボタンをクリックして、TagNameに”Ground”と入力しSaveします。

画像15

これでGroundタグが選択できるようになるため選択して保存します。

画像16

[Assets]-[Script]を右クリックして、[Create]-[C# Script]を選択します。この時、空のC# Scriptが生成されるため、名前を”GameController”に変更します。

画像11

GameControllerをダブルクリックして、VSCodeを開き以下のように中身を書き換えます。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GameController : MonoBehaviour
{
   // 関連引数としてゲームクリアラベル指定
   public GameObject gameClearLabelObject;
   
   // Update is called once per frame
   void Update()
   {
       GameObject[] grounds = GameObject.FindGameObjectsWithTag ("Ground");
       int mountGroundCount = 0;
       foreach (GameObject ground in grounds) {
         Color nowGroundColor = ground.GetComponent<Renderer>().material.color;
         if ( nowGroundColor == Color.blue ){
           mountGroundCount ++;
         }
       }
       if ( grounds.Length == mountGroundCount ) {
         // ゲームクリア
         gameClearLabelObject.SetActive (true);
       }
   }
}

生成している床の数と、青い床の数が一致したらゲームクリアのラベル(gameClearLabelObject)を表示するロジックです。

これらを関連づけます。

[GameObject]-[Create Empty]を選択してください。するとGameObjectが追加されるので、名前とTagを"GameController"に変更します。

画像12

追加した[GameObject]の”GameController”へ、先ほど生成した[C# Script]の”GameController”をドラック&ドロップして関連づけます。

続けて[C# Script]の”GameController”の引数として存在しているGame Clear Label Objectに対して、 ”GameClearLabel”をドラック&ドロップして関連づけます。

画像13

この状態で実行すると猫が全ての床の色を変更すると、ゲームクリアの文字列が表示されるようになりました。

画像17

8. ゲームオーバー条件を実装する

ゲームオーバー用のラベルを追加します。

[Canvas]を右クリックして、[UI]-[Text]を選択します。Textが追加されるため名前をGameOverLabelに変更し細かいパラメータを[Inspector]で変更します。

テキストの枠サイズを630x400に変更します。

画像18

テキスト文字列を以下のように変更しますs。

~ Game Over ~

Next Game >>

FontやFontStyle、Font Size、Colorを好みの見た目に変更します。今回はゲームオーバー時に以下のような見た目で表示されるようにします。

画像19

こちらもゲームクリア時と同じく、ゲームオーバー時に表示されて欲しいため、名前横のチェックボックスを外して、状態を非表示にします。

画像21

次にゲーム性として以下の条件を追加します。
 ・同じところを2度通ったら、ゲームオーバー
 ・ゲーム性としての障害物に触れたら、ゲームオーバー

猫が一度歩いた床は青色に変わっているので、この青色の床を判断条件にします。

GroundControllerをダブルクリックして、VSCodeを開き以下のように中身を書き換えます。
 ・プレイヤー(猫)が乗っている床の上は青色に変更
 ・青色の床にプレイヤー(猫)が乗らなくなったら黒色の床に変更
 ・黒色の床にプレイヤー(猫)が乗ったら、床を消滅

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GroundController : MonoBehaviour
{
   void OnCollisionEnter(Collision collision)
   {
     if (collision.gameObject.tag == "Player") {
       if (GetComponent<Renderer>().material.color == Color.black) {
         // 既に乗ったことのある床に再度乗った。
         // 床を消滅
         Destroy(gameObject);
       } else {
         // プレイヤーと接触した床の色を変更する
         // Debug.Log("Playerとの衝突2");
         GetComponent<Renderer>().material.color = Color.blue;
       }
     }
   }
   void OnCollisionExit(Collision collision)
   {
     if (collision.gameObject.tag == "Player") {
       // プレイヤーと接触したあとに離れた床の色を変更する
       //Debug.Log("Playerとの衝突がおわった");
       if (GetComponent<Renderer>().material.color == Color.blue) {
         GetComponent<Renderer>().material.color = Color.black;
       }
     }
   }
}

床が消滅したらプレイヤー(猫)は重力に従い落ちていきます。

画像21

この時、プレイヤー(猫)のY座標が下がるため、この性質を利用します。

PlayerControllerをダブルクリックして、VSCodeを開き以下のように中身を書き換えます。
 ・状態がゲームクリアの場合は猫を動かせない。
 ・状態がゲームオーバーの場合も猫を動かせない。
 ・プレイヤー(猫)のY座標が-15以下になったら、ゲームオーバーとする。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerController : MonoBehaviour
{
   // 関連引数としてゲームクリアラベル指定
   public GameObject gameClearLabelObject;
   // 関連引数としてゲームオーバーラベル指定   
   public GameObject gameOverLabelObject;

   // Update is called once per frame
   void Update()
   {
     if ( gameOverLabelObject.activeSelf ) {
         // ゲームオーバー状態である
         return;
     }
     if ( gameClearLabelObject.activeSelf ) {
         // ゲームクリア状態である
         return;
     }

     Vector3 pos = transform.position;
     Vector3 catAngle = transform.eulerAngles;

     // Upキーで前に進む
     if (Input.GetKey("up")) {
         pos.z += 0.1f;    // z座標へ0.1加算
         catAngle.y = 0;
     }
     // Downキーで後ろに進む
     if (Input.GetKey("down")) {
         pos.z -= 0.1f;    // z座標へ0.1減算
         catAngle.y = 180;
     }
     //right キーで右に進む
     if (Input.GetKey("right")) {
         pos.x += 0.1f;    // x座標へ0.1加算
         catAngle.y = 90;
     }
     //left キーで左に進む
     if (Input.GetKey("left")) {
         pos.x -= 0.1f;    // x座標へ0.1減算
         catAngle.y = 270;
     }
     //スペースキーで上に進む(ジャンプ)
     if (Input.GetKey(KeyCode.Space)) {
         pos.y += 0.1f;    // y座標へ0.1加算
     }

     transform.position = pos;  // 座標を設定
     transform.eulerAngles = catAngle; // 回転角度を設定

     // プレイヤーの座標が-15以下になったらゲームオーバーラベルを表示
     if(pos.y <= -15) {
       // ゲームオーバー
       gameOverLabelObject.SetActive (true);
     }
   }
}

[C# Script]  ”PlayerController”に引数gameOverLabelObjectと、gameClearLabelObjectを追加したので関連づけます。

画像22

この状態で実行すると同じ床に2回乗ったら床が消滅して、ゲームオーバーが表示されるようになります。

画像23

次に、障害物に触れたらゲームオーバーする仕組みを追加します。
Part3にて作成していた「死の壁」に触れたら即ゲームオーバーとなるようにします。

[Assets]-[Script]を右クリックして、[Create]-[C# Script]を選択します。この時、空の[C# Script]が生成されるため、名前を”DeathWallController”に変更します。

画像24

DeathWallControllerをダブルクリックして、VSCodeを開き以下のように中身を書き換えます。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class DeathWallController : MonoBehaviour
{
   // 関連引数としてゲームオーバーラベル指定
   public GameObject gameOverLabelObject;

   // オブジェクトと接触した時に呼ばれるコールバック
   void OnCollisionEnter (Collision collision)
   {
     // 接触したオブジェクトのタグが"Player"の場合
     if (collision.gameObject.CompareTag ("Player")) {
       // ゲームオーバー
       gameOverLabelObject.SetActive (true);
     }
   }
}

次に作成した[C# Script]のDeathWallControllerをDeathWallプレハブに関連づけます。

[Assets]-[DeathWall]をクリックして、[Inspector]に表示し、[Add Component]をクリックします。

画像25

次に[Scripts]を選択します。

画像3

先ほど生成した”DeathWallController”を選択します。

画像27

なお、DeathWallプレハブを介して動的にDeathWallが生成されるため、ゲームオーバーラベルであるGameOverLabelの関連づけはプログラム上から行う必要があります。

床と壁を自動生成するロジックが含まれたGroundsControllerをダブルクリックして、VSCodeを開き以下のように中身を書き換えます。
 ・死の壁を設置しているロジックに引数追加するよう対応

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GroundsController : MonoBehaviour
{
   // 関連引数として床プレハブ指定
   public GameObject groundPrefab;
   // 関連引数として死の壁プレハブ指定
   public GameObject deathWallPrefab;
   // 関連引数としてゲームオーバーラベル指定
   public GameObject gameOverLabelObject;

   // Start is called before the first frame update
   void Start()
   {
       // 配置する回転角を設定
       Quaternion q = new Quaternion();
       q= Quaternion.identity;

       // 5x5の正方形床をつくる
       int groundMax = 5;
       int groundStartIndex = -2;
       int groundBaseSize = 2;
       // 床初期配置する座標を設定
       // 起点 x:-2,y:0, z:-2
       Vector3 groundPosition = new Vector3(groundStartIndex,0,groundStartIndex);


       // 死の壁をランダムに配置するように座標計算
       List<int> deathWallIndexs = new List<int>();
       for(int i=groundStartIndex; i < (groundBaseSize*(groundMax-1)) ; i+=groundBaseSize){
         deathWallIndexs.Add(i);
       }
       int deathWall_x = GetRandom(deathWallIndexs);
       int deathWall_z = GetRandom(deathWallIndexs);

       // ステージ配置
       for (int zIdx = 0; zIdx < groundMax; zIdx++)
       {
           for (int xIdx = 0; xIdx < groundMax; xIdx++)
           {
               if ( groundPosition.x == deathWall_x && groundPosition.z == deathWall_z) {
                   // 死の壁を設置
                   // TODO:現在決め打ち1個/難易度調整の場合は要調整
                   groundPosition.y = 1;
                   GameObject o = Instantiate(deathWallPrefab, groundPosition, q) as GameObject;
                   DeathWallController s = o.GetComponent<DeathWallController>();
                   s.gameOverLabelObject = gameOverLabelObject;  // 関連引数:ゲームオーバーラベルを指定
               } else {
                   // 床を設置
                   groundPosition.y = 0;
                   Instantiate(groundPrefab, groundPosition, q);
               }
               groundPosition.x += groundBaseSize;
           }
           groundPosition.x = groundStartIndex;
           groundPosition.z += groundBaseSize;
       }
   }

   // Listから要素をランダムで1つ取得する
   public static T GetRandom<T>(List<T> list)
   {
     return list[ UnityEngine.Random.Range(0, list.Count) ];
   }

}

[Hierachry]-[Grounds]を選択し、”GroundsController”の引数として存在しているGame Over Label ObjectへgameOverLabelObjectをドラック&ドロップして関連づけます。

画像28

この状態で実行するとプレイヤー(猫)が死の壁に当たった瞬間に、ゲームオーバーになることが確認できます。

画像29

なお、今回の追加修正が影響し、ゲームクリアの条件を満たさなくなりました。

【上記のゲームクリアの条件】
 ・「生成した床の数」と「青い床の数」が一緒の場合、ゲームクリア

【今のゲームクリアの条件】
 ・ゲームオーバーしていないこと
 ・「生成他床の数」と「青と黒の床の数」が一緒の場合、ゲームクリア

GameControllerをダブルクリックして、VSCodeを開き以下のように中身を書き換えます。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GameController : MonoBehaviour
{
   // 関連引数としてゲームクリアラベル指定
   public GameObject gameClearLabelObject;
   // 関連引数としてゲームオーバーラベル指定   
   public GameObject gameOverLabelObject;
   
   // Update is called once per frame
   void Update()
   {
       if ( gameOverLabelObject.activeSelf ) {
         // ゲームオーバー状態である
         return;
       }

       GameObject[] grounds = GameObject.FindGameObjectsWithTag ("Ground");
       int mountGroundCount = 0;
       foreach (GameObject ground in grounds) {
         Color nowGroundColor = ground.GetComponent<Renderer>().material.color;
         if ( nowGroundColor == Color.blue ){
           mountGroundCount ++;
         } else if ( nowGroundColor == Color.black ){
           mountGroundCount ++;
         }
       }
       if ( grounds.Length == mountGroundCount ) {
         // ゲームクリア
         gameClearLabelObject.SetActive (true);
       }
   }
}

オブジェクトGameOverLabelを引数として関連づけます。

画像30

この状態で実行するとゲームクリアが再び動くようになることが確認できます。

画像31

9. 次のゲーム開始(next game)を実装する

キーボードでEnterを入力するとNext Gameが動くようにしましょう。

[Assets]-[Script]を右クリックして、[Create]-[C# Script]を選択します。この時、空の[C# Script]が生成されるため、名前を”GameLabelController”に変更します。

画像32

GameLabelControllerをダブルクリックして、VSCodeを開き以下のように中身を書き換えます。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement; // 追加

public class GameLabelController : MonoBehaviour
{
   // Update is called once per frame
   void Update()
   {
     if (gameObject.activeSelf && Input.GetKey(KeyCode.Return)) {
         // リターンキー押下でリセット
         // 現在のシーン番号を取得
         int sceneIndex = SceneManager.GetActiveScene().buildIndex;
         // 現在のシーンを再読込する
         SceneManager.LoadScene(sceneIndex);
     }
   }
}

次に作成した[C# Script]のGameLabelControllerをGameClearLabel、GameOverLabelにそれぞれ関連づけます。

画像33

画像34

ゲームオーバー時、ゲームクリア時、関連づけることで、どちらのケースに置いてもEnterキーを押下すると次のゲームを実行することができるようになります。

10. ゲームの終了(Exit game)を実装する

ゲームの終了はキーボードのESCキー押下でアプリ終了するようにしましょう。

GameControllerをダブルクリックして、VSCodeを開き以下のように中身を書き換えます。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GameController : MonoBehaviour
{
   public GameObject gameClearLabelObject;
   public GameObject gameOverLabelObject;
   
   // Update is called once per frame
   void Update()
   {
       if (Input.GetKey(KeyCode.Escape)){
           // ESCキー押下でゲーム終了
           Application.Quit();
       }

       if ( gameOverLabelObject.activeSelf ) {
         // ゲームオーバー状態である
         return;
       }

       GameObject[] grounds = GameObject.FindGameObjectsWithTag ("Ground");
       int mountGroundCount = 0;
       foreach (GameObject ground in grounds) {
         Color nowGroundColor = ground.GetComponent<Renderer>().material.color;
         if ( nowGroundColor == Color.blue ){
           mountGroundCount ++;
         } else if ( nowGroundColor == Color.black ){
           mountGroundCount ++;
         }
       }
       if ( grounds.Length == mountGroundCount ) {
         // ゲームクリア
         gameClearLabelObject.SetActive (true);
       }
   }
}

これでいつでもESCキー押下でゲーム終了できるようになります。

11. ビルドする

[File]-[Build Settings]を選択して、[Build]ボタンをクリックします。

【補足】
初回何も設定しなければ生成したアプリは全画面で表示されます。全画面で表示したくない場合は、[Player Settings]ボタンを押下し、[Player] > [Resolution and Presentation]の[Fullscreen Mode]の値を見直してください。

画像35

任意の名前をつけてSaveします。

画像36

これで完成です。
Buildしたアプリが指定フォルダに出力されます。

画像37

終わりに

いかがでしたでしょうか。
今回、簡単のゲームアプリを開発しながら、あれっってどうするんだろう、これってどうするんだろうって謎が沢山でてきたかと思います。

私の場合、これに、アプリのアイコンをつけたいなとか、作ってみたけど、シンプルすぎて勝率や連続クリア回数などを残しておきたいとか、そんなことを思いながら開発をしておりました。

よって、どこかでまた時間を作って気になったことを、一つ一つ解決していきたいと思います。

しかし、それは個人の趣味時間に取っておこうと思います。今回の記事を最後に「ゼロから始めるUnity開発パート」は終了としようと思います。

一つの足掛かりとして誰かの役に立てれば幸いです。

参考にしたサイト

■球転がしゲーム
https://github.com/unity3d-jp/FirstTutorial/wiki
 ・「ゲームのクリア」のロジックの参考にした。
 ・「ぶつかってはいけない壁」の設定にとても参考になった。
 ・「シーンの再読込」をNextGameとして利用できると気づけた。

■Unity ユーザーマニュアル
https://docs.unity3d.com/ja/2018.4/Manual/UnityManual.html
 ・基本的な用語の理解についてお世話になった。


以上です、ではでは!


この記事が気に入ったらサポートをしてみませんか?