オンラインゲームつくった話
@toRisouP
誰?
• とりすーぷ ( @toRisouP )
– Webエンジニア(プログラマ)
– Unity、C#、Scala、Ruby、Python、PHPとかその辺触ってる
• 最近はScalaが一番お気に入り
• いろいろやってる
– 同人ゲーム制作
– VR開発
– Qiita書く
– 技術書執筆
この秋、
ゲームをリリースしました
ハクレイフリーマーケット
• Windows向けの東方2次創作ゲーム
• 最大6人までネットワーク対戦可能なパーティアクションゲーム
• ロビー機能あり、CPUあり
ウリ
• 物理演算をメインに使ったパーティゲーム
– とりあえず物理演算をガバらせておけば面白くなるという発想
• ガバガバ物理演算は偉大
– 大量の物理演算オブジェクトを同期しながら通信対戦できる
• 今思うと狂気の沙汰
使ってるもの
• 使っているフレームワークとか
– Unity 2017.2
– Photon Cloud
– NCMB
– ADX2 LE
– Effekseer
• 使ってるライブラリ
– UniRx
– Zenject
– PhotonRx
本題
オンラインゲームつくるの
すごくツライ
PhotonCloudの仕様
• クライアントで状態を管理する
– サーバサイドに状態はもたせられない
– サーバサイドにロジックはおけない
– 誰か1人がマスタークライアントになる
– クライアントサイドで全ての問題を解決しないといけない!
発生した問題
• 動きがカクカクする
• 通信の待ち時間でテンポが悪くなる
• 帯域、メッセージ数がオーバーする
発生した問題
• 動きがカクカクする
• 通信の待ち時間でテンポが悪くなる
• 帯域、メッセージ数がオーバーする
オブジェクトの管理ルール
• オブジェクトには親の概念がある
– 同期オブジェクトにはPhotonViewコンポーネントがついている
– PhotonView.IsMine = true だと自分が管理(送信モード)
– PhotonView. IsMine = false だと通信相手が管理(受信モード)
親
(IsMine=true)
子
(IsMine=false)
子
(IsMine=false)
子
(IsMine=false)
図解
• 自分の世界
– IsMine = true
• 相手の世界
– IsMine = false
同期されている2つのオブジェクトがある
図解
• 自分の世界 • 相手の世界
自分のオブジェクトが移動すると…
アニメーション
図解
• 自分の世界 • 相手の世界
移動後の座標を送信して
相手側の座標を書き換える
図解
• 自分の世界 • 相手の世界
相手の世界の座標を上書きして同期
図解
• 自分の世界 • 相手の世界
自分の世界ではキレイにアニメーションして移動したが、
相手の世界には結果しか送ってないので突然ワープしたように見える
→ 動きがカクカクする
カクカクする問題
• 同期メッセージの送信頻度が低いのが原因
– 根本対策は同期頻度を上げるしかないが、
帯域やメッセージ数の制限で上げるにも限界がある
→別アプローチで解決する必要がある
解決策いろいろ
• 同期頻度を単純に増やす方法
– これができたら苦労しない
• キー入力を送ってしまう方法
– 入力から一意に結果が決まるゲームシステムならかなり有効
• がんばってごまかす方法
– 補間・補外・エフェクト・アニメーションでごまかす
• そもそも同期しない方法
– ローカルで演算可能ならそっちを優先して使う
解決策いろいろ
• 同期頻度を単純に増やす方法
– これができたら苦労しない
• キー入力を送ってしまう方法
– 入力から一意に結果が決まるゲームシステムならかなり有効
• がんばってごまかす方法
– 補間・補外・エフェクト・アニメーションでごまかす
• そもそも同期しない方法
– ローカルで演算可能ならそっちを優先して使う
採用した方法
(というかこれしかできない)
がんばってごまかす
• 補間処理でなんとかんする
– Vector3.Lerp と Quaternion.Lerp を使う
• 補間の割合を調整して違和感が少ないパラメータを見つける
– 補間を強くする → 滑らかだが動きが鈍くなる
– 補間を弱くする → 機敏だがカクカクが目立つ
そもそも同期しない
相手の演算結果を待っているからラグが生じる
↓
自分が演算して結果を送る側になれば
ラグは起きない
プレイヤの動向を観察してわかったこと
• 「プレイヤは自キャラの周辺しかみてない」
– 正確には「自分の行動に対する周辺のリアクション」しか見てない
• 自分が触ったオブジェクト
• 自分が持ち上げたオブジェクト
• 自分が投げたオブジェクトの挙動
– 少なくともここさえ滑らかに動いていれば、
他がカクカクしててもあまり気にならない
やったこと
• 自キャラが触れたオブジェクトの親権を奪う
– 座標を受け取る側から送る側にしてしまう
– 自キャラが触れた瞬間にIsMineをtrueに書き換えてしまう
• (実際はIsMineに似た別のフラグ用意してそっちを書き換えているけどね)
効果
• かなり効いた
– 少なくとも自分の周辺だけはラグがなく動いてくれる
• 実装は複雑化してしまった
– メンテナンス困難な実装になっている
– 親権のスイッチングコストも結構かかる
発生した問題
• 動きがカクカクする
• 通信の待ち時間でテンポが悪くなる
• 帯域、メッセージ数がオーバーする
テンポが悪くなる
• 入力に対してプレイヤが機敏に反応してくれない
– 原因は行動時に「通信」が発生するから
• 特にこのゲームだと「アイテムを拾う」時に発生する
– アイテムを拾えるかどうか?の判定に通信が発生するため
解決策
• 通信時間をアニメーションでごまかす
– アイテムを拾う時に、
「構える」→「持ち上げる」の2段階のアニメーションを再生する
– 構えモーションの間に通信を終わらせてしまう
アニメーション
構えモーション 構え→持ち上げ 構え→持ち上げ
(構えが長めのキャラ)
構えモーション再生中に裏で通信しちゃうことで、
通信によるテンポの悪化をごまかす
発生した問題
• 動きがカクカクする
• 通信の待ち時間でテンポが悪くなる
• 帯域、メッセージ数がオーバーする
帯域とメッセージ数
• 帯域
– 秒間に通信するデータ量
– 小さければ小さいほうが当然良い
– PhotonCloud的には通信したデータの総量の方が大事
• メッセージ数
– 座標等の同期、RPCの実行命令などの送受信回数
制約
• 通信量
– 1ユーザあたり3GB/月まで
– オーバーすると追加料金
• メッセージ数
– 1部屋につき500メッセージ/秒
– オーバーしても今のところはペナルティなし
メッセージ数の制約がヤバイ
• 部屋に参加している人数倍して計算される
– 6人部屋で1メッセージ送信すると、
送信1+受信5=6メッセージ消費してしまう
– 6人部屋だと約83メッセージ/秒しか送れない
• 60FPSだと1フレームあたり1~2メッセージしか送れない
このゲーム同期対象が多すぎる
• フィールド上のアイテムを同期すると一瞬で上限超える
– こんな仕様のゲームでネットワーク対戦とかバカじゃないの…
対策をうつ必要がある
対策前の状態
• データ通信量
– 1試合あたりの総量6MB/人
• メッセージ数
– 2000メッセージ/s/room
– 規定値の4倍もいってた
取った対策一覧
• 次の位置が確実に予測可能なものは同期しない
• 動いていないオブジェクトは同期しない
• オンライン対戦時にはゲームのフレームレートを30に落とす
• オブジェクトをシャーディングしてグループ単位でまとめて同期する
取った対策一覧
• 次の位置が確実に予測可能なものは同期しない
• 動いていないオブジェクトは同期しない
• オンライン対戦時にはゲームのフレームレートを30に落とす
• オブジェクトをシャーディングしてグループ単位でまとめて同期する
フレームレートを落とす
• オンライン時のみ30FPSに落とす
– メッセージ送信回数を60FPS時の半分以下にできる
– 補間処理でごまかしやすくなる
– 低スペックPCと挙動を揃えることが出来る
– 割とメリットが大きかったので採用
オブジェクトのグループ化
• オブジェクトの座標同期をグループ単位で行う
– ようするにシャーディングして管理する
グループ単位で分割するメリット
• バラバラに送るよりメッセージ数を削減できる
– 1メッセージ内に複数の座標データをまとめて送信できる
• 挙動のチューニングができる
– 「グループにわける数」 と 「何フレームごとに送信するか」の
組み合わせでカクカク度合いとメッセージ数のバランスを調整できる
• 一斉に同期する時の違和感が消せる
– 全オブジェクトが同じタイミングで動くと違和感が出る
– バラバラに動いている感を出してごまかせる
後はログを見ながら調整
• 違和感と帯域・メッセージ数のバランスを見て模索
チューニング結果
• 帯域
– 対策前 1試合あたり6MB → 1試合あたり2MB
60FPS,グループ化なし,毎フレーム送信 30FPS,3グループ分割,2フレームごとに送信
チューニング結果
• メッセージ数
– 1000~1900/s → 450~600/s
なんとか規定値には収まった
• まだところどころオーバーするけど…
– 怒られたら直す
500メッセージ
まとめ
まとめ
• オンラインゲーム制作はとにかく手間がかかる
– 工数がオフラインゲーム開発の5~10倍になる(体感)
– 技術的に実装困難なため諦めた仕様もいくつかある
– 不具合発生時の原因調査にエスパー能力が必要
• まじめに作るとつらいところはできるだけ誤魔化す
– プレイヤが不快に感じさえしなければよい
– ガバ物理はむしろラグった方が面白いから助かった

Unityでオンラインゲーム作った話