プレイヤー参加ゾーン

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