LoginSignup
2
3

More than 3 years have passed since last update.

Monobit Unity Networkingを使ってネットワークマネージャクラスを作る【モノビットエンジン入門】

Last updated at Posted at 2019-07-10

はじめに

これまで、MUNの記事を色々とまとめてきましたが、公式リファレンスがかなり充実していることもあり、
基本的な内容は飛ばして紹介することもしばしばでした。

しかし、いくらリファレンスがあるとは言え、一から読むのもなかなか苦ですから、
サーバ接続/切断、ルーム入室/退室という機能に絞り「とりあえずこれを書けば動かして確認できる」という所までを一度まとめることにしました。
MUNを使ったルームマッチング機能を作る上で参考程度に読んでいただければと思います。

※対象読者さんはオンライン開発を始めたばかりの方ですので初心者向けです。

モノビットエンジンを使う準備がお済でない方は、まずはこちらをご覧ください。
モノビットエンジンクラウドでリアルタイム通信のオンラインゲームを作る:準備編
モノビットエンジンでかんたんサーバ接続

環境は以下の通りです
win10 64bit, Unity2018.3.3f1, Monobit Unity Networking2.0 v2.6

結果

サーバ未接続状態
1.png
サーバ接続状態
2.png
他クライアントから見たロビー画面
3.png

作るもの

まず、以下の3つのスクリプトからなるネットワークマネージャクラスを作ります。
NetworkManager.cs … ネットワーク管理クラス
 |ーServerManager.cs … サーバ接続/切断を管理するクラス
 |ーMatchingManager.cs … ルーム入室/退室、マッチングを管理するクラス

次に、使い方と動作確認のためにuGUI表示クラスを作ります。
InGameGUI.cs
こちらはあくまで確認用なので必要のない方は読み飛ばしてもらって大丈夫です。

注意点として、NetworkManagerクラスはシングルトンクラスなので、Monobehaviourを汎用シングルトン用に改造したSingletonMonobehaviourクラスを継承させます。別の方法でシングルトンにしても全く問題ありません。
書くのが面倒な方はページの最後のほうにSingletonMonobehaviourクラスを載せているので合わせて使ってください。

実装

NetworkManager

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

public class NetworkManager : SingletonMonoBehaviour<NetworkManager> {

    ServerManager m_ServerManager = null;
    MatchingManager m_MatchingManager = null;

    void Awake () {
        m_ServerManager = gameObject.AddComponent<ServerManager>();
        m_MatchingManager = gameObject.AddComponent<MatchingManager>();
    }

    /// <summary>
    /// サーバに接続
    /// </summary>
    public void ConnectServer(string playerName) {
        if (m_ServerManager == null) return;
        m_ServerManager.ConnectServer(playerName);
    }

    /// <summary>
    /// サーバから切断
    /// </summary>
    public void DisconnectServer() {
        if (m_ServerManager == null) return;
        m_ServerManager.DisconnectServer();
    }

    /// <summary>
    /// ルーム作成
    /// </summary>
    public void CreateRoom(string roomName) {
        if (m_MatchingManager == null) return;
        m_MatchingManager.CreateRoom(roomName);
    }

    /// <summary>
    /// ルーム入室
    /// </summary>
    public void JoinRoom(string roomName) {
        if (m_MatchingManager == null) return;
        m_MatchingManager.JoinRoom(roomName);
    }

    /// <summary>
    /// ルーム退室
    /// </summary>
    public void LeaveRoom() {
        if (m_MatchingManager == null) return;
        m_MatchingManager.LeaveRoom();
    }

    /// <summary>
    /// サーバからの切断要求がある
    /// </summary>
    /// <returns></returns>
    public bool IsDisconnectRequest() {
        bool res = false;
        if (m_ServerManager != null) {
            res = m_ServerManager.m_IsDisconnectRequest;
        }
        return res;
    }

    /// <summary>
    /// オブジェクトが削除されたタイミングで呼ばれる
    /// </summary>
    protected override void OnDestroy() {
        base.OnDestroy();
    }
}

実際のゲームからはこのNetworkManagerクラスのメソッドを呼び出してサーバ接続/切断、ルーム入室/退室を行っていきます。
このクラスは見ての通り下位クラスメソッドを呼び出すための仲介役です。
細かい処理は下位クラスで行います。見ていきましょう。

ServerManager

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

public enum ConnectionState {
    Connect,            // 接続中
    Disconnect,         // 切断中
}

public class ServerManager : MonobitEngine.MonoBehaviour {

    public ConnectionState m_State = ConnectionState.Disconnect;

    public bool m_IsDisconnectRequest = false;

    /// <summary>
    /// サーバに接続
    /// </summary>
    public void ConnectServer(string playerName) {
        if(!MonobitNetwork.isConnect) {
            MonobitNetwork.autoJoinLobby = true;
            MonobitNetwork.playerName = playerName;
            MonobitNetwork.ConnectServer("v1.0");
            m_State = ConnectionState.Disconnect;
        }
    }

    /// <summary>
    /// サーバを切断
    /// </summary>
    public void DisconnectServer() {
        if (MonobitNetwork.isConnect) {
            m_IsDisconnectRequest = true;
            if (MonobitNetwork.inRoom) {
                MonobitNetwork.LeaveRoom();
            } else if (MonobitNetwork.inLobby) {
                MonobitNetwork.LeaveLobby();
            }
        }
    }

    //--------------------------------------------------
    // 以下、MUNコールバック関数
    //--------------------------------------------------
    /// <summary>
    /// サーバに接続したときに呼ばれる
    /// </summary>
    public void OnConnectedToServer() {
        Debug.Log("OnConnectedToServer");
        m_State = ConnectionState.Connect;
    }

    /// <summary>
    /// サーバから切断されたときに呼ばれる
    /// </summary>
    public void OnDisconnectedFromServer() {
        Debug.Log("OnDisconnectedFromServer");
        m_State = ConnectionState.Disconnect;
        m_IsDisconnectRequest = false;
    }

    /// <summary>
    /// ロビーに入室したときに呼ばれる
    /// </summary>
    public void OnJoinedLobby() {
        if (MonobitNetwork.autoJoinLobby) {
            Debug.Log("OnConnectedToServer");
            m_State = ConnectionState.Connect;
        }
        Debug.Log("OnJoinedLobby");
    }
}

サーバ接続/切断を行うクラスです。
ConnectServer()はサーバ未接続なら接続要求を出します。
サーバがその要求を受理するとOnConnectedToServer()コールバックメソッドが呼ばれます。
(サーバへの接続 - MUN公式リンク)
(コールバック関数一覧 - MUN公式リンク)

DisconnectServer()は反対に切断の要求を出しますが、注意点として入室状態ならルーム退室→ロビー退室→サーバ切断という順に処理を行います。(サーバからの切断 - MUN公式リンク)

MatchingManager

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

public enum RoomErrorType {
    CROWDED_ROOM,       // 部屋が満員
    NOT_EXIST_ROOM,     // 部屋が存在しない
    FAILED_CREATE_ROOM, // 部屋の作成に失敗
    ALREADY_JOIN_ROOM,  // 既に入室済み
    UNKNOWN_ERROR,      // その他のエラー
    NONE,               // エラー無し
}

public class MatchingManager : MonobitEngine.MonoBehaviour {

    Dictionary<RoomErrorType, string> m_RoomErrorMsgDict = new Dictionary<RoomErrorType, string>() {
        {RoomErrorType.CROWDED_ROOM,        "Crowded room."},
        {RoomErrorType.NOT_EXIST_ROOM,      "Not exist room."},
        {RoomErrorType.FAILED_CREATE_ROOM,  "Failed create room."},
        {RoomErrorType.ALREADY_JOIN_ROOM,   "Already join room."},
        {RoomErrorType.UNKNOWN_ERROR,       "Unknown error."},
    };

    /// <summary>
    /// ルームを作成する
    /// </summary>
    public RoomErrorType CreateRoom(string roomName) {
        var ret = RoomErrorType.NONE;

        do {
            if (MonobitNetwork.inRoom) {
                ret = RoomErrorType.ALREADY_JOIN_ROOM;
                break;
            }

            RoomSettings roomSetings = new RoomSettings() {
                isVisible = true,
                isOpen = true,
                maxPlayers = (uint)DefineValue.MAX_ROOM_PLAYERS_COUNT,
                roomParameters = null,
                lobbyParameters = new string[] { }
            };

            if (!MonobitNetwork.CreateRoom(roomName, roomSetings, null)) {
                ret = RoomErrorType.FAILED_CREATE_ROOM;
                break;
            }

        } while (false);

        ParseRoomErrorLog(ret);

        return ret;
    }

    /// <summary>
    /// ルームに入室する
    /// </summary>
    public RoomErrorType JoinRoom(string roomName) {
        var ret = RoomErrorType.NONE;

        ret = EnableJoinRoom(roomName);

        if (ret == RoomErrorType.NONE) {
            MonobitNetwork.JoinRoom(roomName);
        }

        ParseRoomErrorLog(ret);

        return ret;
    }

    /// <summary>
    /// 入室可能かどうか調べる
    /// </summary>
    public RoomErrorType EnableJoinRoom(string roomName) {
        var ret = RoomErrorType.NONE;
        bool isExist = false;
        foreach (RoomData roomData in MonobitNetwork.GetRoomData()) {
            if (roomData.name == roomName) {
                isExist = true;
                if (roomData.playerCount >= DefineValue.MAX_ROOM_PLAYERS_COUNT) {
                    ret = RoomErrorType.CROWDED_ROOM;
                }
                break;
            }
        }

        if (!isExist) {
            ret = RoomErrorType.NOT_EXIST_ROOM;
        }

        ParseRoomErrorLog(ret);

        return ret;
    }

    /// <summary>
    /// ルームを去る
    /// </summary>
    public void LeaveRoom() {
        if (MonobitNetwork.inRoom) {
            MonobitNetwork.LeaveRoom();
        }
    }

    /// <summary>
    /// エラー内容を解読して表示
    /// </summary>
    /// <param name="errorType"></param>
    public void ParseRoomErrorLog(RoomErrorType errorType) {
        if (m_RoomErrorMsgDict.ContainsKey(errorType)) {
            string log = m_RoomErrorMsgDict[errorType];
#if DEBUG_LOG
            Debug.LogWarning("ROOM_ERROR:" + log);
#endif
        }
    }

    //--------------------------------------------------
    // 以下、MUNコールバック関数
    //--------------------------------------------------
    /// <summary>
    /// ルーム作成が成功したときに呼ばれる
    /// </summary>
    public void OnCreatedRoom() {
        Debug.Log("OnCreatedRoom");
    }

    /// <summary>
    /// ルームに入室したときに呼ばれる
    /// </summary>
    public void OnJoinedRoom() {
        Debug.Log("OnJoinedRoom");
    }

    /// <summary>
    /// ルームを退室したとき呼ばれる
    /// </summary>
    public void OnLeftRoom() {
        Debug.Log("OnLeftRoom");
        if (NetworkManager.Instance.IsDisconnectRequest()) {
            MonobitNetwork.LeaveLobby();
        }
    }

    /// <summary>
    /// ロビーから退室したときに呼ばれる
    /// </summary>
    public void OnLeftLobby() {
        Debug.Log("OnLeftLobby");
        if (NetworkManager.Instance.IsDisconnectRequest()) {
            MonobitNetwork.DisconnectServer();
        }
    }
}

ルームの入退室に関する処理をするクラスです。
CreateRoom()のなかではMonobitNetwork.CreateRoom()を使ってルームを作成をしています。第2引数にルーム設定情報を指定することができます。(ルームの作成 - MUN公式リンク)

JoinRoom()のなかではEnableJoinRoom()を使って入室可能かどうか調べています。
foreach (RoomData roomData in MonobitNetwork.GetRoomData())で公開中の全ルームを取得し、ルーム名を比較しています。
(ルームへの入室 - MUN公式リンク)

InGameGUI

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

public class InGameGUI : MonobitEngine.MonoBehaviour {

    string m_PlayerName = "player1";
    string m_RoomName = "room1";

    void OnGUI() {
        if (!MonobitNetwork.isConnect) {
            OnGUI_Connect();
        }
        else if (!MonobitNetwork.inRoom) {
            OnGUI_RoomCreate();
            OnGUI_ChooseRoom();
        }
        else {
            OnGUI_LeaveRoom();
        }
        OnGUI_Disconnect();
    }

    void OnGUI_Connect() {
        GUILayout.BeginHorizontal();
        GUILayout.Label("Your Name");
        m_PlayerName = GUILayout.TextField(m_PlayerName, GUILayout.Width(150));

        if (GUILayout.Button("Connect Server", GUILayout.Width(150))) {
            if (!string.IsNullOrEmpty(m_PlayerName)) {
                NetworkManager.Instance.ConnectServer(m_PlayerName);
            }
        }
        GUILayout.EndHorizontal();
    }

    void OnGUI_Disconnect() {
        if (GUILayout.Button("Disconnect Server", GUILayout.Width(150))) {
            NetworkManager.Instance.DisconnectServer();
        }
    }

    void OnGUI_LeaveRoom() {
        if (GUILayout.Button("Leave Room", GUILayout.Width(150))) {
            NetworkManager.Instance.LeaveRoom();
        }
    }

    void OnGUI_RoomCreate() {
        GUILayout.BeginHorizontal();
        GUILayout.Label("Room Name");
        m_RoomName = GUILayout.TextField(m_RoomName, GUILayout.Width(150));

        if (GUILayout.Button("Create", GUILayout.Width(75))) {
            if (!string.IsNullOrEmpty(m_RoomName)) {
                NetworkManager.Instance.CreateRoom(m_RoomName);
            }
        }
        GUILayout.EndHorizontal();
    }

    void OnGUI_ChooseRoom() {
        foreach (RoomData roomData in MonobitNetwork.GetRoomData()) {
            if (roomData.playerCount < roomData.maxPlayers) {
                GUILayout.BeginHorizontal();
                GUILayout.Label("Room Name: " + roomData.name + "(" + roomData.playerCount + "/" + ((roomData.maxPlayers == 0) ? "-" : roomData.maxPlayers.ToString()) + ")", GUILayout.Width(200));

                if (GUILayout.Button("Join", GUILayout.Width(75))) {
                    MonobitNetwork.JoinRoom(roomData.name);
                }
                GUILayout.EndHorizontal();
            }
        }
    }
}

SingletonMonoBehaviour

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

public class SingletonMonoBehaviour<T> : MonoBehaviour where T : MonoBehaviour {

    private static T m_Instance = null;

    /// <summary>
    /// インスタンス取得
    /// </summary>
    public static T Instance
    {
        get
        {
            if(m_Instance == null) {
                GameObject obj = new GameObject(typeof(T).Name);
                m_Instance = obj.AddComponent<T>();
                DontDestroyOnLoad(obj);
            }
            return m_Instance;
        }
    }

    /// <summary>
    /// 既にインスタンスが存在するか
    /// </summary>
    public static bool IsInstance() {
        var ret = true;
        if (m_Instance == null) {
            ret = false;
        }
        return ret;
    }

    /// <summary>
    /// オブジェクト削除処理
    /// </summary>
    protected virtual void OnDestroy() {
        m_Instance = null;
    }
}

最後に

いかがだったでしょうか。
結果確認についてはビルド後に複数クライアントを立ち上げて確認することをお勧めします。
これで一通りMUNでネットワーク処理を作る土台はできたと思います。
続きが気になる方は以下のリンクから同期処理に挑戦してみてください。

MUN関連記事

かんたんなPinpong対戦ゲームを作った記事はこちらです

リアルタイムシューティングゲームを作った記事はこちらです

機能に関する記事はこちらです

参考リンク

2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3