新增FishNet文档

This commit is contained in:
2026-09-20 15:53:11 +08:00
parent c403145db9
commit 179c282405
431 changed files with 15476 additions and 0 deletions
@@ -0,0 +1,9 @@
---
description: >-
Step-by-step tutorials on how to create simple games in order to show you how
to the core concepts fit together.
hidden: true
---
# Complete Games
@@ -0,0 +1,18 @@
---
description: >-
Whether you're new to network programming or transitioning to Fish-Networking
for your latest project, this guide will provide the essential foundation you
need to get started smoothly.
cover: ../../.gitbook/assets/getting-started-graphic.jpeg
coverY: 0
---
# Getting Started
### What to expect
This section introduces key features of Fish-Networking that you'll use frequently in your development journey. You'll find dedicated resources to deepen your understanding and step-by-step guides tailored for common first-time tasks.
By following this guide, you'll gain insight into setting up your project, getting your clients connected to your server, and leveraging core networking capabilities.
If you're ready to dive in, let’s get started on building robust networked applications with Fish-Networking!
@@ -0,0 +1,23 @@
---
description: Where to go from here?
---
# Beyond the Basics
After having gone through the previous guides, it's recommended that you read the following sections to get a better understanding of how Fish-Networking works and its most commonly used features.
## Important features of FishNet
<table data-view="cards" data-full-width="false"><thead><tr><th></th><th></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><i class="fa-plane-departure">:plane-departure:</i> <strong>High-Level Overview</strong></td><td>Get a high-level overview of multiplayer games with FishNet.</td><td><a href="../../guides/high-level-overview/">high-level-overview</a></td></tr><tr><td><i class="fa-vector-square">:vector-square:</i> <strong>Networked GameObjects and Scripts</strong></td><td>Learn about Networking GameObjects and Scripts.</td><td><a href="../../guides/features/networked-gameobjects-and-scripts/">networked-gameobjects-and-scripts</a></td></tr><tr><td><i class="fa-server">:server:</i> <strong>Server and Client Identification</strong></td><td>Find out how to determine if code is running on the server or client.</td><td><a href="../../guides/features/server-and-client-identification/">server-and-client-identification</a></td></tr><tr><td><i class="fa-fingerprint">:fingerprint:</i> <strong>Network Connections</strong></td><td>Understand how FishNet identifies different clients.</td><td><a href="../../guides/features/server-and-client-identification/networkconnections.md">networkconnections.md</a></td></tr><tr><td><i class="fa-at">:at:</i> <strong>Ownership</strong></td><td>Discover what owning a NetworkObject means and how clients can do so.</td><td><a href="../../guides/features/ownership/">ownership</a></td></tr><tr><td><i class="fa-location-plus">:location-plus:</i> <strong>Spawning</strong></td><td>Get to grips with spawning objects across the network.</td><td><a href="../../guides/features/networked-gameobjects-and-scripts/spawning/">spawning</a></td></tr><tr><td><i class="fa-house-signal">:house-signal:</i> <strong>Synchronized Variables</strong></td><td>Learn about FishNet's automatically synchronized variables and how to use them!</td><td><a href="../../guides/features/network-communication/synchronizing/">synchronizing</a></td></tr><tr><td><i class="fa-messages">:messages:</i> <strong>Remote Procedure Calls</strong></td><td>Find out about how to call methods remotely.</td><td><a href="../../guides/features/network-communication/remote-procedure-calls.md">remote-procedure-calls.md</a></td></tr><tr><td><i class="fa-binary-circle-check">:binary-circle-check:</i> <strong>Data Serialization</strong></td><td>Take a look at how FishNet can automatically serialize your data to send across the network, and how you can manually control it too!</td><td><a href="../../guides/features/data-serialization/">data-serialization</a></td></tr><tr><td><i class="fa-calendar-circle-exclamation">:calendar-circle-exclamation:</i> <strong>Network State Events</strong></td><td>Get information about the most commonly used network events, such as players connecting and the network starting or stopping.</td><td><a href="../../guides/features/network-callbacks.md">network-callbacks.md</a></td></tr><tr><td><i class="fa-house-tree">:house-tree:</i> <strong>Scene Management</strong></td><td>Learn about managing scenes while the network is connected.</td><td><a href="../../guides/features/scene-management/">scene-management</a></td></tr></tbody></table>
### More Feature Guides
<table data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><i class="fa-book-open">:book-open:</i> <strong>Feature Guides</strong></td><td>Explore guides for even more FishNet features, including Client-side Prediction, Area of Interest, Transports, Addressables, and Lag Compensation.</td><td><a href="../../guides/features/">features</a></td></tr></tbody></table>
## Moving on and getting help
After reading through those sections you are encouraged to jump in and start making projects using FishNet! There are some available demos on specific Fish-Networking features included in the FishNet asset itself, these can be a great tool for learning more about the specific features.
If you run into errors or issues you can check out the [troubleshooting](../../guides/troubleshooting/ "mention") guides to find solutions to common problems. You are also welcome to join the official Discord community and ask questions and learn from others there: [https://discord.gg/Ta9HgDh4Hj](https://discord.gg/Ta9HgDh4Hj)
_Now go create something amazing!_
@@ -0,0 +1,73 @@
---
description: >-
Learn how to connect clients to a remote FishNet server, allowing players on
different machines to join your game.
---
# Connecting to Remote Devices
Once you have a basic networked game set up and can connect as a host, the next crucial step for true multiplayer is enabling clients to connect to a server running on a different machine. This guide will walk you through the process of connecting to remote devices.
{% stepper %}
{% step %}
### **Identifying the server's IP address**
For a client to connect to a remote server, it needs to know the server's IP address.
* **Local Network (LAN):** If the server and client are on the same local network (e.g., two computers in the same house), you'll need the server machine's local IP address (e.g., `192.168.1.100`). You can usually find this through your operating system's network settings.
* **Public Network (Internet):** If clients are connecting over the internet, you'll need the server's public IP address. This is often the IP address of the router that the server machine is connected to. You might need to set up **port forwarding** on the server's router to allow incoming connections to the game server's port.
{% hint style="info" %}
For testing on a local network, you can often find your machine's IP address by opening a command prompt (Windows) and typing `ipconfig`, or a terminal (macOS/Linux) and typing `ifconfig` or `ip a`. Look for the IPv4 address associated with your active network adapter.
{% endhint %}
{% endstep %}
{% step %}
### **Configuring the client to connect**
To connect as a client to a remote server, you'll need to manually set the server's address.
1. **Locate the NetworkManager:** In your Unity scene, select the **NetworkManager** game object.
2. **Select or add the Transport Component:** In the Inspector, locate the [Transport](../../guides/high-level-overview/transports.md) component (e.g., [Tugboat](../../fishnet-building-blocks/transports/tugboat.md) if you're using the default). If there is none, add Tugboat now.
3. **Set the Address:** Find the `Client Address` field within the Transport component. Enter the IP address of your remote server here. For example, if your server's local IP is `192.168.1.100`, you would enter that.
4. **Set the Port (if different):** The default port for FishNet is `7777`. If your server is configured to use a different port, update the `Port` field accordingly.
{% hint style="info" %}
The `NetworkHudCanvas` is a convenient debugging tool. In a finished game, you would typically create your own UI for players to enter an IP address and connect.
{% endhint %}
<figure><img src="../../.gitbook/assets/tugboat-component.png" alt=""><figcaption></figcaption></figure>
{% endstep %}
{% step %}
### **Building and testing the connection**
To test connecting to a remote device, you'll either need to build your game or setup your Unity editor with your project on the other devices.
1. **Build the Client:** Go to `File > Build Settings...`. Ensure your scene is added to "Scenes In Build". Select your target platform (e.g., Windows, macOS, Android). Click `Build` and choose a location to save your executable.
2. **Start the Server:** Launch one instance of your game (either from the Unity Editor, a regular build or a dedicated server build) on the machine that will act as the server. If it's a dedicated server build then the FishNet server will start automatically, otherwise you will need to make sure you start it with the NetworkHudCanvas Server button, Autostart option, or through your own code.
3. **Launch the Client:** On a _separate_ machine, launch the client build you just created (or your project from the Unity editor on the separate machine).
4. **Connect:** If you disabled auto-start, click the "Start Client" button from the NetworkHudCanvas in the client build.
If configured correctly, the client should connect to the server, and you should see network-related messages in both the server and client consoles indicating a successful connection. You should also see players spawning as configured in the [preparing-your-player.md](preparing-your-player.md "mention") guide.
{% endstep %}
{% step %}
### **Troubleshooting common connection issues**
If your client fails to connect, consider these common issues:
* **Incorrect IP Address:** Double-check that the IP address entered in the Transport component is correct for the server machine. Also make sure you entered it in the "Client Address" field and not one of the "Server Bind Address" fields.
* **Firewall:** Ensure that the firewall on the server machine is not blocking incoming connections on the game's port (default 7777). You may need to create an inbound rule to allow traffic.
* **Port Forwarding (for Internet connections):** If connecting over the internet, the server's router needs to be configured to forward the game's port (e.g., 7777) to the server machine's local IP address. Without this, external clients cannot reach your server.
* **Server Not Running:** Verify that the server instance of your game is actually running and has successfully started its network services.
* **Different Game Versions:** Ensure both the server and client are running the exact same version of your game build. Mismatched versions can cause connection failures.
* **Network Congestion/Latency:** On public networks, high latency or packet loss can sometimes lead to connection timeouts.
By carefully checking these points, you can resolve most connection issues and get your remote clients connected.
{% hint style="info" %}
If you are trying to test over the Internet, it may be easier and safer to use an application to create a virtual LAN instead of portforwarding your router.
{% endhint %}
{% endstep %}
{% endstepper %}
@@ -0,0 +1,78 @@
---
description: >-
The first step to networking is connecting over the network. Learn about that
process here!
layout:
width: default
title:
visible: true
description:
visible: true
tableOfContents:
visible: true
outline:
visible: true
pagination:
visible: true
metadata:
visible: true
---
# Getting Connected
{% stepper %}
{% step %}
### **Creating the NetworkManager**
The **NetworkManager** is a central component of FishNet that manages the networking lifecycle, including establishing connections, hosting sessions, and handling client-server communication. For convenience, FishNet has a ready-made NetworkManager prefab that you can use. You can find the prefab located in the FishNet directory under `Demos/Prefabs/NetworkManager.prefab`.\
Drag and drop this NetworkManager prefab into your Unity scene.
<figure><img src="../../.gitbook/assets/adding-network-manager.png" alt="Adding the NetworkManager Prefab"><figcaption><p>Adding the NetworkManager prefab</p></figcaption></figure>
{% hint style="info" %}
If you imported FishNet from the git URL instead of the asset store or .unitypackage file, then the location of the FishNet directory will be `Packages/FishNet: Networking Evolved` instead of `Assets/FishNet`
{% endhint %}
{% endstep %}
{% step %}
### **Configuring FishNet to auto start**
The **NetworkHudCanvas** is a user interface provided by the example **NetworkManager** designed for quick testing of network connections. It displays buttons to start as a client and/or server. It also allows you to automatically start as a server, client, or host as soon as the **NetworkManager** loads in the game.
Expand the **NetworkManager's** fold-out icon to see its child game objects and select the **NetworkHudCanvas** game object. Find the **Network Hud Canvases** component on the object and set the **Auto Start Type** to **Host**.
This configuration will ensure that the application automatically starts as a host (a server and a client at the same time) when the scene is loaded.
<figure><img src="../../.gitbook/assets/setting-autostart-type.png" alt="Setting the Auto Start Type"><figcaption><p>Setting the Auto Start Type</p></figcaption></figure>
{% endstep %}
{% step %}
### **Testing the connection**
Once everything is set up, press the Play button in Unity's Editor to launch your scene.
The two Server and Client buttons in the top left of the game window should turn blue to indicate that the server and both active.
{% hint style="info" %}
If the buttons do not look like the example image then it just means your project is using Unity's new input system package; this is not a problem and you can simply check if the server and client are started by the button names being "Stop Server" and "Stop Client" respectively.
{% endhint %}
Additionally the console will log some network-related messages. Successful connection messages should appear, indicating that the host and client are running properly.
<figure><img src="../../.gitbook/assets/successful-connection-logs.png" alt="Successful connection logs"><figcaption><p>Successful connection logs</p></figcaption></figure>
{% endstep %}
{% endstepper %}
{% hint style="success" %}
You will see following warning: "Player prefab is empty and cannot be spawned for connection 0."
This is fine and will be dealt with in the next section.
{% endhint %}
By following these steps, you should now have a functional network setup in your Unity project using FishNet. This guide provides the foundation for more complex networking workflows, such as syncing objects and managing connections.
{% hint style="info" %}
Download the project files with these completed steps here, or explore the repository:
<a href="https://github.com/maxkratt/fish-networking-getting-started/releases/download/getting-connected-complete/getting-connected.unitypackage" class="button primary" data-icon="down-to-line">Source Files</a> <a href="https://github.com/maxkratt/fish-networking-getting-started/tree/getting-connected" class="button secondary" data-icon="github">Repository</a>
{% endhint %}
@@ -0,0 +1,89 @@
---
description: >-
Step-by-step instructions for installing Fish-Networking into your Unity
project.
---
# Installing Fish-Networking
{% hint style="info" %}
If after importing the package you get a message asking to automatically update the API, select either one of the **Yes** options.
{% endhint %}
### The Unity Asset Store
Installing via the Unity Asset Store is the easiest method and ensures you get a stable release:
1. Open your Unity project.
2. Go to **Window > Asset Store** or visit the [Unity Asset Store online](https://assetstore.unity.com/).
3. Search for **FishNet**.
4. Click **Add to My Assets** and then **Open in Unity**.
5. In Unity, go to **Package Manager** (`Window > Package Manager`).
6. Select **My Assets**, find **FishNet**, and click **Download** and then **Import**.
{% hint style="success" %}
Recommended for most users who want a stable release without version control setup.
{% endhint %}
***
### Unity package file
Use this method if you have a `.unitypackage` file, for example, downloaded from [GitHub Releases](https://github.com/FirstGearGames/FishNet/releases), from the [Fish-Networking website](https://www.fish-networking.com/), or shared by a team member.
1. Download the FishNet `.unitypackage` file.
2. In Unity, go to **Assets > Import Package > Custom Package…**
3. Select the downloaded `.unitypackage` file.
4. Click **Import** to bring all the FishNet files into your project.
{% hint style="info" %}
Best for offline installs or specific package versions not yet on the Asset Store.
{% endhint %}
***
### Git URL (via Unity Package Manager)
This method keeps FishNet up to date via a Git repository. Ideal if you want the latest updates or to contribute to development.
1. Open your Unity project.
2. Go to **Window > Package Manager**.
3. Click the **+** button in the top-left corner.
4. Select **Add package from Git URL…**
5. Enter the following URL:
```
https://github.com/FirstGearGames/FishNet.git?path=Assets/FishNet
```
6. Click **Add** to install.
{% 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 %}
***
### Installing FishNet Pro
Installing FishNet Pro can be done in the same way as the first steps listed above.
If you do need more details though, you can find complete steps for purchasing and installing FishNet Pro at these pages:
<table data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><strong>FishNet Pro, Projects, and Support</strong></td><td><mark style="color:blue;">Details about what FishNet Pro is, the available paid Projects, and Professional Support.</mark></td><td><a href="../../overview/readme/pro-projects-and-support.md">pro-projects-and-support.md</a></td></tr><tr><td><strong>Installing FishNet Pro</strong></td><td><mark style="color:blue;">Instructions for how to purchase and install FishNet Pro.</mark></td><td><a href="../../guides/updating-fishnet/upgrading-to-fishnet-pro.md">upgrading-to-fishnet-pro.md</a></td></tr></tbody></table>
***
### How to fix potential errors after importing?
After importing FishNet it is possible you will encounter errors, this can happen if you are using an unsupported version of Unity, such as one that is far too old or yet too new. [unity-compatibility.md](../../overview/readme/features/unity-compatibility.md "mention")
It can also happen if you have a conflicting asset, such as Netcode for GameObjects, or if your API compatibility level is incorrect.
For example, if you receive the following errors after importing FishNet, then make sure you don't have Netcode for GameObjects inside your project. The files it uses will clash with FishNet's files and you will need to remove it first and then import FishNet again.
```
Assets\FishNet\CodeGenerating\ILCore\ILCoreHelper.cs(18,13): error CS0246: The type or namespace name 'PostProcessorAssemblyResolver' could not be found (are you missing a using directive or an assembly reference?)
Assets\FishNet\CodeGenerating\ILCore\ILCoreHelper.cs(24,50): error CS0246: The type or namespace name 'PostProcessorReflectionImporterProvider' could not be found (are you missing a using directive or an assembly reference?)
```
If it's not one of these problems, or you are unsure what exactly it is, feel free to reach out to us in our official Discord server: [https://discord.com/invite/Ta9HgDh4Hj](https://discord.com/invite/Ta9HgDh4Hj)
@@ -0,0 +1,151 @@
---
description: Get your player objects moving around and synchronized!
---
# Moving Your Player Around
Now that your player object is properly spawned and prepared, it's time to get it moving. In this section, we'll implement a simple client-authoritative movement script and utilize FishNet's [NetworkTransform](../../fishnet-building-blocks/components/network-transform.md) component to effortlessly synchronize that movement across all connected devices.
#### Client-authoritative movement script
For many games, particularly those with real-time player input, client-authoritative movement is the most straightforward approach. This means the client directly controls its own player object and then informs the server (and other clients) about its position and rotation.
{% hint style="warning" %}
Client-authoritative movement makes it easier for the client to cheat with speed hacks, teleportation, and other movement related hacks, but it is a lot easier to implement and understand.
{% endhint %}
{% stepper %}
{% step %}
### **Creating a player movement script**
Select your **Player** prefab in the Project window. In the Inspector, click on **Add Component** and search for **New Script**. Name the script `PlayerMovement` and click **Create and Add**.
<figure><img src="../../.gitbook/assets/create-player-movement-script.png" alt="An image showing the Player prefab in the Unity Inspector and creating a new script component called &#x22;PlayerMovement&#x22;." width="377"><figcaption><p>Creating the PlayerMovement script</p></figcaption></figure>
{% endstep %}
{% step %}
### **Editing the script**
Double-click the `PlayerMovement` script to open it in your code editor. Replace the default code with the following:
{% tabs fullWidth="false" %}
{% tab title="Old Input System" %}
Unlike typical single-player scripts that use `MonoBehaviour`, this script inherits from `NetworkBehaviour`. This allows for direct access to the `IsOwner` field. Although you could still check ownership via the [NetworkObject](../../fishnet-building-blocks/components/network-object.md) component on a **MonoBehaviour**, [NetworkBehaviour](../../guides/features/networked-gameobjects-and-scripts/network-behaviour-guides.md) provides significant advantages. It enables [RPCs](../../guides/features/network-communication/remote-procedure-calls.md), [SyncVars](../../guides/features/network-communication/synchronizing/), and [specialized override methods](../../guides/features/networked-gameobjects-and-scripts/network-behaviour-guides.md#callbacks) (akin to Unity's `Start`, `Awake`, and `OnDestroy` but for networked objects).
{% code title="PlayerMovement.cs" lineNumbers="true" fullWidth="false" %}
```csharp
using FishNet.Object;
using UnityEngine;
// Inherit from NetworkBehaviour instead of MonoBehaviour
public class PlayerMovement : NetworkBehaviour
{
public float MoveSpeed = 5f;
private void Update()
{
// Only run this code on the object the local client owns.
// This prevents us from moving other players' objects.
if (!IsOwner)
return;
float horizontal = Input.GetAxis("Horizontal");
float vertical = Input.GetAxis("Vertical");
Vector3 moveDirection = new Vector3(horizontal, 0f, vertical);
if (moveDirection.magnitude > 1f)
moveDirection.Normalize();
transform.position += MoveSpeed * Time.deltaTime * moveDirection;
}
}
```
{% endcode %}
Since there will be multiple player game objects in the game, we need to determine which one is "our" local player's one and only move that with our input. The `IsOwner` guard clause handles this.
{% endtab %}
{% tab title="New Input System" %}
If using [Unity's New Input System](https://learn.unity.com/tutorial/6712ae08edbc2a05a71b5446?version=6.0), you should add the **Player Input** component to your **Player Prefab** and be sure to **disable** it
<figure><img src="../../.gitbook/assets/player-input-component-disabled.png" alt=""><figcaption><p>The PlayerInput component added to the Player Prefab and Disabled</p></figcaption></figure>
Unlike typical single-player scripts that use `MonoBehaviour`, this script inherits from `NetworkBehaviour`. This allows for direct access to the `IsOwner` field. Although you could still check ownership via the [NetworkObject](../../fishnet-building-blocks/components/network-object.md) component on a **MonoBehaviour**, [NetworkBehaviour](../../guides/features/networked-gameobjects-and-scripts/network-behaviour-guides.md) provides significant advantages. It enables [RPCs](../../guides/features/network-communication/remote-procedure-calls.md), [SyncVars](../../guides/features/network-communication/synchronizing/), and [specialized override methods](../../guides/features/networked-gameobjects-and-scripts/network-behaviour-guides.md#callbacks) (akin to Unity's `Start`, `Awake`, and `OnDestroy` but for networked objects).
{% code title="PlayerMovement.cs" lineNumbers="true" %}
```csharp
using FishNet.Object;
using UnityEngine;
using UnityEngine.InputSystem;
// Inherit from NetworkBehaviour instead of MonoBehaviour
public class PlayerMovement : NetworkBehaviour
{
public float MoveSpeed = 5f;
private Vector2 _currentMovementInput;
public override void OnStartClient()
{
if (IsOwner)
GetComponent<PlayerInput>().enabled = true;
}
public void OnMove(InputValue value)
{
_currentMovementInput = value.Get<Vector2>();
}
public void Update()
{
// Only run this code on the object the local client owns.
// This prevents us from moving other players' objects.
if (!IsOwner)
return;
Vector3 moveDirection = new Vector3(_currentMovementInput.x, 0f, _currentMovementInput.y);
if (moveDirection.magnitude > 1f)
moveDirection.Normalize();
transform.position += MoveSpeed * Time.deltaTime * moveDirection;
}
}
```
{% endcode %}
Since there will be multiple player game objects in the game, we need to determine which one is "our" local player's one and only move that with our input. The `IsOwner` guard clause in `Update` handles this. We are also using the NetworkBehaviour `OnStartClient` callback to enable the **PlayerInput** component when `IsOwner` is true. This method runs when the network object is initialized on the network and only on the client side. It's similar to Unity's `Start` callback and is used here to only send the input to our local player object.
You might wonder why we can’t just use `Awake` or `Start` here, but the reason is that FishNet doesn’t always have enough time to link the object on the network and assign its owner before those methods execute.
{% endtab %}
{% endtabs %}
{% endstep %}
{% step %}
### **Synchronizing the movement**
Now we have code that will allow clients to move only their respective player object, but nothing is yet synchronized over the network This means that we won't ever see other players moving, and they won't see us moving.
To fix this, we will use Fish-Networking's built-in [NetworkTransform](../../fishnet-building-blocks/components/network-transform.md) component. This component can be used to synchronize a network object's scale, rotation, position, and parent hierarchy.
Open your **Player Prefab** and add the **NetworkTransform** component to it.
<figure><img src="../../.gitbook/assets/network-transform-component.png" alt="A picture of the NetworkTransform component with its default settings."><figcaption><p>The default settings of the <strong>NetworkTransform.</strong></p></figcaption></figure>
This component has quite a few settings, but the most important ones for us right now are the **Client Authoritative** and the **Synchronize Position** fields. Both of these should be enabled, which will mean positional changes made by the owner client will automatically be synchronized to the server and then to all other clients.
{% endstep %}
{% step %}
### **Testing the player movement**
Save your scene and press the **Play** button in Unity's Editor. Your player capsule should now be visible. Use the **W, A, S, D** keys or arrow keys to move your player around the scene.
To test multi-player movement, you can build and run the game (or use [multiple editor windows](../simple/testing-with-multiple-editors.md)), which will automatically connect as a client to the editor. You should then be able to control your player in the editor, and the second instance will control its own player, seeing both players move independently.
<figure><img src="../../.gitbook/assets/player-movement-gif.gif" alt=""><figcaption></figcaption></figure>
{% 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-getting-started/releases/download/moving-your-player-around-complete/moving-your-player-around.unitypackage" class="button primary" data-icon="down-to-line">Source Files</a> <a href="https://github.com/maxkratt/fish-networking-getting-started/tree/moving-your-player-around" class="button secondary" data-icon="github">Repository</a>
{% endhint %}
@@ -0,0 +1,78 @@
---
description: Spawn an object over the network to represent each client's player.
---
# Preparing Your Player
For many games you will want to have a player object for each client. This guide will walk you through the process step by step.
{% hint style="success" %}
Before You Begin\
Ensure that you're **not in Play Mode** in the Unity Editor. Making changes during Play Mode will not persist after exiting.
{% endhint %}
{% stepper %}
{% step %}
### Create a player game object
In the Unity Scene Hierarchy, create a new 3D object of type **Capsule**.\
Select the capsule in the Hierarchy and rename it to **Player**.\
In the **Inspector**, click on **Add Component** and search for **NetworkObject**.\
Add the **NetworkObject** component to the capsule.
{% hint style="info" %}
The **NetworkObject** component is required for linking a game object over the network. It enables the object to synchronize its state across clients and the server.
{% endhint %}
<figure><img src="../../.gitbook/assets/add-networkobject-to-player.png" alt="NetworkObject component added to capsule"><figcaption><p>NetworkObject component added to capsule</p></figcaption></figure>
{% endstep %}
{% step %}
### Setup the player object for spawning
Drag the player game object into the project window to turn it into a prefab, then delete the game object from the scene.
<figure><img src="../../.gitbook/assets/create-player-prefab.png" alt="Turn the Player Object into a Prefab"><figcaption><p>Turn the player object into a prefab</p></figcaption></figure>
Select the NetworkManager in the scene and assign your new Player prefab into the **Player Prefab** field on the **PlayerSpawner** component.
<figure><img src="../../.gitbook/assets/assign-playerprefab-in-spawner.png" alt="Assign Player Prefab in Player Spawner"><figcaption><p>Assign player prefab in PlayerSpawner</p></figcaption></figure>
{% endstep %}
{% step %}
### Add spawn points
Create two empty Game Objects in the scene to act as spawn points. Position them where you'd like players to spawn on the map.\
Add these Game Objects as **Transform** references to the **PlayerSpawner** component in the **Spawns** list field.
{% hint style="info" %}
The **PlayerSpawner** uses these transforms to determine spawn locations, choosing them in order from top to bottom for each subsequent player and beginning again after reaching then final spawn point. If no spawn points are specified, the spawner defaults to using the prefab's transform properties for placement.
{% endhint %}
<figure><img src="../../.gitbook/assets/add-spawn-points.png" alt="Create and Assign Spawn Points"><figcaption><p>Create and assign spawn points</p></figcaption></figure>
{% endstep %}
{% step %}
### Test the player spawning
Save the scene and press the **Play** button in the Unity Editor.\
As soon as the server and client starts you should see the a capsule spawning as the player object at the location of the first spawn point.\
To see another player spawn you can build and run the game and which will then automatically connect as a client to the editor. You should then see a second player capsule spawn for the second player at the second spawn point.
<figure><img src="../../.gitbook/assets/build-connected-to-editor.png" alt="Built Game Connected to Editor"><figcaption><p>Built game connected to editor</p></figcaption></figure>
Alternatively, you can launch a second Unity Editor instance by using [Unity's Multiplayer Play Mode](https://docs-multiplayer.unity3d.com/mppm/current/about/) package or a third party package such as [ParrelSync](https://github.com/VeriorPies/ParrelSync?tab=readme-ov-file#parrelsync). Find out more about these options and how to use them with this tutorial: [testing-with-multiple-editors.md](../simple/testing-with-multiple-editors.md "mention").
<figure><img src="../../.gitbook/assets/mppm-connected-to-editor.png" alt="Multiplayer Play Mode Connected to Editor"><figcaption><p>Multiplayer Play Mode connected to editor</p></figcaption></figure>
{% hint style="info" %}
If a second game window shows _"Server failed to start"_ it just means multiple instances tried to use the same port. Only the first becomes the server; the rest run as clients, so the error is safe to ignore during local testing. If players don’t spawn correctly, check that **Error Pause** is disabled in the Unity console.
{% endhint %}
{% 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-getting-started/releases/download/preparing-your-player-complete/preparing-your-player.unitypackage" class="button primary" data-icon="down-to-line">Source Files</a> <a href="https://github.com/maxkratt/fish-networking-getting-started/tree/preparing-your-player" class="button secondary" data-icon="github">Repository</a>
{% endhint %}
@@ -0,0 +1,15 @@
---
description: How to setup a player camera for your multiplayer setup.
---
# Setting Up a Camera
When it comes to multiplayer, a player's camera setup can seem quite tricky to understand. In reality it's often quite simple, only one[^1] camera should exist for your local player.&#x20;
The most reliable method is to create a camera prefab and instantiate it locally when the player's character spawns. Alternatively, you can place a camera in the scene ahead of time and have the player’s object "claim" it upon spawning.
Things get a bit more nuanced when working with [Cinemachine](https://unity.com/features/cinemachine). We'll briefly explore how to manage this setup effectively.
<table data-view="cards"><thead><tr><th></th><th data-hidden data-card-target data-type="content-ref"></th><th data-hidden data-card-cover data-type="files"></th></tr></thead><tbody><tr><td>Instantiating a Local Camera</td><td><a href="instantiating-a-local-camera.md">instantiating-a-local-camera.md</a></td><td><a href="../../../.gitbook/assets/instantiated-camera-card.png">instantiated-camera-card.png</a></td></tr><tr><td>Using the Scene Camera</td><td><a href="using-the-scene-camera.md">using-the-scene-camera.md</a></td><td><a href="../../../.gitbook/assets/scene-camera-card.png">scene-camera-card.png</a></td></tr><tr><td>Basic Setup with Cinemachine</td><td><a href="basic-setup-with-cinemachine.md">basic-setup-with-cinemachine.md</a></td><td><a href="../../../.gitbook/assets/cinemachine-camera-card.png">cinemachine-camera-card.png</a></td></tr></tbody></table>
[^1]: This is excluding cameras used for minimaps, rear view mirrors, etc.
@@ -0,0 +1,92 @@
---
description: Managing a Cinemachine Camera in multiplayer.
---
# Basic Setup with Cinemachine
This guide will show you one way you can set-up your player camera for multiplayer when you are using the Cinemachine package.
{% stepper %}
{% step %}
### **Installing Cinemachine**
The first step — if you haven't done so already — is to install [Cinemachine](https://unity.com/features/cinemachine) through the [Unity Package Manager](https://learn.unity.com/tutorial/the-package-manager).
<figure><img src="../../../.gitbook/assets/installing-cinemachine.png" alt=""><figcaption><p>Cinemachine installing</p></figcaption></figure>
{% endstep %}
{% step %}
### **Adding the Cinemachine Brain**
Now that we have **Cinemachine** installed we can set it up. Add the **Cinemachine Brain** component to the **Main Camera** game object in the scene.
<figure><img src="../../../.gitbook/assets/added-cinemachine-brain.png" alt=""><figcaption><p><strong>Cinemachine Brain</strong> added to <strong>Main Camera</strong></p></figcaption></figure>
{% endstep %}
{% step %}
### **Giving the player a camera holder**
Now create an empty Game Object on your **Player Prefab** and position it where you'd like. This will be our container for the **Cinemachine** **Camera**.
<figure><img src="../../../.gitbook/assets/camera-holder-setup.png" alt=""><figcaption><p>Camera Holder created on the Player Prefab</p></figcaption></figure>
{% endstep %}
{% step %}
### **Adding the Cinemachine Camera**
Now simply add the **Cinemachine Camera** component to the newly created **CameraHolder** game object.
<figure><img src="../../../.gitbook/assets/added-cinemachine-camera.png" alt=""><figcaption><p>The <strong>Cinemachine Camera</strong> added to the <strong>Camera Holder</strong></p></figcaption></figure>
{% endstep %}
{% step %}
### **Writing a PlayerCamera script**
Let's now add the following `PlayerCamera` script to the **Player Prefab** that we will use to take control of our **Camera** once our player spawns in.
{% code title="PlayerCamera.cs" lineNumbers="true" %}
```csharp
using FishNet.Object;
using Unity.Cinemachine;
using UnityEngine;
// This script will be a NetworkBehaviour so that we can use the OnStartClient override.
public class PlayerCamera : NetworkBehaviour
{
[SerializeField] private CinemachineCamera _cinemachineCamera;
// This method is called on the client after the object is spawned in.
public override void OnStartClient()
{
// Simply enable our local cinemachine camera on the object if we are the owner.
_cinemachineCamera.enabled = IsOwner;
}
}
```
{% endcode %}
This script uses the [OnStartClient](../../../guides/features/networked-gameobjects-and-scripts/network-behaviour-guides.md#onstartclient) override method from [NetworkBehaviour](../../../guides/features/networked-gameobjects-and-scripts/network-behaviour-guides.md) to enable or disable the **Cinemachine Camera** component on the player objects depending on if they are our local player or not.
{% endstep %}
{% step %}
### **Assign your references to the script**
Now select the **Player Camera** component in your **Player Prefab** and add drag the **Camera Holder** game object into the _Cinemachine Camera_ field to assign it the component.
<figure><img src="../../../.gitbook/assets/assigned-cinemachine-camera-to-player.png" alt=""><figcaption><p>The <strong>Player</strong> prefab with the <strong>Cinemachine Camera</strong> field filled in</p></figcaption></figure>
{% endstep %}
{% step %}
### **Test the camera in-game**
With all that set you should be able to run the game and see how the camera from the scene is controlled by only your local player.
<figure><img src="../../../.gitbook/assets/player-camera-demonstration.gif" alt=""><figcaption><p>Demonstration of the local camera</p></figcaption></figure>
{% 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-getting-started/releases/download/basic-setup-with-cinemachine/basic-setup-with-cinemachine.unitypackage" class="button primary" data-icon="down-to-line">Source Files</a> <a href="https://github.com/maxkratt/fish-networking-getting-started/tree/cinemachine-camera" class="button secondary" data-icon="github">Repository</a>
{% endhint %}
@@ -0,0 +1,77 @@
---
description: Using a Camera Prefab to instantiate your local player's camera.
---
# Instantiating a Local Camera
With this method of camera management we will instantiate a camera from a prefab we create and assign it to our local player object when it spawns in.
{% stepper %}
{% step %}
### **Creating a camera prefab**
Let's start by dragging the **Main Camera** in the scene into our **Project Window** to create a prefab out of it. You can now delete the **Main Camera** from the **Scene Hierarchy**.
{% endstep %}
{% step %}
### **Giving the player a camera holder**
Now create an empty Game Object on your **Player Prefab** and position it where you'd like. This will be where we instantiate and position the **Camera** object.
<figure><img src="../../../.gitbook/assets/camera-holder-setup.png" alt=""><figcaption><p>Camera Holder created on the player prefab</p></figcaption></figure>
{% endstep %}
{% step %}
### **Writing a PlayerCamera script**
Let's now add the following `PlayerCamera` script to the **Player Prefab** that we will use to instantiate our **Camera Prefab** once our player spawns in.
{% code title="PlayerCamera.cs" lineNumbers="true" %}
```csharp
using FishNet.Object;
using UnityEngine;
// This script will be a NetworkBehaviour so that we can use the
// OnStartClient override.
public class PlayerCamera : NetworkBehaviour
{
[SerializeField] private Camera _cameraPrefab;
[SerializeField] private Transform _cameraHolder;
// This method will run on the client once this object is spawned.
public override void OnStartClient()
{
// Since this will run on all clients that this object spawns for
// we need to only instantiate the camera for the object we own.
if (IsOwner)
Instantiate(_cameraPrefab, _cameraHolder.position, _cameraHolder.rotation, _cameraHolder);
}
}
```
{% endcode %}
This script uses the [OnStartClient](../../../guides/features/networked-gameobjects-and-scripts/network-behaviour-guides.md#onstartclient) override method from [NetworkBehaviour](../../../guides/features/networked-gameobjects-and-scripts/network-behaviour-guides.md) to instantiate the camera prefab for our local player.
{% endstep %}
{% step %}
### **Assign your references to the script**
Now select the **Player Camera** component in your **Player Prefab** and add the **Camera Prefab** we made to the _Camera Prefab_ field. Also select the **CameraHolder** game object in the _Camera Holder_ field.
<figure><img src="../../../.gitbook/assets/assigned-player-camera-to-player.png" alt=""><figcaption><p>The player prefab with the Main Camera filled in</p></figcaption></figure>
{% endstep %}
{% step %}
### **Test the camera in-game**
With all that set you should be able to run the game and see how the camera is created for only your local player.
<figure><img src="../../../.gitbook/assets/player-camera-demonstration.gif" alt=""><figcaption><p>Demonstration of the local camera</p></figcaption></figure>
{% 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-getting-started/releases/download/instantiating-a-local-camera/instantiating-a-local-camera.unitypackage" class="button primary" data-icon="down-to-line">Source Files</a> <a href="https://github.com/maxkratt/fish-networking-getting-started/tree/local-camera" class="button secondary" data-icon="github">Repository</a>
{% endhint %}
@@ -0,0 +1,87 @@
---
description: Using the camera in the scene for your local player.
---
# Using the Scene Camera
With this method our local player will take control of the Main Camera it finds in the scene.
{% stepper %}
{% step %}
### **The main camera tag**
Before we start we need to ensure our camera object in the scene is tagged as the "Main Camera"
<figure><img src="../../../.gitbook/assets/main-camera-in-scene.png" alt=""><figcaption><p>Main Camera in scene with the tag</p></figcaption></figure>
{% endstep %}
{% step %}
### **Giving the player a camera holder**
Now create an empty Game Object on your **Player Prefab** and position it where you'd like. This will be where we parent and position the **Camera** object.
<figure><img src="../../../.gitbook/assets/camera-holder-setup.png" alt=""><figcaption><p>Camera Holder created on the Player Prefab</p></figcaption></figure>
{% endstep %}
{% step %}
### **Writing a PlayerCamera script**
Let's now add the following `PlayerCamera` script to the **Player Prefab** that we will use to take control of our **Camera** once our player spawns in.
{% code title="PlayerCamera.cs" lineNumbers="true" %}
```csharp
using FishNet.Connection;
using FishNet.Object;
using UnityEngine;
// This script will be a NetworkBehaviour so that we can use the
// OnOwnershipClient override.
public class PlayerCamera : NetworkBehaviour
{
[SerializeField] private Transform _cameraHolder;
// This method is called on the client after gaining or losing ownership of the object.
// We could have used OnStartClient instead, as we did in the previous example, but using OnOwnershipClient
// means this will work for a player object we don't initially own but are given ownership to later.
public override void OnOwnershipClient(NetworkConnection prevOwner)
{
if (Camera.main == null)
return;
// If we are the new owner of this object, then take control of the camera by parenting it
// and moving it to our camera holder.
if (IsOwner)
{
Camera.main.transform.SetPositionAndRotation(_cameraHolder.position, _cameraHolder.rotation);
Camera.main.transform.SetParent(_cameraHolder);
}
}
}
```
{% endcode %}
This script uses the [OnOwnershipClient](../../../guides/features/networked-gameobjects-and-scripts/network-behaviour-guides.md#onownershipclient) override method from [NetworkBehaviour](../../../guides/features/networked-gameobjects-and-scripts/network-behaviour-guides.md) to instantiate the camera prefab for our local player as soon as he gets ownership of the object. We could just have easily used the [OnStartClient](../../../guides/features/networked-gameobjects-and-scripts/network-behaviour-guides.md#onstartclient) override method like the [instantiating-a-local-camera.md](instantiating-a-local-camera.md "mention") example shows.
{% endstep %}
{% step %}
### **Assign your references to the script**
Now select the **Player Camera** component in your **Player Prefab** and add the **Camera Prefab** we made to the _Camera Prefab_ field. Also select the **CameraHolder** game object in the _Camera Holder_ field.
<figure><img src="../../../.gitbook/assets/assigned-camera-holder-to-player.png" alt=""><figcaption><p>The Player prefab with the <strong>Camera Holder</strong> filled in</p></figcaption></figure>
{% endstep %}
{% step %}
### **Test the camera in-game**
With all that set you should be able to run the game and see how the camera from the scene is controlled by only your local player.
<figure><img src="../../../.gitbook/assets/player-camera-demonstration.gif" alt=""><figcaption><p>Demonstration of the local camera</p></figcaption></figure>
{% 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-getting-started/releases/download/using-the-scene-camera/using-the-scene-camera.unitypackage" class="button primary" data-icon="down-to-line">Source Files</a> <a href="https://github.com/maxkratt/fish-networking-getting-started/tree/scene-camera" class="button secondary" data-icon="github">Repository</a>
{% endhint %}
@@ -0,0 +1,217 @@
---
description: Learn about spawning and despawning by having your players throw cubes around!
---
# Spawning and Despawning Items
Now that we have players moving around, let's learn how to _spawn_ and _despawn_ [NetworkObjects](../../guides/features/networked-gameobjects-and-scripts/networkobjects/).
While you can [Instantiate](https://docs.unity3d.com/ScriptReference/Object.Instantiate.html) and [Destroy](https://docs.unity3d.com/ScriptReference/Object.Destroy.html) regular Game Objects, Network Objects need to be Spawned and Despawned, this tells FishNet to synchronize the action over the network. FishNet stores all network object prefabs in a collection called the [Spawnable Prefabs](../../fishnet-building-blocks/scriptableobjects/spawnableprefabs/). You can see this and customize it on the [NetworkManager](../../fishnet-building-blocks/components/managers/network-manager.md).
{% hint style="success" %}
We have added a large green cube to act as the floor which you can do too, but this isn't a vital step.
{% endhint %}
{% stepper %}
{% step %}
### **Creating the item network object prefab**
Let's create a **Cube** in the scene hierarchy, and add a **NetworkObject** component to it. We've also decreased it's scale by half so that it doesn't look too large.\
Then drag it into the project window to turn it into a prefab; you can destroy the original game object in the scene hierarchy now.
<figure><img src="../../.gitbook/assets/added-network-object-to-cube.png" alt="An image of a Cube Game Object set to half scale and with a NetworkObject component added."><figcaption><p>The Cube with size adjusted and NetworkObject added</p></figcaption></figure>
{% endstep %}
{% step %}
### **Creating a script to spawn the cube**
Now create a script called `PlayerCubeCreator` and add it to your **Player Prefab**. This script is going to be responsible for spawning our new cube item across the network.
{% tabs %}
{% tab title="Old Input System" %}
{% code title="PlayerCubeCreator.cs" lineNumbers="true" %}
```csharp
using FishNet.Object;
using UnityEngine;
public class PlayerCubeCreator : NetworkBehaviour
{
public NetworkObject CubePrefab;
private void Update()
{
// Only the local player object should perform these actions.
if (!IsOwner)
return;
if (Input.GetButtonDown("Fire1"))
SpawnCube();
}
// We are using a ServerRpc here because the Server needs to do all network object spawning.
[ServerRpc]
private void SpawnCube()
{
NetworkObject obj = Instantiate(CubePrefab, transform.position, Quaternion.identity);
Spawn(obj); // NetworkBehaviour shortcut for ServerManager.Spawn(obj);
}
}
```
{% endcode %}
{% endtab %}
{% tab title="New Input System" %}
{% code title="PlayerCubeCreator.cs" lineNumbers="true" %}
```csharp
using FishNet.Object;
using UnityEngine;
using UnityEngine.InputSystem;
public class PlayerCubeCreator : NetworkBehaviour
{
public NetworkObject CubePrefab;
public override void OnStartClient()
{
if (IsOwner)
GetComponent<PlayerInput>().enabled = true;
}
public void OnAttack(InputValue value)
{
if (value.isPressed)
SpawnCube();
}
// We are using a ServerRpc here because the Server needs to do all network object spawning.
[ServerRpc]
private void SpawnCube()
{
NetworkObject obj = Instantiate(CubePrefab, transform.position, Quaternion.identity);
Spawn(obj); // NetworkBehaviour shortcut for ServerManager.Spawn(obj);
}
}
```
{% endcode %}
{% endtab %}
{% endtabs %}
{% hint style="info" %}
This may be a great time to read [this page](../../guides/features/network-communication/remote-procedure-calls.md) about RPCs[^1] such as the [ServerRpc](../../guides/features/network-communication/remote-procedure-calls.md#serverrpc) we use here.
{% endhint %}
Because this script contains a ServerRpc, it will also need to be a **NetworkBehaviour**. The `Update` method listens for the user to press the <kbd>Fire1</kbd>/<kbd>Attack</kbd> button (typically the left mouse button) and then calls `SpawnCube`.
**Understanding `SpawnCube`:**
* The **ServerRpc** attribute: This is crucial. It means this method is intended to be called by a client, but it will _execute only on the server_. The client sends a message to the server, requesting this method to be run.
* `Spawn(obj);`: This is the most critical line for networked objects. Calling `Spawn(obj)` on the `NetworkObject` tells FishNet's `ServerManager` to "spawn" this object over the network. This means the server will now instruct all currently connected clients (and any clients that connect later) to instantiate their own replica of this object.
{% hint style="info" %}
You can read our more in-depth page about **Spawning** [here](../../guides/features/networked-gameobjects-and-scripts/spawning/).
{% endhint %}
{% endstep %}
{% step %}
### **Assigning the prefab to your script**
Now select your **Player Prefab** and assign your **Cube Prefab** to the "Cube Prefab" field in your newly created **Player Cube Creator** script.
<figure><img src="../../.gitbook/assets/player-cube-creator-set.png" alt=""><figcaption><p>PlayerCubeCreator with cube prefab assigned</p></figcaption></figure>
{% hint style="info" %}
You may have noticed that an **Empty Network Behaviour** component was automatically added to your game objects with a **NetworkObject** component. This happens because each FishNet requires every NetworkObject to also have a NetworkBehaviour on it, and it will automatically add an empty one if it doesn't detect any. You can safely remove this empty component as you have added multiple other **NetworkBehaviour** components, but it's not a problem either way.
{% endhint %}
{% endstep %}
{% step %}
### **Test if the spawning works**
Now launch a couple instances of your game and see if you can run around and spawn cube items by pressing the <kbd>Fire1</kbd>/<kbd>Attack</kbd> button. The cubes should be visible on all devices connected to each other.
<figure><img src="../../.gitbook/assets/test-cube-spawning.gif" alt=""><figcaption><p>Player spawning cubes</p></figcaption></figure>
{% endstep %}
{% step %}
### **Add some physics**
Static cubes are boring! Let's add a **Rigidbody** component to the **Cube Prefab** to enable basic physics interactions with them.
<figure><img src="../../.gitbook/assets/rigidbody-added-to-cube.png" alt=""><figcaption><p>Rigidbody added to the cube</p></figcaption></figure>
Now the cubes should roll around and collide with the ground. Let's add a **Rigidbody** to the **Player** now and set it to **Is Kinematic**.
<figure><img src="../../.gitbook/assets/player-with-rigidbody.png" alt=""><figcaption><p>Player with kinematic rigidbody added</p></figcaption></figure>
{% endstep %}
{% step %}
### **Test if the physics works**
If you launch the game now, the cubes should have basics physics working.
<figure><img src="../../.gitbook/assets/players-spawning-physics-cubes.gif" alt=""><figcaption><p>Players spawning physics cubes</p></figcaption></figure>
You may notice the cubes' positions don't always sync up after they are moved around, that's because we haven't done any positional syncing besides the initial position syncing that FishNet automatically did when we called `Spawn`.
{% hint style="info" %}
Networked Rigidbodies are a complex topic, and better suited to a different guide, but if you want to test a really basic setup up, you can add a [NetworkTransform](../../fishnet-building-blocks/components/network-transform.md) to your **Cube Prefab** and set its **Component Configuration** to **Rigidbody.**
{% endhint %}
{% endstep %}
{% step %}
### **Script to despawn cubes on a timer**
Now that we can `Spawn` cubes successfully, let's `Despawn` them after a few seconds so that we don't end up with too many objects at once.
Create a new script called `DespawnAfterTime`:
{% code title="DespawnAfterTime.cs" lineNumbers="true" %}
```csharp
using FishNet.Object;
using System.Collections;
using UnityEngine;
public class DespawnAfterTime : NetworkBehaviour
{
public float SecondsBeforeDespawn = 3f;
public override void OnStartServer()
{
StartCoroutine(DespawnAfterSeconds());
}
private IEnumerator DespawnAfterSeconds()
{
yield return new WaitForSeconds(SecondsBeforeDespawn);
Despawn(); // NetworkBehaviour shortcut for ServerManager.Despawn(gameObject);
}
}
```
{% endcode %}
This script uses the [OnStartServer](../../guides/features/networked-gameobjects-and-scripts/network-behaviour-guides.md#onstartserver) NetworkBehaviour override method to start a Coroutine which will `Despawn` the object. OnStartServer will run on the server when the object is initialized with the network.
`Despawn` needs to be called on the server, and it will destroy the game object locally as well as on all clients automatically for you.
{% endstep %}
{% step %}
### **Assigning the script and testing**
Open your **Cube Prefab** now and add the **Despawn After Time** script you just made to the object.
Launch the game again and observe the objects being Despawned successfully on the server and all clients after a few seconds.
<figure><img src="../../.gitbook/assets/cubes-despawning.gif" alt=""><figcaption><p>Cubes despawning after 3 seconds</p></figcaption></figure>
{% 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-getting-started/releases/download/spawning-and-despawning-complete/spawning-and-despawning.unitypackage" class="button primary" data-icon="down-to-line">Source Files</a> <a href="https://github.com/maxkratt/fish-networking-getting-started/tree/spawning-and-despawning-items" class="button secondary" data-icon="github">Repository</a>
{% endhint %}
[^1]: Remote Procedure Calls
@@ -0,0 +1,160 @@
---
description: Synchronizing color with synchronized variables!
---
# Using SyncVars to Sync Colors
We've got quite a few things synchronized now, but how might you go about synchronizing a specific variable in one of your scripts? SyncVars[^1] are one answer! A [SyncVar](../../guides/features/network-communication/synchronizing/syncvar.md) is a FishNet generic type that can be used within [NetworkBehaviours](../../guides/features/networked-gameobjects-and-scripts/network-behaviour-guides.md) that automatically synchronize their value from the server to all clients.
Let's liven up the colors in our game and synchronize them with SyncVars.
{% stepper %}
{% step %}
### **Creating a script to sync color**
Create a new script called `SyncMaterialColor`. This will be used to synchronize the **Material** color of our **MeshRenderers**.
{% code title="SyncMaterialColor.cs" lineNumbers="true" %}
```csharp
using FishNet.Object;
using FishNet.Object.Synchronizing;
using UnityEngine;
[RequireComponent(typeof(MeshRenderer))]
public class SyncMaterialColor : NetworkBehaviour
{
public readonly SyncVar<Color> Color = new SyncVar<Color>();
private void Awake()
{
Color.OnChange += OnColorChanged;
}
private void OnColorChanged(Color previous, Color next, bool asServer)
{
GetComponent<MeshRenderer>().material.color = Color.Value;
}
}
```
{% endcode %}
This simple script has a `Color` variable of the type `SyncVar<Color>`. It needs be set to `readonly`, but don't worry, we can still get and set its `Value`.
{% hint style="info" %}
Setting your **SyncVar** as `readonly` will cause it to be hidden from the Unity Inspector. You can read about a work-around for this [here](../../guides/features/network-communication/synchronizing/customizing-behavior.md).
{% endhint %}
In `Awake` we subscribe to the SyncVar's `OnChange` event. This event is very useful as it will be invoked as soon as the SyncVar changes, even on a client when the server changes it and it's synced.
`OnColorChanged` is our method that we subscribed to the OnChange event. This method simply gets the MeshRenderer and sets its material color to the SyncVar's Color Value, thus updating the visuals to match on all devices.
{% hint style="success" %}
You may want to read more about **SyncVars** and the other available **SyncTypes** that FishNet has, such as **SyncLists.** You can find those pages [here](../../guides/features/network-communication/synchronizing/).
{% endhint %}
{% endstep %}
{% step %}
### **Add the script component**
Now add your newly created script to your **Cube Prefab**. The script won't currently do anything unless we change the `Color` **SyncVar** in it, so let's do that next.
<figure><img src="../../.gitbook/assets/cube-with-sync-color.png" alt=""><figcaption><p>The Sync Material Color component added</p></figcaption></figure>
{% endstep %}
{% step %}
### **Give the cubes random colors**
Let's give the cubes some color now as soon as we instantiate them.
Reopen the `PlayerCubeCreator.cs` script and make the following addition after the call to `Instantiate` and before the call to `Spawn`:
```csharp
obj.GetComponent<SyncMaterialColor>().Color.Value = Random.ColorHSV();
```
Here is the complete script with the change made:
{% tabs %}
{% tab title="Old Input System" %}
<pre class="language-csharp" data-title="PlayerCubeCreator.cs" data-line-numbers><code class="lang-csharp">using FishNet.Object;
using UnityEngine;
public class PlayerCubeCreator : NetworkBehaviour
{
public NetworkObject CubePrefab;
void Update()
{
// Only the local player object should perform these actions.
if (!IsOwner)
return;
if (Input.GetButtonDown("Fire1"))
SpawnCube();
}
// We are using a ServerRpc here because the Server needs to do all network object spawning.
[ServerRpc]
private void SpawnCube()
{
NetworkObject obj = Instantiate(CubePrefab, transform.position, Quaternion.identity);
<strong> obj.GetComponent&#x3C;SyncMaterialColor>().Color.Value = Random.ColorHSV();
</strong> Spawn(obj); // NetworkBehaviour shortcut for ServerManager.Spawn(obj);
}
}
</code></pre>
{% endtab %}
{% tab title="New Input System" %}
<pre class="language-csharp" data-title="PlayerCubeCreator.cs" data-line-numbers><code class="lang-csharp">using FishNet.Object;
using UnityEngine;
using UnityEngine.InputSystem;
public class PlayerCubeCreator : NetworkBehaviour
{
public NetworkObject CubePrefab;
public override void OnStartClient()
{
if (IsOwner)
GetComponent&#x3C;PlayerInput>().enabled = true;
}
public void OnAttack(InputValue value)
{
if (value.isPressed)
SpawnCube();
}
// We are using a ServerRpc here because the Server needs to do all network object spawning.
[ServerRpc]
private void SpawnCube()
{
NetworkObject obj = Instantiate(CubePrefab, transform.position, Quaternion.identity);
<strong> obj.GetComponent&#x3C;SyncMaterialColor>().Color.Value = Random.ColorHSV();
</strong> Spawn(obj); // NetworkBehaviour shortcut for ServerManager.Spawn(obj);
}
}
</code></pre>
{% endtab %}
{% endtabs %}
This line of code gets the **SyncMaterialColor** component and sets the SyncVar Value to a random color as soon as it's instantiated. Once Spawn is called on the next line, FishNet will spawn this object for all clients with the color SyncVar state synced.
{% endstep %}
{% step %}
### **Test the synchronized cubes**
Now all you need to do is run your game again and see if the cubes spawn with a random color and if that color is synchronized across the network.
<figure><img src="../../.gitbook/assets/synced-cube-colors.gif" alt=""><figcaption><p>Cube color synchronized!</p></figcaption></figure>
{% 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-getting-started/releases/download/using-syncvars-to-sync-colors/using-syncvars-to-sync-colors.unitypackage" class="button primary" data-icon="down-to-line">Source Files</a> <a href="https://github.com/maxkratt/fish-networking-getting-started/tree/syncing-colors" class="button secondary" data-icon="github">Repository</a>
{% endhint %}
[^1]: synchronized variables
@@ -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.&#x20;
* 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.&#x20;
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.
@@ -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.&#x20;
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>
@@ -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 %}
@@ -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.**&#x20;
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 %}
@@ -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 %}
@@ -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.&#x20;
{% 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 %}
@@ -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&#x20;
<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**:&#x20;
```
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>
@@ -0,0 +1,8 @@
---
description: >-
Tutorials on how to convert Unity samples to support multiplayer using
FishNet.
---
# Unity Samples
@@ -0,0 +1,15 @@
---
description: >-
These tutorials will show you how to convert the third person starter assets
to support multiplayer.
---
# Starter Assets - Third Person
<figure><img src="https://assetstorev1-prd-cdn.unity3d.com/key-image/421988f6-a827-428b-86d4-9b815f15cdce.webp" alt=""><figcaption></figcaption></figure>
{% embed url="https://assetstore.unity.com/packages/essentials/starter-assets-thirdperson-updates-in-new-charactercontroller-pa-196526" %}
### Client-authoritative or server-authoritative?
We’ll demonstrate how to convert the asset using both methods. The **client-authoritative approach** is simpler to implement and offers players a smooth experience, but it carries the drawback of being more vulnerable to cheating. In contrast, the **server-authoritative approach** significantly reduces the risk of cheating, though it introduces higher input latency. This latency can be mitigated through **client-side prediction (CSP)**, which adds complexity but ensures responsiveness. The tutorial will guide you step by step through setting up CSP. However, before diving into the **server-authoritative + CSP tutorial**, it’s strongly recommended that you first review the [**Client-side Prediction guides**](../../../guides/features/prediction/what-is-client-side-prediction.md) to build a solid foundation.
@@ -0,0 +1,136 @@
---
description: >-
This tutorial shows how to make the third person starter asset multiplayer in
a client authoritative way.
---
# Client Authoritative
{% stepper %}
{% step %}
### Before we begin
Don't forget to import the third person starter asset into your project and ensure it's working correctly in your Unity version, input system, and rendering pipeline.
{% endstep %}
{% step %}
### Import FishNet
Let's begin by importing Fish-Networking. You can use the free or pro version for this tutorial, and you can get it from the GitHub, Asset Store, or Asset Package. See [installing-fish-networking.md](../../getting-started/installing-fish-networking.md "mention") for assistance with this step.
{% endstep %}
{% step %}
### Create the NetworkManager
With FishNet imported we can now get started with turning the singleplayer setup into a multiplayer one. Open up the **Playground** scene — or your own scene, if you already have one — and drag in the NetworkManager prefab from the `FishNet/Demos/Prefabs` folder.
<figure><img src="../../../.gitbook/assets/add-networkmanager-to-playground.png" alt=""><figcaption></figcaption></figure>
This will allow us to connect instances of the game to each other through the provided network HUD.
{% endstep %}
{% step %}
### Prepare the player components
Next, we’ll prepare the player object for network synchronization. Open the **PlayerArmature** prefab located in `Assets/StarterAssets/ThirdPersonController/Prefabs` and add the following components: **NetworkObject**, **NetworkTransform**, and **NetworkAnimator**.
* **NetworkObject** links the prefab across the network.
* **NetworkTransform** synchronizes its position and rotation.
* **NetworkAnimator** ensures animations are synchronized.
Together, these components enable the player to stay consistent across all connected clients. The default settings of these components should work, just ensure **Client Authoritative** is enabled on both the **NetworkTransform** and **NetworkAnimator**, the **Animator** field is set on the **NetworkAnimator**, and the **Component Configuration** field is set to _CharacterController_ in the **NetworkTransform**.
<div><figure><img src="../../../.gitbook/assets/starter-assets-network-transform.png" alt=""><figcaption></figcaption></figure> <figure><img src="../../../.gitbook/assets/starter-assets-network-animator.png" alt=""><figcaption></figcaption></figure></div>
{% endstep %}
{% step %}
### Setup the player code
We now need to make sure the players can only move their own player objects. Open up the **ThirdPersonController** script and change it from inheriting from `MonoBehaviour` to `NetworkBehaviour` — this will require adding `using FishNet.Object;` to the top of your code.
`public class ThirdPersonController : NetworkBehaviour`
This code will allow us to make use of NetworkBehaviour properties, methods, and so on.
With that done, we can add the following code to the top of the **Update** and **LateUpdate** methods:
```csharp
if (!IsOwner)
return;
```
This code will prevent the method from running on objects the player doesn't own, thus ensuring he can only move and control his own player.
They should look like this now:
```csharp
private void Update()
{
if (!IsOwner)
return;
_hasAnimator = TryGetComponent(out _animator);
JumpAndGravity();
GroundedCheck();
Move();
}
private void LateUpdate()
{
if (!IsOwner)
return;
CameraRotation();
}
```
{% endstep %}
{% step %}
### Set up the camera
One thing we still need to do is tell the camera which player it should be following. We'll do that by instantiating a follow camera when our local player spawns in. Inside the **ThirdPersonController** script, add the following field:&#x20;
```csharp
[Tooltip("The Follow Camera prefab that we spawn for the owner")]
public CinemachineVirtualCamera PlayerFollowCameraPrefab;
```
Then add this override method.
```csharp
public override void OnStartClient()
{
if (!IsOwner)
return;
CinemachineVirtualCamera virtualCam = Instantiate(PlayerFollowCameraPrefab);
virtualCam.Follow = CinemachineCameraTarget.transform;
}
```
{% endstep %}
{% step %}
### Set up the references
Now we can delete the **PlayerFollowCamera** object from the scene and instead assign that prefab into our **PlayerArmature**'s _PlayerFollowCameraPrefab_ field.
<figure><img src="../../../.gitbook/assets/starter-assets-playerfollowcamera.png" alt=""><figcaption></figcaption></figure>
Finally, we will delete the **PlayerArmature** object from the scene and assign that prefab into our **NetworkManager**'s _PlayerPrefab_ field.
<figure><img src="../../../.gitbook/assets/starter-assets-playerspawner.png" alt=""><figcaption></figcaption></figure>
{% endstep %}
{% step %}
### Try it out!
You are now ready to test your game. Launch multiple instances of it and start one as a server and client and the other/s as a client.
<figure><img src="../../../.gitbook/assets/starter-assets-third-person-test.webp" alt=""><figcaption></figcaption></figure>
{% hint style="info" %}
You can launch a second Unity Editor instance by using [Unity's Multiplayer Play Mode](https://docs-multiplayer.unity3d.com/mppm/current/about/) package or a third-party package such as [ParrelSync](https://github.com/VeriorPies/ParrelSync?tab=readme-ov-file#parrelsync). Find out more about these options and how to use them with this tutorial: [testing-with-multiple-editors.md](../../simple/testing-with-multiple-editors.md "mention").
{% endhint %}
{% endstep %}
{% endstepper %}
@@ -0,0 +1,7 @@
---
hidden: true
---
# Server Authoritative
Under construction, stay tuned for updates!