新增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,86 @@
---
description: >-
Understanding how to use ownership, as well how it affects clients and the
server is essential for any project.
---
# Ownership
### What is ownership?
Ownership is a core concept in Fish-Net, determining which client has control over an object.
Every NetworkObject in Fish-Net can be assigned a client to own it, or the object can have no assigned owner. The server is exclusively responsible for changing ownership of objects, even though it cannot own an object itself.
When a client owns an object, they are considered its rightful user, capable of actions like moving a character or using a weapon. Ownership can also be temporarily granted for interactions with world objects, such as using a turret.
Ownership checks are essential for ensuring proper control over objects. Common scenarios include:
* Validating if a client can manipulate an object.
* Checking ownership status for player-related values like name or score.
* Ensuring only the owner executes specific network calls (e.g., ServerRpcs).
### Assigning ownership
Ownership can be granted in several ways:
* Spawning with Ownership: When creating an object, ownership can be assigned to a specific connection.
```csharp
Gameobject go = Instantiate(_yourPrefab);
InstanceFinder.ServerManager.Spawn(go, ownerConnection);
```
* Changing or Adding Ownership: If an object is already spawned, ownership can be modified at any time. Previous owners are replaced with the new owner.
```csharp
networkObject.GiveOwnership(newOwnerConnection);
```
* Removing Ownership: Ownership can be revoked from a client when needed.
```csharp
networkObject.RemoveOwnership();
```
{% hint style="success" %}
`ownerConnection` and `newOwnerConnection` above represent a NetworkConnection that will be set as the new owner.
{% endhint %}
### Checking ownership
Ownership can be verified via the following NetworkObject or NetworkBehaviour properties:
```csharp
base.IsOwner; // Returns true if the local client owns the object.
base.Owner; // Retrieves the current owner NetworkConnection.
base.IsController; // True if the local client owns the object or is the server with no assigned owner.
```
#### Simple movement example
A client must own an object to execute certain actions. Below is an example of movement logic restricted to the object's owner:
```csharp
void Update()
{
// The client's game instance will have several player objects in it if there
// are multiple players playing, but he will only own one of them and should
// should only move that one that he owns.
if (base.IsOwner)
{
float hor = Input.GetAxisRaw("Horizontal");
float ver = Input.GetAxisRaw("Vertical");
transform.position += new Vector3(hor, 0f, ver);
}
}
```
When paired with a [Network Transform](../../../fishnet-building-blocks/components/network-transform.md) set to Client Authoritative, this movement will be relayed to the server and synchronized across all clients.
### Transferring ownership
Only the server can assign, transfer, or remove ownership. Typically, ownership is granted when spawning an object.
* Automatic Ownership Assignment: The PlayerSpawner script (found in the NetworkManager prefab) ensures the spawned player object is owned by its respective client.
* Immediate Ownership: In cases where a client needs immediate ownership—such as controlling a turret without delay—the [PredictedOwner](../../../fishnet-building-blocks/components/prediction/predictedowner.md) component can be used. This component can be extended for customized logic.
@@ -0,0 +1,207 @@
---
description: >-
Learn how to store values for clients and read them on unlinked objects by
reading owner information.
---
# Using Ownership to Read Values
{% hint style="info" %}
This guide requires Share Ids to be enabled on the ServerManager for clients to read values. Share Ids is enabled by default, and does not reveal any sensitive information to clients.
{% endhint %}
We are going to demonstrate how to assign a display name to each client, and display that name on player objects.
{% hint style="warning" %}
This guide assumes you already have a NetworkManager object in your scene.
{% endhint %}
## PlayerNames
First make a new script on an empty scene object and name it PlayerNames; this script needs to inherit NetworkBehaviour.
After adding the script to your scene object a NetworkObject component will automatically be added to the same object. On the NetworkObject enable 'Is Global', make this object a prefab, then delete it from your scene.
<figure><img src="../../../.gitbook/assets/player-names-prefab.png" alt=""><figcaption></figcaption></figure>
{% hint style="success" %}
Notice that on the NetworkObject we also set the Initialize Order to -128. Doing so ensures that this NetworkObject will initialize before any other object does, which promises the OnStart callbacks will execute before other scripts. This step is most likely not needed, but given this is more-or-less a managing script for player names giving it execution priority is good practice.
{% endhint %}
We are now going to populate the PlayerNames script with the following code.
```csharp
using System;
using FishNet.Connection;
using FishNet.Object;
using FishNet.Object.Synchronizing;
using FishNet.Transporting;
using Random = UnityEngine.Random;
public class PlayerNames : NetworkBehaviour
{
/* Since this is a syncType it will automatically be synchronized to clients
* whenever it updates, and when clients spawn this object! */
/// <summary>
/// Called when a player name is updated.
/// </summary>
public event Action<NetworkConnection, string> OnPlayerNameChanged;
/// <summary>
/// Names of all connected clients.
/// </summary>
public readonly SyncDictionary<NetworkConnection, string> Names = new();
private void Awake()
{
Names.OnChange += NamesOnOnChange;
}
public override void OnStartNetwork()
{
// Register this to the NetworkManager so it can be found easily by any script!
NetworkManager.RegisterInstance(this);
}
public override void OnStartServer()
{
ServerManager.OnRemoteConnectionState += ServerManagerOnOnRemoteConnectionState;
}
public override void OnStopServer()
{
ServerManager.OnRemoteConnectionState -= ServerManagerOnOnRemoteConnectionState;
}
public override void OnStopNetwork()
{
// Unregister to clean up.
if (NetworkManager != null)
NetworkManager.UnregisterInstance<PlayerNames>();
}
private void ServerManagerOnOnRemoteConnectionState(NetworkConnection conn, RemoteConnectionStateArgs args)
{
// If disconnecting remove from the dictionary.
if (args.ConnectionState == RemoteConnectionState.Stopped)
{
Names.Remove(conn);
}
// If connecting then add.
else if (args.ConnectionState == RemoteConnectionState.Started)
{
/* When a player connects assign them a random number
* as their name. */
// Another cog in the machine.
string randomName = Random.Range(1000, 999999).ToString();
Names.Add(conn, randomName);
}
}
/// <summary>
/// Calls whenever the _names collection updates.
/// </summary>
private void NamesOnOnChange(SyncDictionaryOperation op, NetworkConnection key, string value, bool asServer)
{
// If an add or modify then invoke.
if (op == SyncDictionaryOperation.Add || op == SyncDictionaryOperation.Set)
OnPlayerNameChanged?.Invoke(key, value);
}
/// <summary>
/// Allows a client to call this RPC, updating their name.
/// </summary>
[ServerRpc(RequireOwnership = false)]
public void ServerSetName(string newName, NetworkConnection caller = null)
{
// Caller will never be null; the server will assign it automatically when a client calls this since RequireOwnership is false.
// ReSharper disable once AssignNullToNotNullAttribute
Names[caller] = newName;
}
}
```
The above code snippet will give players a random name when they connect, and allow clients to change their name by calling the SetName RPC.
## Automatically spawning PlayerNames
After you have made the prefab select your scene NetworkManager, make a child object named ServerSpawner, and add the script ServerSpawner. You may place this script anywhere in your scene or game, but for simplicity sake we're going to nest it beneath the NetworkManager. After you add the script, insert your newly created PlayerNames prefab into the 'Network Objects' field and ensure Automatically Spawn is enabled.
<figure><img src="../../../.gitbook/assets/player-names-server-spawner.png" alt=""><figcaption></figcaption></figure>
## Displaying player names
Next we are going to make a very simple script which changes the text value on a TextMeshPro component. This is a very simple example which might go on the players character. You could use similar scripts for chat names, and more.
Make a new script named CharacterName. Add the following:
```csharp
using FishNet.Connection;
using FishNet.Object;
using TMPro;
using UnityEngine;
public class CharacterName : NetworkBehaviour
{
/// <summary>
/// Text box used to display the name of this character.
/// </summary>
[Tooltip("Text box used to display the name of this character.")]
[SerializeField]
private TextMeshProUGUI _text;
// Cached value for unsubscribing to save a little perf.
private PlayerNames _playerNames;
public override void OnStartClient()
{
// If owner is not set then do not get the name, as this does not belong to a client.
if (!base.Owner.IsValid)
return;
// Get the PlayerNames instance to read this characters name(the player name).
_playerNames = base.NetworkManager.GetInstance<PlayerNames>();
// If cannot be found exit method; this shouldn't ever happen.
if (!_playerNames.Names.TryGetValue(base.Owner, out string theName))
return;
_text.text = theName;
// Also listen for updates for
_playerNames.OnPlayerNameChanged += PlayerNamesOnOnPlayerNameChanged;
}
public override void OnStopClient()
{
// Unsubscribe from events to clean up.
if (_playerNames != null)
_playerNames.OnPlayerNameChanged -= PlayerNamesOnOnPlayerNameChanged;
}
/// <summary>
/// Called when a player name changes after initially being set, or when added for the first time.
/// </summary>
private void PlayerNamesOnOnPlayerNameChanged(NetworkConnection conn, string theName)
{
// If the name being changed is not for this owner then do not update anything.
if (conn != base.Owner)
return;
// Set new name.
_text.text = theName;
}
}
```
Now when an object spawns containing the script above, the \_text field will be updated to the owners player name. In addition, if the owner changes their name at any time, the text will be updated again.
{% hint style="success" %}
Notice how we make use of **NetworkManager.Register**, **Unregister**, and **GetInstance** in this guide. This is a very useful feature for accessing global networked scripts.
{% endhint %}