0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

お題は不問!Qiita Engineer Festa 2024で記事投稿!
Qiita Engineer Festa20242024年7月17日まで開催中!

【Unity】〇✖ゲームをサクッと作ってみる ①

Last updated at Posted at 2024-07-13

○✕ゲームとは?

〇✖ゲーム(三目並べ)とは、シンプルで楽しい2人用のゲームです。
3×3のマス目に、交互に「〇」と「✖」のマークをつけて、縦・横・斜め
のいずれかに、自分のマークを3つ連続で並べることを目指すゲームです。


最初に、適当なスクリプトを適当なオブジェクトにアタッチしましょう。
今回は、このスクリプトで〇✖ゲームを全て実装していきます。(面倒なので

マス目の実装

まず、マス目を実装するにあたって、マス目を表示するためのキャンバスを作ります。
ヒエラルキー上で作成してもいいのですが、気が向いたのでスクリプトから作ります。
GridLayoutGroupコンポーネントがマス目の整列を担っています。

private void Start()
{
    // CanvasのGameObjectを作成
    GameObject canvasObj = new GameObject("Canvas");

    // Canvasコンポーネントを追加して設定する
    var canvas = canvasObj.AddComponent<Canvas>();
    canvas.renderMode = RenderMode.ScreenSpaceOverlay;

    // CanvasScalerコンポーネントを追加して設定する
    var canvasScaler = canvasObj.AddComponent<CanvasScaler>();
    canvasScaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
    canvasScaler.referenceResolution = new Vector2(1920, 1080);

    // GridLayoutGroupコンポーネントを追加して設定する
    var gridLayoutGroup = canvasObj.AddComponent<GridLayoutGroup>();
    gridLayoutGroup.cellSize = new Vector2(250, 250);
    gridLayoutGroup.spacing = new Vector2(25, 25);
    gridLayoutGroup.childAlignment = TextAnchor.MiddleCenter;
    gridLayoutGroup.constraint = GridLayoutGroup.Constraint.FixedRowCount;
}

次に、マス本体(Cell)を作成していきます。
キャンバスを作成する一連の処理はメソッドにまとめましょう。
その際、「gridLayoutGroup.constraintCount = Size;」を追記し、
行数と列数を自動的に調整できるようにします。

using UnityEngine;
using UnityEngine.UI;

public class TicTacToe : MonoBehaviour
{
    // キャンバス
    private Canvas canvas;

    // マス目のサイズ
    private const int Size = 3;

    // セルの配列
    private Image[,] cells;

    private void Start()
    {
        // キャンバスを作成
        SetUpCanvas();

        // セルの配列を初期化
        cells = new Image[Size, Size];

        for(int r = 0; r < Size; r++)
        {
            for(int c = 0; c < Size; c++)
            {
                // セルを作成して配列に登録する
                var cellObj = new GameObject();
                cellObj.transform.parent = canvas.transform;
                var cell = cellObj.AddComponent<Image>();
                cells[r, c] = cell;
            }
        }
    }

    private void SetUpCanvas()
    {
        // Canvasのオブジェクトを作成
        GameObject canvasObj = new GameObject("Canvas");

        // Canvasコンポーネントを追加して設定する
        canvas = canvasObj.AddComponent<Canvas>();
        canvas.renderMode = RenderMode.ScreenSpaceOverlay;

        // CanvasScalerコンポーネントを追加して設定する
        var canvasScaler = canvasObj.AddComponent<CanvasScaler>();
        canvasScaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
        canvasScaler.referenceResolution = new Vector2(1920, 1080);

        // GridLayoutGroupコンポーネントを追加して設定する
        var gridLayoutGroup = canvasObj.AddComponent<GridLayoutGroup>();
        gridLayoutGroup.cellSize = new Vector2(250, 250);
        gridLayoutGroup.spacing = new Vector2(25, 25);
        gridLayoutGroup.childAlignment = TextAnchor.MiddleCenter;
        gridLayoutGroup.constraint = GridLayoutGroup.Constraint.FixedRowCount;
        gridLayoutGroup.constraintCount = Size;
    }
}

セルの選択状態の実装

セルに〇や✕を付けるために、現在どのセルを選択しているか分かるようにしましょう。
具体的には、セルの選択情報を記録し、選択しているセルだけ色が変わるようにします。

using UnityEngine;
using UnityEngine.UI;

public class TicTacToe : MonoBehaviour
{
    // キャンバス
    private Canvas canvas;

    // マス目のサイズ
    private const int Size = 3;

    // セルの配列
    private Image[,] cells;

    // セルの色(非選択時)
    private Color defaultColor = Color.white;

    // セルの色(選択時)
    private Color selectedColor = Color.yellow;

    // 選択中の行数
    private int selectedRow;

    // 選択中の列数
    private int selectedColumn;

    private void Start()
    {
        // キャンバスを作成
        SetUpCanvas();

        // セルの配列を初期化
        cells = new Image[Size, Size];

        for(int r = 0; r < Size; r++)
        {
            for(int c = 0; c < Size; c++)
            {
                // セルを作成して配列に登録する
                var cellObj = new GameObject();
                cellObj.transform.parent = canvas.transform;
                var cell = cellObj.AddComponent<Image>();
                cells[r, c] = cell;
            }
        }
    }

    void Update()
    {
        // 入力に応じて選択中の行数・列数を変更
        if (Input.GetKeyDown(KeyCode.LeftArrow)) selectedColumn--;
        if (Input.GetKeyDown(KeyCode.RightArrow)) selectedColumn++;
        if (Input.GetKeyDown(KeyCode.UpArrow)) selectedRow--;
        if (Input.GetKeyDown(KeyCode.DownArrow)) selectedRow++;

        // 配列の範囲に収める
        if (selectedColumn < 0) selectedColumn = 0;
        if (selectedColumn >= Size) selectedColumn = Size - 1;
        if (selectedRow < 0) selectedRow = 0;
        if (selectedRow >= Size) selectedRow = Size - 1;

        // セルの色を変更する
        for (var r = 0; r < Size; r++)
        {
            for (var c = 0; c < Size; c++)
            {
                var cell = cells[r, c];
                cell.color = (r == selectedRow && c == selectedColumn) ? selectedColor : defaultColor;
            }
        }
    }

    private void SetUpCanvas()
    {
        // Canvasのオブジェクトを作成
        GameObject canvasObj = new GameObject("Canvas");

        // Canvasコンポーネントを追加して設定する
        canvas = canvasObj.AddComponent<Canvas>();
        canvas.renderMode = RenderMode.ScreenSpaceOverlay;

        // CanvasScalerコンポーネントを追加して設定する
        var canvasScaler = canvasObj.AddComponent<CanvasScaler>();
        canvasScaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
        canvasScaler.referenceResolution = new Vector2(1920, 1080);

        // GridLayoutGroupコンポーネントを追加して設定する
        var gridLayoutGroup = canvasObj.AddComponent<GridLayoutGroup>();
        gridLayoutGroup.cellSize = new Vector2(250, 250);
        gridLayoutGroup.spacing = new Vector2(25, 25);
        gridLayoutGroup.childAlignment = TextAnchor.MiddleCenter;
        gridLayoutGroup.constraint = GridLayoutGroup.Constraint.FixedRowCount;
        gridLayoutGroup.constraintCount = Size;
    }
}

〇と✖の実装

セルを選択して特定のキーを入力した際に、〇と✖を付けられるようにします。
様々な方法が考えられますが、今回は〇と✖のスプライトを用意して実装します。

using UnityEngine;
using UnityEngine.UI;

public class TicTacToe : MonoBehaviour
{
    // キャンバス
    private Canvas canvas;

    // マス目のサイズ
    private const int Size = 3;

    // セルの配列
    private Image[,] cells;

    // セルの色(非選択時)
    private Color defaultColor = Color.white;

    // セルの色(選択時)
    private Color selectedColor = Color.yellow;

    // 選択中の行数
    private int selectedRow;

    // 選択中の列数
    private int selectedColumn;

    // 〇のスプライト
    [SerializeField] private Sprite circle;

    // ✕のスプライト
    [SerializeField] private Sprite cross;

    private void Start()
    {
        // キャンバスを作成
        SetUpCanvas();

        // セルの配列を初期化
        cells = new Image[Size, Size];

        for(int r = 0; r < Size; r++)
        {
            for(int c = 0; c < Size; c++)
            {
                // セルを作成して配列に登録する
                var cellObj = new GameObject();
                cellObj.transform.parent = canvas.transform;
                var cell = cellObj.AddComponent<Image>();
                cells[r, c] = cell;
            }
        }
    }

    void Update()
    {
        // 入力に応じて選択中の行数・列数を変更
        if (Input.GetKeyDown(KeyCode.LeftArrow)) selectedColumn--;
        if (Input.GetKeyDown(KeyCode.RightArrow)) selectedColumn++;
        if (Input.GetKeyDown(KeyCode.UpArrow)) selectedRow--;
        if (Input.GetKeyDown(KeyCode.DownArrow)) selectedRow++;

        // 配列の範囲に収める
        if (selectedColumn < 0) selectedColumn = 0;
        if (selectedColumn >= Size) selectedColumn = Size - 1;
        if (selectedRow < 0) selectedRow = 0;
        if (selectedRow >= Size) selectedRow = Size - 1;

        // セルの色を変更する
        for (var r = 0; r < Size; r++)
        {
            for (var c = 0; c < Size; c++)
            {
                var cell = cells[r, c];
                cell.color = (r == selectedRow && c == selectedColumn) ? selectedColor : defaultColor;
            }
        }

        // セルのスプライトを変更する
        if (Input.GetKeyDown(KeyCode.Space))
        {
            var cell = cells[selectedRow, selectedColumn];
            cell.sprite = circle;
        }
    }

    private void SetUpCanvas()
    {
        // Canvasのオブジェクトを作成
        GameObject canvasObj = new GameObject("Canvas");

        // Canvasコンポーネントを追加して設定する
        canvas = canvasObj.AddComponent<Canvas>();
        canvas.renderMode = RenderMode.ScreenSpaceOverlay;

        // CanvasScalerコンポーネントを追加して設定する
        var canvasScaler = canvasObj.AddComponent<CanvasScaler>();
        canvasScaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
        canvasScaler.referenceResolution = new Vector2(1920, 1080);

        // GridLayoutGroupコンポーネントを追加して設定する
        var gridLayoutGroup = canvasObj.AddComponent<GridLayoutGroup>();
        gridLayoutGroup.cellSize = new Vector2(250, 250);
        gridLayoutGroup.spacing = new Vector2(25, 25);
        gridLayoutGroup.childAlignment = TextAnchor.MiddleCenter;
        gridLayoutGroup.constraint = GridLayoutGroup.Constraint.FixedRowCount;
        gridLayoutGroup.constraintCount = Size;
    }
}

シーンを実行してスペースキーを入力すると、選択しているセルに〇を付けられます。
しかし、画像の切り替え処理がないため、✕を付けることができません。
そこで、自分と相手のターン切り替え処理・スプライト切り替え処理を実装します。

自分と相手のターンの実装

自分と相手のターンの管理にはコルーチンを用います。
自分の入力を待つために、セルを変更するメソッドがBool型になっています。
終了判定とリセット処理が仮置きの状態ですが、これでリセットした場合も
ゲームフローが繰り返されるようになっています。

using UnityEngine;
using UnityEngine.UI;
using System.Collections;

public class TicTacToe : MonoBehaviour
{
    // キャンバス
    private Canvas canvas;

    // マス目のサイズ
    private const int Size = 3;

    // セルの配列
    private Image[,] cells;

    // セルの色(非選択時)
    private Color defaultColor = Color.white;

    // セルの色(選択時)
    private Color selectedColor = Color.yellow;

    // 選択中の行数
    private int selectedRow;

    // 選択中の列数
    private int selectedColumn;

    // 〇のスプライト
    [SerializeField] private Sprite circle;

    // ✕のスプライト
    [SerializeField] private Sprite cross;

    private void Start()
    {
        // キャンバスを作成
        SetUpCanvas();

        // セルの配列を初期化
        cells = new Image[Size, Size];

        for(int r = 0; r < Size; r++)
        {
            for(int c = 0; c < Size; c++)
            {
                // セルを作成して配列に登録する
                var cellObj = new GameObject();
                cellObj.transform.parent = canvas.transform;
                var cell = cellObj.AddComponent<Image>();
                cells[r, c] = cell;
            }
        }

        // ゲームフローを開始する
        StartCoroutine(GameFlow());
    }

    void Update()
    {
        // 入力に応じて選択中の行数・列数を変更
        if (Input.GetKeyDown(KeyCode.LeftArrow)) selectedColumn--;
        if (Input.GetKeyDown(KeyCode.RightArrow)) selectedColumn++;
        if (Input.GetKeyDown(KeyCode.UpArrow)) selectedRow--;
        if (Input.GetKeyDown(KeyCode.DownArrow)) selectedRow++;

        // 配列の範囲に収める
        if (selectedColumn < 0) selectedColumn = 0;
        if (selectedColumn >= Size) selectedColumn = Size - 1;
        if (selectedRow < 0) selectedRow = 0;
        if (selectedRow >= Size) selectedRow = Size - 1;

        // セルの色を変更する
        for (var r = 0; r < Size; r++)
        {
            for (var c = 0; c < Size; c++)
            {
                var cell = cells[r, c];
                cell.color = (r == selectedRow && c == selectedColumn) ? selectedColor : defaultColor;
            }
        }
    }

    // キャンバスのセットアップ
    private void SetUpCanvas()
    {
        // Canvasのオブジェクトを作成
        GameObject canvasObj = new GameObject("Canvas");

        // Canvasコンポーネントを追加して設定する
        canvas = canvasObj.AddComponent<Canvas>();
        canvas.renderMode = RenderMode.ScreenSpaceOverlay;

        // CanvasScalerコンポーネントを追加して設定する
        var canvasScaler = canvasObj.AddComponent<CanvasScaler>();
        canvasScaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
        canvasScaler.referenceResolution = new Vector2(1920, 1080);

        // GridLayoutGroupコンポーネントを追加して設定する
        var gridLayoutGroup = canvasObj.AddComponent<GridLayoutGroup>();
        gridLayoutGroup.cellSize = new Vector2(250, 250);
        gridLayoutGroup.spacing = new Vector2(25, 25);
        gridLayoutGroup.childAlignment = TextAnchor.MiddleCenter;
        gridLayoutGroup.constraint = GridLayoutGroup.Constraint.FixedRowCount;
        gridLayoutGroup.constraintCount = Size;
    }

    // 選択しているセルのスプライトを変更する
    private bool ChangeSelectedCellSprite()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            var cell = cells[selectedRow, selectedColumn];

            if (cell.sprite == null)
            {
                cell.sprite = circle;
                return true;
            }
        }
        return false;
    }

    // ランダムなセルのスプライトを変更する
    private void ChangeRandomCellSprite()
    {
        Image randomCell = GetRandomCell();

        while(randomCell.sprite != null)
        {
            randomCell = GetRandomCell();
        }

        randomCell.sprite = cross;
    }

    private int GetRandomRow() => Random.Range(0, Size);

    private int GetRandomCol() => Random.Range(0, Size);

    private Image GetRandomCell() => cells[GetRandomRow(), GetRandomCol()];

    // ゲームの終了判定を行う
    private bool IsGameOver()
    {
        return false;
    }

    // 自分のターン
    private IEnumerator PlayerTurn()
    {
        bool isTurnEnded = false;

        while (!isTurnEnded)
        {
            if (ChangeSelectedCellSprite())
            {
                isTurnEnded = true;
            }

            yield return null;
        }
    }

    // 相手のターン
    private IEnumerator EnemyTurn()
    {
        bool isTurnEnded = false;

        while (!isTurnEnded)
        {
            ChangeRandomCellSprite();
            isTurnEnded = true;

            yield return null;
        }
    }

    // ゲームをリセット
    private void ResetGame()
    {

    }

    // ゲームフロー
    private IEnumerator GameFlow()
    {
        while (true)
        {
            while (!IsGameOver())
            {
                yield return PlayerTurn();

                if (IsGameOver()) break;

                yield return EnemyTurn();
            }

            ResetGame();
        }
    }
}

それでは、長くなってしまったので今回はここまでにします。
次回は、終了判定・リセット処理から実装していきます。

最後までご覧いただきありがとうございました :laughing:

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?