Unityでリバーシを作る

Unityでリバーシを作る4(駒を置く編)

どうも、りくらぼです。

前回は盤面を表示しました。

Unityでリバーシを作る3(盤面作成後編)どうも、りくらぼです。 今回はUnityでリバーシを作るの3回目、盤面作成の後編です。 前編はこちら https://...

今回は駒を置く処理について解説します。

前回大変だったので今回は短めです!

クリック時のイベントを追加する

前回は盤面を表示しました。盤面の1つ1つがボタンになっていて、マウスで押せるようになっています。現段階ではクリック時のイベントが設定されていないので何も起きません。

まずは、ボタンの1つ1つにイベントを設定しましょう。

手順
  1. どんなイベントを設定するか考える
  2. イベントを作成する
  3. ボタンにイベントを追加する

どんなイベントを設定するか考える

ボタンにイベントを設定するといっても、どんなイベントを設定するのでしょうか。まずはそれを考えてみましょう。

盤をクリックしたらどうなるのか、実際にリバーシをするときの動きを順を追って書くと以下のようになると思います。

  1. 駒を置きたい場所に駒を置く
  2. ひっくり返せる駒をひっくり返す

シンプルですね。まずは駒を置いて、次にひっくり返す。

これで、クリック時のイベントがはっきりしました。クリック時にまずやりたいことは、

クリックした場所に駒を置くことです!

イベントを作成する

それでは、クリックした場所に駒を置くイベントを作成しましょう。

GameControllerに次の関数を作りました。

    //駒を置く
    public void PutStone(string position)
    {
        Debug.Log(position); //コンソールに座標を表示
    }

1つずつ見ていきます。

まずはvoidの前にある「public」ですね。publicを付けるとGameControllerの外からこの関数を指定することができます。つまり、GameControllerをアタッチしていないprefabのボタンからもPutStone関数を指定することができます。

publicは「アクセス修飾子」といい、他にも「private」や「protected」、「アクセス修飾子なし」があります。それぞれアクセスできる範囲が異なり、必要に応じて使い分けていきます。

次に引数のstring positionです。stringは文字列型でしたね。

駒を置く関数なので、どこに駒を置くのかの「座標」が必要になります。この座標をボタンから引数として受け取ります。

そして受け取った座標をDebug.Logでコンソールに出力しています。ここは正しい座標が受け取れているのかの動作確認用です。

ここで、なぜ座標がint型ではなくstring型なのかについて説明します。

本来であれば引数は次のようにしたいところです。

public void PutStone(int x, int y)

x座標とy座標をそれぞれint型で受け取り、そのままboard[x,y]のように使いたいですね。

ですがUnityのボタンコンポーネントからは引数を1つしか渡すことができません。

そこでstring型にして「x,y」のように座標を,(カンマ)で区切って引数に渡すことにしました。

ボタンにイベントを追加する

それでは、作成したPutStone関数をボタンのクリックイベントに設定しましょう。

インスペクターから直接イベントを指定することもできますが、今回はスクリプトからprefabを作成しているのでこの方法は使えません。

スクリプトからボタンのクリックイベントを追加します。

ShowBoard関数に処理を追加しましょう。

 // boardの色に合わせて適切なPrefabを取得
GameObject piece = GetPrefab(board[h, v]);

 //座標を一時的に保持
int x = h;
int y = v;
//pieceにイベントを設定
piece.GetComponent<Button>().onClick.AddListener(() => { PutStone(x + "," + y); });

//取得したPrefabをboardDisplayの子オブジェクトにする
piece.transform.SetParent(boardDisplay.transform);

ポイントは以下の1行です。

piece.GetComponent<Button>().onClick.AddListener(() => { PutStone(x + "," + y); });

まず、「GetComponent<Button>()」ですが、これはpieceのボタンコンポーネントを取得しています。インスペクターでは画像の部分です。

button_component

なお、ButtonのようにUnityのUI関連の処理をする際にはusingに以下を追加する必要があります。

using UnityEngine.UI;

これを追加しないとエラーが出ますので注意してください。

次に、「OnClick」はButtonコンポーネントのクリックイベントのことです。

最後、「AddListener(() => { PutStone(x + “,” + y); });」の部分ですが、

クリックイベントにAddListenerの内容を指定しています。

「() => { PutStone(x + “,” + y); }」がイベントの内容です。

関数をラムダ式という方法で書いています。ラムダ式は関数をできるだけ簡単に書けるものです。詳しくは検索をお願いします。

何をしているのか簡単に言うと、PutStone関数を呼び出し、引数に(“x,y”)を指定しています。

x,yについてですが、ループに使われている変数i,jを使うとメモリの関係上全てのボタンにPutStone(8,8)が設定されてしまいます。そこでi,jの値を別の変数x,yに格納して使うことで正しい座標を設定できるようにしています。変数とメモリについての説明をすると大変になるのでここでは省きます。

かなり難しい説明になってしまいましたが、今全部を理解しなくても徐々にわかってくると思います。

ポイントだけまとめておきます。

  • GetComponentでアタッチされているコンポーネントにアクセスできる
  • ButtonコンポーネントはOnClick.AddListenerでクリックイベントを設定できる
  • ラムダ式

ここまでを保存して実行するとクリックした座標がコンソールに出力されると思います。

console

イベントに処理を追加する

正しい座標を取得することができたら、PutStone関数に駒を置く処理を追加しましょう。

以下の手順で駒を置く処理を追加していきます。

手順
  1. 黒の駒を置けるようにする
  2. 画面の盤面を更新できるようにする
  3. すでに駒がある場所には置けないようにする
  4. 黒と白を交互に置けるようにする

黒の駒を置けるようにする

 //駒を置く
    public void PutStone(string position)
    {
        //positionをカンマで分ける
        int h = int.Parse(position.Split(',')[0]);
        int v = int.Parse(position.Split(','));
        //クリックされた座標に駒を置く
        board[h, v] = COLOR.BLACK;
    }

まずはpositionからh,vの座標を取得します。

int.Parse()でint型に変換。

position.Split(‘,’)では、文字列”x,y”を,で区切って配列にしています。

例えばposition=”2,5″だった場合

position.Split(‘,’)は要素数2、要素が”2″と”5″の配列になります。

position.Split(‘,’)[0]は配列の0番目なので”2″になります。

なのでint.Parse(“2”)となり文字列の”2″が数値の2になり、これがhに代入されます。

yも同様です。

こうして取得したx座標とy座標を使い、board[h,v]を黒にします。

これで、黒の駒を置けるようになりました。

画面の盤面を更新できるようにする

これで、黒の駒を置けるようになりましたが、画面を更新する処理をしていないので画面が変わらないままです。

駒を置いた後、画面を更新する処理を追加していきます。

 //クリックされた座標に駒を置く
board[h, v] = COLOR.BLACK;
//画面を表示
ShowBoard();

駒を置いた後にShowBoard関数を呼び出す処理を追加しました。

ですがこのまま実行すると画面がおかしなことになってしまいます。

すでに表示されている盤面に、追加でPrefabを足してしまっているからです。

なのでShowBoard関数の最初にboardDisplayの子オブジェクトをすべて削除する必要があります。

GameObjectを削除するにはDestory関数を使います。ShowBoard関数の最初に次の処理を追加しましょう。

 //boardDisplayの全ての子オブジェクトを削除
foreach (Transform child in boardDisplay.transform)
{
    Destroy(child.gameObject); //削除
}

foreachは要素を順番に見ていくことができる命令です。このように書くと、boardDisplayの子オブジェクトを1つ1つに対してDestory関数で削除することができます。

これで、画面の盤面を更新できるようになりました。

すでに駒がある場所には置けないようにする

実は今の状態では1つ不具合が起きています。それは、すでに駒が置かれている場所にも駒を置けてしまうということです。リバーシのルールで駒を置けるのは駒が置かれていない場所だけです。

この部分を追加しましょう。

駒が置かれていない場所はつまりboardがCOLOR.EMPTYの場所なのでそれを使って判断します。

//値がEMPTYならpieceに押下時のイベントを設定
if (board[h, v] == COLOR.EMPTY)
{
    //座標を一時的に保持
    int x = h;
    int y = v;
    //pieceにイベントを設定
    piece.GetComponent<Button>().onClick.AddListener(() => { PutStone(x + "," + y); });
}

pieceにイベントを設定する部分にif文で条件を付けます。

これで、board[h,v]がCOLOR.EMPTYの場合のみクリックイベントを追加するようになりました。

それ以外の場合にはクリックイベントが追加されないので、ボタンを押しても何も起きなくなります。

黒と白を交互に置けるようにする

最後に、黒と白の駒を交互に置けるようにしていきましょう。

まずは、次に置く駒の色を保持する変数を作ります。

GameController上部の変数を宣言している部分に追加します。

COLOR player = COLOR.BLACK;

ウィキペディアでルールを確認したところ黒色からスタートするようなので初期値は黒にしました。このplayer変数が、置く駒の色になります。

次に、PutStone関数の駒を置く部分を書き換えます。

 //クリックされた座標に駒を置く
board[h, v] = player;
ShowBoard();
//駒の色を変更
player = player == COLOR.BLACK ? COLOR.WHITE : COLOR.BLACK;

board[h,v]にplayerを代入するように変更し、最後にplayerの色を変更します。

この書き方は三項演算子といいます。簡単に書くと以下の構成になります。

変数名 = 条件式 ? trueの場合の値 : falseの場合の値

今回はplayerの値がCOLOR.BLACKならCOLOR.WHITEに、違うならCOLOR.BLACKにするという処理になります。

駒を置くたびにplayerの値がCOLOR.BLACKとCOLOR.WHITEで交互に入れ替わるようになりますね。

これで、黒と白の駒を交互に置けるようになりました。

まとめ

ここまでお疲れさまでした。

この記事では、交互に駒を置けるようにするまでを解説しました。

なかなかリバーシっぽくなってきましたね。

次はひっくり返す処理を実装していきます。

リバーシの要となる処理なので今までよりプログラムを書く量が増えますが、アルゴリズムを考える練習になると思いますので一緒に進めていきましょう。

ここまでお付き合い頂けた方、ありがとうございました。
Unityでリバーシを作る5(ひっくり返す編)どうも、りくらぼです。 前回は駒を置けるようにしました。 https://riku-lab.com/reversi4 ...
ABOUT ME
りくらぼ
ブログ歴2ヵ月です。「りくらぼ」では雑記のほか、趣味のUnity関連のなど書きたいことを書いていきます