【Unite Tokyo 2019】Unityだったら簡単!マルチプレイ用ゲームサーバ開発 ~実践編~

1.1K Views

September 26, 19

スライド概要

2019/9/25-6に開催されたUnite Tokyo 2019の講演スライドです。
小端 みより(株式会社ミクシィ)

こんな人におすすめ
・Unityでより本格的なマルチプレイのゲームを作りたい方
・そもそも通信や同期処理ってどうやって実装するの?という方

受講者が得られる知見
・Unityで専用サーバを開発するメリットやその方法
・Unityでサーバとクライアントを同時に開発するテクニック
・通信に関する知識、専用サーバを運用する方法

Unityのイベント資料はこちらから:
https://www.slideshare.net/UnityTechnologiesJapan/clipboards

profile-image

リアルタイム3Dコンテンツを制作・運用するための世界的にリードするプラットフォームである「Unity」の日本国内における販売、サポート、コミュニティ活動、研究開発、教育支援を行っています。ゲーム開発者からアーティスト、建築家、自動車デザイナー、映画製作者など、さまざまなクリエイターがUnityを使い想像力を発揮しています。

シェア

埋め込む »CMSなどでJSが使えない場合

関連スライド

各ページのテキスト
2.

Unityだったら簡単! マルチプレイ用ゲームサーバ開発 ~実践編~ 株式会社ミクシィ 小端 みより

3.

自己紹介 小端 みより (ゲームエンジニア) コンシューマゲーム業界から株式会社ミクシィに入社 未発表タイトルを含む、複数の新規タイトル開発を行う (すべてがオンラインマルチプレイ対応) 3

4.

Unityサーバを作るまで 4

5.

これまでのマルチプレイ実装 リレーサーバを用いたマルチプレイ実装が主流 これは結局のところP2P型の通信モデルなので、 サーバは原則的にロジックに関与しない つまりクライアント処理型の実装ということ 5

6.

クライアント処理型 リレーサーバを用いたマルチプレイ実装なので 次のようなメリットがある — 対応したミドルウェアやサービスが豊富 — クラウド型のサービスならサーバ不要であり手軽 — クライアントで完結するのでプロトタイピングに最適 6

7.

その一方で — クライアント処理なのでチートされやすく 通信も不安定になりやすい (ホストマイグレーションなど) — 実装が複雑化しやすい傾向にあり大人数もしくは 大規模なタイトルの開発は難しい と決して万能ではない 7

8.

それならば クライアント処理型で難しかった — チート耐性および通信の安定性を備え持ち — リアルタイムで大人数、大規模なマルチプレイゲーム これらを実現するために、専用サーバを用いた サーバ処理型の実装にすればよい? 8

9.

でも専用サーバ開発って — サーバを実装するのって言語とかが違って難しそう — どうやって作ればよいのかわからない — クライアント処理型の手軽さに及ばない こういったネガティブな印象がある気がする・・ 9

10.

極論として PU〇GやF〇rtniteのようなゲームを気軽に開発できるような そんな便利な方法があったら嬉しいですよね (いや・・そんなことできるのか・・・???????) 10

11.

我々にはUnityがあります! 11

12.

Unityサーバは その名の通りUnityで作る専用サーバなので — 我々が得意(ですよね?)とするC#で実装できる — Unityの機能、AnimationやPhysicsやAIを活用できる — サーバとクライアントを同時に単一プロジェクトで開発 などなど 12

13.

よさそうですね・・? というわけで実際に作ってみます 13

14.

目標を高めに設定 技術難易度の高いことに挑戦すれば幅広いゲームジャンルに 対応できるはず・・という理由で — アクション性の高いオンラインマルチプレイのゲーム — サーバとクライアントを単一Unityプロジェクトで作成 (サーバはLinux、クライアントはiOS/Android上で動作) — サーバをクラウド上で動作させ20人以上でマルチプレイ 14

15.

おおまかな設計 — サーバ集中処理型の通信モデルを採用し 上り/下り共に非同期的に最大で毎秒30回の通信を行う — ゲームは60FPSで動作し (物理は30FPS) DOTSでなく使い慣れたコンポーネントモデルを採用 — サーバ/クライアント間でコードおよびアセットを共有し Editor上で任意にサーバやクライアントを実行可能 15

16.

サーバ集中処理型とは 1. 入力を送信 クライアント A 2. アップデート ゲームサーバ クライアント B 4. 結果を表示 3. 結果を送信 非常にシンプルな構成ですね 16

17.

ただし 次のような懸念点が考えられる — サーバ集中処理型は設計をシンプルにできる一方で ネットワーク遅延の影響が大きそう — 特にアクションゲームにおいてどれほど影響を与えるのか そもそもちゃんとゲームプレイが成立するのか 17

18.

と、いう感じで我々は Unityサーバの開発を開始したのであった・・ そして、その中で直面した課題と その解決方法について次章で説明していきます 18

19.

Unityサーバ 7つの課題と解決法 19

20.

7つの課題とは Q1 Q2 Q3 Q4 Q5 Q6 Q7 どの通信ライブラリを使えばいい? TCPとUDPどっちを使う? 再送制御による遅延を回避するには? MTUって何? ラグはなくせるのか? サーバとクライアント同一プロジェクトで管理できる? 専用サーバをどうやって運用したらいい? 20

21.

Q1 どの通信ライブラリを使えばよい? 21

22.

Unity公式のもの (UNETのことはもう忘れよう・・) — Unity Transport Package (com.unity.transport) https://github.com/Unity-Technologies/multiplayer — マニュアルやサンプルプロジェクトを見れば簡単 — DOTSなどの新機能にも対応 — ただし現時点においてプレビュー版である点に注意 22

23.

非公式ライブラリ 非公式のC#で書かれた通信ライブラリも利用可能 — 例としてはLiteNetLib https://github.com/RevenantX/LiteNetLib — RUDP(TCPライクに扱えるUDP)通信ライブラリ — 認証付きコネクションやMTU探索、IPv6にも対応 — こちらもシンプルで扱いやすい 23

24.

Unity公式ブログに ゲーム設計によってどんな実装を行うのが望ましいかという フローチャートがあるのでこちらもご覧ください Navigating Unity’s multiplayer Netcode transition https://blogs.unity3d.com/2019/06/13/navigating-unitysmultiplayer-netcode-transition/ 24

25.

結論 — 公式のMultiplayerパッケージがわかりやすく新機能も サポートしているが、まだ開発中なので注意 — 現時点においてはLiteNetLibのようなライブラリが選択肢 となるため、我々はこれを採用した 25

26.

Q2 TCPとUDPどっちを使う? 26

27.

初めて使うならTCP — ポピュラーなので情報が多い(HTTPやWebSocketもこれ) — プロトコルとしての信頼性が高く扱いやすい — なので通信実装の入門用に最適 ただし — リアルタイム性を要求される通信において問題がある 27

28.

なぜTCPは信頼できる? — 確認応答(Ack)および再送制御 相手に届いたことが確認できるまで送信を繰り返すので コネクションが維持されている限り必ず届く — 順序制御 受信時シーケンス番号をもとに並び替えが行われるので 送信順序と受信順序が必ず一致する 28

29.

再送制御による遅延の問題 送信側 受信側 1 2 3 4 (2はまだ・・?) 1 2 5 6 2,3,4,5 一旦パケットロスが生じて再送制御が行われている間、 順序制御により以後の受信がドミノ倒し的に遅延してしまう 29

30.

TCPの設計思想は ”どんなに遅れても順序通り必ず届く” なので仕方がない しかしこの特性は 毎秒何十回という頻度で通信を行い、即時到達性が要求 されるゲーム用途において、非常に致命的である 30

31.

結論 — 情報の充実性や信頼性においてTCPは優れており、 入門用に最適である — 一方遅延を生じやすい性質のため、 アクション性を重視するゲームジャンルに適さず 我々はTCPを採用しなかった 31

32.

Q3 再送制御による遅延を 回避するには? 32

33.

そこで出番のUDP — データグラムと呼ばれるシンプルな通信操作のみ行える — コネクション処理も再送制御も順序制御も存在しない — だから遅延が生じにくい — 逆に信頼性を要求される用途では工夫が必要 33

34.

UDPで信頼性を得るには RUDP(ReliableUDP)として知られる実装が有効 ただしこれは一般に、 再送制御や順序制御をUDP上で実装したものであって TCP同様に再送制御による遅延が発生する ではどうするか・・? 34

35.

“Redundant”UDP — 送信メッセージを冗長化させることにより 一定のパケットロス耐性を持たせた実装 — ただし冗長性を上回る量のパケットロスが発生した場合 対処できないため別途、復帰処理を実装する必要がある 35

36.

冗長化の例 送信側 受信側 1,-,- 2,1,- 1 3,2,1 2 4,3,2 5,4,3 3,4 5 再送制御なしでも冗長性の範囲ならメッセージを復元できる 36

37.

結論 — UDPは低遅延である一方で信頼性に問題がある — しかしUDPとメッセージの冗長化を組み合わせることで TCPでは難しかった低遅延と信頼性が両立できるので 我々はこの方法を採用した 37

38.

Q4 MTUって何? 38

39.

MTUとは — 1回の通信で転送可能なデータグラムの上限サイズ ≒ メッセージサイズの上限 — そのサイズわずか576~1500バイト (通信経路によって変動する) — 超過した場合はパケットの分割が行われるか そもそも到達しない 39

40.

つまり 一度に送信できるメッセージのサイズは限られる上に、 前記の冗長化テクニックと組み合わせるとさらに少なくなる (MTU 1500 / 3 で1フレームあたり 500バイト以下とか・・) なのでメッセージサイズをギリギリまで削減する必要がある 40

41.

メッセージサイズを削減するには — プレイヤーの視界外のオブジェクトをカリングする — 対象オブジェクトとの距離や優先順位に応じて 通信頻度を落とし、通信のタイミングを分散させる — データそのものを圧縮する float -> half 変換などが定番 (値域と精度に注意) 41

42.

カリングおよび通信分散 1 2 3 4 プレイヤーキャラクタやNPC、近くのオブジェクトを優先 42

43.

デルタ圧縮 その他にはデルタ圧縮というテクニックもある — 送信内容をキャッシュしておく — 次回の送信時にキャッシュと比較し更新された オブジェクトやプロパティのみを送信する (全く更新されていないなら、そもそも送信を行わない) 43

44.

またZlibなどの 圧縮ライブラリを組み合わせる手もあるが・・ — 圧縮後のサイズがわからないと使いづらい (逐次圧縮可能なライブラリならあるいは) — 前記のテクニックにより元々のデータが効率的な配列に なっている場合はあまり有効でなかったりする 44

45.

結論 — UDPのメッセージサイズはMTUに依存するだけでなく 冗長化テクニックにより、さらに制限される — 我々はカリングやデルタ圧縮などのテクニックを 組み合わせることにより、メッセージサイズを削減 することにした 45

46.

Q5 ラグはなくせるのか? 46

47.

ラグはなぜ発生するのか 一般に ”ラグ = 通信遅延” と思われがちだが 近年の通信環境の改善により、国内であれば通信遅延は 非常に小さい (東京都内~データセンタ間で1、2ミリ秒) 実際には、前記の再送制御により生じる一時的な遅延や 通信サイクルに起因する遅延の影響が大きい 47

48.

そもそもオフラインゲームでは 17ミリ秒 (= 1000 / 60) 入力の反映 ダリング 待機 アップデート レンダリング 待機 タイミング次第で入力の反映に17ミリ秒の揺れ幅が生じる 48

49.

通信サイクルによる遅延 ベスト ワースト Client Server 通信サイクルが33ミリ秒の場合、タイミング次第で遅延に 最大66ミリ秒の振れ幅が生じる 49

50.

というわけで オンラインゲームは宿命としてラグが発生するものであって また遅延量に、かなりの振れ幅があるということ (遅延が一定であると見なしてはいけない) これを踏まえた上で、体感上のラグを打ち消すための テクニックを利用したい 50

51.

時刻差異を利用する例 — サーバと各クライアント間でゲーム時刻を同調しておく — サーバでは送信する際にメッセージにその時点の ゲーム時刻を書き出しておく — クライアントはこのメッセージ上の時刻とクライアントの 時刻の差異を利用し、体感上のラグを相殺できる 51

52.

例えば位置予測 #2: 0.10秒 (2.0, 1.0) #1: 0.00秒 (0.0, 0.0) 今(予測): 0.15秒 (3.0, 1.5) 前回の時刻および座標から、現時点の位置を予測できる 52

53.

結論 — オンラインゲームにおいてラグは宿命であり、なおかつ 遅延の量は常に一定ではない — 我々はサーバとクライアント間の時刻差異を利用した 位置予測などのテクニックで体感上のラグを 打ち消すことにした 53

54.

Q6 サーバとクライアント 同一プロジェクトで管理できる? 54

55.

同一プロジェクトだと — サーバとクライアントで使用するアセットやコードが混在 — しかしサーバ実行時にTextureやAudioClipは必要ない (消費メモリやロード時間が長くなるだけ) つまりビルド設定に応じて 使用するアセットを切り替えられることが望ましい 55

56.

シーンおよびアセットは — その名称だけでサーバ用なのかクライアント用かを 区別できるようにしておく (名称に”_Client”のようなサフィックスを含めるなど) — こうしておけばビルド用のスクリプトで対象外のアセットを 除外することができる 56

57.

クライアント実行時の Editorシーンビュー サーバ実行時のシーンビュー (Rendererなどを除去) 57

58.

コードの分割 — サーバとクライアント間で共有したくないコードは AssemblyDefinition を使用すれば除外することができる (ただし異なるアセンブリ間は循環参照できないので注意) — ただし現状(Unity2019.1)、DefineConstraints (シンボル定義に応じ含める/含めないを切り替えられる) の挙動が怪しいので今後の修正に期待したい 58

59.

結論 — 各シーンやアセットがサーバ用かクライアント用か判別 できるようにしおけば、ビルド時に切り替えられる — AssemblyDefinitionを使えば使用するコードの切替も 可能であり、我々はビルド用スクリプトでそれらを切り替え られるようにした 59

60.

Q7 専用サーバをどうやって 運用したらいい? 60

61.

ホスティングサービスの利用 — 例としてはAWSのGameLiftというサービス https://aws.amazon.com/jp/gamelift/ — ゲームサーバのホスティングに特化されている — サーバのバイナリをアップロードすれば、起動や監視 オートスケーリングなどの運用を自動でやってくれる — Unity向けのSDKも提供されている 61

62.

注意点として — 一般的なEC2インスタンスではGPUをサポートしていない ので、サーバはHeadlessモードでビルドする必要がある 62

63.

その他にも Azureなどホスティングサービスの選択肢があり、 今後はUnity公式のホスティングサービスも開始されるはず 専用サーバの利用はより手軽になっていくことが予想される 63

64.

結論 — ホスティングサービスを利用することで、 専用サーバは比較的手軽に運用することができる — 我々はホスティングサービスのうちのひとつである GameLiftを利用する事にした 64

65.

Unityサーバを開発してみて まとめ 65

66.

これらは結局どうだった? — クライアント処理型で難しかった チート耐性や通信の安定性、大人数や大規模なゲーム — 専用サーバのネガティブな印象 言語、作り方がわからない、手軽さが損なわれないか — サーバ集中処理型の懸念点 遅延がどの程度影響するのか、ゲームとして成立するのか 66

67.

これらは結局どうだった? — クライアント処理型で難しかった チート耐性や通信の安定性、大人数や大規模なゲーム — 専用サーバのネガティブな印象 言語、作り方がわからない、手軽さが損なわれないか — サーバ集中処理型の懸念点 遅延がどの程度影響するのか、ゲームとして成立するのか 67

68.

チート耐性は — サーバ集中処理型はクライアント側で行える操作が 限られるため基本的にチート耐性は高いと考えられる — とはいえ、パケット分析による自動化ツール作成などが 想定されるため、通信の暗号化を行う必要がある 68

69.

通信の安定性は — ほんとうに通信状態が悪い場合、を除き安定的に動作した — とはいえ WiFi <-> キャリア回線 の切り替わりなどでは 切断を生じるため、再接続および復帰処理の実装が重要 69

70.

大人数や大規模なゲームにも — プロファイラを使用しサーバの通信処理を最適化 最終的に1スレッドあたり40人程度まで捌けるように — 無論このあたりの数値はゲームデザインに依存するもので あり、逆にリアルタイム性が重視されないジャンルならば 100人規模であっても対応できるのではないか 70

71.

これらは結局どうだった? — クライアント処理型で難しかった チート耐性や通信の安定性、大人数や大規模なゲーム — 専用サーバのネガティブな印象 言語、作り方がわからない、手軽さが損なわれないか — サーバ集中処理型の懸念点 遅延がどの程度影響するのか、ゲームとして成立するのか 71

72.

やってみると意外に簡単 — やはりC#や.NETなど普段から使い慣れているUnityの 開発環境がそのまま使えるメリットは大きい — もちろんネットワークの知識は必要とされるものの そこを身に着けさえすれば、通常の開発とそう大差はない — Unityだからこそのデバッグのしやすさもある 72

73.

手軽さも損なわれない — サーバとクライアントが単一プロジェクトで完結できる という点では手軽 — もっとも、ホスティングサービス上でサーバを動作させる ため更新の都度、バイナリをデプロイする必要があり手間 — ローカル環境上でサーバを起動し、クライアントから 直接接続できるしくみを用意しておくと捗る 73

74.

これらは結局どうだった? — クライアント処理型で難しかった チート耐性や通信の安定性、大人数や大規模なゲーム — 専用サーバのネガティブな印象 言語、作り方がわからない、手軽さが損なわれないか — サーバ集中処理型の懸念点 遅延がどの程度影響するのか、ゲームとして成立するのか 74

75.

遅延も対策すれば怖くない — 通信ライブラリのドロップ&遅延シミュレーションの機能で 低品質な通信環境を前提としたテストが行える — これによりUDP+冗長化や、位置予測などの遅延対策が 正しく動作することを確認できた — ダメ元で50ms程度遅延させてみたところ・・ “まあ、すごくラグいけど操作できなくもない” 75

76.

むしろ・・ モバイル機器では負荷の高いPhysicsなどの処理を オフロードでき、クライアントの負荷を軽減できる 76

77.

そして・・ 77

78.

社内プレイ会のもよう 78

79.

その他ここがよかった — 使い慣れた言語&エンジンなので、通信のハードルさえ 乗り越えればゲームロジックの実装に集中できる — Editor上で直接サーバとクライアントをデバッグや プロファイリングできるので作業効率が高かった — Unityを扱える企画メンバーがゲームを直接、拡張できる 79

80.

今後の課題 — サーバとクライアントのコードが混在しているがゆえの 紛らわしさがあるため、より分離性のある設計にしたい — Unityサーバ開発を基盤化し、他のプロジェクトでも 手軽に扱えるようにしたい 今後、機会があればもっとUnity寄りの話などできれば・・ 80

81.

本講演は以上です 皆様もぜひ試してみてください 81

82.

ご清聴ありがとうございました Ver. 2019_09_24_1 82