【Unity】2048ゲームオーバーの判定

操作系の処理も完成して?
バグとか出そうな気もするのですが…

ま、完成したと言うことにします。
(;^o^) \(ToT )あんたほんとにそれでいいの


いよいよ大詰めのゲームオーバーの処理を作って行こうと思います。

2048のゲームオーバーは
・全マスが埋まる
・ピースが動けない状態

この二つを判定する必要があるかと思います。


さて、どのタイミングで判定をするかが問題かと…
ピース移動の度に呼び出すと重くなりそうですし、
ピース生成の度に呼び出すのもちょっと…

少しフローもどきを見て流れを考えます
2048㉟.jpg
全ての流れはSelectPosition()で終了を迎えます。
更にこのメソッド

//空きマスからランダムに2カ所を選ぶ
public void SelectPosition()
{
List<int> list = new List<int>();
int num = 2;
int rot = 0;

for(int i = 0; i < 16; i++)
{
if (Flag[i] == false)
{
list.Add(i);
}
}

if (list.Count == 1)
{
num = 1;
}

while (rot < num)
{
int pos = list.GetAndRemoveAtRandom();
int x = pos % frame;
int y = pos / frame;

CreatePiece(x, y, 0);
rot++;
}
create = false;   
}

おあつらえ向きにマスの埋まり具合をリスト化しています。
これは使えそうです。

呼び出し部分は、ココで問題なさそうなので、
ピースの移動判定を考えてみます。

ピースは全ピース隣接するピースが同じかを判断する必要があるので、

//ピースの移動が出来るか判定する
void EndJudge()
{
for(int i = 0; i < 4; i++)
{
for(int a = 0; a < 4; a++)
{
SetNum[i, a]
}
}
}

こんな感じのループになります。
見るからに重そうです(;^_^A アセアセ・・・

SetNum[0,0]から順番にピースナンバーを取り出して、
次のピースと比較、取り出して比較…

こんな感じの繰り返しなんですが、これってどこかで見たような…
そうです!移動マス計算の時の処理に似ています。

流用できそうなので処理を作ってみます。

private const int hydepiece = 0; //判定用の隠しピース

//ピースの移動が出来るか判定する
void EndJudge()
{
//ピースナンバー仮取得する変数XY
int tempX=0;
int tempY=0;

for(int i = 0; i < 4; i++)
{
for(int a = 0; a < 4; a++)
{
//横軸の隣接ピースを確認する
if (SetNum[i, a] == tempX)
{
return;
}
else if (SetNum[i, a] == MAX_No)
{
tempX = hydepiece;
}
else
{
tempX = SetNum[i, a];
}

//縦軸の隣接ピースを確認する
if (SetNum[a, i] == tempY)
{
return;
}
else if (SetNum[a, i] == MAX_No)
{
tempY = hydepiece;
}
else
{
tempY = SetNum[a, i];
}
}
tempX = 0;
tempY = 0;
}
//ゲームオーバーなら呼び出す
メソッドや処理を書く
}

説明すると、
最初のループfor(int i = 0; i < 4; i++)で縦・横を数値化しています。
次のループfor(int a = 0; a < 4; a++)で列を数値化しています。

移動マス計算でも説明した、隣接ピースとの比較方法ですが、
ピースナンバー取り出し、仮変数に代入してループさせてます。

最後の方に変なtempX = 0; tempY = 0;を代入していますが、
これは、列の替わり目です。
for(int a = 0; a < 4; a++)で列の端まで行ったら、次の列に移動します。
その時にピースナンバーを持ち越すとおかしな事になるので、
リセットしています。

隣接してるピースが同じならループをreturnで抜けるようにしました。
これで少しは軽くなるかと思います。

ループが回り切ると隣接ピースも無い事が分かるので、
ゲームオーバーの処理となります。

移動マス計算でも導入しましたが、Maxピースナンバーを設定しているので、
Maxピースが並んで同じと判断されるとマズイので、hydepieceを用意しました。

これで、ピースが移動できるか判断できるので、
呼び出し側のSelectPosition()メソッドを修正します。

//空きマスからランダムに2カ所を選ぶ
public void SelectPosition()
{
List<int> list = new List<int>(); //空きマスを取得する変数
int num = 2; //ピースの生成回数
int rot = 0; //ピースの生成カウント
int limit=0; //空マスのカウント

for(int i = 0; i < 16; i++)
{
if (Flag[i] == false)
{
list.Add(i);
limit++;
}
}

//空きマスが一つならnumを修正する
if (list.Count == 1)
{
num = list.Count;
}

//ピースをnum回生成する
while (rot < num)
{
int pos = list.GetAndRemoveAtRandom();
int x = pos % frame;
int y = pos / frame;

CreatePiece(x, y, 0);
rot++;
}

//空マスが2つ以下なら移動できるか判定する
if (limit<=2)
{
ここで呼び出したいのだけれど…
}
create = false;
}

処理が増えて分かりにくくなったので、
コメントを入れてみました。

まず、変数limitを追加します。
ポジションから空きマスをリスト化している部分があるので、
空きマスの数をlimitにカウントします。

ここで注意なんですが、list.Countを使えばlimitでカウントする必要が
ないように思いますが、次の処理while (rot < num)で、
list.GetAndRemoveAtRandom();拡張メソッドを使っています。

これは、作成者の説明によると
ランダムに選択したリストの値を取得後、削除する。
との事です。

if (limit<=2)の部分をif (list.Count<=2)としたいのですが、
ピースの生成で削除されてるので、空きマスの数が変わって
しまいバグが出ます。
なので、while (rot < num)前でカウント出来る部分にlimitを仕込みました。


全マス埋まる状態は、空きマスが2つ以下の時なので、
limitが2つ以下ならEndJudge()メソッドを呼び出せば、
かなり負担も減らせそうでっす。(^ё^) ♪♪

と言う事でif (limit<=2)でEndJudge()メソッドを呼び出したいのですが…
実は、問題があります。

ピースは生成された時にポジション登録をするようになっています。
なので、生成後すぐに判定に移るとポジションが空になっている場合が…

流れですが、

  ピースの生成呼び出し:while (rot < num)
    ↓              ↓
移動判定呼び出し:EndJudge()   ピースの生成CreatePiece()
    ↓              ↓
移動可能かの判定         ピースの初期ポジション登録

かなり処理が並んでいます。
実際はピースの生成後にポジション登録されるので、
もしかすると移動判定より後に登録になる可能性もあります。

なので、ピースの生成を待つ為にウェイトを掛けて処理します。

//ピース生成まで待機して移動判定を呼び出す
IEnumerator waitJudge()
{
yield return new WaitForSeconds(0.2f);

EndJudge();
yield break;
}

計ってないのでハッキリした事は言えないのですが、
ピースの生成呼び出しから生成までが0.03秒前後、
ピース側からポジション登録されるまでが0.02秒前後、
2ピースでバラつきがあるとすれば0.1秒あれば十分対応できそうです。

ただ、少し余力を持たせて0.2秒のウェイトを掛ける事にします。

あとは

//空マスが2つ以下なら移動できるか判定する
if (limit<=2)
{
StartCoroutine("waitJudge");
}

コルーチンを呼び出せば完成でっす。

ゲームオーバーの処理を作って、
ついでにリスタートと終了ボタンを用意して…
PLAYしてみます。
Maxピースを2にしてPLAYしています。
無事に移動の判断ができております。

ゲームオーバーの処理が酷いのはスルーしてね(* v v)。ハズカシイ

これでビルドしてもスマホでも操作できるかと思います。


最後におまけなんですが、
リスタートとゲーム終了の処理を少々載せておきます。

//ゲームをリロードする
public void PushReStart()
{
for(int i = 0; i < 4; i++)
{
for(int a = 0; a < 4; a++)
{
SetNum[i, a] = 0;
}
}
create = false;
SceneManager.LoadScene(SceneManager.GetActiveScene().name);
}
//ゲームを終了する
public void PushGameEnd()
{
Application.Quit();
}

スマホでゲーム終了はコマンドを呼び出せばいいだけなのですが、

リスタートについては、少々注意点があります。
staticに設定しているSetNum[ , ]は、Sceneを変えてもリロードしても
残り続けると言う事です。

リスタートを導入される場合は、初期化する必要があります。
それとcreateはfalseに戻るようになっていますが、念の為初期化しとくと安心でっす。

なんとかPLAYできる所まで持ってこれました。
正直、ピースの移動処理を考えてる時は完成できないと思っていました。

考えればなんとかなるものですね~(^○^)v ワーイ

次はスマホでの実地になるかと思います。
あ!その前に得点の表示を忘れてました(;^_^A

ま、適当にこの辺を次回で処理していきたいと思います。
今日はこの辺で(^^)/~~デハデハ

この記事へのコメント