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();

    }*/

}

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




0 件のコメント:

コメントを投稿