2014年8月21日木曜日

photoncloudを使ったシーン切り替えについて

あついーあついーよ、どうもsiroponnです。

今回はシーンの切り替えについてです。
オンラインゲームのFPSとかみたいに、まずはある程度プレイヤーがルームに集まってから、ゲーム を開始したい場合どうすればいいのだろうと流れを考えてコードを組んでみました。

・スタート時のシーンをロード。
・ConnectUsingSettingsで繋ぐ。
・ルームを探す。
・ルームに接続。
・ある程度プレイヤーが集まったらゲームプレイ用のシーンをロード。
・途中参加OK。

こんな感じですかね。

で、実際のコードですが。

ルームに接続の部分ですが、ルームが作られていない場合ルームを作らなきゃなりません。
このとき、ルームのカスタムプロパティを設定してあげます。後々にこのカスタムプロパティは使います。

        RoomOptions ro = new RoomOptions();
                    ro.maxPlayers = playerMaxCount +1;
                    ro.isOpen = true;
                    ro.isVisible = true;
                    string[] s = { "BS" }; //BS:BattleState. 
                    ro.customRoomPropertiesForLobby = s;  //ロビーで表示される値. 
                    ro.customRoomProperties = new ExitGames.Client.Photon.Hashtable() { { "BS","idle" } };
                    PhotonNetwork.CreateRoom(RoomName, ro, TypedLobby.Default);
                    sceneState = SceneState.Room;


ルームに入った後呼ばれるこのコールバックでプレイヤーのカスタムプロパティを設定してあげます。後々使います。

 void OnJoinedRoom()
    {
        HashTable h = new HashTable() { { "GS", GameState.Room } }; //playerのカスタムプロパティ.

        //ルームプロパティとは違います.
        PhotonNetwork.player.SetCustomProperties(h);

        RoomInfo r = PhotonNetwork.room;

        //ゲームプレイ中に入ったら一時的にイベントをシャットアウト.
        //そうしないと、Roomに入った瞬間インスタンス情報が流れてきてしまう.
        if ((string)r.customProperties["BS"] != "idle")
        {
            PhotonNetwork.isMessageQueueRunning = false;

            Application.LoadLevel(1);
        }
    }


で、ルームを作るor接続したらキリのいいタイミングでゲームプレイ用のシーンをロードします。
シーンのロードはマスタークライアントがRPCをコールしてあげます。

 if (PhotonNetwork.inRoom && PhotonNetwork.isMasterClient)
                {
                    if (GUI.Button(new Rect(100, 100, 100, 100), "GameStart"))
                    {
                        PhotonNetwork.room.open = false; //エラーが起きたら困るので一回ここでルームを閉じる.

                        HashTable h = new HashTable() { { "BS", "Standing" } }; //ルームのステータスを変更.スタートアップ中.

                        PhotonNetwork.room.SetCustomProperties(h);

                        sceneState = SceneState.ChildsGamePlay;//現在意味をなしていない.

                        //room情報をセットし終わったらリモートクライアントにシーンを呼ばせる.

                        PhotonNetwork.DestroyAll();

                        SendGameStart();
                    }

                }
 void SendGameStart()
    {
        pView.RPC("SendGameStartRPC", PhotonTargets.All);
    }

[RPC]
    void SendGameStartRPC()
    {
        //メッセージを一時的に遮断.
        PhotonNetwork.isMessageQueueRunning = false;

        Application.LoadLevel(1);

    }

シーンを変更する際にはこれを呼ばないとエラーが起きます。
  PhotonNetwork.DestroyAll();

また上のソースコードにも書かれていますが、こいつを必ずシーンを移行する前にfalseにしてください。そうしないと、別シーン上のイベントが受信してまったり、別シーン上にイベントを送信してしまうことになります。
 PhotonNetwork.isMessageQueueRunning

次はゲームプレイ用シーンでやることです。

void Start () {

        PhotonNetwork.isMessageQueueRunning = true; //ゲームプレイ用のシーンが読み込まれたら必ずtrueにする!

        //-------------Playerがゲーム中だとする------

        HashTable h = new HashTable() { { "GS", GameState.Play } }; //ルームプロパティじゃないです!
        PhotonNetwork.player.SetCustomProperties(h);

        //--------------------------------------

        if ((string)PhotonNetwork.room.customProperties["BS"] != "Play")
        {
            phaze = Phase.Prepare;
        }
        else //途中参加
        {
            phaze = Phase.Instance;
        }

       
    }

まずはシーン移行する前にfalseにしたPhotonNetwork.isMessageQueueRunningを再びtrueにしてください。これで再びイベントを送受信できるようになりました。

ここでルームプロパティを使います。まだゲームが開始されているか?それともされていないかを判断します。

判断したらマスタークライアント以外のプレイヤーがちゃんと、このシーンをロードしたか確認します。(エラー処理していない)

 case Phase.Prepare:

                if (PhotonNetwork.player.isMasterClient)
                {
                    int count = 0;
                    int otherPlayers = PhotonNetwork.otherPlayers.Length;

                    if (otherPlayers != 0)
                    {
                        foreach (PhotonPlayer pp in PhotonNetwork.otherPlayers)
                        {
                            if ((GameState)pp.customProperties["GS"] == GameState.Play)
                                count++;
                        }

                    }

                    //他プレイヤーの準備が整っていればプレイヤー逹をインスタンスし始める.
                    if (count == otherPlayers)
                    {                      
                        pView.RPC("SetPhazeRPC", PhotonTargets.All, (int)Phase.Instance);
                    }
                }

                break; 

ここでプレイヤーのカスタムプロパティを使いますね。こうすれば、まだリモートクライアントがこのシーン上にいなくても判定を行うことができます。

次にインスタンスです。自分としてはここでプレイヤーのインスタンスをすると思っていましたが、背景のロードを行ってもかまいません。しかし、今掲載しているコードにはありませんが、背景はシーンに入った直後非同期で行うことを推奨します。nowlowdingとか表示しながら。

  case Phase.Instance:

                TestInstance();

                //MCだけルームの状態を変更させる.
                if (PhotonNetwork.isMasterClient)
                {
                    HashTable hh = new HashTable { { "BS", "Play" } };

                    PhotonNetwork.room.SetCustomProperties(hh);

                    PhotonNetwork.room.open = true; //Start時に閉めておいたのオープンにさせる.

                }

                phaze = Phase.Play; //インスタンスしたら各々プレイ状態にはいる.
                break;


さて↑の部分でルームのプロパティ"BS"を"Play"状態にしました。

先ほど書いたOnJoinedRoom()の一部分を見てみます。

//ゲームプレイ中に入ったら一時的にイベントをシャットアウト.
        //そうしないと、Roomに入った瞬間インスタンス情報が流れてきてしまう.
        if ((string)r.customProperties["BS"] != "idle")
        {
            PhotonNetwork.isMessageQueueRunning = false;

            Application.LoadLevel(1);
        }

こうすることによって他の、まだルームに入っていないプレイヤーが入ってきた時、問題なくゲームを始められるようになります。
また、ルームにバッファリングされたイベントはOnJoinedRoomが呼ばれた後に呼ばれるっぽいのでここでPhotonNetwork.isMessageQueueRunningをfalseにしておけばゲームプレイ用のシーンに入った時にイベントが受信できるようになるわけです。

そして、Start()の部分をもう一回。
   if ((string)PhotonNetwork.room.customProperties["BS"] != "Play")
        {
            phaze = Phase.Prepare;
        }
        else //途中参加
        {
            phaze = Phase.Instance;
        }

"BS"を"Play"にしたので途中から入ってきた人はprepareを飛ばしてインスタンスできるようになります。

インスタンス後はみなさんかなり独自実装になると思うので省きました。というか自分が書くスピードが遅いので止めました。
でも、インスタンスするときにPhotonNetowork.Instantiateを使わない方法でやると(マニュアルでインスタンスとviewIDをつけるやつの)バグるのは解決したい。あとはルームに入れなかった時とか、ルームから退出したときとか、ルームでマスタークライアントが変わった時とかその辺の処理もいずれかは解決したいと思います。
後々にちゃんとした加筆修正をしたいなぁ。いつになるかは分からんが。



さて長々と書きました。はっきり言って説明されるより、コードを見ながら自分で覚えた方が早いと思うので以下に使ったコードを書きます。


無駄な表記(使わないコードなど)あるのでC#対応のテキストエディタにコピペした後、見てもらえばコメント部分が色わけされていいかもしれません。

駄コードですが、参考になれば幸いです。

では、siroponnでした!




 以下,コード。








 ゲームを始めた時に呼ばれるシーンでのスクリプト

//---------------.----------------

using UnityEngine;
using System.Collections;
using HashTable = ExitGames.Client.Photon.Hashtable;
using System.Collections.Generic;

enum SceneState : int {

    Title = 0,
    select = 1,
    Loby = 2,
    Room = 3,
    ChildsGamePlay = 4,
    HostGamePlay = 5,

}

public enum GameState : int //今プレイヤーがどの状態にあるか?
{
    Room = 0,
    Play = 1,

}

public class StartScene : MonoBehaviour {

    float ConnectTime = 0.0f;

    int selectGrid;

    SceneState sceneState;

    string RoomName = "PressRoomName";
    int playerMaxCount = 0;
    string[] playerCount ={ "1", "2", "3", "4" };

    PhotonView pView;

    int playerCnt = 0;

    List<PhotonPlayer> pList = new List<PhotonPlayer>();
   

    void Awake()
    {
        pView = this.GetComponent<PhotonView>();

        //PhotonNetwork.isMessageQueueRunning = false;
       
    }

    // Use this for initialization
    void Start () {

        sceneState = SceneState.Title;
   
    }
   
    // Update is called once per frame
    void Update () {

        if (ConnectTime >= 0.1f)
        {
            ConnectTime += Time.deltaTime;
            if (PhotonNetwork.connectionStateDetailed == PeerState.JoinedLobby)
            {
                ConnectTime = 0f;
                sceneState = SceneState.Loby;
               
            }

        }

        if (Input.GetKey(KeyCode.A))
        {
            Debug.Log(PhotonNetwork.isMessageQueueRunning);
        }

    }

    void OnGUI()
    {
        int sw = Screen.width;
        int sh = Screen.height;

        switch (sceneState)
        {
            case SceneState.Title: //接続するまで

                if (GUI.Button(new Rect(sw / 2f, sh / 2f, 30f, 30f), "接続"))
                {
                    PhotonNetwork.ConnectUsingSettings("v1.0");
                    ConnectTime = 0.1f;
                }

                if (ConnectTime >= 0.1f && PhotonNetwork.connectionStateDetailed != PeerState.JoinedLobby)
                {
                    GUILayout.Label("接続中");
                }
                break;

            case SceneState.Loby://接続後.

               
                RoomName = GUI.TextField(new Rect(100, 100, 150, 20), RoomName, 16);

                GUI.Label(new Rect(100f, 170f, 50f, 50f), "Player数");
                playerMaxCount = GUI.SelectionGrid(new Rect(100, 200, 100, 20), playerMaxCount,playerCount,4);
               

                if (GUILayout.Button("createroom"))
                {
                    RoomOptions ro = new RoomOptions();
                    ro.maxPlayers = playerMaxCount +1;
                    ro.isOpen = true;
                    ro.isVisible = true;
                    string[] s = { "BS" }; //BS:BattleState.
                    ro.customRoomPropertiesForLobby = s;  //ロビーで表示される値.
                    ro.customRoomProperties = new ExitGames.Client.Photon.Hashtable() { { "BS","idle" } };
                    PhotonNetwork.CreateRoom(RoomName, ro, TypedLobby.Default);
                    sceneState = SceneState.Room;

                }

                 if (PhotonNetwork.countOfRooms == 0)
                    return;

                foreach (RoomInfo game in PhotonNetwork.GetRoomList())
                {
           
                    GUI.Label(new Rect(sw/2f,(sh*2/3),500,30),game.name + " " + game.playerCount + "/" + game.maxPlayers + "/" + game.customProperties["BS"]);
                    if (GUILayout.Button("JOIN"))
                    {
                        PhotonNetwork.JoinRoom(game.name);

                        sceneState = SceneState.Room;

                    }
           
                }

                break;
            case SceneState.Room://ルームに入った後.

                if (PhotonNetwork.inRoom && PhotonNetwork.isMasterClient)
                {
                    if (GUI.Button(new Rect(100, 100, 100, 100), "GameStart"))
                    {
                        PhotonNetwork.room.open = false; //エラーが起きたら困るので一回ここでルームを閉じる.

                        HashTable h = new HashTable() { { "BS", "Standing" } }; //ルームのステータスを変更.スタートアップ中.

                        PhotonNetwork.room.SetCustomProperties(h);

                        sceneState = SceneState.ChildsGamePlay;//現在意味をなしていない.

                        //room情報をセットし終わったらリモートクライアントにシーンを呼ばせる.

                        PhotonNetwork.DestroyAll();

                        SendGameStart();
                    }

                }

                if (GUILayout.Button("testInstance"))
                    PhotonNetwork.Instantiate("TestInstance", Vector3.zero, Quaternion.identity, 0);
               
                break;
           /* case SceneState.ChildsGamePlay:

                int cnt = 0;
                //マスタークライアント以外のローカルクライアントがしっかりシーンを読み終わったかを調べる.
                foreach (PhotonPlayer pp in PhotonNetwork.otherPlayers)
                {
                    if ((int)pp.customProperties["GS"] == (int)GameState.Play)
                    {
                        cnt++;

                    }
                }
                if (cnt == PhotonNetwork.otherPlayers.Length)
                    sceneState = SceneState.HostGamePlay;


                break;
            case SceneState.HostGamePlay:

                Debug.Log("MC以外の準備完了");

                PhotonNetwork.isMessageQueueRunning = false;

                Application.LoadLevel(1);
               

                break;*/
        }

       

        GUILayout.Label(PhotonNetwork.connectionStateDetailed.ToString());
        GUILayout.Label(ConnectTime.ToString());
        GUILayout.Label(PhotonNetwork.connectionState.ToString());
    }


    void SendGameStart()
    {
        pView.RPC("SendGameStartRPC", PhotonTargets.All);
    }

    [RPC]
    void SendGameStartRPC()
    {
        //メッセージを一時的に遮断.
        PhotonNetwork.isMessageQueueRunning = false;

        Application.LoadLevel(1);

    }

    //ルームに入った時呼ばれる.
    void OnJoinedRoom()
    {
        //プレイヤーがバトル用のシーンを読み込んだかチェック.
        HashTable h = new HashTable() { { "GS", GameState.Room } }; //playerのカスタムプロパティ.

        //ルームプロパティとは違います.
        PhotonNetwork.player.SetCustomProperties(h);

        RoomInfo r = PhotonNetwork.room;

        //ゲームプレイ中に入ったら一時的にイベントをシャットアウト.
        //そうしないと、Roomに入った瞬間インスタンス情報が流れてきてしまう.
        if ((string)r.customProperties["BS"] != "idle")
        {
            PhotonNetwork.isMessageQueueRunning = false;

            Application.LoadLevel(1);
        }
    }

    void OnPhotonJoinRoomFailed()
    {
        //ルームに入れなかった場合
        sceneState = SceneState.Loby;

    }

}


//------------------------------------------------------------------









 ゲームプレイ用のシーンに入った後に呼ばれるスクリプト



 //---------------------------------------------------------
 using UnityEngine;
using System.Collections;
using HashTable = ExitGames.Client.Photon.Hashtable;

enum Phase:int
{
    Prepare = 0,
    Instance = 1,
    Play = 2,
    GameOver = 3,
    None = 4,

};

public class TestGameMain : MonoBehaviour {

    PhotonView pView;

    public GameObject g;

    Phase phaze = Phase.None;
 

    void Awake()
    {
        pView = GetComponent<PhotonView>();
    }

    // Use this for initialization
    void Start () {

        PhotonNetwork.isMessageQueueRunning = true; //ゲームプレイ用のシーンが読み込まれたら必ずtrueにする!

        //-------------Playerがゲーム中だとする------

        HashTable h = new HashTable() { { "GS", GameState.Play } };

        PhotonNetwork.player.SetCustomProperties(h);

        //--------------------------------------

        if ((string)PhotonNetwork.room.customProperties["BS"] != "Play")
        {
            phaze = Phase.Prepare;
        }
        else //途中参加
        {
            phaze = Phase.Instance;
        }

      
    }
  
    // Update is called once per frame
    void Update () {

        if (Input.GetKeyDown(KeyCode.Q))
            Debug.Log(PhotonNetwork.player.ID);

        switch (phaze)
        {
            case Phase.Prepare:

                if (PhotonNetwork.player.isMasterClient)
                {
                    int count = 0;
                    int otherPlayers = PhotonNetwork.otherPlayers.Length;

                    if (otherPlayers != 0)
                    {
                        foreach (PhotonPlayer pp in PhotonNetwork.otherPlayers)
                        {
                            if ((GameState)pp.customProperties["GS"] == GameState.Play)
                                count++;
                        }

                    }

                    //他プレイヤーの準備が整っていれば始める.
                    if (count == otherPlayers)
                    {
                        //MC用のキャラインスタンス通知,
                        /*TestInstance(); //親

                        //他プレイヤーに自分のキャラインスタンスを通知.
                        if (otherPlayers != 0)
                        {
                            pView.RPC("SendTestInstanceRPC", PhotonTargets.OthersBuffered);
                        }*/
                      
                        pView.RPC("SetPhazeRPC", PhotonTargets.All, (int)Phase.Instance);
                    }
                }

                break;
            case Phase.Instance:

                TestInstance();

                //MCだけルームの状態を変更させる.
                if (PhotonNetwork.isMasterClient)
                {
                    HashTable hh = new HashTable { { "BS", "Play" } };

                    PhotonNetwork.room.SetCustomProperties(hh);

                    PhotonNetwork.room.open = true; //Start時に閉めておいたのオープンにさせる.

                }

                phaze = Phase.Play; //インスタンスしたら各々プレイ状態にはいる.

                break;
            case Phase.Play:
                break;
            case Phase.GameOver:
                break;
            case Phase.None:
                break;
        }

  
    }

    void OnGUI()
    {
        string s = "";
        if(PhotonNetwork.player.isMasterClient)
            s = "MCです";
        else
            s = "MCではありません";

        GUILayout.Label("私は" + s);
    }

  
    void TestInstance() //モデルをマニュアルでインスタンス.
    {
        //int id1 = PhotonNetwork.AllocateViewID();

        //pView.RPC("TestInstanceRPC",PhotonTargets.AllBuffered,id1,PhotonNetwork.player);

        PhotonNetwork.Instantiate("TestInstance", Vector3.zero, Quaternion.identity, 0);

    }


 
    [RPC]
    void SetPhazeRPC(int phaze)
    {
        this.phaze = (Phase)phaze;
    }
  //以降下のコードバグあり。コメント化。
    /*[RPC]
    void TestInstanceRPC(int vid,PhotonPlayer pp)
    {
        GameObject go = (GameObject)GameObject.Instantiate(g, Vector3.zero, Quaternion.identity);

        PhotonView p = go.GetComponent<PhotonView>();

        p.viewID = vid;

        p.ownerId = pp.ID;
    }

    [RPC]
    void SendTestInstanceRPC()
    {

        TestInstance();

    }*/

}

//--------------------------------------------------------------------




2014年8月6日水曜日

PhotonCloud OnPhotonInstantiateでのエラーについて

おっす、siroponnです。

今回はOnPhotonInstantiateのエラーについてです。

OnPhotonInstantiateはPhotonNetwork.Instantiate により生成された GameObject(とその子オブジェクト)上のすべてのスクリプトでコールされる(公式抜粋)

さて、こういったエラーが出たことありませんか?

TargetParameterCountException: parameters do not match signature.

これは
void OnPhotonInstantiate()
と書いてしまうと、出ます。

ただしくは

void OnPhotonInstantiate(PhotonMessageInfo info)
という風に引数を入れてあげないとダメです。

これは簡単なエラーだったのですぐ見つかりました。
が、びっくりしたので書き記しておきます。

RPCでは引数いらないのにこれは引数いるのはなんでじゃー。って思ったのは秘密。


では凡人siroponnでした~~。ノシシ

次はどうしよう。ネタがないから、うーん。しばらく考えてみます。

追記:08/07 05:34

OnPhotonInstantiateの使い方を書き忘れていました。


public class A :  MonoBehaviour{

    int TestId;
   
    void Test()
    {
        object[] obj = new object[1];

        int[] id = new int[1];

        id[0] = TestId;

        obj[0] = (int)id[0];
       
        PhotonNetowork.Instantiate("TestPrefab",this.transform.position,Quaternion.identity,0,obj)
   
    }
}

public class TestPrefabScript : MonoBehaviour{

    int catchId;
   
    PhotonView pView;
   
    void Awake()
    {
        pView = GetComponent<PhotonView>();
    }
   
    void OnPhotonInstantiate(PhotonMessageInfo info)
    {
        this.catchId = (int)this.pView.instantiationData[0];
    }

}

PhotonNetwork.Instantiateの時にobject型を送ります。
インスタンスされたオブジェクトに付随するPhotonViewにアクセスする。
するとinstantiationDataにobject型の送られてきた値が入っているのでOnPhotonInstantiateで受け取る。
調べていませんが、別にOnPhotonInstantiateで受け取る必要はないかもしれません。

2014年8月4日月曜日

photoncloud ownerIDについて

にっこにっこにー、siroponnです。ラブライブの曲いいですよね。元気が出ます。
さて、早くも五回目です。そろそろ失速してきそうです。
基本的にgoogle先生にきいて、検索結果1ページで他の人が書いていない情報しか書かない主義ですから。だって1ページ目に出てくるのなんて有名で検索すればすぐ出てくるでしょ?それをわざわざ書くのはめんどいちーん。

で、ownerIDです。

PhotonViewクラスのメンバーです。
こいつはなんぞやというと、読んで字のごとくownerのIDです。
このPhotonViewがついているObjectを、どのプレイヤーが管理しているかちゅーことです。
基本的に、自分が生成した(PhotonNetwork.Instantiateなどで) Objectには自分のIDがつきます。
自分のIDとはPhotonPlayerクラスの"ID"メンバーで得られる値です。

で、このownerIDはアクセスレベルがpublicなので書きかえられます。

自分が生成していないObjectを自分の管理化に置きたい場合、このメンバーを変えてやればOKです。

僕がやった例だと、
シーン上に落ちているボックスをプレイヤーキャラの子Objectにしたい。
しかし、 プレイヤーキャラはローカルクライアント。ボックスはリモートクライアント。
子Objectにすると、同期が狂いラグが発生しまくる。
そんな時にボックスのownerIDをプレイヤーのownerIDと一緒にする。
以上で、同期が狂うことはなくなりました。

さて、以上です。

次はOnPhotonInstantiateしたときのエラーについて書きましょうか。明日か明後日には書きます。

ではでは。



2014年8月2日土曜日

なんかphotonでエラーログが出る

お疲れ様です、siroponnです。日々熱いですね。

さて、タイトルの件についてですが。

今回やりたかった処理は、
ボックスがプレイヤーか地面に接触した時破壊する。
その後エフェクトプレファブをインスタンスして、爆発を出し、周りのボックスも破壊する。というものです。

photonを使っていて以下のエラーログを見たことありませんか?

「PhotonView with ID 2002 has no method "DestroyObjectRPC" marked with the [RPC](C#) or @RPC(JS) property! Args: 」

えーと、 "DestroyObjectRPC"というメソッドを持っていない、そして[RPC]表記していないっと。
ソースを見る。

↓こちらのメソッドを通してRPC通信する
 public void DestroyThisObject()
{
           myPhotonView.RPC("DestroyObjectRPC", PhotonTargets.All);
}

[RPC]
void DestroyObjectRPC()
{
        if (myPhotonView.isMine)
        {
            PhotonNetwork.Destroy(this.gameObject);
        }       
}

されていますね。
そこで、RPCを呼ばないで直接PhotonNetwork.Destroyを呼んだ。


public void DestroyThisObject()
{
         if(myPhotonView.isMine)
         {
                  PhotonNetwork.Destroy(this.gameObject);
         }
         //myPhotonView.RPC("DestroyObjectRPC", PhotonTargets.All);
}

こうしたら、一応エラーは消えました。

が。
リモートクライアントに当たった時反応してくれません。当たり前で、isMineにて判定を行っていますからね。

次の策。↓ というか完全に直感で書いた。

public void DestroyThisObject()
{
        if (this.myPhotonView.isMine)
            PhotonNetwork.Destroy(this.gameObject);
        else
            myPhotonView.RPC("DestroyObjectRPC",PhotonTargets.Others);

        //myPhotonView.RPC("DestroyObjectRPC", PhotonTargets.All);
}

こうしたら意図したとおりに直りました。なぜなんだ。おい。

RPCでPhotonNetwork.Destoryを呼ぶのがいけないと思っていたが。
RPCのTargets.AllでPhotonNetwork.Destroyを呼ぶのがいけないのか???

わからん。

思い当たる節があるとすれば、まだブログには書いてませんが、ownerIDをゲーム処理中に書き変えていることでしょうか……。ローカルとリモートを入れ替えたりしているので、それかもしれません。

また、エフェクトのプレファブのスクリプトにも問題がありそうです。
爆発した際、爆発に巻き込まれたボックスを破壊している処理がありますから。ただ、エフェクトを呼んだボックスはインスタンスIDで判定して破壊されるのを防いでいるんですけどね。

とりあえず、PhotonNetwork.InstantiateやPhotonNetwork.Destroyを呼ばないで、マニュアルでインスタンスとデストロイを行った方が安全かもしれない。

もやもやするが、ここで立ち止まっているとプロジェクトが進まないので一旦、置いておく。

だれか、この原因を詳しく解明してくれ。

次回は、問題があるかもしれないownerIDについて書こうと思います。

では、ノシ