In this four-part “Building Your Multiplayer VR Experience” blog and video series we’ll discuss the Platform SDK multiplayer features in Quest by exploring our open-sourced SharedSpaces sample made in Unity, and show you how to use the sample to build your own multiplayer application. This is Part 3. If you missed Part 2, you can read the blog post here and the video here.
If you're interested in learning by watching or listening, check out this video on the Meta Open Source YouTube channel.
In today’s blog, we’ll go over the steps involved in making a simple game on top of the SharedSpaces Unity sample. You’ll learn how using this sample makes it easier to prototype and build multiplayer VR applications without having to set up all the functionality by yourself. This can save you time and help you focus on the design and gameplay without worrying about underlying technicalities. We are building off the sample project that we created in Unity in Part 2 of this series. If you missed it and need to catch up, check out the blog post or video.
In your sample Unity project, create an empty folder called “GameAssets” in your Assets, under which you’ll create folders for all assets that are needed for this game. All game scripts will live under a folder called “GameScripts” under the main SharedSpaces Scripts.
The SharedSpaces sample starts with the Startup Scene, which creates game objects and prefabs that you might need throughout the experience. Then it takes you to the lobby, where you can choose to stay or enter a different room. Before you start creating the custom room, open the Startup scene to familiarize yourself with the game objects and scripts.
Some of the main objects you’ll notice in this scene are:
SharedSpacesApplication: This game object contains the SharedSpaces application script, which has references to the network layer, the scene loader, the spawner and the VoIP. This script consists of various callback methods for when the host or client connections are established, disconnected or restored. It also includes methods that allow for functions such as sending invitations through the invite panel, logging errors, joining and loading the correct rooms.
SharedSpacesNetworkLayer: This game object has references to the Photon Realtime transport script. This serves as the network manager and enables communication between players that share a space and the network layer. The network manager script is a Unity netcode script that handles all the networking related settings, such as allowing you to start or stop the networking, letting you provide the networked prefabs and registering scene names. In our SharedSpaces sample, we have provided references to our player prefab and the session as shown below.
SharedSpacesSpawner: This object contains the script ShareSpacesSpawner, which has methods that spawn network prefabs. These methods return NetworkObject, which is a necessary component that you need to add to your prefabs if you want them to persist over the network. We’ll discuss more on this later.
For ease of demonstration, we are using the public Purple Room, but this can be implemented in any room that you prefer and can change based on your game’s needs. Please note that the other rooms are private and require you to invite your friends.
Now that you have a basic understanding of some of the methods in your Startup scene, it’s time to move to your next scene. Since you’re creating this game in the Purple Room, open the Purple Room scene from your Unity Assets. You’ll see that there are some elements already set up. Use this setup to build your world.
For this example, you may use Unity’s Asset Store for all your game assets. Here is where you can find all the free assets to use for this game:
Note that the use of these assets should be as per their license terms. Please visit the Unity Asset Store to read the license terms in more detail for each of these assets, linked above.
Before diving into the assets, let’s learn more about the game. For this example, your game will be a two-player game in which both players compete to collect escaped chickens roaming free in the forest. The first person to collect 30 chickens wins. Through this simple game, you’ll learn how to use the SharedSpaces sample to easily create multiplayer games. We’ll discuss netcode basics, inviting a friend to play with you and other aspects of multiplayer features.
Going back to your scene, import the following assets as prefabs: an asset for a chicken, a farmer and the environment. First, drag the environment prefab into your scene as shown. Your environment is a low poly forest scene.
Next, add the positions where you want your chickens to spawn and make them move around randomly within that area. To do this, make a child object of the environment and call it “Positions.” This will contain all of the positions where you want the chickens to spawn.
To control the spawning and general scorekeeping of the game, add a game object called GameController with a GameController script attached to it. This will be your main controller and where logic related to the instantiation of the chickens is added.
The GameController script contains functionality for controlling the chickens, where they spawn, enabling and disabling the triggers and spawn points, the logic to display who is winning and play a unique sound when the chickens are collected.
Next, you’ll want the chickens to get instantiated in random positions that you set in your game controller. To achieve this, add a coroutine called PopulateChickens that controls where and how often the chickens get instantiated. To enable random movement for the chickens once they are instantiated, add a script to your chicken prefab and call it RandomMovement. This script makes the chickens move towards random points within your area and controls their animations. Add a tag to your chickens called “Chicken” so that you can identify them. Make sure that your chicken has a collider attached to the prefab with the “IsTrigger” flag enabled.
Going back to your scene, add your audio source that gets triggered when a chicken gets collected. Add two sound effects: one on GameController to trigger a “Collect” sound when a chicken is caught, and another on SfxAmbience for “Background” sound.
Now you have your basic scene setup for your players. Next, we’ll look at setting up your players.
In your game, the main player is a farmer prefab. Open the SharedSpacesPlayer prefab and inspect the elements it contains, after which you’ll have a good understanding of what components the player object needs and how you can customize it to use your player prefab.
Some of the components you’ll notice in the player prefab are:
NetworkObject: Since you want your player prefab to persist over the network, you need to have the network object component attached to it.
SharedSpacesPlayerColor: This will be used to update the color of your player over the network.
SharedSpacesPlayerName: This is added to show a player’s name over the character’s mesh.
To update this prefab so that it uses your farmer model, first update the Avatar under the Animator to use your Farmer’s mesh. Next, add your farmer’s mesh under the Geometry tab instead of the default, Armature_Mesh. Be sure to update your reference for Mesh Renderer under the SharedSpacesPlayerColor script to match your player’s shirt. Now your player’s prefab is ready to be referenced in your scene. You can either replace the existing prefab or you can choose to save it as a new prefab.
Now that your player prefab is ready, it’s time to update the references to instantiate your farmer as the player.
To do this, open the Startup scene and select SharedSpacesNetworkLayer. Here you’ll make sure that the network prefab is referencing your farmer model in the NetworkManager script.
Update the reference under the SharedSpacesSpawner script to reflect this prefab.
Also make sure that all SharedSpaces Launch Triggers check for the “Chicken” tag and don’t get triggered if the game object is a chicken. This can be done by adding this check:
if (other.tag == "Chicken") return;
Now when you start the sample, your player is updated to the farmer. If you enter the Purple Room, you’ll see that chickens are spawning. The next step is to add some game and networking logic.
You’ll want to configure your game so that when one player collects a chicken, the score is correctly propagated through the network and visible to both players. To do that, you’ll use Unity Netcode’s Network Variables and Remote Procedure Calls (RPCs). To learn more about NetworkVariable, visit the Unity NetworkVariable documentation. To learn more about RPCs, visit Unity Netcode RPC documentation.
A NetworkVariable’s values are replicated to other nodes in the network regularly. When a client initially connects to a host, the NetworkVariable’s latest value will be replicated to that new client. In SharedSpaces, the logic for adding NetworkVariables is in the SharedSpacesPlayerState class. Note that this script is extended from NetworkBehaviour, which is an abstract class that derives from MonoBehaviour, and is the base class from which all your networked scripts should derive.
In this script, add two new NetworkVariables, one for your first player’s score and another for your second player’s score.
public NetworkVariable<int> firstPlayerScore = new NetworkVariable<int>(); public NetworkVariable<int> secondPlayerScore = new NetworkVariable<int>();
Designate the first person to enter the Purple Room in your game as Player 1. Next, create a method that gets called each time a chicken is collected, and name it SetScore.
public void SetScore() { if (!LocalPlayerState) return; if (IsServer) { Debug.Log("First Player value changed"); FirstPlayerCatchChickenServerRpc(); } else { Debug.Log("Second Player value changed"); SecondPlayerCatchChickenServerRpc(); } }
In this method, based on whether it's a server or client, you’ll update the first Player’s score or second Player’s score using RPCs. Add two Server RPCs for that as shown. To learn more about Server RPCs, visit the Unity Netcode Server RPC documentation.
[ServerRpc] private void FirstPlayerCatchChickenServerRpc() { firstPlayerScore.Value++; } [ServerRpc] private void SecondPlayerCatchChickenServerRpc() { secondPlayerScore.Value++; }
Now that you have these set up, make sure that you call the SetScore method when a player collects the chicken. To detect collisions between the player and the chickens, add a new script called DetectCollision and attach it to your farmer player. This script will call your SetScore method when it collects a chicken. Something similar to:
private void OnTriggerEnter(Collider other) { if (other.gameObject.tag == "Chicken") { Destroy(other.gameObject); GameController.chickenCollected = true; playerState.SetScore(); } }
Now that you have the player, chickens and the network code set up, you’ll need a place to show these scores and who the winner is. You also want the scores to be viewed by both players simultaneously.
Add two scoreboards in the PurpleRoom, one for Player 1, and the other for Player 2. Next, add a third one, which shows the status of who is winning. Since the third scoreboard doesn’t need any value to be sent or updated over the network, add it as a normal game object to your scene. The example below shows free assets from the Unity Asset Store that were used to create a low poly scoreboard.
Just like your player, these two scoreboards also need to be updated over the network. Both scoreboards should have a NetworkObject component attached to them as shown below.
Now, ensure that the spawner spawns your newly created scoreboards when the host connects and that they only appear when players have entered the Purple Room. To achieve this, add references to your scoreboards and add two new spawn methods in the SharedSpacesSpawner script and call them from your SharedSpacesApplication script:
SharedSpacesSpawner:
public NetworkObject firstPlayerScorePrefab; public NetworkObject secondPlayerScorePrefab; public NetworkObject SpawnFirstPlayerScoreBoard() { NetworkObject score = Instantiate(firstPlayerScorePrefab); score.Spawn(); return score; } public NetworkObject SpawnSecondPlayerScoreBoard() { NetworkObject score = Instantiate(secondPlayerScorePrefab); score.Spawn(); return score; }
SharedSpacesApplication:
public NetworkObject scoreBoardFirstPlayer; public NetworkObject scoreBoardSecondPlayer; private void OnHostStarted() { … scoreBoardFirstPlayer = spawner.SpawnFirstPlayerScoreBoard(); scoreBoardSecondPlayer = spawner.SpawnSecondPlayerScoreBoard(); } private void OnHostRestored() { … scoreBoardFirstPlayer = spawner.SpawnFirstPlayerScoreBoard(); scoreBoardSecondPlayer = spawner.SpawnSecondPlayerScoreBoard(); }
Update the references in the spawner and application scripts and then add these new score prefabs to the Network Prefabs list in the NetworkManager.
The last step to set up the scoreboards is to enable score updates when chickens are collected. For this, add two scripts, one for each score board. These scripts provide the updated score from the appropriate callbacks in your SharedspacesPlayerState script.
ScoreControllers:
Two new scripts, ScoreControllerFirstPlayer and ScoreControllerSecondPlayer, control how the score is updated and displayed on the board. The method below shows the function that gets called when the score changes for the first player:
public static void UpdateScore(int newScore) { firstPlayerCurrentScore = newScore; }
SharedSpacesPlayerState:
Be sure to add a subscription for OnValueChanged that informs the scoreboards when the score has changed so it can update.
OnEnable:
firstPlayerScore.OnValueChanged += OnFirstPlayerScoreChanged; secondPlayerScore.OnValueChanged += OnSecondPlayerScoreChanged;
OnDisable:
firstPlayerScore.OnValueChanged -= OnFirstPlayerScoreChanged; secondPlayerScore.OnValueChanged -= OnSecondPlayerScoreChanged; private void OnFirstPlayerScoreChanged(int oldScore, int newScore) { ScoreControllerFirstPlayer.UpdateScore(newScore); } private void OnSecondPlayerScoreChanged(int oldScore, int newScore) { ScoreControllerSecondPlayer.UpdateScore(newScore);
Lastly, go back to your GameController to make sure that the scoreboards are correctly displaying who is winning. Here you must also ensure that all panels are disabled when the game starts, and chickens are populated only when both players are in the arena.
Now you’re ready to build your game. Click “Build,” making sure that you have set the keystore and that the platform is set to Android. Once the APK is ready, install and run it on your Quest headset using ODH as shown in our last blog.
This was a quick walkthrough of how you can use the Unity SharedSpaces sample to build a simple game. You learned how to set up a player, how to make use of NetworkVariables and RPCs to ensure your data is replicated across the network, and how you can build a simple VR multiplayer game on Quest in just a few steps. If you are interested in a walkthrough that shows all the scripts showcased in this blog, you can check out the detailed video on our YouTube channel. In our next blog, we’ll go over some more multiplayer features and learn about travel reliability, best practices, takeaways and resources.
Be sure to check out our previous blog in the “Building your multiplayer VR experience” series to learn more about what multiplayer features offer, what you can build with them and how you can use the SharedSpaces sample to help you get started:
In this series, we discuss the Platform SDK multiplayer features in Quest by exploring our open-sourced SharedSpaces sample made in Unity. This blog is the first of a four-part series of blogs supporting the video series “Building your multiplayer VR experience.”
To learn more about the Platform SDK multiplayer features and how the SharedSpaces sample works, check out this Connect session that discusses building and growing multiplayer apps for Quest. Try out the Unity SharedSpaces sample by visiting App Lab and running it on your own headset. To learn more about how to use Platform SDK multiplayer features in your own apps, checkout our documentation for multiplayer features.
To learn more about Meta Quest, visit our website, subscribe to our YouTube channel, or follow us on Twitter and Facebook. If you have any questions, suggestions or feedback, please let us know in the developer forums.
To learn more about Meta Open Source, visit our open source site, subscribe to our YouTube channel, or follow us on Twitter and Facebook.