Asteroidsデモ

目次

 

uNetからPUNへ移植する

このページでは、UnityのNetworkMeteoroidに基づいたuNetからPUNへの移植プロセスを説明しています。NetworkMeteoroidはアセットストアのこちらのリンク からご確認いただけます。このページでは最終的に納得の行く結果を得るためにプロセスのうち、かなり扱いやすい面と、どちらかというと処理の難しい面を紹介しています。必要なステップ、重要なステップ、全てを網羅するため、ページをいくつかのセクションに分けました。これらのステップは大まかに簡単なものから難しいものへという順番で並んでいます。

Screenshot of the ported Asteroids demo
移植されたAsteroidsデモのスクリーンショット

以下の説明は、uNetからPUNへ既存のアプリケーションを移植する際の汎用の方法というわけではなく、どのステップが重要で、どういった問題に対処していかなければならないのかという気づきを与えてくれるものだということを気に留めておいてください。

 

アセットを再インポートし、プレハブとシーンを再構築する

uNetからPUN既存プロジェクトの移植を始めるとき、基本的に既存プロジェクトを使用して、uNETのネットワークロジックの全てをPUNのネットワークロジックと置き換えられます。今回はあまり見慣れないデモに沿って行うので、新しいプロジェクトを使ってデモのほとんどの部分を再構築することにしました。こうすることで、まっさらなところからの移植の手順全体も紹介できます。実際にはする必要のないことですが、元のデモと自分で移植したデモの違いを、少なくともソースコードで見ることができます。既存のNetworkMeteoroidデモを使えばかからなかった手間がかかってしまうと思われるかもしれませんが、どちらにしろ細々とした処理を加えていくことになります。なので、どちらの移植方法をとっても、このデモの目的においてはだいたい同じような作業負荷になるでしょう。プロジェクトの複雑さによってこの作業負荷は変わってきます。

まず始めにUnityで新しいプロジェクトを作成し、元のデモのフォルダ構成を(だいたい)再作成して、必要なアセット(モデルやテクスチャ)を再度インポートしました。この完了後、材料やプレハブ、シーンなどのその他の必要なアセットを再構築しました。特に「ロビー」シーンを再構築する際、専用サーバオプションなどの後々必要にならない、ロビーの部品については再作成しないことを確認しました。見た目と雰囲気がPUNのデモのハブに合っていることも確認しました。

 

ゲームロジックに細かい調整を加える

アプリケーションのソースコードに修正を加える際は、どこか始めるポイントが必要です。今回は、特に他にないので、NetworkMeteoroidデモのゲームロジックから始めることにします。もちろん、1つ1つのコードをまるまる再利用することはできませんが、デモのソースコードをひな形として利用し、そこに修正を加えることは可能です。ですので、コード全体をコピーしてから修正を加えるやり方でも、最初から修正したバージョンで書き直す方法でも、どちらも大丈夫です。このデモでは両方の方法を組み合わせています。最終的にゲームロジックそのものは、ネットワーク関連で加えた細かい修正以外は、元のデモとほとんど同じです。

例としてここで挙げるのは、元のデモで小惑星がどのように生成されているかということです。修正後のバージョンでは、ネットワーク関連のコードが問題なく動作するように加えた細かい調整はあるものの、基本的には元のデモと(「Game Manager」と、ゲームの実行と共に実行されるコルーチンを使用して)同じように動作します。この例では、単純にuNetのインスタンス化コールNetworkServer.Spawn(...)をPUNのシーンオブジェクトインスタンス化コールPhotonNetwork.InstantiateSceneObject(...)と置き換えました。このコールを使用すると、例えば小惑星のRigidbodyに追加した詳細の共有ができるInstantiationDataを加えることができます。また、これらの情報の同期のため別のRPCやRaiseEventコールを使う必要がないというメリットもあります。

もちろん、全く手を加えないコードもあります。例えばプレイヤー入力が処理される方法は、元のデモと全く同じです。動作に問題がなく、必要がないので修正はしません。

 

ネットワークロジックに主要な調整を加える

このパートで(やっと)内容が面白くなってきます。ここでは、デモのソースコードに大幅な変更を加えて、uNetのネットワークロジック全てをPUNのネットワークロジックと置き換えます。残念なことに、uNetからPUNへ既存のアプリケーションを移植する際に、汎用の方法はありません。なので、かならずしもある特定のuNetの属性(例:[ClientRpc])が、特定のPUNの属性(例:[PunRPC])にいつもマッピングされるとは限りません。なぜなら、ネットワークロジック内で別の処理方法があるか、属性そのものがPUNに存在しないこともあるからです。修正を加える際には、何についての修正を、ネットワーク関連のコードのどのセグメントに適用しようとしているのか、コード一行一行で考えながら進めるようにしてください。

このデモの目的では、サーバーサイドロジックを使用しないので、元のデモでサーバーに制御されているシミュレーションの扱いについて、もう1つ重要な決定をしなければいけません。カスタムサーバーサイドロジックなしでPUNを使用する場合、シミュレーションを処理するのに全クライアントを使うか、1つのクライアントのみを使うか、選択肢はこの2つだけとなります。今回のケースでは後者の方法をとり、シミュレーションの実行と制御についてはMasterClientを使用します。つまり、asteroidsをインスタンス化できるクライアントはMasterClientのみとなり、プレイヤーの宇宙船から撃たれる弾の衝突検出も処理します。なお、これらの小惑星はシーンオブジェクトとしてインスタンス化されます。これにはゲームの実行中でもMasterClientが切断されていた場合は破壊されないというメリットがあります。そのかわりシミュレーション制御は、新しいMasterClientとしてその役割を取って代われる他のクライアントがいれば、そこへパスされます。

ネットワークロジックのもう一つの重要な側面は、もちろん前述した小惑星とプレイヤー宇宙船の同期です。納得のいく結果になるように、カスタムOnPhotonSerializeView機能を実装します。この機能は全ての必要なデータの送受信を処理します。このデータにはRigidbodyの位置や回転、速度などが含まれます。さらに修正を適用していくと、このカスタムソリューションは後々新しいPhotonRigidbodyViewコンポーネントに変身します。

 

ラグ補正を追加して同期の問題を解決する

シミュレーションを設定して複数のクライアントで実行するとすぐにわかるのですが、モニターを2つ以上並べてゲームを実行してみると、残念な結果になり同期機能に顕著な障害があることに気づきます。その1つの例は別の画面で見たときに宇宙船の位置が揃っていないのです。ラグによって発生するもので、後で同期全体の問題につながります。あるクライアントのゲーム画面ではプレイヤーの宇宙船が小惑星に激突したのに他のクライアントのゲーム画面ではそれが反映されていないケースもあります。これは後にMasterClientに、物理システムが他のクライアントには見えない衝突を検出したら、他のプレイヤーの宇宙船を強制的に爆破させることもあります。(MasterClientはシミュレーションと衝突検出を制御していました。)この種の障害はマルチプレイヤーゲームにとっては致命的です。

上記の同期にまつわる問題を取り除くために、小惑星と宇宙船、そして発射された弾にラグ補正を追加します。このケースにおいてラグ補正とは、同期されたオブジェクトの情報を受信するクライアントが前に受信した情報を参考にして、より正確に最新のデータが計算できることを意味します。例えば、クライアントが他の宇宙船の情報を受信したとき、そのプレイヤーは、受信した位置と速度とともにメッセージのタイムスタンプと自分の現在のタイムスタンプを用いて、他の宇宙船の最新の位置を計算します。こうしてより正確に位置の計算ができるようになったので、UnityのFixedUpdate機能を使って、だんだんと宇宙船を「本当の」位置になるように(少なくともそのオブジェクトの本当の位置だと考えられる場所に近づくように)動かします。上記の機能の実装を説明した以下のコードスニペットを参考にして、頭を整理してみてください。

public void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)
{
   if (stream.IsWriting)
   {
       stream.SendNext(rigidbody.position);
       stream.SendNext(rigidbody.rotation);
       stream.SendNext(rigidbody.velocity);
   }
   else
   {
       networkPosition = (Vector3)stream.ReceiveNext();
       networkRotation = (Quaternion)stream.ReceiveNext();
       rigidbody.velocity = (Vector3)stream.ReceiveNext();

       float lag = Mathf.Abs((float)(PhotonNetwork.Time - info.timestamp));
       networkPosition += (rigidbody.velocity * lag);
   }
}

オーナーが送信するのは、宇宙船の位置、回転、速度など大切な情報だけです。受信者はこの情報を使って、ローカルに保管している値を更新したり位置のラグ補正を適用したりします。

public void FixedUpdate()
{
   if (!photonView.IsMine)
   {
       rigidbody.position = Vector3.MoveTowards(rigidbody.position, networkPosition, Time.fixedDeltaTime);
       rigidbody.rotation = Quaternion.RotateTowards(rigidbody.rotation, networkRotation, Time.fixedDeltaTime * 100.0f);
   }
}

リモートオブジェクトがローカルに保管されたデータとFixedUpdateで更新される前にです。

注意事項: 上記のコードスニペットはPUNパッケージの実際のPhotonRigidbodyViewコンポーネントを表しているわけではありません。

最後に移植されたデモにラグ補正を追加したことでデモは格段に良くなりました。ネットワーク同期が改善されたことで、全体的に満足のいくゲームプレイとなり、モニターを2つ以上並べ見比べてみても納得のいくものになりました。

Screenshot of the ported Asteroids demo
デモのスクリーンショット2

ドキュメントのトップへ戻る