UnityとPhotonで対戦型ボードゲーム「犬猫将棋」を作成したい(16):ターン制のタイマーを駒の動きでトリガーさせる

前回作成したターン制のタイマーを犬猫将棋に組み込み、駒の動きをトリガーにタイマーを次のターンに回すことに成功しました。

 

駒が動いたかどうかは、OnMouseDown時の駒の位置をRoomCustomPropertyに保存し、OnMouseUP時の駒の位置と比べ、違いがあればturnManager.SendMoveを行うというやりかたで判断しています。

   

   

まず、ヒエラルキー上でemptyObjectの作成を行いTurnSystemsと名前を付けました。これはGameシーンの中に作っています。

   

TurnSystemsにはINSCore.cs、PhotonView、TurnChecker、TurnCheckerEndをコンポーネントとして追加しています。

   

ターンをスタートするためにINSCore.csのstartTurn()をRPCで呼んでいます。

 

それは、DDOLがついていてゲームシーンに依存しないスクリプトのPlayerNetwork.csに下記のように書いてあります。

   

 [PunRPC]
    private void RPC_CreatePlayer()//  3bmaster,other
    {
        networkManager = GameObject.Find("NetworkManager").GetComponent<NetworkManager>();
        networkManager.StartingProcess();

     
        insCore = GameObject.Find("TurnSystems").GetComponent<INSCore>();
        insCore.StartTurn();
          
      
    }

そして、INSCore.csのStartTurn()では、ターンを開始するように書いています。

 

後々、この部分が問題になるのですが、それについては後で述べます。

   

 public void StartTurn()//ターン開始メソッド(シーン開始時にRPCから呼ばれる呼ばれるようにしてあります。)
    {
       if (PhotonNetwork.isMasterClient)
       {
            Debug.Log("StartTurn");
            this.turnManager.BeginTurn();//turnmanagerに新しいターンを始めさせる
            PhotonView.RPC("RPC_AutomaticSend", PhotonTargets.All);//のちに出てきますが、2ターンに1回ターンを無条件で回すスクリプトを呼んでいます。
       }
    }

一方、TurnCheckerとEndTurnCheckerでは、まず、komaタグが付いているオブジェクトを配列として全部拾ったうえで、for文によってどのマスにいるかを確認し、現在のマスの値をリストにして取得しています。

 

TurnCheckerではそのリストをfor文を用いてルームカスタムプロパティに送信し、EndTurnCheckerではそれを受信し、現在のマスの値と比べています。比べた結果が違う場合は駒が動いた場合として、MakeTurnを行うようにしています。

   

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class TurnChecker : Photon.MonoBehaviour
{
    KomaModel komaModel;
    GameObject[] koma;
    float x;
    float y;
    int masuNum;
    int owner;
  
    public void TurnCheck()
    {
        var komaPosition = new List<int>();
        var komaIndex = new List<int>();
        var komaNari = new List<bool>();
        //var komaFlag = new List<bool>();

        koma = GameObject.FindGameObjectsWithTag("koma");//駒タグが付いているものを全部配列として拾う
      

        int i;
        for (i = 0; i < koma.Length; i++)
        {
            x = koma[i].transform.position.x;
            y = koma[i].transform.position.y;

            KomaModel komaModel = koma[i].GetComponent<KomaModel>();
            komaIndex.Add(komaModel.cardIndex);//駒インデックスのリストの作成(のちに使用予定
            komaNari.Add(komaModel.naru);//駒が成っているかのリストの作成(のちに使用予定

            if (-1.5f > x && x > -2.5f)
            {
                if (1.5f < y && y < 2.5f)
                {
                    masuNum = 1;
                    komaPosition.Add(masuNum);
                }
                else if (0.5f < y && y < 1.5f)
                {
                    masuNum = 6;
                    komaPosition.Add(masuNum);
                }
                else if (-0.5f < y && y < 0.5f)
                {
                    masuNum = 11;
                    komaPosition.Add(masuNum);
                }
                else if (-1.5f < y && y < -0.5f)
                {
                    masuNum = 16;
                    komaPosition.Add(masuNum);
                }
                else if (-2.5f < y && y < -1.5f)
                {
                    masuNum = 21;
                    komaPosition.Add(masuNum);
                }
                else
                {
                    masuNum = 26;
                    komaPosition.Add(masuNum);
                }
            }
            else if (-0.5f > x && x > -1.5f)
            {
                if (1.5f < y && y < 2.5f)
                {
                    masuNum = 2;
                    komaPosition.Add(masuNum);
                }
                else if (0.5f < y && y < 1.5f)
                {
                    masuNum = 7;
                    komaPosition.Add(masuNum);
                }
                else if (-0.5f < y && y < 0.5f)
                {
                    masuNum = 12;
                    komaPosition.Add(masuNum);
                }
                else if (-1.5f < y && y < -0.5f)
                {
                    masuNum = 17;
                    komaPosition.Add(masuNum);
                }
                else if (-2.5f < y && y < -1.5f)
                {
                    masuNum = 22;
                    komaPosition.Add(masuNum);
                }
                else
                {
                    masuNum = 26;
                    komaPosition.Add(masuNum);
                }
            }
            else if (0.5f > x && x > -0.5f)
            {
                if (1.5f < y && y < 2.5f)
                {
                    masuNum = 3;
                    komaPosition.Add(masuNum);
                }
                else if (0.5f < y && y < 1.5f)
                {
                    masuNum = 8;
                    komaPosition.Add(masuNum);
                }
                else if (-0.5f < y && y < 0.5f)
                {
                    masuNum = 13;
                    komaPosition.Add(masuNum);
                }
                else if (-1.5f < y && y < -0.5f)
                {
                    masuNum = 18;
                    komaPosition.Add(masuNum);
                }
                else if (-2.5f < y && y < -1.5f)
                {
                    masuNum = 23;
                    komaPosition.Add(masuNum);
                }
                else
                {
                    masuNum = 26;
                    komaPosition.Add(masuNum);
                }
            }
            else if (1.5f > x && x > 0.5f)
            {
                if (1.5f < y && y < 2.5f)
                {
                    masuNum = 4;
                    komaPosition.Add(masuNum);
                }
                else if (0.5f < y && y < 1.5f)
                {
                    masuNum = 9;
                    komaPosition.Add(masuNum);
                }
                else if (-0.5f < y && y < 0.5f)
                {
                    masuNum = 14;
                    komaPosition.Add(masuNum);
                }
                else if (-1.5f < y && y < -0.5f)
                {
                    masuNum = 19;
                    komaPosition.Add(masuNum);
                }
                else if (-2.5f < y && y < -1.5f)
                {
                    masuNum = 24;
                    komaPosition.Add(masuNum);
                }
                else
                {
                    masuNum = 26;
                    komaPosition.Add(masuNum);
                }
            }
            else if (2.5f > x && x > 1.5f)
            {
                if (1.5f < y && y < 2.5f)
                {
                    masuNum = 5;
                    komaPosition.Add(masuNum);
                }
                else if (0.5f < y && y < 1.5f)
                {
                    masuNum = 10;
                    komaPosition.Add(masuNum);
                }
                else if (-0.5f < y && y < 0.5f)
                {
                    masuNum = 15;
                    komaPosition.Add(masuNum);
                }
                else if (-1.5f < y && y < -0.5f)
                {
                    masuNum = 20;
                    komaPosition.Add(masuNum);
                }
                else if (-2.5f < y && y < -1.5f)
                {
                    masuNum = 25;
                    komaPosition.Add(masuNum);
                }
                else
                {
                    masuNum = 26;
                    komaPosition.Add(masuNum);
                }
            }
            else
            {
                masuNum = 26;
                komaPosition.Add(masuNum);
            }
        }

        int j;
        for (j = 0; j < komaPosition.Count; j++)
        {
            var properties = new ExitGames.Client.Photon.Hashtable();
            string card = PhotonNetwork.player.ID + "komaPosition" + j;
            properties.Add(card, komaPosition[j]);
            PhotonNetwork.room.SetCustomProperties(properties);//駒の現在いるマスの値をルームカスタムプロパティに送る。

        }
    }
}

TurnCheckerと、TurnChekerEndの違いは送信しているか、受信してMakeTurnしているかの違いだけです。

 

下の文がTurnChekerEndの、受信して違いを比べ、違う場合はMakeTurnをしている部分になります。

   

        if(endkomaPosition[0] != (int)PhotonNetwork.room.customProperties[PhotonNetwork.player.ID + "komaPosition0"])//駒の現在値とルームカスタムプロパティの値を比べる
        {
            Debug.Log(endkomaPosition[0]);
            insCore.MakeTurn(0);//値が違う場合ターンを回す
        }
        if (endkomaPosition[1] != (int)PhotonNetwork.room.customProperties[PhotonNetwork.player.ID + "komaPosition1"])
        {
            Debug.Log(endkomaPosition[1]);
            insCore.MakeTurn(1);
        }
           if (endkomaPosition[2] != (int)PhotonNetwork.room.customProperties[PhotonNetwork.player.ID + "komaPosition2"])
        {
            Debug.Log(endkomaPosition[2]);
            insCore.MakeTurn(2);
        }
           if (endkomaPosition[3] != (int)PhotonNetwork.room.customProperties[PhotonNetwork.player.ID + "komaPosition3"])
        {
            Debug.Log(endkomaPosition[3]);
            insCore.MakeTurn(3);
        }
           if (endkomaPosition[4] != (int)PhotonNetwork.room.customProperties[PhotonNetwork.player.ID + "komaPosition4"])
        {
            Debug.Log(endkomaPosition[4]);
            insCore.MakeTurn(4);
        }
           if (endkomaPosition[5] != (int)PhotonNetwork.room.customProperties[PhotonNetwork.player.ID + "komaPosition5"])
        {
            Debug.Log(endkomaPosition[5]);
            insCore.MakeTurn(5);
        }
           if (endkomaPosition[6] != (int)PhotonNetwork.room.customProperties[PhotonNetwork.player.ID + "komaPosition6"])
        {
            Debug.Log(endkomaPosition[6]);
            insCore.MakeTurn(6);
        }
           if (endkomaPosition[7] != (int)PhotonNetwork.room.customProperties[PhotonNetwork.player.ID + "komaPosition7"])
        {
            Debug.Log(endkomaPosition[7]);
            insCore.MakeTurn(7);
        }
           if (endkomaPosition[8] != (int)PhotonNetwork.room.customProperties[PhotonNetwork.player.ID + "komaPosition8"])
        {
            Debug.Log(endkomaPosition[8]);
            insCore.MakeTurn(8);
        }
           if (endkomaPosition[9] != (int)PhotonNetwork.room.customProperties[PhotonNetwork.player.ID + "komaPosition9"])
        {
            Debug.Log(endkomaPosition[9]);
            insCore.MakeTurn(9);
        }
           if (endkomaPosition[10] != (int)PhotonNetwork.room.customProperties[PhotonNetwork.player.ID + "komaPosition10"])
        {
            Debug.Log(endkomaPosition[10]);
            insCore.MakeTurn(10);
        }

MakeTurnというのはどこにあるかというと、INSCore.csの中のメソッドになります。

 

シンプルに、自分のターンを行うスクリプトが書いてあります。

   

 public void MakeTurn(int index)//手を決めるメソッド
    {
        this.turnManager.SendMove(index, true);//アクションを送るときに呼ぶ(アクション,ターンを終了するかどうか(true))
    }

MakeTurnを行わない側はターンを無条件で回すようにINSCore.csにスクリプトを書いてあります。

 

それは、2ターンに一回ということになっています。ターンを無条件で回す際には、自分のターンでないことを分かり易くするために赤いバーを非表示にしています。

   

  [PunRPC]
    public void RPC_AutomaticSend()
    {
        Debug.Log(turnManager.Turn);

        if ((this.turnManager.Turn % 2) + 1 == PhotonNetwork.player.ID)//2ターンに1回自分のターンを無条件で終わらせる
        {
            int index = 0;
            this.turnManager.SendMove(index, true); //無条件でターン終了
            this.WaitingText.text = "waiting for another player..."; //待ちの表示テキスト

            redFill = GameObject.Find("Red Fill").GetComponent<Image>();//赤いバーを表示しない
            redFill.enabled = false;

            Debug.Log("RPC_AutomaticSend");
        }
        else
        {
            this.WaitingText.text = "";
            
        }

各ターンの終了時に、赤いバーを表示させるスクリプトを加えています。

   

  public void OnEndTurn()//エンドターンのメソッド
    {
        //ターンエンドで必要な処理を書く(後で使うかもしれません)
        
            redFill = GameObject.Find("Red Fill").GetComponent<Image>();
            redFill.enabled = true;
                    
    }

これで、全部うまくいくはずだったのですが、バグが発生しました。バグの詳細としては、ロビーからルームに入る際に、1回目に入るときはすべてがうまくいくのですが、一度ゲームをやめて、ルームからロビーに戻った際に、もう一度ルームに入り直すと、2回目は片方のプレイヤー(猫)しかターンを行えないようになってしまっていました。

   

詳細にバグの様子を観察すると、ピンクで記載している右側の様子が正しいターンの回し方の順序なのですが、(autoとは自動で無条件にターンを回す)実際は、赤で書かれているようにターンが回っていて、どうやら、ターンが逆向きに回り、かつ、プレイヤー1側は毎ターン無条件にターンを回してしまっているということが分かりました。

   

それから、3回目にロビーからルームの作成を行う場合には、ターンが両プレイヤーとも何もしなくても自動でカウントアップがバンバン進むといった具合になっていまして、全部Autoになっているといった具合で、何かが、ロビーに戻るたびに重なっていくというような状態になっていました。

   

このバグの原因がサッパリわからず、2日間費やしまして、やっとこのバグの再現に成功しました。

 

ここに至るまでにはありとあらゆることを試したことの積み重ねがあったわけですが、ここでは結果のみを述べることにします。

 

結果的には、INSCore.csのStartTurn()で、this.TurnManager.BeginTurn();を2回連続で呼んであげると、同様のバグが起こるということが判明しました。下記のように2回呼ぶと必ず同様のバグが出ます。

   

 public void StartTurn()//ターン開始メソッド(シーン開始時にRPCから呼ばれる呼ばれるようにしてあります。)
    {
       if (PhotonNetwork.isMasterClient)
       {
            Debug.Log("StartTurn");
            this.turnManager.BeginTurn();//turnmanagerに新しいターンを始めさせる
       this.turnManager.BeginTurn();//もう一回同じものを呼ぶとバグが起きる。
            PhotonView.RPC("RPC_AutomaticSend", PhotonTargets.All);//のちに出てきますが、2ターンに1回ターンを無条件で回すスクリプトを呼んでいます。
       }
    }

どうやら、このことから、バグが起きる際には、同じターンに何らかの理由でBeginTurnが2回以上呼ばれているらしいということが判明しました。

 

ということは、BeginTurnを1ターンに1回しか呼ばせないようにすればバグを防げると考えました。

 

そこで考えたのは、TurnManagerとは別にBeginTurnが呼ばれた回数を計測しておいて、TurnManager.Turnと一致している時のみ行うということにしてみました。スクリプトとしては以下になります。

   

public void StartTurn()//ターン開始メソッド(シーン開始時にRPCから呼ばれる呼ばれるようにしてあります。)
{
   if (PhotonNetwork.isMasterClient)
   {
        Debug.Log("StartTurn");

        if(number == this.turnManager.Turn)//BeginTurnが1ターンに1回しか回らないことのチェックをする。
        {
            this.turnManager.BeginTurn();//turnmanagerに新しいターンを始めさせる
            PhotonView.RPC("RPC_AutomaticSend", PhotonTargets.All);
            number++;//BeginTurnが2回目以降1ターンに回る場合にはこの変数がターンと一致しないようにする
        }
   }
}

これで、やっと2日間のバグをつぶすことができました。ターンマネージャーがおかしな挙動をする場合、まずチェックすべきは、Begin.Turnが1ターンに複数回呼ばれていないかです。

 

参考までに、現状の犬猫将棋のスクリプトをGITHUBにアップしておきます。

GITHUB犬猫将棋TurnbasedTrialBranch01

    もしよろしければ、Photonについて私が知りえる限りの情報を徹底的にまとめてみたので、下記の記事も読んで下さいね!

おすすめの記事