概要
- 新しいPhotonSDK PhotonFusionを試してみる
目的
- Photon FusionをUnityProjectに取り込んでみて動作させるまでの手順をメモとして残しておく
環境
- Unity 2021.3.0f1
※PhotonFusionはUnity 2020.3以降である必要があるので注意
PhotonFusionとは
UnityProjectを作成する
UnityProjectを設定する
- Unity 2020.3以降;
- アセットシリアライゼーションは 必ず テキストに設定してください(プロジェクト設定(Project Settings)>エディタ(Editor)>アセットシリアライゼーション(Asset Serialization)と移動し、モードをForce textに設定)
- Mono.Cecil (com.unity.nuget.mono-cecilパッケージがプロジェクトにない場合は、パッケージマネージャを使って手動で追加できます。)
UnityEditorのメニューから
Window/PackageManager/左上の「+」ボタン」/Add package add git URL/下のURLを入力
com.unity.nuget.mono-cecil@1.10
PhotonFusion入手する
- 事前にアカウント登録が必要
- Donwnloadはこちらから→リンク
PhotonFisionに使用するアプリケーションIDを発行する
- こちらから発行→リンク
アプリケーションIDをメモしておく
- 見つからない場合はダッシュボードに行くと見つかる→リンク
※FusionではなくRealtimeのアプリケーションIDだった場合は設定ミスになるので注意
PhotonFusionを導入
- 全てのファイルを追加でOK
アプリケーションIDを入力
- プロジェクト内の
Assets/Photon/Fusion/Resources/PhotonAppSettings
のAppIdFusion
の項目にIDを入力
公式のドキュメントを元に作業を行う
作業フローは下
- スクリプト
BasicSpawner.cs
、NetworkInputData.cs
、Player.cs
を作成
動作するまでのスクリプト全容(公式ドキュメントに部分ごとの説明があるので、本ドキュメントでは割愛)
BasicSpawner.cs
using Fusion;
using Fusion.Sockets;
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class BasicSpawner : MonoBehaviour, INetworkRunnerCallbacks
{
private NetworkRunner _runner;
[SerializeField] private NetworkPrefabRef _playerPrefab;
private Dictionary<PlayerRef, NetworkObject> _spawnedCharacters = new Dictionary<PlayerRef, NetworkObject>();
public void OnPlayerJoined(NetworkRunner runner, PlayerRef player)
{
// Create a unique position for the player
Vector3 spawnPosition = new Vector3((player.RawEncoded % runner.Config.Simulation.DefaultPlayers) * 3, 1, 0);
NetworkObject networkPlayerObject = runner.Spawn(_playerPrefab, spawnPosition, Quaternion.identity, player);
// Keep track of the player avatars so we can remove it when they disconnect
_spawnedCharacters.Add(player, networkPlayerObject);
}
public void OnPlayerLeft(NetworkRunner runner, PlayerRef player)
{
// Find and remove the players avatar
if (_spawnedCharacters.TryGetValue(player, out NetworkObject networkObject))
{
runner.Despawn(networkObject);
_spawnedCharacters.Remove(player);
}
}
public void OnInput(NetworkRunner runner, NetworkInput input)
{
var data = new NetworkInputData();
if (Input.GetKey(KeyCode.W))
data.direction += Vector3.forward;
if (Input.GetKey(KeyCode.S))
data.direction += Vector3.back;
if (Input.GetKey(KeyCode.A))
data.direction += Vector3.left;
if (Input.GetKey(KeyCode.D))
data.direction += Vector3.right;
input.Set(data);
}
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) { }
public void OnCustomAuthenticationResponse(NetworkRunner runner, Dictionary<string, object> data) { }
public void OnReliableDataReceived(NetworkRunner runner, PlayerRef player, ArraySegment<byte> data) { }
public void OnSceneLoadDone(NetworkRunner runner) { }
public void OnSceneLoadStart(NetworkRunner runner) { }
private void OnGUI()
{
if (_runner == null)
{
if (GUI.Button(new Rect(0, 0, 200, 40), "Host"))
{
StartGame(GameMode.Host);
}
if (GUI.Button(new Rect(0, 40, 200, 40), "Join"))
{
StartGame(GameMode.Client);
}
}
}
async void StartGame(GameMode mode)
{
// Create the Fusion runner and let it know that we will be providing user input
_runner = gameObject.AddComponent<NetworkRunner>();
_runner.ProvideInput = true;
// Start or join (depends on gamemode) a session with a specific name
await _runner.StartGame(new StartGameArgs()
{
GameMode = mode,
SessionName = "TestRoom",
Scene = SceneManager.GetActiveScene().buildIndex,
SceneObjectProvider = gameObject.AddComponent<NetworkSceneManagerBase>()
});
}
public void OnHostMigration(NetworkRunner runner, HostMigrationToken hostMigrationToken)
{
Debug.Log("On Host Migration");
}
}
NetworkInputData.cs
using Fusion;
using UnityEngine;
public struct NetworkInputData : INetworkInput
{
public Vector3 direction;
}
Player.cs
using Fusion;
public class Player : NetworkBehaviour
{
private NetworkCharacterController _cc;
private void Awake()
{
_cc = GetComponent<NetworkCharacterController>();
}
public override void FixedUpdateNetwork()
{
if (GetInput(out NetworkInputData data))
{
data.direction.Normalize();
_cc.Move(5 * data.direction * Runner.DeltaTime);
}
}
}
- プレイヤー用のPrefabを作成する
Prefab概要
PlayerPrefab → スクリプトをアタッチ
- Body → モデル(メッシュ) Cubeを配置している
- Collision → あたり判定 BoxColliderを設定
シーンにSpawnオブジェクトを配置
-
Spawn
オブジェクトを生成し、BasicSpawner.cs
をアタッチする -
BasicSpawner
にPlayerPrefab
をアタッチする
- 地面判定用にPlaneを配置しています
完了
- 正しく設定できていればこれでマルチプレイが行えるはずです
- Hostを押す
- 別クライアントでJoinを押す
※1PCでマルチプレイを試す場合は、事前にビルドしてexe化してから「UnityEditor+ビルドしたexe」で行うとやりやすいです
- もし、正しく動作しない場合は下記のものを見てみると良いかもしれません(作業していてミスしそうなだなと思った工程を列挙しています)
「アプリケーションIDはFusionのものか?」
「アセットシリアライゼーションのテキストはForceTextに設定しているか?」
「パッケージマネージャーにMono.Cecilを導入しているか?」
「間違えてFusiionのSDKではなくRealtimeのSDKを導入していないか?」
- それでも動作しない場合は公式ドキュメントを見て設定しなおしてみる
環境構築
最小構成のサンプル
ステップアップ(公式ドキュメントやサンプルを確認する)
公式ドキュメントをすすめる
公式サンプルのTankプロジェクト ガッツリつくりこんである印象 中身を覗いてみると良いかもしれません
VR版サンプル