Unity Netcode for GameObjectの備忘録
マルチプレイヤーゲームを作りたい!
そもそも僕がゲーム制作を始めたきっかけはマルチプレイヤーゲームを作りたかったからなのだが、いざゲーム開発の情報収集をしてみるとどうやらマルチプレイヤーゲームを作るというのは途方もなく大変なことらしい。 というわけでしばらくシングルプレイヤー用のゲームのプロトタイプなどを作って試行錯誤していたのだが、やはり初めの動機とは違うことをしているとだんだんとモチベが下がっていってしまう。というわけでおとといあたりからマルチプレイヤーゲームを作ることを考えだした。技術と熱意、どっちかだけあるとしたら絶対熱意の方がいいよね。仕事じゃなくて自分の幸せのためにやっていることなんだから。
Unity Netcode for Gameobject
ここから本編。といってもただの備忘録です。自分なりの要点をとりとめもなくつらつらとまとめていく。内容も順番もごちゃ混ぜのTips形式になっちゃったかも。
PlayerPref
これはNGOとは関係ないけど大事なこと。PlayerPrefはPlayerのPreference保持しておくための静的クラス。このクラスを用いることで、ゲームの設定などをレジストリに保存しておける。今回はサーバーに接続するクライアントを一位的に識別するために用いた。NGOではクライアントを識別するときにClientIdというものが使えるが、これは接続するたびに変わってしまう変数なので、接続が切れちゃったりすると使えない。
GUIDを生成してクライアントを識別して、そのGUIDをPlayerprefで保存しておけば一位的に識別できるので便利だった。BossRoomのサンプルでは、これとClientIDを組み合わせて、再接続したときに切断した時の状態からプレイできるように実装していてすごかった。
Networkvariable<T>
このNetworkvariableで保持した値はネットワーク間で同期される。ここでちょっと落とし穴があるのだが、NetworkvariableにはRead/Writeにアクセス権が設定されており、ReadはOwnerだけかEveryone、WriteはServerだけかOwnerだけ。これ以外のクラスが書き込みしようとすると、エラーは起きないままスルーされるのでバグの原因になる。気をつけよう。ちなみにTはINetworkSerializableを実装していなければいけない。
ServerRpcとClientRpc
[ServerRpc]または[ClientRpc]属性をつけたメソッドはサーバーとクライアント間で通信をするための関数。Rpcとは、ネットワーク上のコンピューターから別のコンピューター上のメソッドを実行するための方法のこと。UnityではNetworkObjectがサーバーとクライアント間で同期されるので、サーバー上とクライアント上にそれぞれインスタンスが存在している。[ServerRpc]をつけたメソッドはサーバー側のインスタンスで実行され、[ClientRpc]をつけたメソッドはクライアント側のインスタンスで実行される。NGOではサーバー側での処理とクライアント側での処理を一つのファイルに一緒に書けるのだが、ここら辺は初見だとマジで頭がこんがらがる。
IsOwnerとかIsServerとかIsClientとかで条件分岐させることで綺麗に(?)一つのコードに両方の処理をまとめれるっぽい。ちなみにRpcをつけたメソッドに渡せる引数は値型かつINetworkSerializable出なきゃいけない。
所有権 Ownership
ここら辺はまだよく理解できていない。
ドキュメント読んでるときにスポーンっていう単語がよく出てきて、オブジェクトのインスタンス化と何が違うんだろうって疑問に思っていたけどどうやら同じことっぽい。
標準でスポーンされたネットワークオブジェクトの所有権はサーバー側にある。
ネットワークプレイヤープレファブの所有権はクライアント側にある。
オブジェクトの所有権は変更可能。
ClientID
これどうやって入手すんねん
NetworkObject.Singleton.LocalCoientIdで自分のClientIDを入手できるっぽい。
NEtworkObject.Singleton.ConnectedClientsIdsはサーバーのみアクセス権を持つ。
INetworkSerializable
NetworkVariableとServerRpcによって同期される値はINetworkSerializable interfaceを実装している値型でなければならない。つまり、クラスはそのまま共有できない。
例えばキャラクターがあるアクションを同期したければ、アクションのクラスをそのまま送るのではなく、アクションを特定するためのIDやターゲットの情報などを構造体に入れて送り、送った先でその情報を元にアクションクラスを生成し、実行するという風にするのがいいっぽい。というかそれしかない気がする。
マルチプレイヤーゲーム開発の難易度
これはゲームの種類によって大きく変わるらしいのだが、めちゃくちゃ大きく分けると決定的可否決定的かで分かれるらしい。決定的というのは、プレイヤーのインプットとその状況から次の状況への変化が一対一で対応しているということ。これならゲームはいろんな値を送受信したりすることなく、プレイヤーがどのボタンを押したかのみを共有すれば状態を同期できるので比較的簡単に実装できそうな気がする¥。
これからの課題
・クリーンなコードを書くこと。今の実力ではマルチプレイヤーゲームなんて作ったらスパゲッティコード化不可避。こういうのってベストプラクティスとかあるのかな?
そういえば長年アップデートされ続けてきたCounterStrikeのコードはぐちゃぐちゃすぎて誰もコードの全貌を理解できていないっていう話があったな。