Photon PUN2で1スクリプトでロビーからゲーム画面に遷移が簡単に作れるコードをPhotion Fusionになおしてみた(チャット機能追加2024/7/6)

2024年7月6日

こんにちは!ジェイです。Unityでオンラインゲームを作る時にはPhotonPUN2を使っていた人も多かったと思います。

しかし、PhotonFusionが出てから更新が止まり、これからはPhotonFusion2でオンラインゲームを作っていく流れに現状はなりそうです。

そこで、私がいつもPUN2で1スクリプトでロビーを作れるテンプレートのスクリプトをPhotonFusion2に対応したバーションのスクリプトを紹介します。

PhotonPUN2でロビーからゲームシーンに遷移

using System.Collections.Generic;
using UnityEngine;
using Photon.Pun;
using Photon.Realtime;
using UnityEngine.SceneManagement;

public class CTitleManager : MonoBehaviourPunCallbacks
{
    private string RoomName = "";
    Rect LobbyGuiRect = new Rect(0, 0, 300, 200);

    void Update()
    {
        LobbyGuiRect.x = Screen.width / 4;
        LobbyGuiRect.y = Screen.height / 4;
        LobbyGuiRect.width = Screen.width / 2;
        LobbyGuiRect.height = Screen.height / 4;
    }
    // 接続したらロビーに入室する
    public override void OnConnectedToMaster()
    {
        PhotonNetwork.JoinLobby();
    }
    //GetRoomListは一定時間ごとに更新され、その更新のタイミングで実行する処理
    //ルームリストに更新があった時(ロビーに入ってるときのみ呼ばれる)
    public override void OnRoomListUpdate(List<RoomInfo> roomList)
    {
        RoomList = roomList;
    }
    List<RoomInfo> RoomList = new List<RoomInfo>();
    void RoomNameUIWindow(int window_id)
    {
        // ルーム名の入力
        GUILayout.BeginHorizontal();
        GUILayout.Label("RoomName : ");
        RoomName = GUILayout.TextField(RoomName, GUILayout.Width(200));
        GUILayout.EndHorizontal();

        // ルームを作成して入室する
        if (GUILayout.Button("Create Room", GUILayout.Width(150)))
        {
            PhotonNetwork.CreateRoom(RoomName);
        }

        // ルーム一覧を検索
        foreach (var room in RoomList)
        {
            // ルームパラメータの可視化
            string room_param = $"{room.Name}{room.PlayerCount}/{((room.MaxPlayers == 0) ? "-" : room.MaxPlayers.ToString())}";

            // ルームを選択して入室する
            if (GUILayout.Button("Enter Room : " + room_param))
            {
                PhotonNetwork.JoinRoom(room.Name);
            }
        }
    }
    void PlayerNameUIWindow(int window_id)
    {
        // プレイヤー名の入力
        GUILayout.BeginHorizontal();
        GUILayout.Label("PlayerName : ");
        PhotonNetwork.NickName = GUILayout.TextField(
            (PhotonNetwork.NickName == null) ?
                "" :
                PhotonNetwork.NickName, GUILayout.Width(200));
        GUILayout.EndHorizontal();

        // MUNサーバに接続する
        if (GUILayout.Button("Connect Server", GUILayout.Width(150)))
        {
            PhotonNetwork.AutomaticallySyncScene = true;
            PhotonNetwork.ConnectUsingSettings();
        }
    }
    public override void OnCreatedRoom()
    {
        Debug.Log("OnCreatedRoom");
    }
    void OnGUI()
    {
        // サーバに接続している場合
        if (PhotonNetwork.IsConnected)
        {
            // ロビーに入室してる場合
            if (PhotonNetwork.InLobby)
            {
                // ルームに入室していない場合
                if (!PhotonNetwork.InRoom)
                {
                    GUILayout.Window(1, LobbyGuiRect, RoomNameUIWindow, "");
                }
            }
            else // ロビーに入室してない場合
            {
                // ルームに入室している場合
                if (PhotonNetwork.InRoom)
                {

                }
            }
        }
        // PUNサーバに接続していない場合
        else
        {
            GUILayout.Window(2, LobbyGuiRect, PlayerNameUIWindow, "");
        }
    }
}

PhotonFusion2でロビーからゲーム画面に遷移

using System.Collections.Generic;
using UnityEngine;
using Fusion;
using Fusion.Sockets;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

public class CTitleManager : MonoBehaviour, INetworkRunnerCallbacks
{
    private string RoomName = "";
    Rect LobbyGuiRect = new Rect(0, 0, 300, 200);
    private NetworkRunner _networkRunner;
    private Dictionary<string, SessionInfo> RoomList = new Dictionary<string, SessionInfo>();

    void Start()
    {
        _networkRunner = gameObject.AddComponent<NetworkRunner>();
        _networkRunner.ProvideInput = true;
        _networkRunner.AddCallbacks(this);
    }

    void Update()
    {
        LobbyGuiRect.x = Screen.width / 4;
        LobbyGuiRect.y = Screen.height / 4;
        LobbyGuiRect.width = Screen.width / 2;
        LobbyGuiRect.height = Screen.height / 4;
    }

    public void OnConnectedToServer(NetworkRunner runner)
    {
        Debug.Log("Connected to Server");
    }

    public void OnConnectFailed(NetworkRunner runner, NetAddress remoteAddress, NetConnectFailedReason reason)
    {
        Debug.Log("Connect Failed");
    }

    public void OnSessionListUpdated(NetworkRunner runner, List<SessionInfo> sessionList)
    {
        RoomList.Clear();
        foreach (var session in sessionList)
        {
            RoomList[session.Name] = session;
        }
    }

    public void OnDisconnectedFromServer(NetworkRunner runner)
    {
        Debug.Log("Disconnected from Server");
    }

    public void OnPlayerJoined(NetworkRunner runner, PlayerRef player)
    {
        Debug.Log("Player Joined");
    }

    public void OnPlayerLeft(NetworkRunner runner, PlayerRef player)
    {
        Debug.Log("Player Left");
    }

    public void OnInput(NetworkRunner runner, NetworkInput input)
    {
    }

    public void OnInputMissing(NetworkRunner runner, PlayerRef player, NetworkInput input)
    {
    }

    public void OnShutdown(NetworkRunner runner, ShutdownReason shutdownReason)
    {
    }

    public void OnConnectRequest(NetworkRunner runner, NetworkRunnerCallbackArgs.ConnectRequest request, byte[] token)
    {
    }

    public void OnUserSimulationMessage(NetworkRunner runner, SimulationMessagePtr message)
    {
    }

    public void OnCustomAuthenticationResponse(NetworkRunner runner, Dictionary<string, object> data)
    {
    }

    public void OnHostMigration(NetworkRunner runner, HostMigrationToken hostMigrationToken)
    {
    }

    void RoomNameUIWindow(int window_id)
    {
        // ルーム名の入力
        GUILayout.BeginHorizontal();
        GUILayout.Label("RoomName : ");
        RoomName = GUILayout.TextField(RoomName, GUILayout.Width(200));
        GUILayout.EndHorizontal();

        // ルームを作成して入室する
        if (GUILayout.Button("Create Room", GUILayout.Width(150)))
        {
            _networkRunner.StartGame(new StartGameArgs()
            {
                GameMode = GameMode.Host,
                SessionName = RoomName,
                Scene = SceneManager.GetActiveScene().buildIndex,
                SceneManager = gameObject.AddComponent<NetworkSceneManagerDefault>()
            });
        }

        // ルーム一覧を検索
        foreach (var room in RoomList.Values)
        {
            // ルームパラメータの可視化
            string room_param = $"{room.Name}{room.PlayerCount}/{((room.MaxPlayers == 0) ? "-" : room.MaxPlayers.ToString())}";

            // ルームを選択して入室する
            if (GUILayout.Button("Enter Room : " + room_param))
            {
                _networkRunner.StartGame(new StartGameArgs()
                {
                    GameMode = GameMode.Client,
                    SessionName = room.Name,
                    SceneManager = gameObject.AddComponent<NetworkSceneManagerDefault>()
                });
            }
        }
    }

    void PlayerNameUIWindow(int window_id)
    {
        // プレイヤー名の入力
        GUILayout.BeginHorizontal();
        GUILayout.Label("PlayerName : ");
        PhotonNetwork.NickName = GUILayout.TextField(
            (PhotonNetwork.NickName == null) ? "" : PhotonNetwork.NickName, GUILayout.Width(200));
        GUILayout.EndHorizontal();

        // サーバに接続する
        if (GUILayout.Button("Connect Server", GUILayout.Width(150)))
        {
            _networkRunner.StartGame(new StartGameArgs()
            {
                GameMode = GameMode.Shared,
                SessionName = "Lobby",
                SceneManager = gameObject.AddComponent<NetworkSceneManagerDefault>()
            });
        }
    }

    void OnGUI()
    {
        // サーバに接続している場合
        if (_networkRunner.IsRunning)
        {
            // ルームに入室していない場合
            if (_networkRunner.SessionInfo == null)
            {
                GUILayout.Window(1, LobbyGuiRect, RoomNameUIWindow, "");
            }
        }
        else
        {
            GUILayout.Window(2, LobbyGuiRect, PlayerNameUIWindow, "");
        }
    }
}

PhotonPUN2でロビーからゲームシーンに遷移(チャット機能付き)

using System.Collections.Generic;
using UnityEngine;
using Photon.Pun;
using Photon.Realtime;

public class ChatScript : MonoBehaviourPunCallbacks
{
    /** ルーム名. */
    private string roomName = "";

    /** チャット発言文. */
    private string chatWord = "";

    /** チャット発言ログ. */
    List<string> chatLog = new List<string>();

    GUIStyle AllChatStyle = new GUIStyle(); //全体チャットStyle
    GUIStyleState AllChatStyleState = new GUIStyleState();
    Rect RoomGuiRect = new Rect(0, 0, 300, 200); //チャットUIの大きさ設定用
    Rect LobbyGuiRect = new Rect(0, 0, 300, 200);
    Vector2 scrollPos = new Vector2(0, 10);   //スクロールバー位置

    bool FocusFlag = false;
    void Start()
    {
        GUI.FocusControl("");
        //全体チャットの場合
        AllChatStyleState.textColor = Color.white;
        //文字がUIからあふれた場合は折り返す設定
        AllChatStyle.normal = AllChatStyleState;
        AllChatStyle.wordWrap = true;
        PhotonNetwork.AutomaticallySyncScene = true;
    }

    [PunRPC]
    void RecvChat(string senderName, string senderWord)
    {
        chatLog.Add(senderName + " : " + senderWord);
        if (chatLog.Count > 10)
        {
            chatLog.RemoveAt(0);
        }
    }

    void Update()
    {
        //ChatUIの位置を調整
        RoomGuiRect.y = Screen.height - RoomGuiRect.height;
        //ChatUIの大きさ調整
        RoomGuiRect.width = Screen.width / 3;
        RoomGuiRect.height = Screen.height / 3;

        LobbyGuiRect.x = Screen.width / 4;
        LobbyGuiRect.y = Screen.height / 4;
        LobbyGuiRect.width = Screen.width / 2;
        LobbyGuiRect.height = Screen.height / 4;
    }

    void ChatUIWindow(int window_id)
    {
        //FocusがチャットUIに乗ってるときにEnterを押すとチャット発言が実行される
        if (Event.current.type == EventType.KeyDown && (Event.current.keyCode == KeyCode.KeypadEnter || Event.current.keyCode == KeyCode.Return))
        {
            if (!string.IsNullOrEmpty(chatWord))  //チャット入力欄がNullやEmptyでない場合
            {
                //チャット送信関数実行
                photonView.RPC("RecvChat",
                                RpcTarget.All,
                                PhotonNetwork.NickName,
                                chatWord);
                chatWord = "";
                return;
            }
            else
            {
                GUI.FocusControl("");
                FocusFlag = false;
            }
        }
        //垂直のコントロールグループ開始
        GUILayout.BeginVertical();
        // ルーム内のプレイヤー一覧の表示
        GUILayout.BeginHorizontal();
        GUILayout.Label("PlayerList : ");
        foreach (Player player in PhotonNetwork.PlayerList)
        {
            GUILayout.Label(player.NickName + " ");
        }
        GUILayout.EndHorizontal();

        //スクロールビュー開始位置
        scrollPos = GUILayout.BeginScrollView(scrollPos);

        //チャットログ表示用フレキシブルスペース生成
        GUILayout.FlexibleSpace();
        // チャットログを表示する
        for (int i = chatLog.Count - 1; i >= 0; --i)
        {
            GUILayout.Label(chatLog[i], AllChatStyle);
        }
        //スクロールビュー終了
        GUILayout.EndScrollView();

        GUILayout.BeginHorizontal();
        //入力テキストフィールド生成、Focusが乗った状態をChatInputと命名
        GUI.SetNextControlName("ChatInput");
        chatWord = GUILayout.TextField(chatWord, GUILayout.Width(200));
        // チャット発言文を送信する
        if (GUILayout.Button("Send", GUILayout.ExpandWidth(false)))
        {
            photonView.RPC("RecvChat",
                            RpcTarget.All,
                            PhotonNetwork.NickName,
                            chatWord);
            chatWord = "";
        }
        // ルームからの退室
        if (GUILayout.Button("Room", GUILayout.ExpandWidth(false)))
        {
            PhotonNetwork.LeaveRoom();
            chatLog.Clear();
        }

        if (!FocusFlag && Input.GetKeyDown(KeyCode.Return))
        {
            FocusFlag = true;
            GUI.FocusControl("ChatInput");
        }
        GUILayout.EndHorizontal();

        //垂直のコントロールグループ終了
        GUILayout.EndVertical();
    }
    //GetRoomListは一定時間ごとに更新され、その更新のタイミングで実行する処理
    //ルームリストに更新があった時(ロビーに入ってるときのみ呼ばれる)
    public override void OnRoomListUpdate(List<RoomInfo> roomList)
    {
        RoomList = roomList;
    }
    List<RoomInfo> RoomList = new List<RoomInfo>();
    void RoomNameUIWindow(int window_id)
    {
        // ルーム名の入力
        GUILayout.BeginHorizontal();
        GUILayout.Label("RoomName : ");
        roomName = GUILayout.TextField(roomName, GUILayout.Width(200));
        GUILayout.EndHorizontal();

        // ルームを作成して入室する
        if (GUILayout.Button("Create Room", GUILayout.Width(150)))
        {
            PhotonNetwork.CreateRoom(roomName);
        }

        // ルーム一覧を検索
        foreach (var room in RoomList)
        {
            // ルームパラメータの可視化
            System.String roomParam =
                System.String.Format(
                    "{0}({1}/{2})",
                    room.Name,
                    room.PlayerCount,
                    ((room.MaxPlayers == 0) ? "-" : room.MaxPlayers.ToString())
                );

            // ルームを選択して入室する
            if (GUILayout.Button("Enter Room : " + roomParam))
            {
                PhotonNetwork.JoinRoom(room.Name);
            }
        }
    }
    void PlayerNameUIWindow(int window_id)
    {
        // プレイヤー名の入力
        GUILayout.BeginHorizontal();
        GUILayout.Label("PlayerName : ");
        PhotonNetwork.NickName = GUILayout.TextField(
            (PhotonNetwork.NickName == null) ?
                "" :
                PhotonNetwork.NickName, GUILayout.Width(200));
        GUILayout.EndHorizontal();

        // MUNサーバに接続する
        if (GUILayout.Button("Connect Server", GUILayout.Width(150)))
        {
            PhotonNetwork.ConnectUsingSettings();
        }
    }
    // マスターサーバーへの接続が成功した時に呼ばれるコールバック
    public override void OnConnectedToMaster()
    {
        Debug.Log("OnConnectedToMaster");
        PhotonNetwork.JoinLobby();
    }
    public override void OnJoinedRoom()
    {
        Debug.Log("OnJoinedRoom");
    }
    public override void OnCreatedRoom()
    {
        Debug.Log("OnCreatedRoom");
    }
    void OnGUI()
    {
        // サーバに接続している場合
        if (PhotonNetwork.IsConnected)
        {
            // ロビーに入っている場合
            if (PhotonNetwork.InLobby)
            {
                // ルームに入室していない場合
                if (!PhotonNetwork.InRoom)
                {
                    GUILayout.Window(1, LobbyGuiRect, RoomNameUIWindow, "");
                }
            }
            else // ロビーに入ってない場合
            {
                // ルームに入室している場合
                if (PhotonNetwork.InRoom)
                {
                    GUILayout.Window(0, RoomGuiRect, ChatUIWindow, "");
                }
            }
        }
        else // サーバーに接続してない場合
        {
            GUILayout.Window(2, LobbyGuiRect, PlayerNameUIWindow, "");
        }
    }
}

PhotonFusion2でロビーからゲーム画面に遷移(チャット機能付き)

using System.Collections.Generic;
using UnityEngine;
using Fusion;
using Fusion.Sockets;
using UnityEngine.SceneManagement;

public class ChatScript : MonoBehaviour, INetworkRunnerCallbacks
{
    /** ルーム名. */
    private string roomName = "";

    /** チャット発言文. */
    private string chatWord = "";

    /** チャット発言ログ. */
    List<string> chatLog = new List<string>();

    GUIStyle AllChatStyle = new GUIStyle(); //全体チャットStyle
    GUIStyleState AllChatStyleState = new GUIStyleState();
    Rect RoomGuiRect = new Rect(0, 0, 300, 200); //チャットUIの大きさ設定用
    Rect LobbyGuiRect = new Rect(0, 0, 300, 200);
    Vector2 scrollPos = new Vector2(0, 10);   //スクロールバー位置

    bool FocusFlag = false;

    private NetworkRunner runner;
    private List<SessionInfo> sessionList = new List<SessionInfo>();

    void Start()
    {
        GUI.FocusControl("");
        AllChatStyleState.textColor = Color.white;
        AllChatStyle.normal = AllChatStyleState;
        AllChatStyle.wordWrap = true;

        // Fusion NetworkRunner の初期化
        runner = gameObject.AddComponent<NetworkRunner>();
        runner.ProvideInput = true;
        runner.AddCallbacks(this);
    }

    public void OnPlayerJoined(NetworkRunner runner, PlayerRef player) { }
    public void OnPlayerLeft(NetworkRunner runner, PlayerRef player) { }
    public void OnInput(NetworkRunner runner, NetworkInput input) { }
    public void OnInputMissing(NetworkRunner runner, PlayerRef player, NetworkInput input) { }
    public void OnShutdown(NetworkRunner runner, ShutdownReason shutdownReason) { }
    public void OnConnectedToServer(NetworkRunner runner) { }
    public void OnDisconnectedFromServer(NetworkRunner runner) { }
    public void OnConnectRequest(NetworkRunner runner, NetworkRunnerCallbackArgs.ConnectRequest request, byte[] token) { }
    public void OnConnectFailed(NetworkRunner runner, NetAddress remoteAddress, NetConnectFailedReason reason) { }
    public void OnUserSimulationMessage(NetworkRunner runner, SimulationMessagePtr message) { }
    public void OnSessionListUpdated(NetworkRunner runner, List<SessionInfo> sessionList)
    {
        this.sessionList = sessionList;
    }
    public void OnCustomAuthenticationResponse(NetworkRunner runner, Dictionary<string, object> data) { }
    public void OnHostMigration(NetworkRunner runner, HostMigrationToken hostMigrationToken) { }
    public void OnReliableDataReceived(NetworkRunner runner, PlayerRef player, ArraySegment<byte> data)
    {
        string message = System.Text.Encoding.UTF8.GetString(data.Array, data.Offset, data.Count);
        chatLog.Add(message);
        if (chatLog.Count > 10)
        {
            chatLog.RemoveAt(0);
        }
    }

    void Update()
    {
        RoomGuiRect.y = Screen.height - RoomGuiRect.height;
        RoomGuiRect.width = Screen.width / 3;
        RoomGuiRect.height = Screen.height / 3;

        LobbyGuiRect.x = Screen.width / 4;
        LobbyGuiRect.y = Screen.height / 4;
        LobbyGuiRect.width = Screen.width / 2;
        LobbyGuiRect.height = Screen.height / 4;
    }

    void ChatUIWindow(int window_id)
    {
        if (Event.current.type == EventType.KeyDown && (Event.current.keyCode == KeyCode.KeypadEnter || Event.current.keyCode == KeyCode.Return))
        {
            if (!string.IsNullOrEmpty(chatWord))
            {
                SendMessageToAll(chatWord);
                chatWord = "";
                return;
            }
            else
            {
                GUI.FocusControl("");
                FocusFlag = false;
            }
        }

        GUILayout.BeginVertical();
        GUILayout.BeginHorizontal();
        GUILayout.Label("PlayerList : ");
        foreach (PlayerRef player in runner.ActivePlayers)
        {
            GUILayout.Label(player.ToString() + " ");
        }
        GUILayout.EndHorizontal();

        scrollPos = GUILayout.BeginScrollView(scrollPos);
        GUILayout.FlexibleSpace();
        for (int i = chatLog.Count - 1; i >= 0; --i)
        {
            GUILayout.Label(chatLog[i], AllChatStyle);
        }
        GUILayout.EndScrollView();

        GUILayout.BeginHorizontal();
        GUI.SetNextControlName("ChatInput");
        chatWord = GUILayout.TextField(chatWord, GUILayout.Width(200));
        if (GUILayout.Button("Send", GUILayout.ExpandWidth(false)))
        {
            SendMessageToAll(chatWord);
            chatWord = "";
        }
        if (GUILayout.Button("Room", GUILayout.ExpandWidth(false)))
        {
            runner.Shutdown();
            chatLog.Clear();
        }

        if (!FocusFlag && Input.GetKeyDown(KeyCode.Return))
        {
            FocusFlag = true;
            GUI.FocusControl("ChatInput");
        }
        GUILayout.EndHorizontal();
        GUILayout.EndVertical();
    }

    void RoomNameUIWindow(int window_id)
    {
        GUILayout.BeginHorizontal();
        GUILayout.Label("RoomName : ");
        roomName = GUILayout.TextField(roomName, GUILayout.Width(200));
        GUILayout.EndHorizontal();

        if (GUILayout.Button("Create Room", GUILayout.Width(150)))
        {
            StartGame(GameMode.Host);
        }

        foreach (var session in sessionList)
        {
            string roomParam = $"{session.Name}({session.PlayerCount}/{session.MaxPlayers})";
            if (GUILayout.Button("Enter Room : " + roomParam))
            {
                runner.JoinSession(session);
            }
        }
    }

    void PlayerNameUIWindow(int window_id)
    {
        GUILayout.BeginHorizontal();
        GUILayout.Label("PlayerName : ");
        string playerName = GUILayout.TextField("", GUILayout.Width(200));
        GUILayout.EndHorizontal();

        if (GUILayout.Button("Connect Server", GUILayout.Width(150)))
        {
            runner.StartGame(new StartGameArgs
            {
                GameMode = GameMode.AutoHostOrClient,
                SessionName = "MyGameSession",
                SceneManager = gameObject.AddComponent<NetworkSceneManagerDefault>()
            });
        }
    }

    void OnGUI()
    {
        if (runner != null && runner.IsRunning)
        {
            if (!runner.SessionInfo.IsValid)
            {
                GUILayout.Window(1, LobbyGuiRect, RoomNameUIWindow, "");
            }
            else
            {
                GUILayout.Window(0, RoomGuiRect, ChatUIWindow, "");
            }
        }
        else
        {
            GUILayout.Window(2, LobbyGuiRect, PlayerNameUIWindow, "");
        }
    }

    private void StartGame(GameMode mode)
    {
        runner.StartGame(new StartGameArgs
        {
            GameMode = mode,
            SessionName = roomName,
            SceneManager = gameObject.AddComponent<NetworkSceneManagerDefault>()
        });
    }

    private void SendMessageToAll(string message)
    {
        byte[] messageBytes = System.Text.Encoding.UTF8.GetBytes(message);
        foreach (var player in runner.ActivePlayers)
        {
            runner.SendReliableData(player, messageBytes);
        }
    }
}

まとめ

PhotonPUN2ではルームを作るのにCreateRoomでルームに参加するのはJoinRoomだったのに対して、PhotonFusionでは、どちらもnetworkRunner.StartGameで対応できるようになりました。

短いコードでも機能面だけでなく、使いやすさの点においても良くなってるといえますね!

PhotonFusionPhotonFusion,Unity

Posted by Jay