新增FishNet文档
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
---
|
||||
description: Simple step-by-step tutorials on how to perform common tasks in FishNet.
|
||||
---
|
||||
|
||||
# Common Tasks
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
---
|
||||
description: Instructions for how to build a dedicated FishNet server.
|
||||
cover: ../../.gitbook/assets/dedicated-server-tutorial-cover.png
|
||||
coverY: 0
|
||||
---
|
||||
|
||||
# Building a Dedicated Server
|
||||
|
||||
FishNet supports running your project as a **dedicated server**, meaning Unity runs without rendering graphics and focuses purely on network logic. This is the recommended way to host multiplayer games at scale. You can deploy this build to a server hosting service, your own physical servers, or even give it to your players to allow them to host their own servers for your game.
|
||||
|
||||
{% hint style="success" %}
|
||||
Learn more about Unity's Dedicated Server Build Profiles [here](https://docs.unity3d.com/Manual/dedicated-server-introduction.html).
|
||||
{% endhint %}
|
||||
|
||||
***
|
||||
|
||||
## Required Unity modules
|
||||
|
||||
Before building your server, ensure you have the correct Unity modules installed. You will need the **Dedicated Server Build Support** module for your **server's** operating system. This is usually Linux, but it can be Windows or MacOS too if needed.
|
||||
|
||||
{% tabs %}
|
||||
{% tab title="Unity Hub" %}
|
||||
To install the modules through the Unity Hub:
|
||||
|
||||
1. Open the **Unity Hub**
|
||||
2. Go to the **Installs** tab
|
||||
3. Find your desired Unity version
|
||||
4. Click "<i class="fa-gear">:gear:</i> **Manage**" and then **Add Modules**
|
||||
5. Tick the relevant build targets
|
||||
6. Click **Continue** and complete the installation.
|
||||
|
||||
<div data-full-width="false"><figure><img src="../../.gitbook/assets/linux-dedicated-server-modules.png" alt=""><figcaption><p>Needed Linux Build Module for a Dedicated Server</p></figcaption></figure></div>
|
||||
{% endtab %}
|
||||
|
||||
{% tab title="Unity Editor" %}
|
||||
To install the modules through the Unity Editor:
|
||||
|
||||
1. Open the Unity Editor
|
||||
2. Go to **File → Build Profiles**
|
||||
3. Select your server's target platform. For example **Linux Server** or **Windows Server.**
|
||||
4. If the module is already installed, you will be able to switch to it, otherwise Unity will prompt you to install it.
|
||||
5. Click **Install with Unity Hub** as necessary
|
||||
|
||||
<figure><img src="../../.gitbook/assets/build-profiles-install-windows-server.png" alt=""><figcaption><p>Installing a Windows Server build module</p></figcaption></figure>
|
||||
{% endtab %}
|
||||
{% endtabs %}
|
||||
|
||||
***
|
||||
|
||||
## Ensuring FishNet's server starts
|
||||
|
||||
With the default settings, FishNet detects when the game is built as a dedicated server and will start the FishNet server as soon as the [NetworkManager](../../fishnet-building-blocks/components/managers/network-manager.md) is loaded. You can change this setting by adding the [ServerManager](../../fishnet-building-blocks/components/managers/server-manager.md) component to your NetworkManager and adjusting the [Start on Headless](../../fishnet-building-blocks/components/managers/server-manager.md#start-on-headless) option. You may want to do this when you are starting the server yourself, for example if you want to dynamically set a custom port or server bind address.
|
||||
|
||||
If you disable this functionality you may want to implement it yourself; in the script below, we use the `UNITY_SERVER` preprocessor directive to only include the server starting line of code when built as a Dedicated Server. You can find more options such as this here: [https://docs.unity3d.com/Manual/platform-dependent-compilation.html](https://docs.unity3d.com/Manual/platform-dependent-compilation.html)
|
||||
|
||||
{% code title="NetworkServerStarter.cs" lineNumbers="true" %}
|
||||
```csharp
|
||||
using FishNet.Managing;
|
||||
using UnityEngine;
|
||||
|
||||
[RequireComponent(typeof(NetworkManager))]
|
||||
public class NetworkServerStarter : MonoBehaviour
|
||||
{
|
||||
private void Start()
|
||||
{
|
||||
#if UNITY_SERVER
|
||||
GetComponent<NetworkManager>().ServerManager.StartConnection();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
```
|
||||
{% endcode %}
|
||||
|
||||
***
|
||||
|
||||
## Configuring the transport
|
||||
|
||||
It's important to set up your chosen transport's settings for your dedicated server. The default transport [Tugboat](../../fishnet-building-blocks/transports/tugboat.md) should in most cases have the [Reuse Address](../../fishnet-building-blocks/transports/tugboat.md#reuse-address) option enabled.
|
||||
|
||||
<figure><img src="../../.gitbook/assets/playflow-tutorial-tugboat-reuse-address.png" alt=""><figcaption><p><strong>Reuse Address</strong> option enabled</p></figcaption></figure>
|
||||
|
||||
In addition to that, you may want to set your **Port** and **Server Bind Address**. We'll take a look at how we can set these dynamically next.
|
||||
|
||||
***
|
||||
|
||||
## Using command line arguments
|
||||
|
||||
If you find yourself needing to set the port from command line arguments, you can use a simple script like the following on your NetworkManager:
|
||||
|
||||
{% code title="NetworkCommandLineArgs.cs" lineNumbers="true" %}
|
||||
```csharp
|
||||
using UnityEngine;
|
||||
using FishNet.Managing;
|
||||
using System.Diagnostics;
|
||||
|
||||
[RequireComponent(typeof(NetworkManager))]
|
||||
public class NetworkCommandLineArgs : MonoBehaviour
|
||||
{
|
||||
private NetworkManager _networkManager;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
_networkManager = GetComponent<NetworkManager>();
|
||||
|
||||
ushort? port = GetPortFromCommandLine();
|
||||
if (port.HasValue)
|
||||
{
|
||||
_networkManager.TransportManager.Transport.SetPort(port.Value);
|
||||
_networkManager.Log($"Port set to {port} via command line.");
|
||||
}
|
||||
else
|
||||
{
|
||||
_networkManager.Log("No valid port found in command line args. Using default.");
|
||||
}
|
||||
|
||||
StartDedicatedServer();
|
||||
}
|
||||
|
||||
[Conditional("UNITY_SERVER")]
|
||||
private void StartDedicatedServer()
|
||||
{
|
||||
_networkManager.ServerManager.StartConnection();
|
||||
}
|
||||
|
||||
private ushort? GetPortFromCommandLine()
|
||||
{
|
||||
string[] args = System.Environment.GetCommandLineArgs();
|
||||
for (int i = 0; i < args.Length; i++)
|
||||
{
|
||||
if ((args[i] == "-port") && i + 1 < args.Length)
|
||||
{
|
||||
if (ushort.TryParse(args[i + 1], out ushort port))
|
||||
return port;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
```
|
||||
{% endcode %}
|
||||
|
||||
This code will look for command arguments in this format: `-port <number>`. For example, running: `game.exe -port 7777` will the set the transport to use port 7777.
|
||||
|
||||
On line 28 you will find the use of the `UNITY_SERVER` conditional added to the `StartDedicatedServer` method. We run this after setting the port to start the FishNet server, but only if it's a dedicated server.
|
||||
|
||||
***
|
||||
|
||||
## Configuring the server's logging
|
||||
|
||||
For your dedicated server, logs are essential for tracking errors, monitoring game states, and resolving issues. Consider customizing the following settings to enhance logging efficiency:
|
||||
|
||||
### FishNet's logging level
|
||||
|
||||
FishNet's **NetworkManager** has a **Logging** field which can be used to select a **Level Logging Configuration** asset to customize the FishNet logging settings. You can create such an asset from the (**Assets → Create → FishNet → Logging → Level Logging Configuration)** menu. If you set the **Headless Logging** option to "**Common**", you will be able to see regular debug logs, including connection events such as clients joining and leaving and the server starting, which can be quite helpful. You may also want to enable timestamps or the local tick in the logs.
|
||||
|
||||
<figure><img src="../../.gitbook/assets/level-logging-configuration.png" alt=""><figcaption></figcaption></figure>
|
||||
|
||||
### Shader errors
|
||||
|
||||
When you run your server you may notice a lot of errors and warnings related to shaders, this is normal as Unity is running without graphics. Fortunately, Unity has added an option to fully strip out shaders and fonts, thereby removing most of these errors. You can enable it in your project's settings. 
|
||||
|
||||
* Go to **Edit → Project Settings → Player → Other Settings**
|
||||
* Under **Optimization**, enable **"Dedicated Server Optimizations"**
|
||||
|
||||
<figure><img src="../../.gitbook/assets/dedicated-server-optimizations.png" alt=""><figcaption><p>Enabling the <strong>Dedicated Server Optimizations</strong> option</p></figcaption></figure>
|
||||
|
||||
***
|
||||
|
||||
## Excluding files from the build
|
||||
|
||||
With server builds you will often want to exclude assets that aren't completely necessary on the server, such as audio files and textures. While Unity can handle stripping of a lot of these itself, you sometimes will want to manually force stripping of others. 
|
||||
|
||||
If you want to do this for various asset file types it's recommended to use one of the following tools:
|
||||
|
||||
* [Exclude From Build](https://assetstore.unity.com/packages/tools/utilities/exclude-from-build-222791?aid=1100lqC54\&pubref=kamgam)
|
||||
* [File Excluder](https://assetstore.unity.com/packages/tools/utilities/file-excluder-294650)
|
||||
* [BuildExcluder](https://github.com/Yansubaev/BuildExcluder)
|
||||
|
||||
### Excluding code from the server
|
||||
|
||||
You can do this by using Assembly Definitions and then choosing to exclude the dedicated server platforms. You can also exclude specific code by using the `!UNITY_SERVER` [preprocessor directive](https://docs.unity3d.com/Manual/platform-dependent-compilation.html).
|
||||
|
||||
### Excluding code from the client
|
||||
|
||||
As mentioned above, you can use Assembly Definitions by choosing to exclude the chosen platforms. You can also exclude specific code by using the `UNITY_SERVER` [preprocessor directive](https://docs.unity3d.com/Manual/platform-dependent-compilation.html), this will remove the code from non dedicated server builds.
|
||||
|
||||
If you have [FishNet Pro](../../overview/readme/pro-projects-and-support.md), then you can make use of its built-in code stripping to strip out server specific code from client-only builds. This includes code inside methods marked with [ServerRpc](../../guides/features/network-communication/remote-procedure-calls.md#serverrpc) or [Server](../../guides/features/server-and-client-identification/executing-on-server-or-client.md#server-attribute) attributes, as well as server only callbacks such as [OnStartServer](../../guides/features/networked-gameobjects-and-scripts/network-behaviour-guides.md#onstartserver). You can access this option in the FishNet Configuration menu through the Unity Toolbar.
|
||||
|
||||
{% hint style="info" %}
|
||||
Be sure to let the code recompile after changing the FishNet Code Stripping option.
|
||||
{% endhint %}
|
||||
|
||||
***
|
||||
|
||||
## Creating a server build
|
||||
|
||||
With the needed build profile is installed, you can switch to it and build the server.
|
||||
|
||||
1. Go to **File → Build Profiles** and select your target server platform.
|
||||
2. Include your game scenes carefully. You can have your server begin in its own scene, a bootstrap scene, or the same scene as client builds. Ensure the [NetworkManager](../../fishnet-building-blocks/components/managers/network-manager.md) is present in the starting scene, a scene loaded via code, or instantiated at runtime.
|
||||
3. Click **Build** and choose an output folder for your server build. Once it's done you can upload it to your server or server hosting service and then launch your game servers.
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
---
|
||||
description: >-
|
||||
Step-by-step instructions for how to write a custom player spawner for various
|
||||
scenarios.
|
||||
---
|
||||
|
||||
# Making a Custom Player Spawner
|
||||
|
||||
In many games, you'll often need to spawn a **GameObject** to represent a player. This can happen at various points during gameplay—such as when a player first connects, once a specific number of players have joined, after a particular scene finishes loading, or when the host initiates it with a button press. 
|
||||
|
||||
FishNet includes a default [PlayerSpawner](../../../fishnet-building-blocks/components/playerspawner.md) component designed primarily as a quick-start tool and reference example. It automatically spawns a player object for each client upon successful connection and authentication. It also has the option to define specific player spawn points, and if multiple spawn points are provided, the component will rotate through them sequentially.
|
||||
|
||||
If this functionality is sufficient for your game, then you can go ahead and use the default one, if not, let's look at how to implement our own to handle different use cases.
|
||||
|
||||
<table data-card-size="large" data-view="cards"><thead><tr><th></th><th></th><th></th><th data-type="rating" data-max="3"></th><th data-hidden data-card-target data-type="content-ref"></th><th data-hidden></th></tr></thead><tbody><tr><td><strong>Spawning Players When Connected</strong></td><td>The default PlayerSpawner handles this for you. So there's nothing more to do besides adding it to your game and assigning a prefab to it.</td><td>Difficulty</td><td>1</td><td><a href="../../../fishnet-building-blocks/components/playerspawner.md">playerspawner.md</a></td><td>Difficulty</td></tr><tr><td><strong>Spawning Players Manually</strong></td><td>Tutorial for creating a script to manually spawn your players when you call a method.</td><td>Difficulty</td><td>2</td><td><a href="manually.md">manually.md</a></td><td>Difficulty</td></tr><tr><td><strong>Spawning a Selected Player</strong></td><td>Tutorial for allowing your players to choose a character object before spawning it.</td><td>Difficulty</td><td>2</td><td><a href="spawning-selected-player.md">spawning-selected-player.md</a></td><td></td></tr><tr><td><strong>Spawning Players When Set Number of Players Joined</strong></td><td>Tutorial for spawning players as soon as a set number of clients have joined your game.</td><td>Difficulty</td><td>3</td><td><a href="set-player-number.md">set-player-number.md</a></td><td></td></tr><tr><td><strong>Spawning Players on Scene Load</strong></td><td>Tutorial for spawning players as soon as they are loaded by FishNet into a specific scene.</td><td>Difficulty</td><td>3</td><td><a href="on-scene-load.md">on-scene-load.md</a></td><td>Difficulty</td></tr></tbody></table>
|
||||
+179
@@ -0,0 +1,179 @@
|
||||
---
|
||||
description: >-
|
||||
Tutorial for creating a script to manually spawn your players when you call a
|
||||
method.
|
||||
---
|
||||
|
||||
# Spawning Players Manually
|
||||
|
||||
You may find yourself needing to spawn your players from some custom action other than the previously mentioned options, we can write a script that has a public method you can easily call to trigger it whenever you need to.
|
||||
|
||||
{% stepper %}
|
||||
{% step %}
|
||||
### Create the script
|
||||
|
||||
Create a new script in your project for your custom player spawner. We have called ours `ManualPlayerSpawner`.
|
||||
{% endstep %}
|
||||
|
||||
{% step %}
|
||||
### Add the namespaces and inheritance
|
||||
|
||||
Our script will make use of a few different FishNet namespaces and will inherit from **NetworkBehaviour** to make use of its exposed properties.
|
||||
|
||||
```csharp
|
||||
using FishNet.Connection;
|
||||
using FishNet.Managing;
|
||||
using FishNet.Object;
|
||||
using UnityEngine;
|
||||
|
||||
public class ManualPlayerSpawner : NetworkBehaviour
|
||||
{
|
||||
}
|
||||
```
|
||||
|
||||
{% hint style="info" %}
|
||||
Remember that since this class is a **NetworkBehaviour**, it should not be placed on the NetworkManager GameObject or any of its children, but rather on a Networked GameObject in the desired scene.
|
||||
{% endhint %}
|
||||
{% endstep %}
|
||||
|
||||
{% step %}
|
||||
### Expose the prefab in the inspector
|
||||
|
||||
Let's add a [NetworkObject](../../../guides/features/networked-gameobjects-and-scripts/networkobjects/) variable for holding a reference to our player prefab.
|
||||
|
||||
```csharp
|
||||
[SerializeField] private NetworkObject _playerPrefab;
|
||||
```
|
||||
{% endstep %}
|
||||
|
||||
{% step %}
|
||||
### Create a SpawnPlayers method
|
||||
|
||||
Now we can create the method that we will call when we want to spawn in the players.
|
||||
|
||||
The method will first to a simple null check to make sure we don't get any null reference errors if we forget to assign the player prefab.
|
||||
|
||||
After that it will loop through all the connected clients and as long as they are authenticated, it will instantiate and spawn the prefab for them.
|
||||
|
||||
```csharp
|
||||
public void SpawnPlayers()
|
||||
{
|
||||
if (_playerPrefab == null)
|
||||
{
|
||||
Debug.LogWarning("Player prefab is not assigned and thus cannot be spawned.");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (NetworkConnection client in ServerManager.Clients.Values)
|
||||
{
|
||||
// Since the ServerManager.Clients collection contains all clients (even non-authenticated ones),
|
||||
// we need to check if they are authenticated first before spawning a player object for them.
|
||||
if (!client.IsAuthenticated)
|
||||
continue;
|
||||
|
||||
NetworkObject obj = Instantiate(_playerPrefab);
|
||||
Spawn(obj, client, gameObject.scene);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Wonderful, with that we already have a functioning player spawner, but we can still make some improvements.
|
||||
{% endstep %}
|
||||
|
||||
{% step %}
|
||||
### Prevent accidentally running on client
|
||||
|
||||
Let's prevent ourselves from accidentally running this method on the client side, remember, spawning should only happen by the server.
|
||||
|
||||
Add the `Server` attribute to the method.
|
||||
|
||||
```csharp
|
||||
[Server]
|
||||
public void SpawnPlayers()
|
||||
```
|
||||
|
||||
This will also allow FishNet to strip out this code from client builds when using FishNet Pro's code stripping feature.
|
||||
{% endstep %}
|
||||
|
||||
{% step %}
|
||||
### Enable use of object pooling
|
||||
|
||||
We can also make the script work with FishNet's [Object Pooling](../../../guides/features/networked-gameobjects-and-scripts/spawning/object-pooling.md) system by changing the `Instantiate` method call to one provided by FishNet. This code will work even if we don't use Object Pooling, so it's a direct improvement here.
|
||||
|
||||
The `NetworkManager.GetPooledInstantiated` method requires an additional argument to indicate if this is being called on the server or client. Since we are only going to call it on the server, we provide `true` to the final `asServer` parameter.
|
||||
|
||||
```csharp
|
||||
NetworkObject obj = NetworkManager.GetPooledInstantiated(_playerPrefab, asServer: true);
|
||||
```
|
||||
{% endstep %}
|
||||
|
||||
{% step %}
|
||||
### Handle starting scenes
|
||||
|
||||
Now the script works well for situations where the clients are loaded into the scene by the [FishNet SceneManager](../../../guides/features/scene-management/), but what if you start the game in this scene and **don't** use the FishNet SceneManager to load it? In that case we can tell FishNet that the client has loaded this scene and should observe it. Let's add the following code after checking that the client is authenticated and before instantiating the object for him.
|
||||
|
||||
```csharp
|
||||
// If the client isn't observing this scene, make him an observer of it.
|
||||
if (!client.Scenes.Contains(gameObject.scene))
|
||||
SceneManager.AddConnectionToScene(client, gameObject.scene);
|
||||
```
|
||||
|
||||
***
|
||||
|
||||
**Alternatively**, perhaps you only want clients who are already loaded in this scene to have a player spawned for them; for example if you have multiple games running in different scenes in the same server instance.
|
||||
|
||||
To do that you can simply `continue` the loop instead of adding the connection to this scene, thus skipping the spawning of that player.
|
||||
|
||||
```csharp
|
||||
// If the client isn't in this scene, ignore him.
|
||||
if (!client.Scenes.Contains(gameObject.scene))
|
||||
continue;
|
||||
```
|
||||
{% endstep %}
|
||||
|
||||
{% step %}
|
||||
### The final script
|
||||
|
||||
And that's all there is to it, you should now have a script like this that you can use. Add it to a game object in your desired scene and assign the player prefab to it.
|
||||
|
||||
{% code title="ManualPlayerSpawner.cs" lineNumbers="true" %}
|
||||
```csharp
|
||||
using FishNet.Connection;
|
||||
using FishNet.Managing;
|
||||
using FishNet.Object;
|
||||
using UnityEngine;
|
||||
|
||||
public class ManualPlayerSpawner : NetworkBehaviour
|
||||
{
|
||||
[SerializeField] private NetworkObject _playerPrefab;
|
||||
|
||||
// The Server attribute here prevents this method from being called except on the server.
|
||||
[Server]
|
||||
public void SpawnPlayers()
|
||||
{
|
||||
if (_playerPrefab == null)
|
||||
{
|
||||
Debug.LogWarning("Player prefab is not assigned and thus cannot be spawned.");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (NetworkConnection client in ServerManager.Clients.Values)
|
||||
{
|
||||
// Since the ServerManager.Clients collection contains all clients (even non-authenticated ones),
|
||||
// we need to check if they are authenticated first before spawning a player object for them.
|
||||
if (!client.IsAuthenticated)
|
||||
continue;
|
||||
|
||||
// If the client isn't observing this scene, make him an observer of it.
|
||||
if (!client.Scenes.Contains(gameObject.scene))
|
||||
SceneManager.AddConnectionToScene(client, gameObject.scene);
|
||||
|
||||
NetworkObject obj = NetworkManager.GetPooledInstantiated(_playerPrefab, asServer: true);
|
||||
Spawn(obj, client, gameObject.scene);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
{% endcode %}
|
||||
{% endstep %}
|
||||
{% endstepper %}
|
||||
+262
@@ -0,0 +1,262 @@
|
||||
---
|
||||
description: >-
|
||||
Tutorial for spawning players as soon as they are loaded by FishNet into a
|
||||
specific scene.
|
||||
---
|
||||
|
||||
# Spawning Players on Scene Load
|
||||
|
||||
To spawn a player when they load into a scene, let's write a `ScenePlayerSpawner.cs` script that we can add to our desired scene to handle this.
|
||||
|
||||
## Basic version
|
||||
|
||||
{% stepper %}
|
||||
{% step %}
|
||||
### Create the script
|
||||
|
||||
Create a new script in your project and name it something like `ScenePlayerSpawner.cs`.
|
||||
{% endstep %}
|
||||
|
||||
{% step %}
|
||||
### Make the script a NetworkBehaviour
|
||||
|
||||
Instead of being a regular [MonoBehaviour](https://docs.unity3d.com/ScriptReference/MonoBehaviour.html), we'll make our class inherit from [NetworkBehaviour](../../../guides/features/networked-gameobjects-and-scripts/network-behaviour-guides.md), this will enable us to use the **NetworkBehaviour** callbacks and properties.
|
||||
|
||||
{% code fullWidth="true" %}
|
||||
```csharp
|
||||
using FishNet.Connection;
|
||||
using FishNet.Managing;
|
||||
using FishNet.Object;
|
||||
using UnityEngine;
|
||||
|
||||
public class ScenePlayerSpawner : NetworkBehaviour
|
||||
```
|
||||
{% endcode %}
|
||||
|
||||
{% hint style="info" %}
|
||||
Remember that since this class is a **NetworkBehaviour**, it should not be placed on the NetworkManager GameObject or any of its children, but rather on a Networked GameObject in the desired scene.
|
||||
{% endhint %}
|
||||
{% endstep %}
|
||||
|
||||
{% step %}
|
||||
### Expose the prefab in the inspector
|
||||
|
||||
Add a [NetworkObject](../../../guides/features/networked-gameobjects-and-scripts/networkobjects/) variable for holding a reference to our player prefab.
|
||||
|
||||
```csharp
|
||||
[SerializeField] private NetworkObject _playerPrefab;
|
||||
```
|
||||
{% endstep %}
|
||||
|
||||
{% step %}
|
||||
### Override the OnSpawnServer method
|
||||
|
||||
Let's utilize the NetworkBehaviour's [OnSpawnServer](../../../guides/features/networked-gameobjects-and-scripts/network-behaviour-guides.md#onspawnserver) method to trigger the player spawning for us. This **OnSpawnServer** method gets called by FishNet on the server as soon as the object is being spawned for a client, thus we know he is in the scene and ready to receive a player object.
|
||||
|
||||
{% code overflow="wrap" %}
|
||||
```csharp
|
||||
public override void OnSpawnServer(NetworkConnection connection)
|
||||
{
|
||||
NetworkObject obj = Instantiate(_playerPrefab);
|
||||
Spawn(obj, connection, gameObject.scene);
|
||||
}
|
||||
```
|
||||
{% endcode %}
|
||||
|
||||
This code simply instantiates the given prefab and then tells FishNet to spawn that object on the network (the `Spawn` method here is directly available because we inherited from **NetworkBehaviour**).
|
||||
{% endstep %}
|
||||
|
||||
{% step %}
|
||||
### Use FishNet's object pooling
|
||||
|
||||
We can optionally make the script work with FishNet's [Object Pooling](../../../guides/features/networked-gameobjects-and-scripts/spawning/object-pooling.md) system by changing the `Instantiate` method call to one provided by FishNet. This code will work even if we don't use Object Pooling, so it's a direct improvement here.
|
||||
|
||||
The `NetworkManager.GetPooledInstantiated` method requires an additional argument to indicate if this is being called on the server or client. Since we are only going to call it on the server, we provide `true` to the final `asServer` parameter.
|
||||
|
||||
{% code overflow="wrap" %}
|
||||
```csharp
|
||||
NetworkObject obj = NetworkManager.GetPooledInstantiated(_playerPrefab, asServer: true);
|
||||
```
|
||||
{% endcode %}
|
||||
{% endstep %}
|
||||
|
||||
{% step %}
|
||||
### Basic final script
|
||||
|
||||
The script can now be added to a game object in your desired scene and assigned a player prefab to it. As long as the client is loaded into this scene after connecting, he should get a player object spawned for him.
|
||||
|
||||
{% code title="ScenePlayerSpawner.cs" lineNumbers="true" %}
|
||||
```csharp
|
||||
using FishNet.Connection;
|
||||
using FishNet.Managing;
|
||||
using FishNet.Object;
|
||||
using UnityEngine;
|
||||
|
||||
public class ScenePlayerSpawner : NetworkBehaviour
|
||||
{
|
||||
[SerializeField] private NetworkObject _playerPrefab;
|
||||
|
||||
// This method runs on the server when the client is about to spawn this object.
|
||||
// Since the player is about to spawn this object, we know he is in this scene.
|
||||
public override void OnSpawnServer(NetworkConnection connection)
|
||||
{
|
||||
NetworkObject obj = NetworkManager.GetPooledInstantiated(_playerPrefab, asServer: true);
|
||||
Spawn(obj, connection, gameObject.scene);
|
||||
}
|
||||
}
|
||||
```
|
||||
{% endcode %}
|
||||
{% endstep %}
|
||||
{% endstepper %}
|
||||
|
||||
{% hint style="warning" %}
|
||||
This will work great as long as the scene is not one of the starting scenes. If it is, then FishNet may complain that we are trying to spawn an object for the client before all the starting scenes have been loaded, to fix that, check out the section below.
|
||||
{% endhint %}
|
||||
|
||||
***
|
||||
|
||||
## Handling starting scenes
|
||||
|
||||
If this scene happens to be a scene that's loaded before or as soon as the client connects, then the previous script will cause a warning. To fix this we need to only spawn the player **after** the client has loaded all starting scenes.
|
||||
|
||||
{% stepper %}
|
||||
{% step %}
|
||||
### Listening for the event
|
||||
|
||||
Let's start solving this by adding the override methods `OnStartServer` and `OnStopServer`. We will use these to subscribe to the [SceneManager](../../../fishnet-building-blocks/components/managers/scenemanager.md)'s [OnClientLoadedStartScenes ](../../../guides/features/network-callbacks.md#shared-events)event.
|
||||
|
||||
```csharp
|
||||
public override void OnStartServer()
|
||||
{
|
||||
SceneManager.OnClientLoadedStartScenes += OnClientLoadedStartScenes;
|
||||
}
|
||||
|
||||
public override void OnStopServer()
|
||||
{
|
||||
if (SceneManager != null)
|
||||
SceneManager.OnClientLoadedStartScenes -= OnClientLoadedStartScenes;
|
||||
}
|
||||
```
|
||||
|
||||
These methods will run on the server side as soon as this [NetworkObject](../../../guides/features/networked-gameobjects-and-scripts/networkobjects/) is spawned and despawned. We use them to subscribe and unsubscribe to the event as shown above.
|
||||
{% endstep %}
|
||||
|
||||
{% step %}
|
||||
### Handling the event
|
||||
|
||||
Now we'll write the code that will spawn the player when this event is triggered, as long as he is actually in the scene (remember this event will be triggered whenever the player has loaded all starting scenes, which may or not include this one at all).
|
||||
|
||||
```csharp
|
||||
private void OnClientLoadedStartScenes(NetworkConnection connection, bool asServer)
|
||||
{
|
||||
// Since this method is called when the client has loaded all start scenes,
|
||||
// we need to check if the client is actually in this scene before spawning the player.
|
||||
// We use Observers.Contains to see if this connection is observing this object, and
|
||||
// thus in this scene. We could have alternatively checked if the connection has this
|
||||
// scene loaded like so: connection.Scenes.Contains(gameObject.scene)
|
||||
if (asServer && Observers.Contains(connection))
|
||||
SpawnPlayer(connection);
|
||||
}
|
||||
```
|
||||
|
||||
You will notice we are calling a `SpawnPlayer` method here, we'll create that shortly.
|
||||
{% endstep %}
|
||||
|
||||
{% step %}
|
||||
### Adjusting the OnSpawnServer method
|
||||
|
||||
Now we will adjust to the `OnSpawnServer` method to make sure it only spawns a player if he's loaded the starting scenes.
|
||||
|
||||
```csharp
|
||||
// This method runs on the server when the client is about to spawn this object.
|
||||
// Since the player is about to spawn this object, we know he is in this scene.
|
||||
// BUT he may not have loaded all start scenes yet, so we check that.
|
||||
public override void OnSpawnServer(NetworkConnection connection)
|
||||
{
|
||||
if (connection.LoadedStartScenes(true))
|
||||
SpawnPlayer(connection);
|
||||
}
|
||||
```
|
||||
|
||||
Checking the `NetworkConnection.LoadedStartScenes` method will return, as expected, whether or not that client has loaded the starting scenes. It takes a Boolean parameter for whether this check is happening on the server or client side. We'll pass true as this will always happen on the server for us.
|
||||
{% endstep %}
|
||||
|
||||
{% step %}
|
||||
### The SpawnPlayer method
|
||||
|
||||
Finally, we moved the actual player spawning logic into its own method called **SpawnPlayer**.
|
||||
|
||||
```csharp
|
||||
private void SpawnPlayer(NetworkConnection connection)
|
||||
{
|
||||
NetworkObject obj = NetworkManager.GetPooledInstantiated(_playerPrefab, asServer: true);
|
||||
Spawn(obj, connection, gameObject.scene);
|
||||
}
|
||||
```
|
||||
|
||||
This is all pretty self-explanatory, but you may be wondering why this method is called twice; once in **OnSpawnServer** and once in **OnClientLoadedStartScenes**. Couldn't this cause two players to be spawned for a single player when he loads into our scene?
|
||||
|
||||
It shouldn't, because in our **OnSpawnServer** method we first checked whether the client had loaded the starting scenes before we allowed it to spawn a player, this prevents it from spawning a player if **OnSpawnServer** runs before **OnClientLoadedStartScenes.** 
|
||||
|
||||
Then in **OnClientLoadedStartScenes** we checked that the client was observing this object before spawning a player, this prevents it from spawning a player if **OnClientLoadedStartScenes** runs before **OnSpawnServer.**
|
||||
|
||||
The result of this is that whichever happens later will be the one to spawn the player, and FishNet won't have any issues with starting scenes or later loading scenes.
|
||||
{% endstep %}
|
||||
|
||||
{% step %}
|
||||
### Final script
|
||||
|
||||
The script is now finished and you can add it to a game object in your desired scene and assign the player prefab to it.
|
||||
|
||||
{% code title="ScenePlayerSpawner.cs" lineNumbers="true" %}
|
||||
```csharp
|
||||
using FishNet.Connection;
|
||||
using FishNet.Managing;
|
||||
using FishNet.Object;
|
||||
using UnityEngine;
|
||||
|
||||
public class ScenePlayerSpawner : NetworkBehaviour
|
||||
{
|
||||
[SerializeField] private NetworkObject _playerPrefab;
|
||||
|
||||
public override void OnStartServer()
|
||||
{
|
||||
SceneManager.OnClientLoadedStartScenes += OnClientLoadedStartScenes;
|
||||
}
|
||||
|
||||
public override void OnStopServer()
|
||||
{
|
||||
if (SceneManager != null)
|
||||
SceneManager.OnClientLoadedStartScenes -= OnClientLoadedStartScenes;
|
||||
}
|
||||
|
||||
private void OnClientLoadedStartScenes(NetworkConnection connection, bool asServer)
|
||||
{
|
||||
// Since this method is called when the client has loaded all start scenes,
|
||||
// we need to check if the client is actually in this scene before spawning the player.
|
||||
// We use Observers.Contains to see if this connection is observing this object, and
|
||||
// thus in this scene. We could have alternatively checked if the connection has this
|
||||
// scene loaded like so: connection.Scenes.Contains(gameObject.scene)
|
||||
if (asServer && Observers.Contains(connection))
|
||||
SpawnPlayer(connection);
|
||||
}
|
||||
|
||||
// This method runs on the server when the client is about to spawn this object.
|
||||
// Since the player is about to spawn this object, we know he is in this scene.
|
||||
// BUT he may not have loaded all start scenes yet, so we check that.
|
||||
public override void OnSpawnServer(NetworkConnection connection)
|
||||
{
|
||||
if (connection.LoadedStartScenes(true))
|
||||
SpawnPlayer(connection);
|
||||
}
|
||||
|
||||
private void SpawnPlayer(NetworkConnection connection)
|
||||
{
|
||||
NetworkObject obj = NetworkManager.GetPooledInstantiated(_playerPrefab, asServer: true);
|
||||
Spawn(obj, connection, gameObject.scene);
|
||||
}
|
||||
}
|
||||
```
|
||||
{% endcode %}
|
||||
{% endstep %}
|
||||
{% endstepper %}
|
||||
+211
@@ -0,0 +1,211 @@
|
||||
---
|
||||
description: >-
|
||||
Tutorial for spawning players as soon as a set number of clients have joined
|
||||
your game.
|
||||
---
|
||||
|
||||
# Spawning Players When Set Number of Players Joined
|
||||
|
||||
{% stepper %}
|
||||
{% step %}
|
||||
### Create the Script
|
||||
|
||||
Create a new script in your project for your custom player spawner. We have given ours the name: `CountBasedPlayerSpawner`.
|
||||
{% endstep %}
|
||||
|
||||
{% step %}
|
||||
### Add the needed fields and namespaces
|
||||
|
||||
Our class will take a NetworkObject prefab which will be spawned for our players and an integer for how many players need to connect before we spawn the players.
|
||||
|
||||
We're also storing a reference to the NetworkManager this script will use. This is necessary as we won't be inheriting from NetworkBehaviour for this player spawner script; as a result, we will be able to place this component directly on the NetworkManager.
|
||||
|
||||
```csharp
|
||||
using FishNet;
|
||||
using FishNet.Connection;
|
||||
using FishNet.Managing;
|
||||
using FishNet.Managing.Server;
|
||||
using FishNet.Object;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
public class CountBasedPlayerSpawner : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private NetworkObject _playerPrefab;
|
||||
[SerializeField] private int _requiredPlayerCount;
|
||||
|
||||
private NetworkManager _networkManager;
|
||||
}
|
||||
```
|
||||
{% endstep %}
|
||||
|
||||
{% step %}
|
||||
### Get a reference to the NetworkManager
|
||||
|
||||
Let's now get a reference to the NetworkManager, we will first try to get it from this object or one of this object's parents, but if that fails we will get it from the [InstanceFinder](../../../guides/features/instancefinder-guides.md).
|
||||
|
||||
```csharp
|
||||
private void Awake()
|
||||
{
|
||||
_networkManager = GetComponentInParent<NetworkManager>();
|
||||
if (_networkManager == null)
|
||||
_networkManager = InstanceFinder.NetworkManager;
|
||||
|
||||
if (_networkManager == null)
|
||||
{
|
||||
Debug.LogWarning($"CountBasedPlayerSpawner cannot work as a NetworkManager couldn't be found.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
```
|
||||
{% endstep %}
|
||||
|
||||
{% step %}
|
||||
### Listen for clients joining
|
||||
|
||||
With access to the **NetworkManager**, we can now monitor when clients join the game. To achieve this, we’ll utilize the `SceneManager.OnClientLoadedStartScenes` event, which is triggered once a client finishes loading the initial scenes after connecting.
|
||||
|
||||
Subscribe to the event at the end of the Awake method.
|
||||
|
||||
{% code overflow="wrap" %}
|
||||
```csharp
|
||||
_networkManager.SceneManager.OnClientLoadedStartScenes += OnClientLoadedStartScenes;
|
||||
```
|
||||
{% endcode %}
|
||||
|
||||
And also unsubscribe when this object is destroyed.
|
||||
|
||||
```csharp
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (_networkManager != null)
|
||||
_networkManager.SceneManager.OnClientLoadedStartScenes -= OnClientLoadedStartScenes;
|
||||
}
|
||||
```
|
||||
{% endstep %}
|
||||
|
||||
{% step %}
|
||||
### Implement the OnClientLoadedStartScenes method
|
||||
|
||||
Now let's implement the `OnClientLoadedStartScenes` method that will do all of the player spawning for us.
|
||||
|
||||
This event runs for the server and client so we will exit early if it's not running on the server.
|
||||
|
||||
Then we'll create a list of all the clients that are currently connected to the server and authenticated. If there aren't enough clients, then we can return already.
|
||||
|
||||
Otherwise, for each of those authenticated clients, let's spawn a player object for them.
|
||||
|
||||
```csharp
|
||||
private void OnClientLoadedStartScenes(NetworkConnection _, bool asServer)
|
||||
{
|
||||
if (!asServer)
|
||||
return;
|
||||
|
||||
List<NetworkConnection> authenticatedClients = _networkManager.ServerManager.Clients.Values
|
||||
.Where(conn => conn.IsAuthenticated).ToList();
|
||||
|
||||
if (authenticatedClients.Count < _requiredPlayerCount) return;
|
||||
|
||||
foreach (NetworkConnection client in authenticatedClients)
|
||||
{
|
||||
NetworkObject obj = Instantiate(_playerPrefab);
|
||||
_networkManager.ServerManager.Spawn(obj, client);
|
||||
}
|
||||
}
|
||||
```
|
||||
{% endstep %}
|
||||
|
||||
{% step %}
|
||||
### Enable use of object pooling
|
||||
|
||||
We can also make the script work with FishNet's [Object Pooling](../../../guides/features/networked-gameobjects-and-scripts/spawning/object-pooling.md) system by changing the `Instantiate` method call to one provided by FishNet. This code will work even if we don't use Object Pooling, so it's a direct improvement here.
|
||||
|
||||
The `NetworkManager.GetPooledInstantiated` method requires an additional argument to indicate if this is being called on the server or client. Since we are only going to call it on the server, we provide `true` to the final `asServer` parameter.
|
||||
|
||||
```csharp
|
||||
NetworkObject obj = NetworkManager.GetPooledInstantiated(_playerPrefab, asServer: tr
|
||||
```
|
||||
{% endstep %}
|
||||
|
||||
{% step %}
|
||||
### Handle starting scenes
|
||||
|
||||
This script functions well when clients are loaded into the scene via the FishNet [SceneManager](../../../guides/features/scene-management/). However, if the game begins in this scene without using the FishNet SceneManager to load it, we need to manually inform FishNet that the client has entered the scene and should begin observing it. To handle this, insert the following code right after our call to `Spawn`.
|
||||
|
||||
```csharp
|
||||
// If the client isn't observing this scene, make him an observer of it.
|
||||
if (!client.Scenes.Contains(gameObject.scene))
|
||||
SceneManager.AddConnectionToScene(client, gameObject.scene);
|
||||
```
|
||||
{% endstep %}
|
||||
|
||||
{% step %}
|
||||
### Final script
|
||||
|
||||
The script is now completed and ready to use. You can add it to your NetworkManager object or a different object that exists before the clients start connecting. Don't forget to assign its `playerPrefab` and `requiredPlayerCount` fields.
|
||||
|
||||
{% code title="CountBasedPlayerSpawner.cs" lineNumbers="true" %}
|
||||
```csharp
|
||||
using FishNet;
|
||||
using FishNet.Connection;
|
||||
using FishNet.Managing;
|
||||
using FishNet.Managing.Server;
|
||||
using FishNet.Object;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
public class CountBasedPlayerSpawner : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private NetworkObject _playerPrefab;
|
||||
[SerializeField] private int _requiredPlayerCount;
|
||||
|
||||
private NetworkManager _networkManager;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
_networkManager = GetComponentInParent<NetworkManager>();
|
||||
if (_networkManager == null)
|
||||
_networkManager = InstanceFinder.NetworkManager;
|
||||
|
||||
if (_networkManager == null)
|
||||
{
|
||||
Debug.LogWarning($"CountBasedPlayerSpawner cannot work as a NetworkManager couldn't be found.");
|
||||
return;
|
||||
}
|
||||
|
||||
_networkManager.SceneManager.OnClientLoadedStartScenes += OnClientLoadedStartScenes;
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (_networkManager != null)
|
||||
_networkManager.SceneManager.OnClientLoadedStartScenes -= OnClientLoadedStartScenes;
|
||||
}
|
||||
|
||||
private void OnClientLoadedStartScenes(NetworkConnection _, bool asServer)
|
||||
{
|
||||
if (!asServer)
|
||||
return;
|
||||
|
||||
List<NetworkConnection> authenticatedClients = _networkManager.ServerManager.Clients.Values
|
||||
.Where(conn => conn.IsAuthenticated).ToList();
|
||||
|
||||
if (authenticatedClients.Count < _requiredPlayerCount) return;
|
||||
|
||||
foreach (NetworkConnection client in authenticatedClients)
|
||||
{
|
||||
NetworkObject obj = _networkManager.GetPooledInstantiated(_playerPrefab, asServer: true);
|
||||
_networkManager.ServerManager.Spawn(obj, client);
|
||||
|
||||
// If the client isn't observing this scene, make him an observer of it.
|
||||
if (!client.Scenes.Contains(gameObject.scene))
|
||||
_networkManager.SceneManager.AddOwnerToDefaultScene(obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
{% endcode %}
|
||||
{% endstep %}
|
||||
{% endstepper %}
|
||||
+199
@@ -0,0 +1,199 @@
|
||||
---
|
||||
description: >-
|
||||
Tutorial for allowing your players to choose a character object before
|
||||
spawning it.
|
||||
---
|
||||
|
||||
# Spawning Selected Player
|
||||
|
||||
Quite a few games allow their players to choose a character before playing the game, we'll go over one way to achieve that here. You will be able to put the script in your game scene and then have your clients call the `SpawnPlayer` [ServerRpc](../../../guides/features/network-communication/remote-procedure-calls.md#serverrpc) method to have the server spawn their chosen character.
|
||||
|
||||
We will also have a list of allowed player prefabs to prevent your clients from spawning any network object prefab in your game, such as NPCs, projectiles, items, etc.
|
||||
|
||||
{% stepper %}
|
||||
{% step %}
|
||||
### Create the script
|
||||
|
||||
Create a new script in your project for your custom player spawner. We have called ours `SelectablePlayerSpawner`.
|
||||
{% endstep %}
|
||||
|
||||
{% step %}
|
||||
### Add the namespaces and inheritance
|
||||
|
||||
Our script will make use of a few different FishNet namespaces and will inherit from **NetworkBehaviour** to make use of its exposed properties.
|
||||
|
||||
```csharp
|
||||
using FishNet.Connection;
|
||||
using FishNet.Managing;
|
||||
using FishNet.Object;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
public class SelectablePlayerSpawner : NetworkBehaviour
|
||||
{
|
||||
}
|
||||
```
|
||||
|
||||
{% hint style="info" %}
|
||||
Remember that since this class is a **NetworkBehaviour**, it should not be placed on the NetworkManager GameObject or any of its children, but rather on a Networked GameObject in the desired scene.
|
||||
{% endhint %}
|
||||
{% endstep %}
|
||||
|
||||
{% step %}
|
||||
### Create an array of permitted prefabs
|
||||
|
||||
Let's add a [NetworkObject](../../../guides/features/networked-gameobjects-and-scripts/networkobjects/) array for holding all the permitted prefabs your players can choose from. We'll populate this array inside the Unity inspector.
|
||||
|
||||
```csharp
|
||||
[SerializeField] private NetworkObject[] _playerPrefabs;
|
||||
```
|
||||
{% endstep %}
|
||||
|
||||
{% step %}
|
||||
### Create a SpawnPlayer method
|
||||
|
||||
Now we can create the method that our players will be able to call when they want to spawn in their chosen player object. It will be a **ServerRpc** because it will be called on the client side and run the body of the method on the server side. We also need to disable the ownership requirement of the ServerRpc, so that any client can call it.
|
||||
|
||||
We'll give the method two parameters, a NetworkObject parameter for the players to fill in their chosen player character, and a [NetworkConnection](../../../guides/features/server-and-client-identification/networkconnections.md) parameter which we'll use to know who sent the RPC.
|
||||
|
||||
```csharp
|
||||
[ServerRpc(RequireOwnership = false)]
|
||||
public void SpawnPlayer(NetworkObject _playerPrefab, NetworkConnection sender = null)
|
||||
```
|
||||
|
||||
{% hint style="info" %}
|
||||
Be sure to make the **NetworkConnection** parameter the final parameter in the method and defaulted to null, this will allow FishNet to fill it in with the NetworkConnection of the sender automatically for us.
|
||||
{% endhint %}
|
||||
|
||||
Now let's write the body of the method. First, we'll check if the client who's called it already has a player object, we don't want them to have multiple. We'll check if the NetworkConnection's [**FirstObject**](https://fish-networking.com/FishNet/api/api/FishNet.Connection.NetworkConnection.html#FishNet_Connection_NetworkConnection_FirstObject) property is null; this property will contain the first network object owned by a client, which will usually be their player object. We can also set this ourselves with [NetworkConnection.SetFirstObject](https://fish-networking.com/FishNet/api/api/FishNet.Connection.NetworkConnection.html#FishNet_Connection_NetworkConnection_SetFirstObject_FishNet_Object_NetworkObject_).
|
||||
|
||||
After that, we'll ensure the player can only spawn one of our permitted prefabs. If they try to spawn some other ones, we'll kick them for trying to cheat.
|
||||
|
||||
```csharp
|
||||
{
|
||||
if (sender.FirstObject != null)
|
||||
{
|
||||
Debug.LogWarning($"Client {sender.ClientId} already has a player object; not spawning another.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_playerPrefabs.Contains(_playerPrefab))
|
||||
{
|
||||
Debug.LogWarning("Invalid player prefab selected, cannot spawn.");
|
||||
// You don't have to kick the player, but there isn't any good reason
|
||||
// they should be trying to spawn a non-permitted prefab.
|
||||
sender.Kick(FishNet.Managing.Server.KickReason.ExploitAttempt);
|
||||
return;
|
||||
}
|
||||
|
||||
NetworkObject obj = Instantiate(_playerPrefab);
|
||||
Spawn(obj, sender, gameObject.scene);
|
||||
}
|
||||
```
|
||||
|
||||
Lastly we instantiate the prefab and then spawn it in the correct scene, while also giving the client ownership of it.
|
||||
|
||||
Now we have a functioning selectable player spawner, but we can still make some improvements.
|
||||
{% endstep %}
|
||||
|
||||
{% step %}
|
||||
### Enable use of object pooling
|
||||
|
||||
We can also make the script work with FishNet's [Object Pooling](../../../guides/features/networked-gameobjects-and-scripts/spawning/object-pooling.md) system by changing the `Instantiate` method call to one provided by FishNet. This code will work even if we don't use Object Pooling, so it's a direct improvement here.
|
||||
|
||||
The `NetworkManager.GetPooledInstantiated` method requires an additional argument to indicate if this is being called on the server or client. Since we are only going to call it on the server, we provide `true` to the final `asServer` parameter.
|
||||
|
||||
```csharp
|
||||
NetworkObject obj = NetworkManager.GetPooledInstantiated(_playerPrefab, asServer: true);
|
||||
```
|
||||
{% endstep %}
|
||||
|
||||
{% step %}
|
||||
### Handle starting scenes
|
||||
|
||||
Now the script works well for situations where the clients are loaded into the scene by the [FishNet SceneManager](../../../guides/features/scene-management/), but what if you start the game in this scene and **don't** use the FishNet SceneManager to load it? In that case we can tell FishNet that the client has loaded this scene and should observe it. Let's add the following three methods to handle this.
|
||||
|
||||
In [OnStartServer](../../../guides/features/networked-gameobjects-and-scripts/network-behaviour-guides.md#onstartserver) we subscribe to the [SceneManager.OnClientLoadedStartScenes](https://fish-networking.com/FishNet/api/api/FishNet.Managing.Scened.SceneManager.html#FishNet_Managing_Scened_SceneManager_OnClientLoadedStartScenes) event and in [OnStopServer](../../../guides/features/networked-gameobjects-and-scripts/network-behaviour-guides.md#onstopserver) we simply unsubscribe from it.
|
||||
|
||||
OnClientLoadedStartScenes is our own method that will be invoked by the event and will tell FishNet that the client should begin observing this object's scene if he isn't already.
|
||||
|
||||
```csharp
|
||||
public override void OnStartServer()
|
||||
{
|
||||
SceneManager.OnClientLoadedStartScenes += OnClientLoadedStartScenes;
|
||||
}
|
||||
|
||||
public override void OnStopServer()
|
||||
{
|
||||
if (SceneManager != null)
|
||||
SceneManager.OnClientLoadedStartScenes -= OnClientLoadedStartScenes;
|
||||
}
|
||||
|
||||
private void OnClientLoadedStartScenes(NetworkConnection conn, bool asServer)
|
||||
{
|
||||
if (asServer && !conn.Scenes.Contains(gameObject.scene))
|
||||
SceneManager.AddConnectionToScene(conn, gameObject.scene);
|
||||
}
|
||||
```
|
||||
{% endstep %}
|
||||
|
||||
{% step %}
|
||||
### The final script
|
||||
|
||||
And that's all there is to it, you should now have a script like this that you can use. Add it to a game object in your desired scene and assign the player prefab to it.
|
||||
|
||||
{% code title="ManualPlayerSpawner.cs" lineNumbers="true" %}
|
||||
```csharp
|
||||
using FishNet.Connection;
|
||||
using FishNet.Managing;
|
||||
using FishNet.Object;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
public class SelectablePlayerSpawner : NetworkBehaviour
|
||||
{
|
||||
[SerializeField] private NetworkObject[] _playerPrefabs;
|
||||
|
||||
public override void OnStartServer()
|
||||
{
|
||||
SceneManager.OnClientLoadedStartScenes += OnClientLoadedStartScenes;
|
||||
}
|
||||
|
||||
public override void OnStopServer()
|
||||
{
|
||||
if (SceneManager != null)
|
||||
SceneManager.OnClientLoadedStartScenes -= OnClientLoadedStartScenes;
|
||||
}
|
||||
|
||||
private void OnClientLoadedStartScenes(NetworkConnection conn, bool asServer)
|
||||
{
|
||||
if (asServer && !conn.Scenes.Contains(gameObject.scene))
|
||||
SceneManager.AddConnectionToScene(conn, gameObject.scene);
|
||||
}
|
||||
|
||||
[ServerRpc(RequireOwnership = false)]
|
||||
public void SpawnPlayer(NetworkObject _playerPrefab, NetworkConnection sender = null)
|
||||
{
|
||||
if (sender.FirstObject != null)
|
||||
{
|
||||
Debug.LogWarning($"Client {sender.ClientId} already has a player object; not spawning another.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_playerPrefabs.Contains(_playerPrefab))
|
||||
{
|
||||
Debug.LogWarning("Invalid player prefab selected, cannot spawn.");
|
||||
// You don't have to kick the player, but there isn't any good reason
|
||||
// they should be trying to spawn a non-permitted prefab.
|
||||
sender.Kick(FishNet.Managing.Server.KickReason.ExploitAttempt);
|
||||
return;
|
||||
}
|
||||
|
||||
NetworkObject obj = NetworkManager.GetPooledInstantiated(_playerPrefab, asServer: true);
|
||||
Spawn(obj, sender, gameObject.scene);
|
||||
}
|
||||
}
|
||||
```
|
||||
{% endcode %}
|
||||
{% endstep %}
|
||||
{% endstepper %}
|
||||
@@ -0,0 +1,113 @@
|
||||
---
|
||||
description: >-
|
||||
A tutorial for creating a loading screen when using FishNet's Scene Management
|
||||
instead of Unity's default one.
|
||||
---
|
||||
|
||||
# Making a Loading Screen
|
||||
|
||||
You may be used to making loading screens when using Unity's regular scene loading, but how can you do it for networked scenes? A good and simple solution is to use FishNet's Scene Processor to add _(or override)_ functionality during when FishNet loads scenes.
|
||||
|
||||
{% stepper %}
|
||||
{% step %}
|
||||
### Creating a loading screen UI
|
||||
|
||||
Let's start by adding a canvas with an image and some text to cover the screen and let the user know that the game is currently loading and not just frozen.
|
||||
|
||||
<figure><img src="../../.gitbook/assets/loading-screen-ui.png" alt=""><figcaption></figcaption></figure>
|
||||
|
||||
We will disable and enable this object when needed, and to prevent it getting destroyed, we will mark it as [DontDestroyOnLoad](https://docs.unity3d.com/ScriptReference/Object.DontDestroyOnLoad.html).
|
||||
{% endstep %}
|
||||
|
||||
{% step %}
|
||||
### Adding a simple loading screen script
|
||||
|
||||
We can now write a really simple script and place it on our **Loading Screen** object.
|
||||
|
||||
This script will act as a singleton, as we will only have one loading screen in our game. To do this we will add a static reference to it and register it with the [NetworkManager](../../fishnet-building-blocks/components/managers/network-manager.md) in Start, or destroy it if an instance is already registered. We will hide the loading screen after this, so it doesn't block our game before we want it to.
|
||||
|
||||
{% code title="LoadingScreen.cs" lineNumbers="true" %}
|
||||
```csharp
|
||||
using FishNet;
|
||||
using UnityEngine;
|
||||
|
||||
public class LoadingScreen : MonoBehaviour
|
||||
{
|
||||
private void Start()
|
||||
{
|
||||
if (InstanceFinder.NetworkManager.HasInstance<LoadingScreen>())
|
||||
{
|
||||
Destroy(gameObject);
|
||||
return;
|
||||
}
|
||||
|
||||
InstanceFinder.NetworkManager.RegisterInstance(this);
|
||||
DontDestroyOnLoad(gameObject);
|
||||
HideLoadingScreen();
|
||||
}
|
||||
|
||||
public static void ShowLoadingScreen()
|
||||
{
|
||||
if (InstanceFinder.NetworkManager.TryGetInstance(out LoadingScreen loadingScreen))
|
||||
loadingScreen.gameObject.SetActive(true);
|
||||
}
|
||||
|
||||
public static void HideLoadingScreen()
|
||||
{
|
||||
if (InstanceFinder.NetworkManager.TryGetInstance(out LoadingScreen loadingScreen))
|
||||
loadingScreen.gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
```
|
||||
{% endcode %}
|
||||
|
||||
We will also give it public static methods to show and hide the loading screen. We can call these whenever we want to, and we will call them from our scene processor.
|
||||
|
||||
{% hint style="info" %}
|
||||
This example script uses FishNet's instance registering to handle the singleton reference, but you can use your own or any other instead if you prefer.
|
||||
{% endhint %}
|
||||
{% endstep %}
|
||||
|
||||
{% step %}
|
||||
### Creating a custom scene processor
|
||||
|
||||
Now let's create a script that will inherit from FishNet's [DefaultSceneProcessor](https://fish-networking.com/FishNet/api/api/FishNet.Managing.Scened.DefaultSceneProcessor.html) and add our extra functionality when loading a scene. After inheriting from **DefaultSceneProcessor**, simply override the [LoadStart](https://fish-networking.com/FishNet/api/api/FishNet.Managing.Scened.DefaultSceneProcessor.html#FishNet_Managing_Scened_DefaultSceneProcessor_LoadStart_FishNet_Managing_Scened_LoadQueueData_) and [LoadEnd](https://fish-networking.com/FishNet/api/api/FishNet.Managing.Scened.DefaultSceneProcessor.html#FishNet_Managing_Scened_DefaultSceneProcessor_LoadEnd_FishNet_Managing_Scened_LoadQueueData_) methods, call the base method to retain the default functionality and then tell our loading screen to be shown and hidden. 
|
||||
|
||||
{% code title="LoadingScreenSceneProcessor.cs" lineNumbers="true" %}
|
||||
```csharp
|
||||
using FishNet.Managing.Scened;
|
||||
|
||||
public class LoadingScreenSceneProcessor : DefaultSceneProcessor
|
||||
{
|
||||
public override void LoadStart(LoadQueueData queueData)
|
||||
{
|
||||
base.LoadStart(queueData);
|
||||
LoadingScreen.ShowLoadingScreen();
|
||||
}
|
||||
|
||||
public override void LoadEnd(LoadQueueData queueData)
|
||||
{
|
||||
base.LoadEnd(queueData);
|
||||
LoadingScreen.HideLoadingScreen();
|
||||
}
|
||||
}
|
||||
```
|
||||
{% endcode %}
|
||||
{% endstep %}
|
||||
|
||||
{% step %}
|
||||
### Add our scene processor to the NetworkManager
|
||||
|
||||
Now add the **LoadingScreenSceneProcessor** script we just created to your network manager game object. Also add the [SceneManager](../../fishnet-building-blocks/components/managers/scenemanager.md) component if it isn't there already. The SceneManager has a **Scene Processor** field which you need to drag your **LoadingScreenSceneProcessor** component into.
|
||||
|
||||
<figure><img src="../../.gitbook/assets/loading-screen-network-manager.png" alt=""><figcaption></figcaption></figure>
|
||||
{% endstep %}
|
||||
|
||||
{% step %}
|
||||
### Success!
|
||||
|
||||
With that all setup, your loading screen should work whenever FishNet loads new scenes.
|
||||
|
||||
<figure><img src="../../.gitbook/assets/loading-screen-demonstration.webp" alt=""><figcaption></figcaption></figure>
|
||||
{% endstep %}
|
||||
{% endstepper %}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
---
|
||||
description: >-
|
||||
This page covers how to simulate poor network conditions when testing with
|
||||
FishNet.
|
||||
cover: ../../.gitbook/assets/latency-tutorial-cover.png
|
||||
coverY: 0
|
||||
layout:
|
||||
width: default
|
||||
cover:
|
||||
visible: true
|
||||
size: full
|
||||
title:
|
||||
visible: true
|
||||
description:
|
||||
visible: true
|
||||
tableOfContents:
|
||||
visible: true
|
||||
outline:
|
||||
visible: true
|
||||
pagination:
|
||||
visible: true
|
||||
metadata:
|
||||
visible: true
|
||||
---
|
||||
|
||||
# Simulating Bad Network Connections
|
||||
|
||||
When developing a multiplayer game, it’s not enough to test only under perfect network conditions. Real-world players will often experience **latency**, **jitter**, **out of order packets,** and **packet loss**, and your game needs to tolerate these. If you only test on your local machine with a flawless connection, you risk shipping a game that breaks down as soon as it’s played online.
|
||||
|
||||
***
|
||||
|
||||
## What conditions affect networked games?
|
||||
|
||||
* **Latency (Ping)** — delays between sending and receiving packets.
|
||||
* **Jitter** — variations in latency over time (unstable ping).
|
||||
* **Out-of-order Packets** — packets may arrive out of the order from when they were sent.
|
||||
* **Packet Loss** — some messages never arrive.
|
||||
|
||||
Testing with these factors early helps you tune interpolation, reconciliation, lag-compensation, and other systems. It also reveals edge cases that only show up at higher latencies (e.g., 500ms+).
|
||||
|
||||
***
|
||||
|
||||
## What values should I test with?
|
||||
|
||||
There’s no one-size-fits-all answer, but some general guidelines:
|
||||
|
||||
* **Desktop broadband**: 100–150ms latency, 1–3% packet loss
|
||||
* **Mobile networks**: 100–300ms latency, 2–5% packet loss, plus jitter
|
||||
* **Stress testing**: also check 500ms, 1000ms+, and extreme packet loss to ensure your game fails gracefully
|
||||
|
||||
{% hint style="info" %}
|
||||
Remember: don’t test with just “added delay.” Real networks get affected by multiple factors.
|
||||
{% endhint %}
|
||||
|
||||
***
|
||||
|
||||
## FishNet’s built-in simulator
|
||||
|
||||
FishNet includes a basic latency simulator that can add artificial latency, packet loss, and tamper with packet order. These can also affect the client host player.
|
||||
|
||||
To use this, add the [TransportManager](../../fishnet-building-blocks/components/managers/transportmanager/) component to your network manager. You will now be able to enable the **Latency Simulator** and tweak its settings.
|
||||
|
||||
<figure><img src="../../.gitbook/assets/transport-manager-component.png" alt=""><figcaption><p>The <strong>Latency Simulator</strong> on the <strong>TransportManager</strong></p></figcaption></figure>
|
||||
|
||||
***
|
||||
|
||||
## External tools
|
||||
|
||||
For more options it's recommended to use an external tool, such as one of the ones listed below:
|
||||
|
||||
* **Windows**: [Clumsy](https://jagt.github.io/clumsy/)
|
||||
* **macOS/iOS**: [Network Link Conditioner](https://nshipster.com/network-link-conditioner/) (in Apple’s Additional Tools for Xcode)
|
||||
* **macOS (advanced)**: [dummynet](https://manpagez.com/man/8/dnctl/), dnctl and pfctl (built into macOS)
|
||||
* **Linux:** [netem](https://wiki.linuxfoundation.org/networking/netem)
|
||||
|
||||
These tools let you add latency, packet loss, bandwidth limits, and more. They operate at the system level, so every application (including your game) experiences the degraded connection.
|
||||
@@ -0,0 +1,122 @@
|
||||
---
|
||||
description: A guide on starting the FishNet server and client connections yourself.
|
||||
---
|
||||
|
||||
# Starting FishNet's Connections
|
||||
|
||||
This guide will show you how you can start the FishNet server and client yourself through code instead of relying only on the example [networkhudcanvas.md](../../fishnet-building-blocks/prefabs/networkhudcanvas.md "mention") or default [autostart](../../fishnet-building-blocks/components/managers/server-manager.md#start-on-headless) option.
|
||||
|
||||
{% stepper %}
|
||||
{% step %}
|
||||
### Install FishNet
|
||||
|
||||
Before we begin, don't forget to install FishNet if you haven't already. You can follow this guide if needed: [installing-fish-networking.md](../getting-started/installing-fish-networking.md "mention")
|
||||
{% endstep %}
|
||||
|
||||
{% step %}
|
||||
### Create a NetworkManager
|
||||
|
||||
Create a new game object in your scene and give it a nice name such as `NetworkManager`.
|
||||
|
||||
Add the [NetworkManager component](../../fishnet-building-blocks/components/managers/network-manager.md) to it.
|
||||
|
||||
Finally, add an [ObserverManager component](../../fishnet-building-blocks/components/managers/observermanager/) and in the **Default Conditions** field, add a new element and select the [SceneCondition](../../fishnet-building-blocks/scriptableobjects/observerconditions/scenecondition.md).
|
||||
|
||||
<figure><img src="../../.gitbook/assets/starting-connections-network-manager.png" alt=""><figcaption><p>The NetworkManager components</p></figcaption></figure>
|
||||
{% endstep %}
|
||||
|
||||
{% step %}
|
||||
### Setup basic UI
|
||||
|
||||
To get started, let's add a basic user interface which we can use to start the FishNet server, client, and allow the user to enter an IP Address to connect to.
|
||||
|
||||
Add three buttons to your game, label them **Start Host**, **Start Server**, and **Start Client**. Now add an input field for the address the client will attempt to connect to.
|
||||
|
||||
<figure><img src="../../.gitbook/assets/starting-connections-scene.png" alt=""><figcaption><p>The scene setup</p></figcaption></figure>
|
||||
|
||||
{% hint style="info" %}
|
||||
You can download the graphics used above here:
|
||||
|
||||
<a href="https://github.com/maxkratt/fish-networking-starting-connections/releases/download/v1.0-starting-connections-guide/Graphics.zip" class="button primary" data-icon="down-to-line">Download</a>
|
||||
{% endhint %}
|
||||
{% endstep %}
|
||||
|
||||
{% step %}
|
||||
### Create a ConnectionManager script
|
||||
|
||||
Let's now create the following script and add it to our [**Canvas**](#user-content-fn-1)[^1] game object.
|
||||
|
||||
{% code title="ConnectionManager.cs" lineNumbers="true" %}
|
||||
```csharp
|
||||
using FishNet.Managing;
|
||||
using UnityEngine;
|
||||
|
||||
public class ConnectionManager : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private NetworkManager _networkManager;
|
||||
|
||||
public void StartHost()
|
||||
{
|
||||
StartServer();
|
||||
StartClient();
|
||||
}
|
||||
|
||||
public void StartServer()
|
||||
{
|
||||
_networkManager.ServerManager.StartConnection();
|
||||
}
|
||||
|
||||
public void StartClient()
|
||||
{
|
||||
_networkManager.ClientManager.StartConnection();
|
||||
}
|
||||
|
||||
public void SetIPAddress(string text)
|
||||
{
|
||||
_networkManager.TransportManager.Transport.SetClientAddress(text);
|
||||
}
|
||||
}
|
||||
```
|
||||
{% endcode %}
|
||||
|
||||
The code show here is very simple, but one thing to observe is that the script does not inherit from [NetworkBehaviour](../../fishnet-building-blocks/components/network-behaviour-components/). This is because this script will be active before the network is started and after it has stopped.
|
||||
{% endstep %}
|
||||
|
||||
{% step %}
|
||||
### Assign the NetworkManager
|
||||
|
||||
Our **ConnectionManager** needs a reference to the NetworkManager component, assign that in the editor now.
|
||||
|
||||
<figure><img src="../../.gitbook/assets/starting-connections-canvas-connection-manager.png" alt=""><figcaption><p>ConnectionManager assigned a NetworkManager</p></figcaption></figure>
|
||||
{% endstep %}
|
||||
|
||||
{% step %}
|
||||
### Hook-up the UI to the code
|
||||
|
||||
Now select your UI components and attach their OnClick events to the relevant methods in our ConnectionManager.
|
||||
|
||||
<figure><img src="../../.gitbook/assets/starting-connections-host-button.png" alt=""><figcaption><p>Hooking up the host button</p></figcaption></figure>
|
||||
|
||||
For the IP Address Input Field, you can hook up the **On Value Changed** event to the `ConnectionManager.SetIPAddress` method we created.
|
||||
|
||||
<figure><img src="../../.gitbook/assets/starting-connections-input-field.png" alt=""><figcaption><p>Setting the IP address input field</p></figcaption></figure>
|
||||
{% endstep %}
|
||||
|
||||
{% step %}
|
||||
### Test it out!
|
||||
|
||||
With that all done you should be able to run the game and use the UI we created to start the FishNet server and/or client.
|
||||
|
||||
<figure><img src="../../.gitbook/assets/starting-connections-example.gif" alt=""><figcaption></figcaption></figure>
|
||||
|
||||
You should try extending these buttons to also include stopping the server and clients, you can do those actions directly from the [ServerManager](../../guides/fishnet-building-blocks/components/managers/server-manager.md) and [ClientManager](../../guides/fishnet-building-blocks/components/managers/clientmanager.md) components as well.
|
||||
{% endstep %}
|
||||
{% endstepper %}
|
||||
|
||||
{% hint style="info" %}
|
||||
Download the project files with these completed steps here, or explore the repository:
|
||||
|
||||
<a href="https://github.com/maxkratt/fish-networking-starting-connections/releases/download/v1.2-starting-connections-guide/starting-connections.unitypackage" class="button primary" data-icon="down-to-line">Source Files</a> <a href="https://github.com/maxkratt/fish-networking-starting-connections" class="button secondary" data-icon="github">Repository</a>
|
||||
{% endhint %}
|
||||
|
||||
[^1]: Or any other object in the scene you prefer.
|
||||
@@ -0,0 +1,140 @@
|
||||
---
|
||||
description: A tutorial for running multiple Unity Editor windows for testing multiplayer.
|
||||
cover: ../../.gitbook/assets/multiple-editors-tutorial-cover.png
|
||||
coverY: 0
|
||||
---
|
||||
|
||||
# Testing with Multiple Editors
|
||||
|
||||
During your multiplayer game development, you may find it useful to run multiple Unity editors to test out your project locally. This can be especially helpful if you want to use the Unity Inspector or Scene Hierarchy to analyze your game at runtime. While you could do this by duplicating your project's files and opening them in a new Unity editor instance, you will run into issues of keeping the projects in sync with one another, among other things.
|
||||
|
||||
This tutorial will go over two solutions to the problem. Firstly by using Unity's own multiplayer tool called [**Multiplayer Play Mode**](https://docs.unity3d.com/Packages/com.unity.multiplayer.playmode@2.0/manual/index.html), and then by using a community extension called [**ParrelSync**](https://github.com/VeriorPies/ParrelSync?tab=readme-ov-file#parrelsync).
|
||||
|
||||
## Multiplayer Play Mode vs ParrelSync
|
||||
|
||||
Before we start, let's briefly look over the differences between the two so you know which one to choose for your project.
|
||||
|
||||
| | ParrelSync | Multiplayer Play Mode |
|
||||
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **Author** | Community (VeriorPies) | Unity |
|
||||
| **Compatibility** | Works with most Unity versions | Unity 6 and later |
|
||||
| **Editor Limit** | No Limit | 4 Editor Clones |
|
||||
| **Limitations** | Changes should be made on main editor | Clone instances are stripped down versions of the Unity editor with limited functionality |
|
||||
| **Development** | Stable, but inactive | Actively developed |
|
||||
| **Features** | Supports [clone detection](https://github.com/VeriorPies/ParrelSync/wiki/List-of-APIs#clonesmanagerisclone), [editor arguments](https://github.com/VeriorPies/ParrelSync/wiki/Argument), and synchronized play mode (through a 3rd party extension, [ParrelLink](https://tactifail.itch.io/parrelink)) | Supports clone detection, tags, scenarios, remote instances, and synchronized play mode |
|
||||
|
||||
If you are using a Unity version older than Unity 6, you will have to use ParrelSync, otherwise you can choose either option, or even both.
|
||||
|
||||
***
|
||||
|
||||
## Multiplayer Play Mode (MPPM)
|
||||
|
||||
The Multiplayer Play Mode package is a newer, official Unity solution for local and remote multiplayer testing. It allows you to simulate multiple players (up to four) directly within a single editor instance. As well as create specific scenarios for testing.
|
||||
|
||||
{% stepper %}
|
||||
{% step %}
|
||||
### Install MPPM
|
||||
|
||||
1. Open the Package Manager in Unity (**Window → Package Manager**).
|
||||
2. Select "**Unity Registry"** in the dropdown.
|
||||
3. Find **Multiplayer Play Mode** in the Packages listing and click **Install**.
|
||||
|
||||
<figure><img src="../../.gitbook/assets/mppm-package-manager.png" alt="Installing from the Unity package manager"><figcaption><p>Installing from the Unity Package Manager</p></figcaption></figure>
|
||||
|
||||
Or alternatively:
|
||||
|
||||
1. Open the Package Manager in Unity (**Window → Package Manager**).
|
||||
2. Click the + dropdown button and then select "**Install package by name"**.
|
||||
3. Enter the following and then press **Install.**
|
||||
|
||||
```
|
||||
com.unity.multiplayer.playmode
|
||||
```
|
||||
{% endstep %}
|
||||
|
||||
{% step %}
|
||||
### Create your clones
|
||||
|
||||
Open the MPPM window (**Window → Multiplayer → Multiplayer Play Mode**) and click the checkbox next to one of the four Virtual Player options:
|
||||
|
||||
After the clone creates the necessary files, it will open a 
|
||||
|
||||
<figure><img src="../../.gitbook/assets/mppm-enable-player.png" alt="Activating a clone instance"><figcaption><p>Activating a clone instance</p></figcaption></figure>
|
||||
{% endstep %}
|
||||
|
||||
{% step %}
|
||||
### Customize your clone window
|
||||
|
||||
Now that your clone window has opened, you can choose which Unity tabs you want to show in it, such as the scene hierarchy, inspector, and console.
|
||||
|
||||
That's all you need to do! Now as soon as you start your main project, the clones will all enter play mode as well.
|
||||
|
||||
<figure><img src="../../.gitbook/assets/mppm-customizing-clone.png" alt=""><figcaption><p>Clone window layout options</p></figcaption></figure>
|
||||
|
||||
{% hint style="info" %}
|
||||
If the options are disabled, try changing them at run-time instead.
|
||||
{% endhint %}
|
||||
{% endstep %}
|
||||
{% endstepper %}
|
||||
|
||||
You can learn more about the other features available and how to set them up from the official documentation: [https://docs.unity3d.com/Packages/com.unity.multiplayer.playmode@2.0/manual/index.html](https://docs.unity3d.com/Packages/com.unity.multiplayer.playmode@2.0/manual/index.html)
|
||||
|
||||
***
|
||||
|
||||
## ParrelSync
|
||||
|
||||
ParrelSync is a popular, open-source Unity extension that creates multiple clones of your project, allowing you to run a multiple editor windows that reference the same core assets. This is an efficient and powerful way to test multiplayer interactions locally.
|
||||
|
||||
{% stepper %}
|
||||
{% step %}
|
||||
### Install ParrelSync
|
||||
|
||||
The recommended method is to use the Unity Package Manager.
|
||||
|
||||
1. Open the Package Manager in Unity (**Window → Package Manager**).
|
||||
2. Click the + dropdown and select "**Install package from git URL**".
|
||||
3. Enter the following URL and then click **Install**: 
|
||||
|
||||
```
|
||||
https://github.com/VeriorPies/ParrelSync.git?path=/ParrelSync
|
||||
```
|
||||
|
||||
{% hint style="warning" %}
|
||||
Ensure you have the [Git client](https://git-scm.com/) (minimum version 2.14.0) installed on your system and added to the PATH system environment variable.
|
||||
{% endhint %}
|
||||
|
||||
Or alternatively:
|
||||
|
||||
1. Download the latest `unitypackage` file from the [releases](https://github.com/VeriorPies/ParrelSync/releases) page.
|
||||
2. From the toolbar menu choose, **Assets → Import Package →**
|
||||
\
|
||||
**Custom Package.**
|
||||
3. Browse to the downloaded `unitypackage` and import it.
|
||||
{% endstep %}
|
||||
|
||||
{% step %}
|
||||
### Create and test your clones
|
||||
|
||||
From the menu bar, open the ParrelSync Clones Manager (**ParrelSync → Clones Manager**). You'll be able to delete, browse, create new clones, as well as set the arguments to use for each.
|
||||
|
||||
Now from the **Clones Manager** window, click **Create new clone**. This will create a new folder next to your original project and copy the necessary files. After the clone is created, select **Open in New Editor** to launch the cloned project in a new Unity window.
|
||||
|
||||
You can now press Play in both the original and the cloned editor windows to test your multiplayer game with two separate "players."
|
||||
|
||||
<figure><img src="../../.gitbook/assets/parrelsync-clones-manager.png" alt=""><figcaption></figcaption></figure>
|
||||
|
||||
{% hint style="warning" %}
|
||||
It's important to only make changes to your project from the original editor instance, not the cloned one, to avoid potential asset conflicts. ParrelSync disables asset modification in clones by default.
|
||||
{% endhint %}
|
||||
{% endstep %}
|
||||
{% endstepper %}
|
||||
|
||||
***
|
||||
|
||||
## Final comments
|
||||
|
||||
With either of those options setup you can now easily test your game with multiple editors without needing to rebuild each time.
|
||||
|
||||
In the example below we have started the main editor instance as a client and server and the second editor instance as a client.
|
||||
|
||||
<figure><img src="../../.gitbook/assets/mppm-connected-to-editor.png" alt=""><figcaption></figcaption></figure>
|
||||
Reference in New Issue
Block a user