Unreal Engineでサクッとオンラインゲーム制作 Part1 基本概念の説明

Post 2022年2月10日木曜日

UE4 UE5 Unreal Engine オンラインゲーム

今回からオンラインゲームの制作をスタートしようと思います。

本当に企画が完成できるのかは、謎!笑


お約束
この記事作成にあたって使用した主なUnreal Engine バージョン特になし!

本日のゴール

サクッとオンラインゲームの基本概念を理解する


参考にしたリンク

色々と参考にしましたが、このあたりをメインで引用しています。


オンラインゲームの3つの基本概念

① サーバー(Server)とクライアント(Client)

オンラインゲームとオフラインゲームで、制作はどのように異なるのでしょうか?

例えば、キャラクターがボスキャラを倒すシーンを思い浮かべてみます。

普通のゲームでは、キャラクターがボスキャラを倒すと、倒した際のアニメーション処理などを行なって、ボスキャラをdeleteして終わりですよね?


ですが、オンラインゲームだとそう簡単にいきません。

例えば3人繋がっているオンラインゲームで、ある人[A] が別の人[B] を倒したとしましょう。

[A]のパソコン上で、[B]を倒したという処理を行なっても、[C]のパソコンにどのようにその結果を反映するのでしょうか?

またほぼ同時に[C]が[A]を倒すという処理をした場合、[B]は倒れるのでしょうか?

この判定はどのようにすれば良いのでしょう?


答えは、こういった処理全てを、サーバーで行うのです

サーバーで処理を行えば、どちらが速かったんだと揉めることなく処理ができますね。

このように、サーバー(Server)に対して各プレイヤーは明確に区別され、「クライアント(Client)」と呼ばれます。ここでいうと、A, B, Cのパソコンがクライアントになりますね。

「Server」と「Client」これがオンラインゲームで重要な要素の一つです。

そして、アクターを消す、というような処理は必ずサーバー側で実施する、というのが原則になります。


② 世界の共通化と、Replicate

オンラインゲームでもう一つ重要なのが「世界の共通化」ですね。

Aのゲーム世界では既にCが倒されていて、Bのゲーム世界ではCは生きてる、なんてことがあってはいけません。


このため、サーバーから世界の状況を常にコピーしてもらう必要があります。このコピーのことをレプリケート(Replicate)といいます。

このReplicatesプロパティがオンになっているActorは、サーバーから常に状況がコピーされるようになります。


ちなみにデフォルトでONになってますので、特に意識しなくてもマルチプレイヤー化が可能です。

※このコピーはあくまでサーバー → クライアントへのコピーであり、逆方向(クライアント→サーバー)のコピーはされていませんので注意。


「え、でもアクターだけサーバーからコピーしていれば本当に同一世界になるの?」

と思ったそこのあなた。鋭い。

Replicateの概念は、アクターだけではなく、変数Movement (Projectile MovementやCharacter Movement) においても重要です。

その他にもTimelineとかもReplicateできます。

変数の"Replication"のプロパティ項目で、「Replicated」を選択すると同じくサーバーからクライアントに変数の内容が同期されます。


Replicateのマーク

ちなみに「RepNotify」を選択すると、変数の値が変更された際のイベント「OnRep_変数名」という関数が自動で作られます。変数がONになったときに、ドアを開けるとか宝箱を開く、といったイベントを作るのに便利です。


さて、各操作キャラクターは、常に状況がアップデートされてほしいですが、炉端の石ころなんかはゲームに大きな影響を及ぼしませんから共有されなくてもいいわけです。なのでキャラクターのReplicatesプロパティはONにして、石ころはOFFにするみたいな感じです。

また、ReplicatesのON/OFFはブループリントで切り替えることもできます。(Set Replicatesノード)


「Client側からServer側に情報を送ることはできないの?」

と思ったそこのあなた。鋭い。

もちろんできます。RPCの解説まで待ってください。


Note

で、常に状況をアップデートするということは、何もしてなくても常に状態を送信するということになって回線を圧迫します。なので、特にプロパティに変化がない場合には状況のアップデートを停止するネット休眠状態(Net Dormancy)という設定を、サーバー上ですることができます。

(ちなみにNet Dormancy状態は、対象のアクターのプロパティを何らか変更すれば自動的に解除されます。されるはず…。)


さてここで問題が生じます。

ReplicatesプロパティがONになっているアクターを動かしたり消そうとしたりした時、Serverと複数のClient間で主導権の奪い合いが生まれないか?ということです。では次の重要な概念にいきましょう。



③ 3つの権限Role (Authority, AutonomousProxy, SimulateProxy)

上記の通り、オンラインゲームではアクターに対する主導権争いが生まれるため、「お前がやっていいことはここまで」という権利の制限(=権限)が定められています。
この「権限 Role」という概念が重要になります。
※Roleを直訳すると役割ですが、いまいちしっくりこないので権限としました。

Roleは大きく分けて3種類

"Authority" 

"AutonomousProxy" 基本的にSimulateProxyと同じだが、後述するRPCを通じてAuthorityに情報を送れる

"SimulateProxy" Authority側に何も情報を送れない

Roleを取得するノード。ターゲットはActor


なので

「サーバー」と「クライアント」

という概念と、

Actorに対する3つのRole (Authority, AutonomousProxy, SimulateProxy)

については分けて考える必要があります。

サーバーは基本的にAuthority権限を持ちます。クライアントは基本的にSimulateProxyです。

※クライアントが、Authorityの権限をもつアクターもあります。(Client側で作ったReplicatesオフのアクターなど)

サーバーかどうかを判断するノード

でもここで一個疑問があります。クライアントがSimulateProxyの権限しか持てないなら、自分が操作しているキャラクターの動きとかはどうやってサーバー側は感知しているの?

そうPlayer Controllerを有しているキャラクターのみ、一部サーバー側に情報を送れないといけないので、AutonomousProxyという権限が与えられるわけです。(間違っていたらすみません)

そして後述するRPCというもので、AutonomousProxyはサーバー側に情報を送れるようになっています。

で、Authorityなのかどうかを見分けるノード(というよりマクロ)なんかも存在します。SwitchHasAuthorityマクロです。これも後述します。



RPC(リモートプロシージャーコール)

さて、3つの概念について分かったところで何度か話にあがっているRPCについても解説しておきます。

RPCはActorに紐づいている、カスタムイベントの一種です。

カスタムイベントを作って、そのカスタムイベントのプロパティを変更することでRPCになります。

概念的にはServer - Client間の通信のような感じで使います

Reliability (信頼性)のチェックをすると、優先して通信帯域を使うようになるみたいです。このため、Reliabilityをチェックしすぎると帯域圧迫するみたいなんで注意してください。(よくわかってない) リアルタイムなゲームであればチェックを外すのが基本みたいですね。

ちなみにRPC自体がかなり通信的に優先されるみたいなんで、多用は無用ってのが原則みたいです。


Multicast Event

呼び出し(Call)をサーバーで行うと、サーバーと全てのクライアントで処理が実行される。

クライアントで実行した場合は、そのクライアントでしか実行されない。

サーバー側からクライアントへの命令的な感じ。

なるべく使わず、変数のReplicateで代用できないか検討しましょう。

変数のReplicateで基本的にはOKですが、通信帯域によっては変数のReplicateは後回しになりがちで、重要な情報に関してはMuticastで一斉配信というのもアリみたいですね。(といってもやったことはないんで全然わかりませんが)

基本的には音楽を鳴らしたり、Effect出したり、そういった変数を伴わない処理に使うみたいです。


Run On Server (サーバーで実行) Event

個人的に一番有用。
呼び出し(Call)をクライアント(ただしAutonomousProxy限定)で行うと、サーバー側で処理が実行される。
Multicastの逆って感じ。
Client側で変更した変数の値を、サーバー側に送りたい場合、このイベントを使う。

ただし呼び出しを実施したクライアントが、当該アクターのControllerを有する必要があります。(SimulateProxyではこのEventは実行できない) ※後述

上記の図がシステムの全体像です。RPC Run On Severイベントによって、Eventをサーバーに実行させます。

このイベントのポイントは2つです。

ポイント1:RPCを実行するだけでは、クライアントAにも結果が反映されない

RPCは、サーバーにイベントを実行させる命令なので、クライアントAに反映するためにはReplicateを使って結果を反映させる必要があります。
上の図でも、Replicateを示すオレンジの矢印がクライアントA, B, Cすべてに入っていますよね?

ポイント2:RPC Run On Serverを実行するBPには、Player Controllerが必要。

このイベントはAutonomousProxyでは実行できますが、SimulateProxyでは実行できません。
例えば、自分の操作キャラのBP内でこのEventを実行することはできるのですが、
そのへんのドアとか宝箱とかのBPで、このEventを実行することはできません。このため、ドアとか宝箱とかから、なにか情報をサーバー側に送りたい場合はCast / BPI (ブループリントインターフェース) / Event Dispatcherかなにかを使ったり、Gamplay Abilityを使ってAuthorityあるいはAutunomousProxy側から情報を送信してもらう必要があります。

超注意です。(この時点では何がなにやら分からんでしょうが…)

なんで?ってことなんですが
さっきも言ったとおり、操作キャラの動きとかがサーバーが感知されないのはよろしくないですよね?
このためにCharacterのControllerは、サーバー側にもクライアント側にも存在しており、その動作の検知は情報交換されているわけです。この操作キャラの操作に関しては、クライアントもサーバーとほぼ同じ権限を有するわけですね。なのでRPC Run On Serverみたいなこともできるんですが、Controllerがない普通のアクターとかでは、そもそもサーバーとクライアント側をつなぐ橋渡しである、Controllerがないため、RPC Run On Serverが動かない…という認識ですが合ってますかね???

Run On Owning Client Event

Multicastイベントとほぼ同じだが、クライアント側がAutonomousProxyの場合のみ実行される。


所有権については、あまり深くは説明しません。私が理解できてないからです。笑

上記への補足なんですが、

サーバーとクライアント間の通信は"NetworkDriver"直下の「NetConnection」さんが担っています。「Net Connection」さんはサーバーにもクライアントにも存在しており、両者の架け橋てきな存在です。

このNetConnectionさんをコントロールしているのがPlayerControllerというわけなんですが、RPCはPlayer Controllerに対して働きかけるので、PlayerControllerを持っていない通常のアクターはRPC Run On Serverが実行されない…そうです。


https://historia.co.jp/archives/12823/

所有権について、よりわかりやすくhistoria様が解説してくれているので参考にしてください。



SwitchHasAuthorityマクロとIs Locally Controlled

例えばClient側でプレイしているキャラのBPを用意します。

シングルプレイヤーのゲームばかり作っていると、「?」になるかもしれませんが、このBPになにか処理を書いた場合、なんと「Server側とClient側」どちらでも処理が実行されます。

現に例えばBeginPlayにPrintをつなぐと、"Server:Hello" "Client1:Hello"と二回表示されます。

考えてみれば当たり前で、Serverの世界とClientの世界でそれぞれこのプレイヤーのBPが実行されているだけです。


処理によってはこれで嬉しい場合もあるわけですが、処理によっては2回実行されてしまって困ることもあります。

例えば弾をSpawnさせる場合。

弾のReplicateをオフにして、Server側とプレイしているClient側でSpawnさせると、他のClient側で弾をSpawnさせることができません。

一方で弾のReplicateをオンにすると、プレイしているClient側では2回弾が生まれてしまうので空中でぶつかってしまったりします。

これでは困るわけです。そこでAuthorityだけで実行するよという「SwitchHasAuthority」、プレイしているServer/Client側だけで実行するよという「Is Locally Controlled」が非常に使えます。


SwitchHasAuthority」は基本的にはAuthorityにつなぐことが多いですが、Client側でのみ実行したい処理もまれにあるので、どちらも使いますね。


一方で「Is Locally Controlled」は、自分が操作しているときのみ生じる処理を書くことができます。
例えば、照準とか、UIとか、自分しか見えない罠とか…。様々。


以上基本概念のまとめ

なんとなくオンラインゲームの世界観が分かってきたでしょうか?

オンラインゲームでは「Server」があり、それにぶら下がる「Client」がいる。


オンラインゲームの世界は1つではなく、「Server」と「Client」のそれぞれに存在し、ServerからClientに常にコピー(Replicate)されている。

各Actorをコピーするかどうかは、各Actorの「Replicates」プロパティで設定できる。変数やMovement、Timelineも同様


この各Actorに対して、「Server」や「Client」がどれだけ弄れるかの権限を表したものがRoleであり、「Authority」「AutonomousProxy」「SimulateProxy」の三種に分けることができる。


「Server」から「Client」に情報を伝達する手段は、Replicateの他に"RPC"の「Multicast Event」なんかがあり、

逆に「Client」から「Server」に情報を伝達する手段は、"RPC"の「Run On Server Event」しかない。しかも、PlayerControllerを有しているBPからでないと、「Run On Server Event」は実行できない。


キャラクターを操作する権限を有しているのは「Authority」「AutonomousProxy」だが、それらを区別する必要がちょくちょく出てくるので、「SwitchHasAuthorityマクロ」「Is Locally Controlledノード」を使いながら実装を行っていく。

------


オンラインゲームと通信量

さてここからが応用編。

何も考えずに作ると通信量がめちゃめちゃ多くなって、ラグが発生しまくることが予想されますよね。

基本的にオンラインゲームにおいては、この通信量とラグとの戦いになります。(細かく言うと、セキュリティとかも入ってきますけど…)

なるべくReplicateのアクターや変数を減らせればいいですが、そうなると世界の共通性がどんどん失われていきますよね。

最近のオンラインゲームの共通項として、

①接続数の増加

Fall Guys, Apex Legendsは同時接続で60人プレイが可能。Fortniteは100人プレイも可能。

②アクターがアクターを生成するゲームの増加

代表例としてFortnite、Splatoon、Minecraftなど

など、どんどん通信量が多くなる要素が増しているわけです。

なので、Unreal Engineでは通信量を節約するための方策がいくつかあります。


どうやって通信量を減らす?

①プロパティに変更がないときに、休眠状態を作る Net Dormancy プロパティ

→常に動かすキャラクターはAwake or Naver、休眠状態の場合も多いアクターはDormantAll、ほぼほぼ動かないアクターはInitialという感じで使い分けるべし。

Initialを選んだ場合、なんか動きがない限りは監視対象からも外れます。初期状態で配置されているアクターを選ぶのがいいです。動きがあった場合はDormantAllに移行します。


②プレイヤーとの距離を判断してReplicateするかどうかを判断する Net Relevancy

Net Cull Distance Squared」プロパティで設定します。

ここでは四角形の面積cm2で表しますので、25,000,000と設定した場合、5000 x 5000 cmなので、50 m四方よりも外に出た場合にはReplicateしなくなります。

Always Relevant」プロパティをオンにすると、上記を全て無視して、絶対Replicateするマンになります。


③通信量がいっぱいいっぱいの時に優先順位をつける Net Priority

大きければ大きいほど優先順位Priorityが上がります。

ただ実際の優先度はこのように計算される

優先度 = Net Priority値 x (前回送信時からの経過時間 + 距離などの係数)

つまり前回送信時からしばらく経つと、優先度が低くても同期される。


④どれくらい遅延が許容される? NetUpdateFrequency

値が小さいほど遅延が許容される。1秒に対して何回更新するか?

素早い動作がいらないアクターとかは低めに設定しましょう。


⑤サーバー上にはあるが、クライアント上で生成しないアクター NetLoadOnClient

Gamemode/GameStateに設定。あまり気にしないでいいかな…。



ゆくゆくは覚えたいこと

MoveFlag

Movementの同期は結構ややこしくて、現状はRPCでクライアントの位置情報を送ってなんとかやってるんですが、シームレスな同期の実現にはゆくゆくはMoveFlagの実装が必要になってくるかもしれません。ただ、現状よく分からんのでパス。

Replication Graph

Replicationするアクターがめちゃめちゃ増えてきた場合、それこそFortniteとかFall Guysみたいな場合ですね。その場合どういう優先順位でReplicationするか、がめちゃめちゃ重要になってきますが、それを体系化したものっぽいです。まー4.27でもBeta版なんですけどね。今回はReplicationの範囲はそこまで広くないのでパス。


次回いよいよ制作スタート

さて長々と説明してきましたけど、実際に制作し始めると多分「えっこんなもん?」となるような気がします。

それくらいUnreal Engineでのマルチプレイヤーゲーム制作は簡単なんですよね。ホント。


次回