Unityで作るシンプルマインスイーパー
こんにちは、ゲーム制作系VTuberのジェイです!今回はUnityを使って、シンプルなマインスイーパーを作成する手順をご紹介します。スクリプトの説明やエフェクトの追加、スコア管理まで、一通りの流れを分かりやすく解説しますので、ぜひ最後までお付き合いください。
ちなみに完成品はこちらから遊ぶことができます!
ステップ1: プロジェクトの作成
まずはUnity Hubから新しいプロジェクトを作成しましょう。
- Unity Hubを開き、新しいプロジェクトを作成します。
- プロジェクト名を「Minesweeper」とし、テンプレートとして「2D」を選択します。
- 「Create」ボタンをクリックしてプロジェクトを作成します。
ステップ2: スプライトの準備
ゲームに使用するスプライトを用意します。今回は、空のタイル、地雷、フラグ、数字1、2、3のタイルを使用します。
- スプライト画像をダウンロードし、Assetsフォルダ内にSpritesフォルダを作成してそこにインポートします。
- スプライト画像を選択し、InspectorウィンドウでSprite ModeをMultipleに変更します。
- Sprite Editorを開き、スプライトシートをそれぞれのタイルに分割します。
ステップ3: プレハブの作成
次に、タイルのプレハブを作成します。
- Hierarchyウィンドウで右クリックし、「2D Object」 -> 「Sprite」を選択します。
- スプライトオブジェクトをTileと名付け、InspectorウィンドウでSprote RenderコンポーネントのSpriteフィールドに空のタイルのスプライトを設定します。
- このスプライトオブジェクトをPrefabsフォルダにドラッグ&ドロップしてプレハブにします。
ステップ4: スクリプトの作成
Gridスクリプト
Assets/Scriptsフォルダに新しいスクリプトGrid.csを作成し、以下のコードを追加します。
Gird.csは、マインスイーパーのゲームグリッドを管理するスクリプトです。このスクリプトは、ゲームのタイルを生成し、ゲームオーバーの処理やスコア管理を行います。
using UnityEngine;
using System.Collections;
public class Grid : MonoBehaviour
{
public int width = 10; // グリッドの幅
public int height = 10; // グリッドの高さ
public GameObject tilePrefab; // タイルのプレハブ
private Tile[,] tiles; // タイルの配列
private GameManager gameManager; // GameManagerの参照
void Start()
{
gameManager = FindObjectOfType<GameManager>(); // GameManagerを見つける
GenerateGrid(); // グリッドを生成する
}
void GenerateGrid()
{
tiles = new Tile[width, height]; // タイルの配列を初期化
for (int x = 0; x < width; x++)
{
for (int y = 0; y < height; y++)
{
GameObject tile = Instantiate(tilePrefab, new Vector3(x, y), Quaternion.identity); // タイルを生成
tile.transform.parent = transform; // タイルをグリッドの子オブジェクトに設定
Tile tileComponent = tile.GetComponent<Tile>(); // Tileスクリプトを取得
bool isMine = Random.value < 0.15f; // 15%の確率で地雷を配置
tileComponent.Init(this, x, y, isMine); // タイルを初期化
tiles[x, y] = tileComponent; // 配列にタイルを保存
}
}
}
public Tile GetTile(int x, int y)
{
if (x >= 0 && y >= 0 && x < width && y < height)
{
return tiles[x, y]; // 指定位置のタイルを取得
}
return null; // 範囲外の場合はnullを返す
}
public void GameOver()
{
Debug.Log("Game Over. Reveal all mines.");
// 全ての地雷を表示する
for (int x = 0; x < width; x++)
{
for (int y = 0; y < height; y++)
{
Tile tile = tiles[x, y];
if (tile.isMine)
{
tile.GetComponent<SpriteRenderer>().sprite = tile.mineSprite;
}
}
}
// 5秒後にリセット
StartCoroutine(RestartAfterDelay(5f));
}
private IEnumerator RestartAfterDelay(float delay)
{
yield return new WaitForSeconds(delay);
gameManager.ResetScore(); // スコアをリセット
Restart(); // ゲームをリスタート
}
public void Restart()
{
// グリッドを再生成
foreach (Transform child in transform)
{
Destroy(child.gameObject); // 既存のタイルを削除
}
GenerateGrid(); // 新しいグリッドを生成
}
public void AddScore(int points)
{
gameManager.AddScore(points); // スコアを追加
}
}
Tileスクリプト
同様にして、Tile.csを作成し、以下のコードを追加します。
Tile.csは、各タイルの動作を管理するスクリプトです。このスクリプトは、タイルの状態(地雷かどうか、フラグが立てられているか、開かれているか)を管理し、タイルをクリックしたときの処理を行います。
using UnityEngine;
public class Tile : MonoBehaviour
{
private Grid grid; // グリッドの参照
public int x; // タイルのX座標
public int y; // タイルのY座標
public bool isMine; // タイルが地雷かどうか
public bool isRevealed = false; // タイルが開かれているかどうか
public bool isFlagged = false; // タイルにフラグが立てられているかどうか
public Sprite[] emptySprites; // 空のタイル用のスプライト配列
public Sprite mineSprite; // 地雷のスプライト
public Sprite flagSprite; // フラグのスプライト
public GameObject explosionEffectPrefab; // 爆発エフェクトのプレハブ
public AudioClip explosionSound; // 爆発音
public AudioClip flagSound; // フラグ音
private AudioSource audioSource; // オーディオソースの参照
public void Init(Grid grid, int x, int y, bool isMine)
{
this.grid = grid;
this.x = x;
this.y = y;
this.isMine = isMine;
audioSource = GetComponent<AudioSource>(); // オーディオソースを取得
}
void Update()
{
if (Input.GetMouseButtonDown(1)) // 右クリックを検出
{
Vector2 worldPoint = Camera.main.ScreenToWorldPoint(Input.mousePosition);
RaycastHit2D hit = Physics2D.Raycast(worldPoint, Vector2.zero);
if (hit.collider != null && hit.collider.gameObject == gameObject)
{
ToggleFlag(); // フラグを切り替える
}
}
if (Input.GetMouseButtonDown(0)) // 左クリックを検出
{
Vector2 worldPoint = Camera.main.ScreenToWorldPoint(Input.mousePosition);
RaycastHit2D hit = Physics2D.Raycast(worldPoint, Vector2.zero);
if (hit.collider != null && hit.collider.gameObject == gameObject)
{
OnLeftClick(); // 左クリック時の処理を実行
}
}
}
private void OnLeftClick()
{
if (isFlagged || isRevealed) return; // フラグが立っているか、既に開かれている場合は何もしない
if (isMine)
{
GetComponent<SpriteRenderer>().sprite = mineSprite;
Debug.Log("Boom! Game Over.");
Instantiate(explosionEffectPrefab, transform.position, Quaternion.identity); // 爆発エフェクトを生成
audioSource.PlayOneShot(explosionSound); // 爆発音を再生
grid.GameOver(); // ゲームオーバーをトリガー
}
else
{
Reveal(); // タイルを開く
}
}
public void Reveal()
{
if (isRevealed) return; // 既に開かれている場合は何もしない
isRevealed = true; // タイルを開く
int adjacentMines = CountAdjacentMines(); // 隣接する地雷の数を数える
GetComponent<SpriteRenderer>().sprite = emptySprites[adjacentMines]; // 適切なスプライトを表示
Debug.Log($"Safe. {adjacentMines} mines around.");
grid.AddScore(10); // スコアを追加
if (adjacentMines == 0)
{
RevealAdjacentTiles(); // 隣接タイルを開く
}
}
public int CountAdjacentMines()
{
int count = 0;
for (int dx = -1; dx <= 1; dx++)
{
for (int dy = -1; dy <= 1; dy++)
{
if (dx == 0 && dy == 0) continue;
Tile tile = grid.GetTile(x + dx, y + dy);
if (tile != null && tile.isMine)
{
count++;
}
}
}
return count;
}
private void RevealAdjacentTiles()
{
for (int dx = -1; dx <= 1; dx++)
{
for (int dy = -1; dy <= 1; dy++)
{
if (dx == 0 && dy == 0) continue;
Tile tile = grid.GetTile(x + dx, y + dy);
if (tile != null)
{
tile.Reveal(); // 隣接タイルを開く
}
}
}
}
private void ToggleFlag()
{
if (isRevealed) return; // 既に開かれている場合は何もしない
isFlagged = !isFlagged; // フラグを切り替える
GetComponent<SpriteRenderer>().sprite = isFlagged ? flagSprite : emptySprites[0]; // スプライトを更新
audioSource.PlayOneShot(flagSound); // フラグ音を再生
Debug.Log(isFlagged ? "Flagged" : "Unflagged");
}
}
GameManagerスクリプト
最後に、スコア管理を行うためにGameManager.csを作成します。
GamaManagerは、ゲーム全体のスコアを管理するスクリプトです。このスクリプトは、スコアの追加、リセット、およびスコアテキストの更新を行います。
using UnityEngine;
using UnityEngine.UI;
public class GameManager : MonoBehaviour
{
public Text scoreText; // スコアを表示するテキスト
private int score; // スコアの変数
void Start()
{
score = 0; // スコアを初期化
UpdateScoreText(); // スコアテキストを更新
}
public void AddScore(int points)
{
score += points; // スコアにポイントを追加
UpdateScoreText(); // スコアテキストを更新
}
private void UpdateScoreText()
{
scoreText.text = "Score: " + score.ToString(); // スコアテキストを更新
}
public void ResetScore()
{
score = 0; // スコアをリセット
UpdateScoreText(); // スコアテキストを更新
}
}
ステップ5: オーディオの追加
- AssetsフォルダにAudioフォルダを作成し、爆発音やフラグ音などのオーディオクリップをインポートします。
- Tileプレハブを選択し、AudioSourceコンポーネントを追加します。
- TileスクリプトのexplosioniSoundとflagSoundフィールドにそれぞれのオーディオクリップを設定します。
ステップ6: UIの作成
- Hierarchyウィンドウで右クリックし、「UI」->「Canvas」を選択します。
- Canvas内に
Text
オブジェクトを作成し、名前をScoreTextに変更します。 - ScoreTextのテキストフィールドに初期値として「Score: 0」と入力し、フォントやサイズを調整します。
ステップ7: 統合
- GameManagerオブジェクトを作成し、GameManagerスクリプトをアタッチします。
- ScoreTextフィールドにCanvas内のScoreTextオブジェクトをドラッグ&ドロップします。
- GridオブジェクトにGridスクリプトをアタッチし、Tile Prefab
b
フィールドにTileプレハブを設定します。
ゲームオーバーとゲームクリアの処理
このままでは、ゲームが終わらないので、ゲームオーバーとゲームクリアの処理を追加しましょう!
ステップ1: UIの作成
- Canvasの設定
- Hierarchyウィンドウで
ScoreText
とGameOverText
のText
コンポーネントを削除し、それぞれTextMeshPro - Text
コンポーネントを追加します。
- Hierarchyウィンドウで
- TextMeshProの設定
ScoreText
とGameStatusText
の見た目を調整します(フォント、サイズ、位置など)。
- GameStatusTextの追加
Canvas
に新しいTextMeshPro - Text
コンポーネントを追加し、名前をGameStatusText
に変更します。GameStatusText
の初期テキストを空にし、フォント、サイズ、位置を調整します。
ステップ2: GameManagerスクリプトの更新
GameManager
スクリプトを更新して、ゲームオーバーとゲームクリアのメッセージを表示する機能を追加します。
using UnityEngine;
using TMPro;
public class GameManager : MonoBehaviour
{
public TMP_Text scoreText; // スコアを表示するTextMeshProテキスト
public TMP_Text gameStatusText; // ゲームオーバーとゲームクリアを表示するTextMeshProテキスト
private int score;
void Start()
{
score = 0;
UpdateScoreText();
gameStatusText.text = ""; // 初期状態ではテキストを空にする
}
public void AddScore(int points)
{
score += points;
UpdateScoreText();
}
private void UpdateScoreText()
{
scoreText.text = "Score: " + score.ToString();
}
public void ResetScore()
{
score = 0;
UpdateScoreText();
gameStatusText.text = ""; // スコアリセット時にステータステキストを消す
}
public void ShowGameOver()
{
gameStatusText.text = "Game Over"; // ゲームオーバーテキストを表示
}
public void ShowGameClear()
{
gameStatusText.text = "Game Clear"; // ゲームクリアテキストを表示
}
}
ステップ3: Gridスクリプトの更新Grid
スクリプトを更新して、ゲームクリアのチェック機能を追加し、適切なメッセージを表示するようにします
using UnityEngine;
using System.Collections;
public class Grid : MonoBehaviour
{
public int width = 10;
public int height = 10;
public GameObject tilePrefab;
private Tile[,] tiles;
private GameManager gameManager;
void Start()
{
gameManager = FindObjectOfType<GameManager>(); // GameManagerを見つける
GenerateGrid(); // グリッドを生成する
}
void GenerateGrid()
{
tiles = new Tile[width, height];
for (int x = 0; x < width; x++)
{
for (int y = 0; y < height; y++)
{
GameObject tile = Instantiate(tilePrefab, new Vector3(x, y), Quaternion.identity);
tile.transform.parent = transform;
Tile tileComponent = tile.GetComponent<Tile>();
bool isMine = Random.value < 0.15f; // 15%の確率で地雷を配置
tileComponent.Init(this, x, y, isMine);
tiles[x, y] = tileComponent;
}
}
}
public Tile GetTile(int x, int y)
{
if (x >= 0 && y >= 0 && x < width && y < height)
{
return tiles[x, y];
}
return null;
}
public void GameOver()
{
Debug.Log("Game Over. Reveal all mines.");
gameManager.ShowGameOver(); // ゲームオーバーのテキストを表示
// 全ての地雷を表示する
for (int x = 0; x < width; x++)
{
for (int y = 0; y < height; y++)
{
Tile tile = tiles[x, y];
if (tile.isMine)
{
tile.GetComponent<SpriteRenderer>().sprite = tile.mineSprite;
}
}
}
// 5秒後にリセット
StartCoroutine(RestartAfterDelay(5f));
}
public void CheckGameClear()
{
// 全てのタイルが開かれ、かつ地雷を踏んでいない場合ゲームクリア
foreach (Tile tile in tiles)
{
if (!tile.isRevealed && !tile.isMine)
{
return; // まだ開かれていないタイルがある場合はリターン
}
}
Debug.Log("Game Clear. You win!");
gameManager.ShowGameClear(); // ゲームクリアのテキストを表示
// 5秒後にリセット
StartCoroutine(RestartAfterDelay(5f));
}
private IEnumerator RestartAfterDelay(float delay)
{
yield return new WaitForSeconds(delay);
gameManager.ResetScore(); // スコアをリセット
Restart();
}
public void Restart()
{
// グリッドを再生成
foreach (Transform child in transform)
{
Destroy(child.gameObject);
}
GenerateGrid();
}
public void AddScore(int points)
{
gameManager.AddScore(points);
}
}
ステップ4: Tileスクリプトの更新
Tile
スクリプトを更新して、タイルがすべて開かれたかどうかをチェックするようにします。
using UnityEngine;
public class Tile : MonoBehaviour
{
private Grid grid; // グリッドの参照
public int x; // タイルのX座標
public int y; // タイルのY座標
public bool isMine; // タイルが地雷かどうか
public bool isRevealed = false; // タイルが開かれているかどうか
public bool isFlagged = false; // タイルにフラグが立てられているかどうか
public Sprite[] emptySprites; // 空のタイル用のスプライト配列
public Sprite mineSprite; // 地雷のスプライト
public Sprite flagSprite; // フラグのスプライト
public GameObject explosionEffectPrefab; // 爆発エフェクトのプレハブ
public AudioClip explosionSound; // 爆発音
public AudioClip flagSound; // フラグ音
private AudioSource audioSource; // オーディオソースの参照
public void Init(Grid grid, int x, int y, bool isMine)
{
this.grid = grid;
this.x = x;
this.y = y;
this.isMine = isMine;
audioSource = GetComponent<AudioSource>(); // オーディオソースを取得
}
void Update()
{
if (Input.GetMouseButtonDown(1)) // 右クリックを検出
{
Vector2 worldPoint = Camera.main.ScreenToWorldPoint(Input.mousePosition);
RaycastHit2D hit = Physics2D.Raycast(worldPoint, Vector2.zero);
if (hit.collider != null && hit.collider.gameObject == gameObject)
{
ToggleFlag(); // フラグを切り替える
}
}
if (Input.GetMouseButtonDown(0)) // 左クリックを検出
{
Vector2 worldPoint = Camera.main.ScreenToWorldPoint(Input.mousePosition);
RaycastHit2D hit = Physics2D.Raycast(worldPoint, Vector2.zero);
if (hit.collider != null && hit.collider.gameObject == gameObject)
{
OnLeftClick(); // 左クリック時の処理を実行
}
}
}
private void OnLeftClick()
{
if (isFlagged || isRevealed) return; // フラグが立っているか、既に開かれている場合は何もしない
if (isMine)
{
GetComponent<SpriteRenderer>().sprite = mineSprite;
Debug.Log("Boom! Game Over.");
Instantiate(explosionEffectPrefab, transform.position, Quaternion.identity); // 爆発エフェクトを生成
audioSource.PlayOneShot(explosionSound); // 爆発音を再生
grid.GameOver(); // ゲームオーバーをトリガー
}
else
{
Reveal(); // タイルを開く
}
}
public void Reveal()
{
if (isRevealed) return; // 既に開かれている場合は何もしない
isRevealed = true; // タイルを開く
int adjacentMines = CountAdjacentMines(); // 隣接する地雷の数を数える
GetComponent<SpriteRenderer>().sprite = emptySprites[adjacentMines]; // 適切なスプライトを表示
Debug.Log($"Safe. {adjacentMines} mines around.");
grid.AddScore(10); // スコアを追加
grid.CheckGameClear(); // ゲームクリアをチェック
if (adjacentMines == 0)
{
RevealAdjacentTiles(); // 隣接タイルを開く
}
}
public int CountAdjacentMines()
{
int count = 0;
for (int dx = -1; dx <= 1; dx++)
{
for (int dy = -1; dy <= 1; dy++)
{
if (dx == 0 && dy == 0) continue;
Tile tile = grid.GetTile(x + dx, y + dy);
if (tile != null && tile.isMine)
{
count++;
}
}
}
return count;
}
private void RevealAdjacentTiles()
{
for (int dx = -1; dx <= 1; dx++)
{
for (int dy = -1; dy <= 1; dy++)
{
if (dx == 0 && dy == 0) continue;
Tile tile = grid.GetTile(x + dx, y + dy);
if (tile != null)
{
tile.Reveal(); // 隣接タイルを開く
}
}
}
}
private void ToggleFlag()
{
if (isRevealed) return; // 既に開かれている場合は何もしない
isFlagged = !isFlagged; // フラグを切り替える
GetComponent<SpriteRenderer>().sprite = isFlagged ? flagSprite : emptySprites[0]; // スプライトを更新
audioSource.PlayOneShot(flagSound); // フラグ音を再生
Debug.Log(isFlagged ? "Flagged" : "Unflagged");
}
}
ステップ5: Hierarchyでの設定
- GameManagerオブジェクトの作成
- Hierarchyウィンドウで右クリックし、「Create Empty」を選択して空のオブジェクトを作成し、「GameManager」と名前を付けます。
GameManager
オブジェクトにGameManager
スクリプトをアタッチします。ScoreText
フィールドに、Canvas内のScoreText
オブジェクトをドラッグ&ドロップします。GameStatusText
フィールドに、Canvas内のGameStatusText
オブジェクトをドラッグ&ドロップします。
これで、ゲームオーバー時に「Game Over」、ゲームクリア時に「Game Clear」のテキストが表示され、5秒後にゲームが初期化されて再プレイできるようになります。
これで、基本的なマインスイーパーが完成しました。タイルをクリックしてゲームを楽しんでください。ゲームオーバー時には5秒後にリセットされ、新しいゲームが始まります。スコアもリアルタイムで更新されるので、ぜひ挑戦してみてください!
以上が、Unityでマインスイーパーを作成する手順のブログ記事です。簡単なマインスイーパーの作り方でしたが、まだまだオリジナルの要素など入れる余地があるので、ぜひチャレンジして見てください!