Player Join Zones

この例では、プレイヤーの位置に基づいて収集する方法と、基本的なロビー機能を制御する方法を紹介します。これを利用することで、ユーザーがゲームに参加・プレイし、結果を確認してから新しいゲームを開始するという一連の流れが可能になります。インスタンス内の特定のプレイヤーのみが参加するゲームなど、オプトイン方式の体験を構築するのに役立ちます。また、特定のプレイヤーをランダムに選んで大きくしたり、プレハブのロジックに新しいモードを追加したりする方法も示しています。
Player Join Zones Example World にアクセスして、実際に体験してみてください!
サンプルの使い方
これらの例はプレイヤー1人でも動作しますが、2人以上でテストするとより効果的です。まず player-join-zones シーンを開いて Unity Editor 上でテストするか、上記にリンクされているサンプルワールドに VRChat Client でアクセスしてください。この例では TextMeshPro が必要です。プロジェクトに TextMeshPro が含まれていない場合、「Import TMP Essentials」を促すウィンドウが表示されます。インポートを承諾し、完了後にシーンを再度開いてください。
プレイヤー参加ゾーン:
- まず、キャンバスの下部にあるボタンが「Players Needed」と表示されており、操作できない状態であることを確認してください。
- 床のハイライトされたエリアに入ると、目の前のボードに自分の
displayNameが表示されます。 - ゾーンに出入りするたびに、自分の名前が表示されたり消えたりするはずです。
- 「Start Game」ボタンを押すとプレイヤーリストが固定され、その後プレイヤーが出入りしてもリストは変化しなくなります。
- 「Reset」と表示されているボタンを押すとプレイヤーリストがクリアされ、新しいリストを作成できるようになります。
#### BossPicker
- 床のハイライトされたエリアに入ると、目の前のボードの「Possible Bosses:」というラベルの下に自分の
displayNameが表示されます。 - ゾーンに出入りするたびに、自分の名前が表示されたり消えたりするはずです。
- 「Start Game」ボタンを押すと、ランダムに選ばれた1人のプレイヤーが巨大化し、Bossになります。ラベルが「Possible Bosses:」から「Boss:」に変わったことに注目してください。
- 「Reset」と表示されたボタンを押すと、ボスは元のサイズに戻り、プレイヤーリストがクリアされて新しいリスト作成の準備が整います。
サンプルのインポート
以下の手順に従って、このサンプルをUnityプロジェクトに追加してください。
- Unityエディタのメニューから「VRChat SDK > 🏠 Example Central」を開き、Example Centralウィンドウを表示します。
- リストの中からこのプレハブを探すか、タイトル(このページのタイトルと同じ)で検索してください。
- 「Import」ボタンを押して、Unitypackageをプロジェクトにインポートします。
技術解説
このセクションでは、ベースとなるプログラムと、その拡張である JoinZoneWithDisplay および BossPicker について解説します。
PlayerJoinZone プログラムは、特定のゾーン内にいるプレイヤーを管理するための基底クラスです。これは以下の処理を扱います。
- Modes:3つの
Modesを定義します。MODE_JOINはプレイヤーリストへの変更を受け付け、MODE_GAMEはゲームプレイのためにプレイヤーリストを固定し、MODE_ENDはゲーム終了時の情報を表示する場所を提供します。このモードは全プレイヤー間で同期され、FieldChangeCallbackを使用して、モード変更時に追加の機能を呼び出します。 - プレイヤーの追跡:3つのイベントを使用してプレイヤーを追跡し、
Playersという名前のDataListに追加します。OnPlayerTriggerStayは、ゾーンに入ったプレイヤーや、モード変更時にゾーン内にいたプレイヤーを検知します。OnPlayerTriggerExitは、ゾーンから出たプレイヤーを検知します。OnPlayerLeftは、インスタンスから退出したプレイヤーを検知し、リスト内に「スタック」してしまうのを防ぎます。
- Interaction: UIボタンから実行するように設計された
_ToggleModeメソッドがあります。このメソッドは、誰かがボタンを押すと3つのModes間を切り替えます。
インタラクションの例:
- ゾーンがモードを
MODE_JOINに設定すると、このプログラムを持つGameObjectのOwnerがトリガーされ、ResetPlayers()が実行されます。ResetPlayers()は、Owner上で新しいPlayersDatalistを作成します。
- プレイヤー「Dingbat」がゾーンのコライダーに入ると、インスタンス内の全員に対して
OnPlayerTriggerStayイベントがトリガーされます。- OwnerはDingbatがすでにリストに存在するかを確認し、他の全員はそのトリガーを無視します。Dingbatはリストに見つからないため、OwnerはリストにDingbatを追加します。
- Dingbatがゾーン内に留まっている間はこのイベントがトリガーされ続けますが、すでにDatalistに存在するため、それ以上の処理は行われません。
- プレイヤー「SquirrelFam」がゾーンのコライダーに入ると、上記のアクションが再度トリガーされ、これで
PlayersDatalistには2人のプレイヤーが入ることになります。 - Dingbatは別のゲームで遊ぶことにし、インスタンスから退出します。
- ゾーンはDingbatの
OnPlayerLeftイベントを受け取り、PlayersDatalistからDingbatを削除します。これでSquirrelFamのみが残ります。 - SquirrelFamがゾーンから出ると、
OnPlayerTriggerExitイベントがトリガーされ、オーナーがそれに応答してDatalistからSquirrelFamを削除します。これでDatalistにプレイヤーはいなくなりました。
クラスの拡張
上記のインタラクションでは、Players リストが何度も作成・更新されましたが、インスタンス内のユーザーには何の表示もされませんでした。これは、ベースクラスには多くのシナリオで有用なロジックが含まれているものの、それ単体では未完成であるためです。機能を追加する方法を実演するために、このベースクラスの拡張を2つ用意しました。
JoinZoneWithDisplay
この拡張では、いくつかのUIフィールドとボタンをコアロジックに接続し、すべてを利用可能な状態にします。ベースクラスにはUI項目への参照が含まれていないため、自身のプロジェクトでより簡単に再利用や拡張が可能です。
これには以下のUIオブジェクトが含まれます:
- _playerNamesField: ゾーン内に現在いる全プレイヤーの名前を表示するためのTextfieldです。
- _buttonLabelField: ボタン内のTextfieldで、現在の
Modeを切り替えるために使用され、Modeが変更されるたびに更新されます。 - _toggleButton:
_ToggleModeメソッドをトリガーするためのUIボタンです。 - _ownerField: デバッグ用にこのGameObjectのオーナーを表示するためのTextfieldです。
このクラスは、すべてのプレイヤー名をコンマで区切った文字列である PlayerNamesString という新しい文字列を追加します。これはオーナー側で構築され、他のすべてのプレイヤーに同期されます。また、FieldChangeCallback を使用して _playerNamesField を更新します。さらに、MODE_WAIT という新しいモードが追加され、UIボタンが3秒間固定されることで「ゲーム」の即時終了を防ぎます。この持続時間は、Inspector の _waitDuration フィールドで設定可能です。
前述の PlayerJoinZone プログラムのインタラクション例を再考すると、JoinZoneWithDisplay プログラムを使用した場合は以下のような動作の違いが生じます。
PlayersDatalistを作成した後、OnPlayersChanged()メソッドが呼び出されます。このメソッドは基底クラスでは空ですが、クラスの拡張により、基底クラスのGetPlayersAsStringList()メソッドを実行して同期変数PlayerNamesStringを設定するようになります。- インスタンス内の全プレイヤーがこの更新された文字列を受け取り、その値を使って自身の
_playerNamesFieldのテキストを設定します。 - 各プレイヤーは
SetupButtonFromPlayers()も呼び出し、ゾーン内に誰もいない場合は_toggleButtonのテキストを「Players Needed」に更新します。
- インスタンス内の全プレイヤーがこの更新された文字列を受け取り、その値を使って自身の
- オーナーがDingbatを
PlayersDatalistに追加すると、再びOnPlayersChanged()メソッドがトリガーされます。これにより上記と同じ変更が反映され、同期されたPlayerNamesString値が更新されるとともに、必要に応じてテキストフィールドとボタンラベルの更新がトリガーされます。
オーナーが Players リストに加えたすべての変更の後には、インスタンス内の他の全員に対してデータを更新し表示させるため、必ず OnPlayersChanged() が呼び出されます。
この例には、以下のように Modes を切り替えるボタンも用意されています:
- プレイヤーがボタンを押すと、リンクされている
UdonBehavior.SendCustomEvent(_ToggleMode)が実行されます。オーナーではない場合、ToggleModeRPC()イベントがオーナーに送信されます。オーナー本人の場合は、自身でToggleModeRPC()を呼び出します。いずれの場合も、そのメソッドが実行され、スイッチが切り替わって次のModeに移行します。 ModeはFieldChangeCallbackを持つ同期変数であるため、インスタンス内の全員に対してその値が更新され、各プレイヤーの環境でローカルにOnModeChanged()関数が実行されます。- このメソッドは基底クラスではほとんど空ですが(外部リスナーにロジックを伝播させる処理のみが含まれています)、今回の例では、現在の
ModesがMODE_CHOSENの場合、_toggleButtonのラベルを「Reset」に設定します。
このクラスで設定されるすべてのテキストは、更新しやすいようにクラス上部のいくつかの文字列として定義されています。
#### BossPicker
この拡張には JoinZoneWithDisplay と同じフィールドがいくつか含まれており、選択されたグループからプレイヤーを1人選んでスケールを適用するロジックが追加されています。これは、1人のキャラクターを他のプレイヤーより大きくしたいゲームを作成する際に役立ちます。また、スコアを確認するための MODE_GAMEOVER という別のモードも追加されます。
同期される PlayerNamesString フィールドが含まれており、リストの作成、インスタンス内の全プレイヤーへの同期、およびテキストフィールドの更新に同じロジックを使用します。
Mode が MODE_CHOSEN に変更されると、オーナーはデータリストからプレイヤーをランダムに1人選択し、その PlayerId を _bossPlayerId として保存します。これは _OnBossChanged() をトリガーする FieldChangeCallback を備えた新しい同期フィールドです。
OnBossChanged() 内では、各プレイヤーが自分がボスであるかどうかを確認します。ボスである場合、自身の AvatarEyeHeight を最大サイズ(現在は5ユニット)に設定します。それ以外のプレイヤーは、_maxPlayerHeight(プログラムのInspectorで調整可能)より大きい場合にのみ、身長が変更されます。
このクラスでは新しいモードを処理するために ToggleModeRPC() メソッドがオーバーライドされています。この Udon Graphプログラムは MODE_CHOSEN になるとタイマーを開始し、5秒後(またはInspectorで gameDuration に設定した値)に自動的に MODE_END へ移行します。また、このメソッドは MODE_CHOSEN 状態の間はボタンを無効化し、ゲームが早期終了しないようにします。最後に、UIボタンが押された際に MODE_END から MODE_JOIN へ移行するロジックも含まれています。
OnModeChanged() では、モードが MODE_JOIN に戻ると各プレイヤーの身長が元の高さにリセットされます。これは通常、ゲーム終了後に新しいラウンドが開始される際に行われます。
Udon Graphおよび既存プログラムとの統合
これらの各プログラムには、UdonBehaviourの配列である targets フィールドがあります。Udonプログラムを作成または修正することで、常に最新の状態に保たれる特別な変数をいくつか含めることができます。それらは以下の通りです。
intMode - すべてのプログラム内のOnModeChanged()実行時に更新されますDatalistPlayers - すべてのプログラム内のOnPlayersChanged()実行時に更新されますintBossPlayerId - BossPickerプログラム内のOnBossChanged()実行時に更新されます
「Graph Listener」キャンバスはこの仕組みを実証するもので、最新のイベントや両プログラムからの更新情報を表示に反映します。このUdon Graphプログラムは、これら3つのパブリック変数を実装し、OnVariableChanged ノードを使用してそれらの変更に反応し、結果をテキストフィールドに書き込むという単純なものです。
最終更新: