○✕ゲームとは?
〇✖ゲーム(三目並べ)とは、シンプルで楽しい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();
}
}
}
それでは、長くなってしまったので今回はここまでにします。
次回は、終了判定・リセット処理から実装していきます。
最後までご覧いただきありがとうございました