ConnectionApproval を利用して入室時のパスワードチェック機能を実装する【MLAPI】

Environment

  • Unity 2020.3.1f1
  • MLAPI v0.1.0
  • UniRx 7.1.0

引き続きこちらのQuickStartで追加実装します.

xrdnk.hateblo.jp

Connection Approval

旧 MLAPI にもあった機能です.接続の際に許可チェックを実装することが出来ます.

mp-docs.dlt.it.unity3d.com

How To Use

ConnectionApproval を利用するためには,NetworkManager の Connection Approval にチェックを入れる必要があります.

f:id:xrdnk:20210331090603p:plain

また,NetworkManagerHudを利用するとConnectionApprovalの動作チェックができないので,
今回は利用せず,各自で接続用のViewを作成する必要があります.適当に以下の感じで作りましょう.

f:id:xrdnk:20210331092623p:plain

サンプルスクリプトの中で説明を入れました.

ConnectionView.cs

using System.Text;
using MLAPI;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
using UniRx;

namespace MLAPIQuickStart.View
{
    public class ConnectionView : MonoBehaviour
    {
        [SerializeField] private TMP_InputField inputFieldPassword;
        [SerializeField] private Button buttonHost;
        [SerializeField] private Button buttonClient;
        [SerializeField] private Button buttonLeave;

        private void Start()
        {
            NetworkManager.Singleton.OnServerStarted += HandleServerStarted;
            NetworkManager.Singleton.OnClientConnectedCallback += HandleClientConnected;
            NetworkManager.Singleton.OnClientDisconnectCallback += HandleClientDisconnect;

            buttonHost.OnClickAsObservable()
                .Subscribe(_ => Host())
                .AddTo(this);
            buttonClient.OnClickAsObservable()
                .Subscribe(_ => Client())
                .AddTo(this);
            buttonLeave.OnClickAsObservable()
                .Subscribe(_ => Leave())
                .AddTo(this);

            buttonLeave.interactable = false;

            // InputField に値が設定されるまで,ボタンを非活性にする
            inputFieldPassword.ObserveEveryValueChanged(inputField => inputField.text)
                .Subscribe(text =>
                {
                    buttonHost.interactable = !string.IsNullOrEmpty(text);
                    buttonClient.interactable = !string.IsNullOrEmpty(text);
                })
                .AddTo(this);
        }

        private void OnDestroy()
        {
            if (NetworkManager.Singleton == null) { return; }

            NetworkManager.Singleton.OnServerStarted -= HandleServerStarted;
            NetworkManager.Singleton.OnClientConnectedCallback -= HandleClientConnected;
            NetworkManager.Singleton.OnClientDisconnectCallback -= HandleClientDisconnect;
        }

        private void Host()
        {
            // サーバ側(ここではホスト側)は接続許可設定を行う
            NetworkManager.Singleton.ConnectionApprovalCallback += ApprovalCheck;
            NetworkManager.Singleton.StartHost(new Vector3(-2f, 0f, 0f), Quaternion.Euler(0f, 135f, 0f));
        }

        private void Client()
        {
            // クライアント側はサーバ側へ接続時に必要なデータを送る必要がある
            NetworkManager.Singleton.NetworkConfig.ConnectionData = Encoding.ASCII.GetBytes(inputFieldPassword.text);
            NetworkManager.Singleton.StartClient();
        }

        private void Leave()
        {
            if (NetworkManager.Singleton.IsHost)
            {
                NetworkManager.Singleton.StopHost();
                NetworkManager.Singleton.ConnectionApprovalCallback -= ApprovalCheck;
            }
            else if (NetworkManager.Singleton.IsClient)
            {
                NetworkManager.Singleton.StopClient();
            }
        }

        private void HandleServerStarted()
        {
            if (NetworkManager.Singleton.IsHost)
            {
                HandleClientConnected(NetworkManager.Singleton.ServerClientId);
            }
        }

        private void HandleClientConnected(ulong clientId)
        {
            if (clientId == NetworkManager.Singleton.LocalClientId)
            {
                inputFieldPassword.interactable = false;
                buttonLeave.interactable = true;
            }
        }

        private void HandleClientDisconnect(ulong clientId)
        {
            if (clientId == NetworkManager.Singleton.LocalClientId)
            {
                inputFieldPassword.interactable = true;
                buttonLeave.interactable = false;
            }
        }

        /// <summary>
        /// 接続チェック用のコールバック
        /// </summary>
        /// <param name="connectionData">ペイロード</param>
        /// <param name="clientId">ClientId</param>
        /// <param name="callback">デリゲート</param>
        private void ApprovalCheck(byte[] connectionData, ulong clientId,
            NetworkManager.ConnectionApprovedDelegate callback)
        {
            // クライアント側がサーバ側に送る時のペイロード
            var password = Encoding.ASCII.GetString(connectionData);

            // 今回ここでは inputFieldPassword.text の値は「ホスト側」が設定した値になる
            // なので,ホスト側とクライアント側のパスワードが一致した時に true になる
            // ここで false になった場合,callback は発火せず,接続できない
            var approveConnection = password.Equals(inputFieldPassword.text);

            var spawnPos = new Vector3(Random.Range(-5, 5), 0, Random.Range(-5, 5));
            var spawnRot = Quaternion.identity;

            // 第一引数はデリゲートを通してプレイヤーオブジェクトを生成するか否か.個人でカスタマイズしたい場合は false にする.
            // 第二引数はどのプレハブを生成するか(ハッシュ値で)."Default Player Prefab" にチェックが入っている場合は null にする.
            // 第三引数は接続の許可が得られたか否か.大方,パスワードが合っているかどうかの判定をここに入れる.脳死で通したい場合は true にする.
            // 第四引数,第五引数は生成時にプレイヤーに渡すPositionとRotation.プレハブのデフォルト値を渡したい場合は null にする.
            callback(true, null, approveConnection, spawnPos, spawnRot);
        }
    }
}

以前,MLAPI で接続処理の方法については説明済です.新 MLAPI でも書き方はあまり変わらないです.

xrdnk.hateblo.jp

ConnectionApproval 側でスポーン時の設定を行うことが出来るので,
PlayerScript の Start() にあった以下のスクリプトコメントアウトします.

// thisTransform.position = new Vector3(Random.Range(-5, 5), 0, Random.Range(-5, 5));

ホスト側で ConnectionApprovalCallback の設定を行います.
ここでは,ホスト側が InputField に入力した値がパスワードになります.
クライアント側はホスト側の入力した値と一致しないと接続できません.

以下,動作確認です.

まずはホスト側でパスワードを設定して入室.

gyazo.com

次にクライアント側で入室.
最初間違ったパスワードで入力するとスポーンされず,正しいパスワードを入力するとスポーンされたのを確認できます.

gyazo.com

Reference

www.youtube.com

Dapper Dino 氏は Mirror の動画も結構わかりやすく説明されています.
MLAPI の Discord でのやり取りを見た感じ,今後 MLAPI のチュートリアル動画をアップロードしていく模様なので,
新 MLAPI の How To Use を追いたい方はチャンネル登録した方が良さみあります.