Player Join Zones
Player Join Zones Example World
Description
This example shows how to collect players based on their position, and handle basic lobby functionality, enabling users to join a game, play it, see results, then start a new game. It can be used to build opt-in experiences, like a game played by only some of the players in the instance. It also shows how to randomly choose a player to make larger, and extend the prefab’s logic with additional modes.
How to Use This Example
These examples work with one player, but benefit from testing with two or more.
Open the player-join-zones
scene to first test it out in the Unity Editor, or visit the example world linked above in the VRChat Client.
This example requires TextMeshPro, a window will show offering to “Import TMP Essentials” if you don’t already have TextMeshPro in your project. Accept this offer and re-open the scene after it’s done importing.
Player Join Zone:
- First, notice that the button at the bottom of the canvas reads “Players Needed”, and is not interactable.
- Walk into the highlighted area on the floor and see your displayName appear on the board in front of it.
- Your name should appear and disappear as you walk in and out of the zone.
- Press the “Start Game” button to lock-in the list of players, it will no longer change as players enter and exit.
- Press the button that now reads “Reset”, and the list of players clears to make a new one.
Boss Picker:
- Walk into the highlighted area on the floor and see your displayName appear on the board in front of it under the label “Possible Bosses:”.
- Your name should appear and disappear as you walk in and out of the zone.
- Press the “Start Game” button to randomly choose one Player to make large, they are now the “Boss”. Notice that the label reads “Boss:” instead of “Possible Bosses:”.
- Press the button that now reads “Reset”, the Boss should shrink to their original size and the list of players clears to make a new one.
How it Works
This section explains the base program and its two extensions - JoinZoneWithDisplay
and BossPicker
.
The PlayerJoinZone
program is a base class for managing Players within a specific zone. It handles the following:
- Modes: It defines three modes -
MODE_JOIN
accepts changes to the player list,MODE_GAME
freezes the player list for gameplay, andMODE_END
offers a place to show end-of-game information. This mode is synced to all players, and used a FieldChangeCallback to call additional functionality when the mode is changed. - Player Tracking: It uses three events to track players, adding them to a DataList called
Players
.OnPlayerTriggerStay
detects players entering the zone, as well as players who were in the zone when the mode was changed.OnPlayerTriggerExit
detects players leaving the zone.OnPlayerLeft
detects players leaving the instance, who may otherwise get ‘stuck’ in the list.
- Interaction: It has a method
_ToggleMode
designed to be triggered from a UI Button. This method will toggle between the three modes when anyone presses it.
Example Interaction:
- The zone sets the mode to
MODE_JOIN
, which triggers the Owner of the GameObject with this program to runResetPlayers()
.ResetPlayers()
creates a newPlayers
Datalist on the Owner.
- A player “Dingbat” enters the zone’s collider, triggering the
OnPlayerTriggerStay
event for everyone in the instance.- The Owner checks whether Dingbat is already in the list, everyone else ignores the trigger. Dingbat is not found in the list, so the Owner adds them.
- Dingbat will continue to trigger this event as they stay in the zone, but no further action will be taken since they are already in the Datalist.
- A player “SquirrelFam” enters the zone’s collider, triggering the above action again, so there are now two players in the
Players
Datalist. - Dingbat decides to play a different game and leaves the instance.
- The zone receives the
OnPlayerLeft
event for Dingbat, and removes them from thePlayers
Datalist, now only SquirrelFam remains. - SquirrelFam exits the zone, triggering the
OnPlayerTriggerExit
event, which the Owner will respond to by removing them from the Datalist. There are no more players in the Datalist.
Extending the Class
In the interaction described above, the Players
list was created and updated several times, but no information was shown to any users in the instance. This is because the base class contains logic which is useful for many scenarios, but is incomplete on its own. We include two extensions of this base class to demonstrate how you can add functionality.
JoinZoneWithDisplay
This extension connects some UI fields and a button to the core logic to make it all usable. The base class doesn’t contain any references to UI items so that it can be more easily reused and extended in your own projects.
It includes these UI objects:
- _playerNamesField: A Textfield to display the names of all players currently in the Zone.
- _buttonLabelField: The Textfield within the Button used to toggle the current
Mode
, which is updated whenever the Mode changes. - _toggleButton: A UI Button to trigger the
_ToggleMode
method. - _ownerField: A Textfield which shows the owner of this GameObject for debugging.
This class adds a new string PlayerNamesString
, which is a line of text containing all the current player names with commas between them. This is constructed on the Owner and then Synced to all other players and uses a FieldChangeCallback to update the _playerNamesField
. It also adds a new mode called MODE_WAIT
which freezes the UI Button for 3 seconds to keep the ‘game’ from ending too quickly. The duration can be set in the Inspector field for _waitDuration
.
Revisiting the Example Interaction for the PlayerJoinZone
program above, here is what would happen differently when using the JoinZoneWithDisplay
program:
- After creating the
Players
Datalist, the methodOnPlayersChanged()
is called. This method is empty in the base class, but in our extension it will set the synced stringPlayerNamesString
by running the methodGetPlayersAsStringList()
from the base class.- All the players in the instance receive this updated string, and set the text in their
_playerNamesField
from the value. - Each player will also call
SetupButtonFromPlayers()
, which will update the text of their_toggleButton
to show “Players Needed” if no one is in the zone.
- All the players in the instance receive this updated string, and set the text in their
- After the Owner adds Dingbat to the
Players
Datalist, the methodOnPlayersChanged()
is triggered again, which will propagate the same changes above, updating the syncedPlayerNamesString
value and triggering updates the text field and button label if needed.
Every change the Owner makes to the Players
list is followed by a call to OnPlayersChanged()
to update the data and display for everyone else in the instance.
This example also has a Button to toggle the mode like this:
- Any player presses the Button, which has been linked to
UdonBehavior.SendCustomEvent(_ToggleMode)
. If they are not the owner, then the eventToggleModeRPC()
will be sent to the Owner. If they are the owner, then they will callToggleModeRPC()
themselves. Either way, that method runs and flips switches to the nextMode
. Mode
is a synced variable with a FieldChangeCallback, so its value will be updated for everyone in the instance, and the functionOnModeChanged()
will be run locally for each player.- This method is mostly-empty in the base class (it only contains logic to propagate its logic to external listeners), but in this example, it will set the label of the
_toggleButton
to “Reset” if the mode is nowMODE_CHOSEN
.
All the text set by this class is defined in a few strings near the top of the class for easy updating.
BossPicker
This extension has some of the same fields as JoinZoneWithDisplay, adds logic to pick one player from the chosen group and apply some scaling to them, which is useful if you’re making a game where one character should be bigger than the others. It also adds another Mode called MODE_GAMEOVER
for reviewing scores.
It includes a synced PlayerNamesString
field and uses the same logic to create the list, sync it to all players in the instance, and update the text field.
When the Mode
is changed to MODE_CHOSEN
, the Owner chooses one Player from the Datalist at random, and saves their PlayerId as _bossPlayerId
, which is a new synced field with a FieldChangeCallback that triggers _OnBossChanged()
.
In OnBossChanged()
, each player will check if they are the Boss. If they are, then they will set their AvatarEyeHeight to the maximum possible size (currently 5 units). All other players will only have their height changed if it is bigger than _maxPlayerHeight
, which can be adjusted in the program’s Inspector.
The ToggleModeRPC()
method has been overridden in this class in order to handle the new Mode. Now, MODE_CHOSEN
will start a timer to automatically transition to MODE_END
after 5 seconds, or whatever value you’ve set for gameDuration
in the Inspector. This method also disables the button while in the MODE_CHOSEN
state so the game can’t be ended early. Finally, it has logic to transition from MODE_END
to MODE_JOIN
when the UI button is pressed.
In OnModeChanged()
, each player resets to their original height when the mode changes back to MODE_JOIN
, which would typically happen after a game finishes and a new round is opened up.
Integration with Udon Graph & Existing Programs
Each of these programs has a targets
field, which is an array of UdonBehaviours. You can write / modify Udon Programs to include some special variables which will be kept up-to-date. They are:
int
Mode - updated duringOnModeChanged()
in all programsDatalist
Players - updated duringOnPlayersChanged()
in all programsint
BossPlayerId - updated duringOnBossChanged()
in the BossPicker program
The “Graph Listener” canvas demonstrates this, updating a display with the latest events and updates from both programs. Its Udon Graph program simply implements all three public variables, and used OnVariableChanged
nodes to react to their changes, writing the results to a text field.