ミニゲームを作ってUnityを学ぶ![3Dマインスイーパー編]
###第4回目: ブロックを操作する
前回は通常ブロックと爆弾ブロックを並べたフィールドを作成し、カメラがフィールドの中心を向くような実装を行いました。
今回はプレイヤーの入力によってブロックを開けたりマーカーをつけたりといった、ブロックを操作する仕組みを実装していきます。
#プレイヤーの入力を受け取る
まずはプレイヤーの入力を受け取れるようにSceneMainとBlockManagerを修正します。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class SceneMain : MonoBehaviour
{
private GameController mGame;
[SerializeField]
1: private BlockManager mBlock;
void Awake()
{
mGame = GameController.Instance;
mGame.Init();
}
//-------------
// 状態と更新 //
//---------------------------------------------------------------------------------
private enum STATE
{
DEFAULT = 0
}
private STATE mState = STATE.DEFAULT;
void Update()
{
追加 mBlock.CheckMouseInput();
switch (mState)
{
}
}
}
/// <summary>
/// マウスクリックを監視
/// </summary>
追加 public void CheckMouseInput()
{
if (Input.GetMouseButtonDown(0))
{
OnLeftClick();
}
else if (Input.GetMouseButtonDown(1))
{
OnRightClick();
}
}
private void OnLeftClick()
{
}
private void OnRightClick()
{
}
1: BlockManagerをインスペクタから設定
これでプレイヤーの左クリックと右クリックを受け取ることができるようになりました。
続いてクリックに対応したアクションを実装していきます。
#ブロックにマーカーをつける
右クリックでブロックにマーカーをつける機能を実装します。
- 新しいタグ「Block」を作り、Blockプレハブのタグに設定
- Blockプレハブの子要素「NumberPlane」のLayerを「Ignore Raycast」に設定
- GameControllerにタグの定数を追加
- BlockManagerを修正
// タグ
public static string TAG_BLOCK = "Block";
/// <summary>
/// 右クリック
/// 対象ブロックのチェックフラグを切り替える
/// </summary>
private void OnRightClick()
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if (Physics.Raycast(ray, out hit))
{
GameObject go = hit.collider.gameObject;
if (go.tag == GameController.TAG_BLOCK)
{
CheckBlock(go.GetComponent<BlockModel>());
}
}
}
private void CheckBlock(BlockModel target)
{
target.ChangeCheckFlg();
}
右クリックのタイミングでカメラの位置からマウスポインタの置かれた位置に向かって画面奥へ進むRayを飛ばし、Rayが接触したオブジェクトがブロックだった場合にはクリックの対象になったBlockModelのChangeCheckFlg()を呼び出しています。
そして呼び出されたBlockModel#ChangeCheckFlg()の内容がこちら。
/// <summary>
/// チェック済フラグを反転させる
/// それによってチェックマークを表示・非表示にする
/// </summary>
public void ChangeCheckFlg()
{
if (IsOpen) return;
IsCheck = !IsCheck;
if (IsCheck)
{
mNumChanger.ChangeUvToCheck();
}
else
{
mNumChanger.ChangeUvToBlank();
}
}
対象になったブロックがまだ開かれていない場合はプロパティのIsCheckを反転させ、その結果によってUVマップを変更することでブロックの上面にチェックマークを表示したり消したりしています。
#ブロックを開ける
続いて、左クリックでブロックを開ける機能を実装します。
- BlockManagerを修正
/// <summary>
/// 左クリック
/// 対象ブロックを開く
/// </summary>
private void OnLeftClick()
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if(Physics.Raycast(ray, out hit))
{
GameObject go = hit.collider.gameObject;
if(go.tag == GameController.TAG_BLOCK)
{
// 対象が爆弾ブロックか判定
BlockModel target = go.GetComponent<BlockModel>();
if (target.HasBomb)
{
// チェック済ならば何もしない
if (target.IsCheck) return;
// チェックしていないなら開いてゲームオーバー
//GameOver(target);
}else
{
// 爆弾でないならば一連の開く処理
OpenBlock(target);
// ゲームクリアの判定
//JudgeGameClear();
}
}
}
}
/// <summary>
/// 指定座標のブロックを取得する
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
private BlockModel GetBlock(int x, int y)
{
return mBlockList.FirstOrDefault(block => block.X == x && block.Y == y);
}
/// <summary>
/// 指定座標の隣接1マスにあるブロックを取得する
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
private List<BlockModel> GetAroundBlocks(int x, int y)
{
List<BlockModel> result = new List<BlockModel>();
// 定義済みデリゲート
Action<int, int> action = (posX, posY) =>
{
BlockModel model = GetBlock(posX, posY);
if (model != null) result.Add(model);
};
// 各座標をチェックしてリストに追加していく
action(x - 1, y - 1);
action(x - 1, y);
action(x - 1, y + 1);
action(x, y - 1);
action(x, y + 1);
action(x + 1, y - 1);
action(x + 1, y);
action(x + 1, y + 1);
return result;
}
private void OpenBlock(BlockModel target)
{
// 対象がすでに開かれている or チェック済の場合は何もしない
if (target.IsOpen || target.IsCheck) return;
// 対象ブロックの隣接1マスにあるブロックを取得
List<BlockModel> aroundBlocks = GetAroundBlocks(target.X, target.Y);
// 周囲の爆弾の数を取得
int bombCount = aroundBlocks.Count(block => block.HasBomb);
// 対象ブロックを開く
target.Open(bombCount);
// 周囲に爆弾が0だった場合に限り、隣接するブロックを連鎖的に開いていく
if(bombCount == 0)
{
foreach(BlockModel model in aroundBlocks)
{
if (!model.HasBomb) OpenBlock(model);
}
}
}
少しわかりづらいので1つずつ確認していきますが、今回の追記部分にはLINQやラムダ式、匿名メソッドといった要素が含まれていますので、もしつまづいてしまった場合は以下の紹介記事を参考にしてみてください。
参考: 【LINQの前に】ラムダ式?デリゲート?Func?な人へのまとめ
###OnLeftClick()
左クリックの入力を受け取ると、右クリックのときと同じようにRayを飛ばして対象になるブロックオブジェクトを取得します。
対象を取得できた場合はそのブロックに爆弾が設置されているかの判定を行い、通常のブロックだった場合にOpenBlock()を実行します。
もしも爆弾ブロックを左クリックしている場合はそこにマーカーが付いているかの判定を行い、マーカーが付いていない場合はゲームオーバー(今はコメントアウト)となります。
###GetBlock()
フィールド内のxy座標を引数に渡すことで該当ポジションに存在するBlockModelをListから取得して返します。
###GetAroundBlocks()
フィールド内のxy座標を引数に渡すことでその周囲1マス内にあるBlockModelを新しく生成したListに格納して返します。
###OpenBlock()
引数に与えられた対象のブロックがまだ開かれていなく、マーカーもつけられていない場合にBlockModel#Open()を呼び出しています。
BlockModel#Open()を呼び出す際には周囲1マスに設置されている爆弾の数を引数に与えることで対象ブロックの上面に適切なテクスチャを表示していますが、このとき周囲に爆弾が1つも設置されていない場合は周囲のブロックを対象にして再度BlockManager#OpenBlock()を実行します。
これによって爆弾が無いエリアのブロックが連鎖的に開いていくことになります。
#ゲームの挙動を確認する
以上でブロックの操作が完成しました。
まだゲームクリアやゲームオーバーの概念がなく爆弾ブロックを開くこともできませんが、プロジェクトを実行することでマインスイーパーのようなものが出来上がっていることが確認できます。