LoginSignup
8
3

More than 5 years have passed since last update.

UNETのNetworkBehaviourクラスのコールバックとコンテキストフラグの罠

Last updated at Posted at 2017-04-28

UNETメモ の続き

はじめに

Unityのマルチプレイヤー関連の機能(通称UNET)について、NetworkBehaviourのコールバックが多すぎたので検証と整理(Unity5.6.0f3にて検証)。

処理を書いてるとローカル上のプレイヤーとサーバ上のプレイヤーについては意識するけどローカル上の他プレイヤーのことを忘れがちなので注意が必要。

Unity公式にある以下の図を常に頭に思い浮かべる必要がある。

「Client 1」の「Player 2」のことを忘れがち。コンテキストフラグ(と属性)を正しく使って処理を分ける。

また、ホストは「サーバ」と「クライアント」の両方の振る舞いを持ち、その挙動は「サーバ」と「クライアント」の和集合(×積集合)と理解する。これはフラグ使うときに大事になってくる。

コールバック一覧

公式のマニュアルから引用。サーバ用のコールバックとクライアント用のコールバックがあって、ホストではその両方が実行される。

「プレイヤー」と書いてあるがノンプレイヤーオブジェクトもあるのでその場合呼ばれるのかは要調査。

サーバー上のプレイヤーコールバック

これらのコールバックを取得するために、2 つのゲームインスタンスが必要です。1 つは Server モードで実行するもの、もう 1 つは Client モードで実行するものです。これらのコールバックは Server インスタンス上のプレイヤーオブジェクトに対してのみ呼び出されます。

コールバックを得るには、最初に Server モードのインスタンスを起動し、それから Client インスタンスを開始します。

  • OnStartServer
  • OnRebuildObservers
  • (Start() function is called)

クライアント上のプレイヤーコールバック

これらのコールバックを取得するためには、2 つのゲームインスタンスが必要です。1 つは Server モードで実行するもの、もう 1 つは Client モードで実行するものです。これらのコールバックは Client インスタンス上のプレイヤーオブジェクトに対してのみ呼び出されます。

コールバックを得るには、最初に Server モードのインスタンスを起動し、それから Client インスタンスを開始します。

  • OnStartClient
  • OnStartLocalPlayer
  • OnStartAuthority
  • (Start() function is called)

ホスト上のプレイヤーコールバック

これらのコールバックを取得するために、エディターかスタンドアロンビルドとして Host モードで実行する 1 つのゲームインスタンスが必要です。これらのコールバックはプレイヤーオブジェクトに対してのみ呼び出されます。

  • OnStartServer
  • OnStartClient
  • OnRebuildObservers
  • OnStartAuthority
  • OnStartLocalPlayer
  • (Start() function is called)
  • OnSetLocalVisibility

OnNetworkDestroy

OnNetworkDestroy コールバックを得るためには、1 つは Server モードで実行し、2 つは Client モードで実行する 3 つのスタンドアロンのゲームインスタンスが必要です。

OnNetworkDestroy コールバックを得る手順は以下のとおりです。

  1. Server インスタンスを開始します。
  2. Client インスタンスを開始します。
  3. 2 番目の Client インスタンスを開始します。
  4. 両方の Client インスタンスが自動的に Server インスタンスに接続したら、一方の Client インスタンスを止めます。
  5. OnNetworkDestroy が残りの Client インスタンスで呼び出されますが、Server インスタンス上では呼び出されません。

プレイヤーなどNetworkServer.Spawn(GameObject obj)でスポーンしてサーバとクライアントで同期しているオブジェクトは、GameObject.Destroy()ではなくOnNetwork.Destroy()で削除する。OnNetworkDestroyコールバックはそのときに呼ばれるコールバック。

マニュアルだとクライアントの接続を切ることで呼ばれるようにしている。

ホスト上でも呼び出されるし、マニュアルと違ってインスタンスを止めてなければNetworkServer.Destroy()が呼ばれたプレイヤーのオーナーのクライアント上でも呼ばれる。

コンテキストを表すフラグの挙動

フラグ 説明
isServer サーバ(もしくはホスト)上のオブジェクトならtrue。ただしOnStartServerが呼ばれるまでは必ずfalse
isClient クライアント(もしくはホスト)上のオブジェクトならtrue。ただしOnStartClientが呼ばれるまでは必ずfalse
isLocalPlayer クライアント(もしくはホスト)上のオブジェクトで自分が権限を持っているならtrue。ただしOnStartLocalPlayerが呼ばれるまでは必ずfalse

OnXXXが呼ばれるまでは」というのは厳密に言うとそのメソッド呼び出し時点ですでにtrueになっているので、メソッド内で判定に利用できる。逆にAwake内だと必ず全部falseになる。

ホストだと状況的に3つすべてtrueになる場合もある。

さらにisServerについてはOnDestroyが呼ばれる前に必ずfalseになる(参考スレッド)。この場合はNetworkServer.activeを使ってサーバ上かどうか判断できるとのこと。

プレイヤーが複数人いる場合の順番

ホストとクライアントで大きな違いがある。もっと言うと実行環境でも違う。はっきり言って酷い。

以下の3人のプレイヤーがいるものとする。BとCについてはマッチングで入ってきた順にB,Cとする。

  • A:ホスト
  • B:クライアント
  • C:クライアント

ホストAにおけるコールバックの実行順

プレイヤーごとに直列実行といった感じ。

  1. Aのコールバック
    1. Awake
    2. OnStartServer
    3. OnStartClient
    4. OnRebuildObservers
    5. OnSetLocalVisibility
    6. OnStartAuthority
    7. OnStartLocalPlayer
    8. Start
  2. Bのコールバック
    1. Awake
    2. OnStartServer
    3. OnStartClient
    4. OnRebuildObservers
    5. OnSetLocalVisibility
  3. Cのコールバック
    1. Awake
    2. OnStartServer
    3. OnStartClient
    4. OnRebuildObservers
    5. OnSetLocalVisibility
  4. クライアントのStart
    1. BのStart
    2. CのStart

クライアントのStartについては実行する環境によって実行順が異なり、上記はエディタ上での実行順。Macのスタンドアロンビルド版だとBのStartは「Bのコールバック」の最後に呼ばれる。なんだこれ。

iOSとAndroidは未検証だけどさらに違ってたらやばいので知ってる人いたら教えてください。

クライアントBにおけるコールバックの実行順

ホストとは異なりプレイヤーごとに順番を守った並列実行といった感じ。Bのコールバックには(★)をつけてわかりやすくしました。

  1. 全てのAwake
    1. AのAwake
    2. BのAwake(★)
    3. CのAwake
  2. 全てのStartClient
    1. AのOnStartClient
    2. BのOnStartClient(★)
      1. BのOnStartLocalPlayer(★)
      2. BのOnStartAuthority(★)
    3. CのOnStartClient
  3. 全てのStart
    1. AのStart
    2. BのStart(★)
    3. CのStart

クライアントCにおけるコールバックの実行順

Bと大差ない感じ。Cのコールバックには(★)をつけてわかりやすくしました。OnStartClient->OnStartLocalPlayer->OnStartAuthorityは一連の流れで呼び出されるっぽい。

  1. 全てのAwake
    1. AのAwake
    2. BのAwake
    3. CのAwake(★)
  2. 全てのStartClient
    1. AのOnStartClient
    2. BのOnStartClient
    3. CのOnStartClient(★)
      1. CのOnStartLocalPlayer(★)
      2. CのOnStartAuthority(★)
  3. 全てのStart
    1. AのStart
    2. BのStart
    3. CのStart(★)

OnNetworkDestroyOnDestroy

以下の順。

  1. OnNetworkDestroy
  2. OnDestroy

順番の保証

検証はしてみたものの、公式ではある一つのNetworkBehaviour内の呼ばれる順番しか書いてない。なのでおそらく組み合わせについてはランダム扱いなのだと推測。

ちなみにAwakeの呼び出し順は公式でランダムとされてるが、実は指定がなければInstance ID順に実行される。エディタの再起動で条件付きで振り直されるけど。

何度か試してもマッチングのスロット順によるコールバック呼び出し順は保証されているように見える。見えるだけ?正直よくわからん。

最後に

属性とかフラグとかわけがわからなくなるのでいっそのことサーバとクライアントでクラス分けて疑似通信したほうが良い気がしてきた。となるとHLAPIじゃなくてLLAPIに片足突っ込むことになりそう。ぐぬぬ…。

TODO

  • ノンプレイヤーオブジェクトのコールバックは呼ばれるのか調査
  • どのコールバックで何をすべきかまとめる
  • それぞれのコールバックをUniRxでIObservable<T>にする
8
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
3