オフラインのUnityゲーム
をFusionでオンライン化
してみた
手軽にみなさんのゲームのオンライン化ができるよう Photon Fusion の導入、使い方、仕組みを解説します手軽にオフラインのゲームをオンライン化の概要

シーンに追加したのは 赤枠で示している3つのスクリプ
トコンポーネントだけ
一切オフラインのゲームに変更を加えずにオンライン化（本発表のここがすごい！）発表内容
1. Fusion の概要に触れながら、オフライン部分を一切変更せず、オンライン化する手法を紹介します（サンプルコードは公開しています: URL、実装詳細のメモを取る必要ありません）
2. 休憩：Web公開しているオンラインゲームを皆さんと遊びます（同時 100人まで参加可能）
3. WebGLビルドを簡単に Web で公開する手順を紹介します自己紹介
趣味で Unity ゲーム開発しています
普段はこの Twitter アカウントでシンプルスターとしてゲーム進捗を中心につぶやいてます（たまに思ったことなど）
CubeArtWorld というオンライン3Dサンドボックス
ゲーム: URLを2年運営してます（今はFusion検討中）

オンライン化のイメージ 1/4

ここにオフラインで遊べる3 つのクライアントゲームがあります

オフライン

オフライン

オフライン

オンライン化のイメージ 2/4

Fusion では NetworkRunner をシーンに配置し、StartGame 関数を実行してセッションへの参加を試みます

オフライン
Runner

オフライン
RunnerStartGame…

オフライン
Runnerオンライン化のイメージ 3/4

最初にセッションに参加したシーンが Host となりました

オフライン
RunnerStartGame…
Host
オンライン

オフライン

Runner

RunnerTick : 1

StartGame…

オンライン化のイメージ 4/4
Client1

後から参加したシーンが Client となりました
すべてのシーンが同じセッションに参加しました

オンライン
RunnerClient2

Tick: 3

Host
オンライン

オンライン

RunnerTick: 2

RunnerClient の方が Tick が進んでいるのは… Host に届く
頃の Tick に合わせるため

Tick: 3

先ほどのイメージを具体的な手順で見ていきましょう

Unity StarterAssets
でオフラインのサンプルシーンを作成する
Unity は 2021.3.19f1 を使用し新規プロジェクトは 3Dコアをテンプレートに作成し、ビルドター
ゲットはこの時点で WebGL にしておきます
まずは次の Starter Assets Third Person Character
Controller アセットを Package
Manager からインポートします（無料です）
URP 専用なので Project Settginsの設定更新を忘れずに行います現在は WebGLでも Fusion が使えるようになっています！（すばらしいですね）

WebGL ビルド確認
1
１．スマホからも動かせるよう UI
ボタンをアクティブにしたいので黄色枠のUI用 Game Object をアクティブに切り替えます
２．Canvas Scaler で縦画面や横画面に切り替わっても、いい感じのスケールで UI が表示されるように赤枠の Match を 0.5 に設定します
３．Build Settings でPlayground シーンをビルド対象に追加して Build & Run すればlocalhost で動作確認できます2
3

オフラインのゲームが完成！
ではゲームをオンライン化していきましょう！
まずは Photon Fusion SDK 最新版をダウンロードページの赤枠をクリックして入手します
Fusion SDK 1.1.5 F2 Build 643
を今回は使いました.unitypackage ファイルが得られるので、これをプロジェクトにインポートしますFUSION App ID をUnityプロジェクトに登録
Fusion SDK パッケージをインポート直後、App ID の入力を求められます

SDK のダウンロードページから Dashboard を開けます。そこから自身の FUSION アプリを作成（無料でいくつもアプリが作れます！）アプリに記載されているApp ID を赤枠の欄に貼り付けます
これでプロジェクトは無料枠の 20
人同時接続まで可能な設定になりましたAutoHostOrClientModeで接続する( C on n ection M a n a g er の お 仕 事 )ユーザースクリプト

起動後すぐにオンライン接続するゲームを作ります
赤い矢印 で示している
AutoHostOrClient は、最初にSession に参加したシーンが Hostになり、後続で参加したシーンがClient になります
ユーザーは Host や Client を選択するフローをスキップしてオンラインゲームに参加できます

Task InitializeNetworkRunner(NetworkRunner runner){var sceneManager =runner.GetComponents(typeof(MonoBehaviour)).OfType<INetworkSceneManager>().FirstOrDefault();
if (null == sceneManager){
sceneManager = runner.gameObject.AddComponent<NetworkSceneManagerDefault>();}return runner.StartGame(new StartGameArgs{GameMode = GameMode.AutoHostOrClient,Address = NetAddress.Any(this.serverPort),
SessionName = this.sessionName,
SceneManager = sceneManager,});}PlayerJoined 関数がプレイヤーがセッションに参加するたびに呼ばれます
プレイヤーの入力と状態を同期するため プレイヤーオブジェクトをSpawn します

入力と状態の同期 1/5
Client1
オンライン
RunnerPlayerJoined (Client1)
PlayerJoined (Cleint2)

Host
オンライン

全プレイヤーの入退出タイミングを Host なら処理できる
RunnerPlayerJoined (Host)
PlayerJoined (Client1)
PlayerJoined (Cleint2)

Client2

オンライン
RunnerPlayerJoined (Cleint2)

入力と状態の同期 2/5

Spawn する際、InputAuthorityに参加してきたプレイヤーを設定します

Client1
オンライン

何もしない
RunnerClient2

Host
オンライン

PlayerObject を Swawn

RunnerPlayerObject 0 (InputAuthority : Host )PlayerObject 1 (InputAuthority : Client1 )PlayerObject 2 (InputAuthority : Client2 )オンライン

何もしないRunner入力と状態の同期 3/5
出現PlayerObject(Spawn 可能な NetworkObject) はHost で Spawn すると Client シーンにも出現します

Client1

PlayerObject 0

オンライン

PlayerObject 1
PlayerObject 2
Client2
Host
オンライン

PlayerObject 0
PlayerObject 1
PlayerObject 2

出現

オンライン

PlayerObject 0
PlayerObject 1
PlayerObject 2

入力と状態の同期 4/5
Client1
Runner.OnInput(NetworkInput input)
input.Set(INetworkStruct)
オンライン

InputAuthority の理解として、
NetworkObject の GetInput では、InputAuthority の Client の入力が得られます
送る側はどのスクリプトから入力を行っても構わない (非NetworkObject でも大丈夫)
Client2

Host

Runner.OnInput(NetworkInput input)
input.Set(INetworkStruct)
オンライン

Runner.OnInput(NetworkInput input)
input.Set(INetworkStruct)
オンライン

PlayerObject 0

FixedUpdateNetwork()
if (GetInput(out INetworkStruct input))

PlayerObject 1

FixedUpdateNetwork()
if (GetInput(out INetworkStruct input))

PlayerObject 2

FixedUpdateNetwork()
if (GetInput(out INetworkStruct input))StateAuthority の理解としてはHost で変更した State([Networked] 属性の プロパティ)
はすべての Client に同期されますmain/replica の関係

入力と状態の同期 5/5
Client1
PlayerObject 0
オンライン
PlayerObject 1
PlayerObject 2

RunnerClient2

Host
オンライン
PlayerObject 0

Runnerオンライン
PlayerObject 0

PlayerObject 1

PlayerObject 1

PlayerObject 2

PlayerObject 2

Runnerプレイヤーオブジェ
クトの作成( N etw ork Ru n n er Prefa b の 詳 細 )NetworkRunner の Prefab にアタッチした SimulationBehaviour
継承クラスにて IPlayerJoined を実装すると、プレイヤーが Sessionに参加したタイミング
で PlayerJoined が呼ばれます
ここで inputAutority を Join したプレイヤーに設定してプレイヤーオ
ブジェクトを Spawn しています

ユーザースクリプト

public class NetworkGameManager : SimulationBehaviour, IPlayerJoined, IPlayerLeft{[SerializeField] NetworkGame networkGame;[SerializeField] NetworkPlayer networkPlayer;
void IPlayerJoined.PlayerJoined(PlayerRef playerRef){
if (!this.Runner.IsServer)
Host 意外は何もしない早期リターン{（Host は IsServer フラグが True）
return;}
参加してきたプレイヤーを指定
if (!this.networkGameSpawned){
this.Runner.Spawn(this.networkGame);
this.networkGameSpawned = true;}
var networkPlayer = this.Runner.Spawn(this.networkPlayer, inputAuthority: playerRef);
this.networkPlayers.Add(playerRef, networkPlayer);this.Runner.SetPlayerObject(playerRef, networkPlayer.Object);}Cleint シーンの INetworkRunnerCallbacks 実装クラス

INetworkRunner Callbac ks を実装したクラスをRunner.AddCallbac ks に渡すとTick ごとに OnInput が呼ばれるようになります
NetworkInput にINetworkStruc t を継承した構造
体を Set します
Host で NetworkBehaviour を継承したクラスにてFixedUpdateNetwork など
で Tick ごとに GetInput すると、InputAuthority に設定された Client の入力を受け取ります

通信

InputAuthority

Host シーンの NetworkBehaviour 継承クラス

StateAuthority
赤枠で示している[Networded] 属性を与えたプロパ
ティが状態(State)です
Host モードではすべての NetworkObjectの StateAuthority に Host が設定されています
Host にて状態を更新すると、Client にも変更した状態が伝わります (Client にて状態の更新を実装しても処理は無視されます)
OnChanged に関数を指定できるので、状態が変化したタイミングに処理を記述できます（便利）落とし穴

Prefab Table をリビルドする
ことを忘れずに！
NetworkObject の Spawn は Prefab を利用していますSpawn 時にテーブルに Prefab が無いというエラーが出る場合があります
Prefab Table が古い場合はリビルドする必要があるので、忘れずに行っておきましょう
メニューの Fusion > Rebuild Prefab Table

基本のおさらいHost で Spawn した
NetworkObject (Prefab) は Client
シーンでも Spawn されます

オフラインのゲームを変更する
オフライン実装を変更しない仕組み 1/2 ことなく、どうやってオンライン化するのか？
Client1

オンライン？

？
RunnerClient2

Hostオンライン？

？
Runnerオンライン？
？
Runnerオフライン実装を変更しない仕組み 2/2
Client1
OnInput(NetworkInput input)
input.Set(INetworkStruct)
オンライン

1

3

１．オフラインのプレイヤーエージェントのTransform を入力として送信
２．Host で受け取った Transform をプレイヤーエージェントに設定
３．InputAuthority の Client シーンではプ
レイヤーエージェントを非表示

RunnerClient2

Hostオンライン

オンライン
RunnerPlayerObject 12

Runner
GetInput(out INetworkStruct input)

オフラインゲーム
を非破壊で参照
Host に Client の入力を送信する
方法を解説します
オフラインのゲームに一切変更を加えない制約を守っています
ゲーム入力をシーン参照することで解決してます
シーンに配置されている
PlayerArmature オブジェクトの StarterAssetsInputs を参照
することで、ユーザーの Move,Look Vector2 や Jump, Sprint の状態を参照できます
PlayerAmature の Transformも同様に参照します

参照NetworkGame クラスの Spawned でインタフェース実装を登録

Client 入力はどこからでも送信できる
INetworkRunnerCallb ac ks を実装したクラスを NetworkRunner のAddCallbacks に登録します赤い矢印

Singleton パターンなら NetworkObject の処理にもシーンの参照を渡せます

OnInput が Tick ごとに呼ばれるようになるため、ここでオフラインのゲーム状
態を Set します
Host 側のプレイヤーエージェントの状
態を入力として送信し Host で上書きしてしまえば、Client が状態を持つオンラインゲームもデザインできます

補足：入力に状態を渡すのは、データ通信量が増えたり、チートに弱くなったりするので、Host ではなくShared モードも検討しましょう（今回は Host 利用にこだわってみました）

オフラインゲームの状態を参照

状態を入力として送信

プレイヤーエージェントの Spawn と非表示
プレイヤーオブジェクトとプレイ
ヤーエージェントは分けて Prefabを用意します
今回は NetworkGame の Join 関
数に Spawn するコードを書いています
オフラインゲームで存在しているキャラクターモデルとプレイヤーエージェントが重なって表示されてしまいますので InputAuthorityを持っているプレイヤーエージェ
ントの Renderer を非表示にしていますプレイヤーエージェントのアニメーション同期

プレイヤーエージェントの Owner がプレイヤーオブジェクトを指しています
そのプレイヤーオブジェクトのStarterAssetsInputs の入力をプレイヤーエージェ
ントの SatarterAssetsInputs に設定していますTransform の同期だけだと、エージェントが棒立ちしたまま移動と回転するだけなので、自然に振る舞わせるようゲームの入力も一緒に送信します（本来の使い方）

Host で取得した入力を一度 Stateに設定し、他の Client はそのState を入力に使ってプレイヤーエージェントのアニメーションを再生します

それぞれのプレイヤーのカメラの向きも送信させました、このカメラ向きを使えば入力の軸の向きを調整することができ、他プレイヤーの入力もオフラインゲームのコードを修正することなく適用することができました技術ポイント：NetworkBehaviour の Render 関数は MonoBehaviour のUpdate 関数の代替で利用しよう( Spawn 後、シミュレーション後になってから呼ばれる利点がある)

Transform のなめ
らかなずれ修正
入力だけを使って動かすと、なめらかに他プレイヤーエージェントが動きました

ただしこれには、積み重なる位置ずれを修正しなければならない課
題がありますNetworkTransform を使うと解決するのですが、ガタついてしまったので、なめらかにゆっくりとずれを修正する仕組みがほしいとこ
ろで、今回作りました

NetworkBehaviour を継承して [Networked] 属性を プロパティに与えて State を同期しますStateAuthority だったら Transform を State にProxy だったら State を Transform に設定する

オフラインゲームのオンライン化まとめ
オフラインゲームに変更を加える
ことなく、他プレイヤーのエージェントがゲームシーンに登場する実装を具体的な手順と Fusionの仕組みの図解を通して解説しま
した

ぜひみなさんのオフラインゲームをオンライン化してみてください
サンプルプロジェクトはこちらに公開しました→ URL

小休止：みんなで遊んでみよう
https://is.gd/Fq0RN3
上記アドレスにアクセスしてみてくだ
さい
ブラウザでゲームが起動し、すぐにオンライン化して他プレイヤーのエージェントと空間を共有できると思います

公開用に、ちょっとだけゲームをいじりまして、シーソーやボールを配置していますWebGLビルドのゲームをWeb公開する手順(用語説明～)

ビルド成果物を Web ページとして公開しても、残念ながらエラーが出て Unity ゲームは動きません

この問題の解決方法を紹介します
まず WebGL ビルド結果のフォルダを AWS の CloudFront のオリジンに設定した S3 に配置します…

CloudFront は AWS のサービスの一つでデータ配信に使えます
S3 は AWS のサービスの一つでデータ配置に使えます
CloudFront で配信するデータの一番元となるデータ置き場に S3 を設定することができて、これをオリジンに設定したと表現しました

じゃあ WebGL ビルド結果のフォル
ダをその CloudFront のオリジンに設定済みの S3 に配置すればゲーム公開完了？というところで、そうもいかないエラーが発生するので、その課題を対象にこれから説明をはじめます

つまり… CloudFront のオリジンに設定した S3 にデータを置くと、URL など Web ブラウザ経由でデータを配信できるようになるわけです

少し用語説明からしますね
AWS とは Amazon Web Services
有名なクラウドサービスですね

オリジンにS3を指定

メタデータを .gz
ファイルに追加
Build フォルダ以下に .gz 拡張子のファイルが 3つあります
これらのファイルにメタデータの追加を行い

タイプ：システム定義
キー：Content-Encodi ng
値：gzipを設定しますメタデータを .gzファイルに追加2.wasm.gz ファイルだけ追加でメタデータの追加を行います
タイプ：システム定義

キー：Content-Type
値：application/wasmを設定します

以上です
このメタデータ追加によって、エラーが解消されます参考にした公式
サンプル
Fusion Projectiles: URL•複数人でシューティングゲームするサンプル•

シーンに登場する多数の弾丸をどうやって同期すると効率的なのか回答が示されています

Fusion VR ホスト: URL
Fusion VR 共有: URL•

VR機器をつけて複数人でワールドを共有し、シーンのモノをつかんだり投げたりするサンプル発表は以上です
ご清聴ありがとうございました