【Unity】Fusionを使って接続→操作まで作るメモ
Fusionを使って接続→操作までの処理を作ります。
プレイヤーが抜けたときなどの処理は記述せず、
なるべく接続→操作できるまでのデバッグに必要最低限なもののみに絞ります。
Fusionのインストール・登録
Fusion公式サイトへ行き、パッケージマネージャをインストールします。
公式のチュートリアルを踏めば問題なく進められると思います。
doc.photonengine.com
接続処理を作る
ネットワークへ接続するクラス、NetworkConnectManager .csを作ります。
デバッグ用のボタンが押された際に接続処理をさせるコンポーネントです。
スクリプトの大枠を作る
まずは以下のように処理を記述していきます。
INetworkRunnerCallbacksを継承させたクラスです。
using System; using System.Collections; using System.Collections.Generic; using System.Threading.Tasks; using UnityEngine; using Fusion; using Fusion.Sockets; public class NetworkConnectManager : MonoBehaviour, INetworkRunnerCallbacks { /// <summary> /// 接続状態を表す列挙型 /// </summary> private enum State { Offline = 0, Connecting, Online, }; /// <summary> /// 接続状態 /// </summary> private State m_state=State.Offline; /// <summary> /// デバッグ用GUI /// </summary> private void OnGUI() { GUILayout.Box($"NetworkState : {m_state}"); if(m_state==State.Offline) { // ボタンを押したら接続 if (GUILayout.Button("Network Connect")) { NetworkConnect(); } } } /// <summary> /// 接続処理 /// </summary> private async void NetworkConnect() { // 接続処理を記述 } #region INetworkRunnerCallbacks // INetworkRunnerCallbacksの関数をすべて記述(後述) #endregion }
State列挙型は現在の状態を可視化するために使用します。
ここから、接続処理をさせるNetworkConnect()を書いていきます。
#region INetworkRunnerCallbacksについて
INetworkRunnerCallbacksで使用している関数は以下の通りです。
・Photon Fusion: INetworkRunnerCallbacks Interface Reference
以下の処理を記述してください。
コメントは翻訳したもの使用して付けています。
/// <summary> /// NetworkRunner がサーバーまたはホストに正常に接続したときのコールバック /// </summary> /// <param name="runner"></param> public void OnConnectedToServer(NetworkRunner runner) { } /// <summary> /// NetworkRunner がサーバーまたはホストへの接続に失敗したときのコールバック /// </summary> /// <param name="runner"></param> /// <param name="remoteAddress">リモートアドレス</param> /// <param name="reason">理由</param> public void OnConnectFailed(NetworkRunner runner, NetAddress remoteAddress, NetConnectFailedReason reason) { } /// <summary> /// NetworkRunner がリモート クライアントから接続要求を受信したときのコールバック /// </summary> /// <param name="runner">ローカルNetworkRunner</param> /// <param name="request">リクエスト</param> /// <param name="token">トークン</param> public void OnConnectRequest(NetworkRunner runner, NetworkRunnerCallbackArgs.ConnectRequest request, byte[] token) { } /// <summary> /// 認証手順が認証サーバーから応答を返すと、コールバックが呼び出されます /// </summary> /// <param name="runner">このオブジェクトが存在するランナー</param> /// <param name="data">カスタム認証応答値</param> public void OnCustomAuthenticationResponse(NetworkRunner runner, Dictionary<string, object> data) { } /// <summary> /// NetworkRunner がサーバーまたはホストから切断されたときのコールバック /// </summary> /// <param name="runner"></param> public void OnDisconnectedFromServer(NetworkRunner runner) { } /// <summary> /// ホスト移行プロセスの開始時にコールバックが呼び出される /// </summary> /// <param name="runner">このオブジェクトが存在するランナー</param> /// <param name="hostMigrationToken">Fusion Runnerを再起動するために必要なすべての情報を格納する移行トークン</param> public void OnHostMigration(NetworkRunner runner, HostMigrationToken hostMigrationToken) { } /// <summary> /// ユーザー入力をポーリングするNetworkRunnerからのコールバック。提供される NetworkInput は次を想定しています。 /// </summary> /// <param name="runner"></param> /// <param name="input"></param> public void OnInput(NetworkRunner runner, NetworkInput input) { } /// <summary> /// ユーザー入力失敗のコールバック /// </summary> /// <param name="runner"></param> /// <param name="player"></param> /// <param name="input"></param> public void OnInputMissing(NetworkRunner runner, PlayerRef player, NetworkInput input) { } /// <summary> /// 新しいプレイヤーが参加したときのNetworkRunnerからのコールバック /// </summary> /// <param name="runner"></param> /// <param name="player"></param> public void OnPlayerJoined(NetworkRunner runner, PlayerRef player) { } /// <summary> /// プレーヤーが切断されたときのNetworkRunnerからのコールバック /// </summary> /// <param name="runner"></param> /// <param name="player"></param> public void OnPlayerLeft(NetworkRunner runner, PlayerRef player) { } /// <summary> /// /// </summary> /// <param name="runner"></param> /// <param name="player"></param> /// <param name="data"></param> public void OnReliableDataReceived(NetworkRunner runner, PlayerRef player, ArraySegment<byte> data) { } /// <summary> /// /// </summary> /// <param name="runner"></param> public void OnSceneLoadDone(NetworkRunner runner) { } /// <summary> /// /// </summary> /// <param name="runner"></param> public void OnSceneLoadStart(NetworkRunner runner) { } /// <summary> /// このコールバックは、Photon Cloudからセッションの新しいリストを受信したときに呼び出されます /// </summary> /// <param name="runner"></param> /// <param name="sessionList"></param> public void OnSessionListUpdated(NetworkRunner runner, List<SessionInfo> sessionList) { } /// <summary> /// ランナーがシャットダウンされたときに呼び出されます /// </summary> /// <param name="runner">シャットダウン中のランナー</param> /// <param name="shutdownReason"> Fusion がシャットダウンされた理由を説明します</param> public void OnShutdown(NetworkRunner runner, ShutdownReason shutdownReason) { } /// <summary> /// このコールバックは、手動でディスパッチされたシミュレーション メッセージがリモート ピアから受信されたときに呼び出されます /// </summary> /// <param name="runner">このメッセージの対象となるランナー</param> /// <param name="message">メッセージポインタ</param> public void OnUserSimulationMessage(NetworkRunner runner, SimulationMessagePtr message) { }
NetworkConnect()の記述
接続処理を記述します。
/// <summary> /// 接続処理 /// </summary> private async void NetworkConnect() { m_state = State.Connecting; var runner = GetComponent<NetworkRunner>(); // 接続時設定構造体 var startGameArgs = new StartGameArgs() { GameMode = GameMode.AutoHostOrClient, }; // 接続開始 await runner.StartGame(startGameArgs); // INetworkInputを使用して // 入力情報を使えるようにする runner.ProvideInput = true; m_state = State.Online; }
StartGameArgsは接続時にRunnerに渡す構造体です。
・Photon Fusion: StartGameArgs Struct Reference
今回はGameModeをGameMode.AutoHostOrClientに変更しています。
Fusion側でホスト・クライアントを自動的に振り分けてくれる設定です。
・Photon Fusion: Fusion Namespace Reference
接続用のGameObjectを作る
空のGameObjectを生成し、NetworkRunnerコンポーネントを付け、
その後、同じGameObjectに先ほど記述したNetworkConnectManager.csを追加してください。
INetworkRunnerCallbacksを継承したクラスは
基本的にNetworkRunnerと同じGameObject内に配置しないと
INetworkRunnerCallbacks内のコールバック関数が呼ばれません。
動作確認をして、接続できていることを確かめます。
別GameObjectでINetworkRunnerCallbacksを使用する
もしNetworkRunnerではないGameObjectで使用したい時や、
MonoBehaviourを継承させないクラスなどでコールバックをさせたい場合は、
Runner.AddCallbacks()を使用し、
引数にINetworkRunnerCallbacksを継承したクラスを入れることで使用可能です。
・Photon Fusion: NetworkRunner Class Reference
public class SuperCallBacks : INetworkRunnerCallbacks { // #region INetworkRunnerCallbacks... } public class TMPBehaviour : MonoBehaviour { private SuperCallBacks m_super = null; private void OnGUI() { m_super = new SuperCallBacks(); // 追加 var runner = FindObjectOfType<NetworkRunner>(); runner.AddCallbacks(m_super); // 削除 //runner.RemoveCallbacks(m_super); } }
キャラクター生成処理を作る
キャラクター生成するクラス、CharacterSpawner.csを作ります。
接続に成功した際、キャラクターを生成する処理を持つクラスです。
生成場所のGameObjectを作る
生成場所となる空のGameObjectを1つ以上生成します。
キャラクターのPrefabを作る
空のGameObjectを生成し、その下にキャラクターのモデルを置きます。
その後、キャラクターのRootGameObjectにNetworkObjectと、
NetworkTransformのコンポーネントを追加します。
NetworkTransformのInterpolation Targetに
Rootより一個下のGameObjectの参照を持たせておきます。
NetworkObjectは同期させたいGameObjectに必ず使用するコンポーネントです。
NetworkTransformは付けられているGameObjectの
Transformを同期してくれるコンポーネントです。
その後、プロジェクトフォルダに入れてPrefab化させた後に
Fusion -> RebuildPrefabTable を押します。
・プレハブ | Photon Engine
生成処理を作る
以下のように処理を記述します。
NetworkConnectManager.cs側からSpawn()を呼べるようにしておきます。
using UnityEngine; using Fusion; public class CharacterSpawner : MonoBehaviour { /// <summary> /// 生成プレハブ /// </summary> [SerializeField] private NetworkPrefabRef m_prefab = NetworkPrefabRef.Empty; /// <summary> /// 生成位置 /// </summary> [SerializeField] private Transform[] m_spawnPoints = null; /// <summary> /// 生成 /// </summary> /// <param name="playerRef">プレイヤー情報</param> public void Spawn(NetworkRunner runner, PlayerRef playerRef) { var obj = runner.Spawn(m_prefab, GetSpawnPostion(playerRef), Quaternion.identity, playerRef); runner.SetPlayerObject(playerRef, obj); } /// <summary> /// スポーン位置の取得 /// </summary> /// <param name="player">プレイヤー情報</param> /// <returns>スポーン位置</returns> private Vector3 GetSpawnPostion(PlayerRef player) { if (m_spawnPoints == null || m_spawnPoints.Length <= 0) { return Vector3.zero; } int index = player % m_spawnPoints.Length; return m_spawnPoints[index].position; } }
Instantiate()ではなくRunner.Spawn()で生成します。
また、Runnerで識別しやすいようにSetPlayerObject()でセットしておきます。
NetworkConnectManagerから生成処理を呼ぶ
上記の処理をNetworkConnectManager.cs側で呼びます。
INetworkRunnerCallbacks.OnPlayerJoined()を使用し、
プレイヤーが参加した時に呼ばれるようにしておきます。
// NetworkConnectManager.cs /// <summary> /// 新しいプレイヤーが参加したときのNetworkRunnerからのコールバック /// </summary> /// <param name="runner"></param> /// <param name="player"></param> public void OnPlayerJoined(NetworkRunner runner, PlayerRef player) { var spawner = GetComponent<CharacterSpawner>(); spawner.Spawn(runner, player); }
コンポーネントを追加する
NetworkRunner&NetworkConnectManager.csが付いた
GameObjectにCharacterSpawnerコンポーネントを追加し、
作成したCharacterPrefab・スポーンポイントを設定します。
動作確認をして、出現していることを確かめます。
【GIF】
入力処理を作る
入力を取り扱うクラス、InputPoller.csを作ります。
今回はキャラクターの移動に使用します。
以下のように処理を記述します。
INetworkRunnerCallbacksを継承させたクラスです。
using System; using System.Collections.Generic; using UnityEngine; using Fusion; using Fusion.Sockets; /// <summary> /// 入力構造体 /// </summary> public struct CharacterInput : INetworkInput { /// <summary> 移動 </summary> public Vector2 Direction; } public class InputPoller : MonoBehaviour, INetworkRunnerCallbacks { /// <summary> /// ユーザー入力をポーリングするNetworkRunnerからのコールバック。提供される NetworkInput は次を想定しています。 /// </summary> /// <param name="runner"></param> /// <param name="input"></param> public void OnInput(NetworkRunner runner, NetworkInput input) { // 値の読み取り Vector2 direction = Vector2.zero; if (Input.GetKey(KeyCode.W)) direction.y += 1.0f; if (Input.GetKey(KeyCode.S)) direction.y -= 1.0f; if (Input.GetKey(KeyCode.D)) direction.x += 1.0f; if (Input.GetKey(KeyCode.A)) direction.x -= 1.0f; direction.Normalize(); // 値のセット input.Set(new CharacterInput { Direction = direction, }); } // #region INetworkRunnerCallbacks // ... // #endregion }
INetworkInputを継承した構造体を作り、そこに入力値を割り当てます。
NetworkRunnerが付いたGameObjectにInputPoller.csを追加します。
移動処理を作る
キャラクターの移動処理を扱うクラス、NetworkCharacterMovement.csを作ります。
スクリプトを作る
以下のように処理を記述します。
NetworkBehaviourを継承したクラスです。
using UnityEngine; using Fusion; public class NetworkCharacterMovement : NetworkBehaviour { [SerializeField] [Min(1.0f)] private float m_speed; /// <summary> /// 通信同期に対応した更新 /// </summary> public override void FixedUpdateNetwork() { // 入力値の受け取り if (GetInput<CharacterInput>(out var input)) { Vector3 move = new Vector3(input.Direction.x, 0.0f, input.Direction.y); transform.position += move * m_speed * Runner.DeltaTime; } } }
GetInput<INetworkInput継承構造体>()で3で記述した入力の値が返されます。
ローカルで入力した情報を送信し、それを受け取って移動を行います。
・Photon Fusion: NetworkBehaviour Class Reference
transformはNetworkTransformを付けているため自動的に同期されます。
Prefabに追加する
CharacterPrefabのRootObjectにNetworkCharacterMovement.csを追加します。
動作確認をして、出現していることを確かめます。