~ブロック崩しを作ろう~ 3.ゲームシーンを作る

2.ブロック崩しを作ろう
Screenshot

壁を作っていく

りんちゃん
りんちゃん

まずはGameシーンを開いて下さい。初めに壁を作ります。2D Object→Sprites→Squareと選択してください。そしてLeftという名前をつけます。Inspectorでの変更点についてはPosition X→-8.5 Scale Y→10とします。さらにBox Collider 2DをAdd Componentからアタッチします。すると上図のようにゲーム画面左側に白い縦の壁ができます。

りんちゃん
りんちゃん

次にLeftオブジェクトを右クリックしてDuplicateを選択します。するとLeftオブジェクトの複製物ができます。これをRightに名前を変更します。そしてInspectorでPosition X→8.5 に変更します。すると上図のようにゲーム画面右側に白い縦の壁ができます。

りんちゃん
りんちゃん

今度は上の壁を作ります。Leftオブジェクトを右クリックからDuplicateし、これをTopに名前を変更します。そしてInspectorでPosition X→0、Position Y→5、Scale X→16、Scale Y→1に変更します。すると上図のようにゲーム画面上側に白い横の壁ができます。

りんちゃん
りんちゃん

そして下の壁を作ります。Topオブジェクトを右クリックからDuplicateし、これをBottomに名前を変更します。そしてInspectorでPosition Y→-5に変更します。そして、このBottomに玉が接触するとゲームオーバーとしたいのでColorを赤に設定すると良いでしょう。すると上図のようにゲーム画面下側に赤い横の壁ができます。

りんちゃん
りんちゃん

また、このBottomオブジェクトについてタグの設定をします。例えばRedLineタグを用意しておいて、玉がRedLineタグがついたオブジェクトに接触したらゲームオーバー、という風にゲームのロジックを作ることができます。BottomオブジェクトにRedLineというタグをつける方法はInspector上部にあるTagの欄をクリックして、Add Tag…という項目を選択します。そして+アイコンを押して名前をつけてSaveボタンをクリックします。そしてHierarchyでBottomオブジェクトを選択してInspectorのTagの項目で先ほど作成したRedLineのタグを選択します。

パドルを作る

りんちゃん
りんちゃん

Hierarchyの空白スペースで右クリック→2D Object→Sprites→Squareと選択してください。そしてPaddleという名前をつけます。Inspectorでの変更点についてはPosition Y→-3、Scale X→2、Scale Y→0.5とします。さらにBox Collider 2DおよびRigidbody 2DをAdd Componentからアタッチします。すると上図のようなパドルができます。

りんちゃん
りんちゃん

ここでPaddleのInspectorのRigidbody 2DについてBody TypeをKinematicに変更しましょう。こうするとパドルを左右にシンプルに動かすのに適しています。そして、そのパドルを左右にシンプルに動かすためのスクリプトがこちらです。

using UnityEngine;

public class PaddleController : MonoBehaviour
{
    public float speed = 5f;

    void Update()
    {
        float horizontalInput = Input.GetAxisRaw("Horizontal"); // 左右の入力を取得 (GetAxisRawに変更)
        Vector2 position = transform.position;
        position.x += horizontalInput * speed * Time.deltaTime;

        // パドルが画面外に出ないように制限
        float halfWidth = GetComponent<SpriteRenderer>().bounds.size.x / 2f;
        float screenWidth = Camera.main.orthographicSize * Camera.main.aspect;
        position.x = Mathf.Clamp(position.x, -screenWidth + halfWidth, screenWidth - halfWidth);

        transform.position = position; // transform.positionを直接変更
    }
}
りんちゃん
りんちゃん

Assetsにて空のC#スクリプトを作成して名前をPaddleControllerとします。そしてファイルの内容をこのコードにします。

りんちゃん
りんちゃん

そしてPaddleのInspectorのAdd ComponentにPaddleControllerをドラッグ&ドロップして、このスクリプトをPaddleオブジェクトにアタッチします。ここでプレイボタンを押してテストプレイすると、パドルが左右キーに反応して動くと思われます。

玉を作る

りんちゃん
りんちゃん

Hierarchyの空白スペースで右クリック→2D Object→Sprites→Circleと選択してください。そしてBallという名前をつけます。さらにCircle Collider 2DおよびRigidbody 2DをAdd Componentからアタッチします。すると上図のようなボールができます。

りんちゃん
りんちゃん

ここでBallのInspectorのRigidbody 2DについてGravity Scaleを0に変更しましょう。こうすると玉に重力が影響しなくなります。

りんちゃん
りんちゃん

さらに玉の設定をしていきます。Assetsで右クリック→Create→2D→Physics Material 2Dと選択して下さい。そうすると黄緑のボールのようなアイコンが出てくるので、名前をPhisicsMaterial2Dとして下さい。

りんちゃん
りんちゃん

このPhisicsMaterial2DについてInspectorでFriction→0、Bounciness→1として下さい。Frictionは摩擦の設定で0にすると摩擦0、Bouncinessは跳ね返りの設定で1にすると減速無しで跳ね返り、となりブロック崩しの玉の挙動としてふさわしくなります。

りんちゃん
りんちゃん

このPhisicsMaterial2DについてBallのInspectorのCircle Collider 2DのMaterialにドラッグ&ドロップして下さい。これでBallオブジェクトにFrictionとBouncinessの設定を付与できます。

using UnityEngine;

public class BallController : MonoBehaviour
{
    public float speed = 5f;
    private Rigidbody2D rb;

    void Start()
    {
        rb = GetComponent<Rigidbody2D>();
        // 初期速度を与える
        rb.linearVelocity = Vector2.up * speed; // 上方向に発射
    }

    private void OnCollisionEnter2D(Collision2D collision)
    {
        if (collision.gameObject.CompareTag("RedLine"))
{
   // GameManager.Instance.GameOver();
}
        if(collision.gameObject.CompareTag("Block"))
        {
            Destroy(collision.gameObject);
           // GameManager.Instance.CheckForGameClear();
        }
    }
}
りんちゃん
りんちゃん

さらにこのスクリプトを用意します。名前はBallControllerとして下さい。初期速度を与えたり、RedLineタグのついたオブジェクトに接触するとゲームオーバーになる、Blockタグのオブジェクトに接触すると、そのオブジェクトを消すという設定が書かれています。

りんちゃん
りんちゃん

それではBallControllerスクリプトについてBallのInspectorのAdd Componentにドラッグ&ドロップしてアタッチしましょう。このときテストプレイをするとボールがゲーム画面を跳ね回ると思われます。

ブロックを作る

りんちゃん
りんちゃん

Hierarchyの空白スペースで右クリック→2D Object→Sprites→Squareと選択してください。そしてBlockという名前をつけます。そしてBoxCollider 2Dをアタッチして下さい。このBlockオブジェクトについてAssetsに設置してプレファブ化して下さい。この時HierarchyにあるBlockオブジェクトについては右クリックしてDeleteを選択して削除して下さい。

りんちゃん
りんちゃん

次にAssetsに設置されたBlockのプレファブについてHierarchyの空白スペースへドラッグ&ドロップして下さい。

りんちゃん
りんちゃん

Blockオブジェクトについて右クリックからDuplicateをして合計4つのBlockオブジェクトを作って下さい。そしてそれぞれのInspectorで位置を設定していきます。Block→[PositionX=-6、PositionY=3]、Block(1)→[PositionX=-3、PositionY=3]、Block(2)→[PositionX=3、PositionY=3]、Block(3)→[PositionX=6、PositionY=3]というふうに設定します。

りんちゃん
りんちゃん

次にAssetsにあるBlockプレファブについて次のように変更を加えて下さい。TransformのScale X→1.5、Scale Y→0.5、Color→黄緑など。そうするとHierarchyにあるBlockオブジェクトへ一斉に変更が反映されます。これがPrefab機能の強みの一つです。

りんちゃん
りんちゃん

そしてTagの設定をします。AssetsにあるBlockプレファブについてInspectorでAdd Tag…を選択し、Blockタグを追加、そして再度AssetsにあるBlockプレファブを選択してInspectorでBlockタグを選択して下さい。こうするとHierarchyのBlockオブジェクトにもBlockタグが付いているはずです。テストプレイをすると玉がブロックに当たると消滅すると思われます。

タイトルに戻るボタンを作る

りんちゃん
りんちゃん

ゲームオーバーあるいはゲームクリア時に出現する、タイトルに戻るボタンを作ります。Hierarchyの空白スペースで右クリック→UI→Canvasと選択していきます。

りんちゃん
りんちゃん

Canvasで右クリック→UI→Legacy→Buttonと選択します。このボタンオブジェクトの名前はToTitleとします。そして、このボタンのInspector設定についてはPosY→-300、Width→500、Height→150とし、ボタンの内側のテキストオブジェクトについてはText→タイトルへ、Font Size→48とします。

using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.EventSystems; // EventSystemを使用するために必要

public class BackToTitle : MonoBehaviour, IPointerClickHandler
{
    public string sceneName = "Title"; // 遷移先のシーン名

    public void OnPointerClick(PointerEventData eventData)
    {
        // クリックされた時の処理
        Debug.Log("Back to Title Button Clicked!");
        SceneManager.LoadScene(sceneName);
    }
}
りんちゃん
りんちゃん

そしてこのボタンにクリック時の挙動についてのスクリプトをつけていきます。Assetsで空のC#スクリプトを作り、名前をBackToTitleとします。そしてファイルの中身をこのコード内容にして保存します。このスクリプトについてボタンオブジェクトのInspectorのAdd Componentへドラッグ&ドロップしてアタッチして下さい。

using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

public class ResultCanvasController : MonoBehaviour
{
    public Canvas resultCanvas; // InspectorからCanvasをアサインするための変数
    public Text resultText; // リザルトテキスト
    public string titleSceneName = "Title"; // タイトルシーン名

    void Start()
    {
        // Canvasがアサインされているか確認
        if (resultCanvas == null)
        {
            Debug.LogError("Result Canvas is not assigned!");
            return;
        }

        // 初期状態ではCanvasを非表示にする
        resultCanvas.gameObject.SetActive(false);
    }

    public void ShowResult(bool isClear)
    {
        // リザルトテキストを設定
        if (resultText != null)
        {
            resultText.text = isClear ? "ゲームクリア!" : "ゲームオーバー…";
        }

        // Canvasを表示
        resultCanvas.gameObject.SetActive(true);
    }

    public void GoToTitle()
    {
        SceneManager.LoadScene(titleSceneName);
    }
}
りんちゃん
りんちゃん

また、Canvasオブジェクトについて普段は非表示、ゲームクリアまたはゲームオーバー時に表示されるようにスクリプトを用意します。そのスクリプトの内容がこちらです。Assetsで空のC#スクリプトを作り、名前をResultCanvasControllerとします。そしてファイルの中身をこのコード内容にして保存します。このスクリプトについてCanvasオブジェクトのInspectorのAdd Componentへドラッグ&ドロップしてアタッチして下さい。

りんちゃん
りんちゃん

するとCanvasオブジェクトのInspectorにResultCanvasControllerのコンポーネントが追加されます。Result CanvasとResult Textについての項目があり、ここにドラッグ&ドロップで適切なオブジェクトをアタッチしていくのですが・・・
その前にゲームの結果を表示するテキストオブジェクトを作る必要があります。Canvasオブジェクトで右クリック→UI→Legacy→Textと選択します。このテキストオブジェクトについて名前をResultTextとして、Width→800、Height→300としてFont Size→100、フォントカラーは黄色、中横揃えにして下さい。
こういうテキストオブジェクトを用意したら上図のようにCanvasオブジェクトのInspectorのResultCanvasControllerのコンポーネントのResult CanvasへCanvasオブジェクトを、Result TextへResultTextオブジェクトをドラッグ&ドロップでアタッチします。
テストプレイをすると、このCanvasは非表示設定なのでゲーム開始しても見えないのですが、今のままだとゲームクリアあるいはゲームオーバーになっても表示されません。それを管理するために後述のゲームマネージャーを作っていきます。

ゲームマネージャーの設定

りんちゃん
りんちゃん

続いてゲームマネージャーを作ります。ゲームマネージャーとはスコア管理やゲームクリアまたはゲームオーバー状態などゲームの状態を管理するためのオブジェクトです。Hierarchyの空白スペースで右クリックをしてCreate Emptyを選択して下さい。このオブジェクトの名前はGameManagerとして下さい。

using UnityEngine;
using UnityEngine.SceneManagement;

public class GameManager : MonoBehaviour
{
    public static GameManager Instance;

    public enum GameState { Playing, Clear, GameOver }
    public GameState currentGameState;

    private int blockCount;
    public ResultCanvasController resultCanvasController;

    void Awake()
    {
        if (Instance == null)
        {
            Instance = this;
            DontDestroyOnLoad(gameObject);
        }
        else
        {
            Destroy(gameObject);
        }
    }

    void OnEnable()
    {
        SceneManager.sceneLoaded += OnSceneLoaded;
    }

    void OnDisable()
    {
        SceneManager.sceneLoaded -= OnSceneLoaded;
    }

    private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
    {
        // ResultCanvasControllerの再取得
        GameObject resultCanvasObject = GameObject.FindGameObjectWithTag("ResultCanvas");
        if (resultCanvasObject != null)
        {
            resultCanvasController = resultCanvasObject.GetComponent<ResultCanvasController>();
            if (resultCanvasController == null)
            {
                Debug.LogError("ResultCanvasController component not found on the object with tag ResultCanvas");
            }
        }
        else
        {
            Debug.LogError("ResultCanvas object not found in the loaded scene.");
            resultCanvasController = null;
        }

        // Gameシーンがロードされた場合、ブロック数をリセット
        if (scene.name == "Game")
        {
            blockCount = GameObject.FindGameObjectsWithTag("Block").Length;
            Debug.Log("Block count reset to: " + blockCount); // デバッグ用ログ
        }
        // タイトルシーンがロードされた場合、ゲームの状態をリセット
        if (scene.name == "Title")
        {
            ResetGame();
        }
    }

    void Start()
    {
        currentGameState = GameState.Playing;

        //最初のシーンがGameの場合にのみ実行
        if (SceneManager.GetActiveScene().name == "Game")
        {
            if (resultCanvasController == null)
            {
                Debug.LogError("ResultCanvasController is not assigned!");
            }
        }
    }

    public void GameOver()
    {
        if (currentGameState != GameState.Playing) return;
        currentGameState = GameState.GameOver;
        Debug.Log("Game Over!");
        if (resultCanvasController != null)
        {
            resultCanvasController.ShowResult(false);
        }
    }

    public void GameClear()
    {
        if (currentGameState != GameState.Playing) return;
        currentGameState = GameState.Clear;
        Debug.Log("Game Clear!");
        if (resultCanvasController != null)
        {
            resultCanvasController.ShowResult(true);
        }
    }

    public void CheckForGameClear()
    {
        blockCount--;
        if (blockCount <= 0)
        {
            GameClear();
        }
    }

    private void ResetGame()
    {
        currentGameState = GameState.Playing;
        Debug.Log("Game state reset!"); // デバッグ用ログ
    }

    public bool IsPlaying()
    {
        return currentGameState == GameState.Playing;
    }

    public bool IsGameOver()
    {
        return currentGameState == GameState.GameOver;
    }

    public bool IsGameClear()
    {
        return currentGameState == GameState.Clear;
    }
}
りんちゃん
りんちゃん

そしてAssetsでC#スクリプトを新規作成して、名前をGameManager、中身をこのコードにしていきます。このスクリプトファイルをHierarchyにあるGameManagerオブジェクトのAdd Componentにドラッグ&ドロップしてアタッチして下さい。

りんちゃん
りんちゃん

次にGameManagerオブジェクトのInspectorにあるGameManagerコンポーネントについてResultCanvasControllerという項目にHierarchyのCanvasオブジェクトをドラッグ&ドロップしてアタッチします。

りんちゃん
りんちゃん

また、このGameManagerスクリプトはResultCanvasタグがついているものを取り扱う設定になっているので、HierarchyにあるCanvasオブジェクトについてResultCanvasタグをつけましょう。それと最後に・・・

りんちゃん
りんちゃん

AssetsにあるBallControllerのスクリプトファイルを開いて、GameManager~の頭についている//を削除します。(頭に//がついているとただのコメントとして扱われ、プログラムとして扱われない状態です。)実はBallControllerにGameManagerに関するコードの記述をしていたのですが、後で有効化した方が色々と都合がいいので//をつけてコメントアウトしていました。
これでゲーム制作について一通りできました。もちろん作業内容のセーブもお忘れなく!

テストプレイ

ツクルくん
ツクルくん

いや〜やること多くて大変だったなあ・・・

りんちゃん
りんちゃん

これで簡単なブロック崩しについては出来上がりました。それでは実際にプレイをしてみましょう。タイトルシーンを開いてからゲームプレイボタンをクリックして下さい。

りんちゃん
りんちゃん

このスタートボタンを押すと

Screenshot
ツクルくん
ツクルくん

お!ゲームがうまく動作している!

りんちゃん
りんちゃん

そしてゲームの結果として

りんちゃん
りんちゃん

ゲームオーバーになったり

りんちゃん
りんちゃん

ゲームクリアになればOKです。

ツクルくん
ツクルくん

タイトルへボタンを押すとタイトルシーンへ移動するね!そこからまたゲームスタートができる!

りんちゃん
りんちゃん

お疲れ様でした。今回は一からゲームを作る、をテーマに簡単なブロック崩しを作ってみました。もちろんこのブロック崩しを拡張して、ブロックの数を増やしたり、ステージを縦長にしたり、もっとステージを増やしたりなどができます。興味のある方はオリジナルのブロック崩しを作ってみて下さい!

コメント

タイトルとURLをコピーしました