### Initialize and Manage Connections with NetworkManager Source: https://context7.com/firstgeargames/fishnet/llms.txt Use NetworkManager to access sub-managers, check connection status, and start or stop server/client instances. ```csharp using FishNet.Managing; using UnityEngine; public class GameStarter : MonoBehaviour { private NetworkManager _networkManager; private void Start() { // Get NetworkManager from scene or InstanceFinder _networkManager = FindObjectOfType(); // Check if server/client are running if (_networkManager.IsServerStarted) Debug.Log("Server is running"); if (_networkManager.IsClientStarted) Debug.Log("Client is connected"); // Access sub-managers var serverManager = _networkManager.ServerManager; var clientManager = _networkManager.ClientManager; var timeManager = _networkManager.TimeManager; // Start server _networkManager.ServerManager.StartConnection(); // Start client (connects to localhost by default) _networkManager.ClientManager.StartConnection(); // Or start as host (both server and client) _networkManager.ServerManager.StartConnection(); _networkManager.ClientManager.StartConnection(); } private void OnDestroy() { // Stop connections _networkManager.ServerManager.StopConnection(true); _networkManager.ClientManager.StopConnection(); } } ``` -------------------------------- ### Spawn Player with Ownership Source: https://context7.com/firstgeargames/fishnet/llms.txt Spawns a player prefab over the network and assigns ownership to a specific client connection. Ensure the server is started before calling. ```csharp using FishNet.Object; using FishNet.Connection; using FishNet.Managing; using UnityEngine; public class SpawnManager : NetworkBehaviour { [SerializeField] private NetworkObject _playerPrefab; [SerializeField] private NetworkObject _projectilePrefab; [SerializeField] private Transform[] _spawnPoints; // Spawn with ownership public void SpawnPlayerForClient(NetworkConnection conn) { if (!IsServerStarted) return; Vector3 spawnPos = _spawnPoints[Random.Range(0, _spawnPoints.Length)].position; // Instantiate the prefab NetworkObject nob = Instantiate(_playerPrefab, spawnPos, Quaternion.identity); // Spawn over network with owner ServerManager.Spawn(nob, conn); // Alternative: spawn from NetworkBehaviour // Spawn(nob, conn); } // Spawn without owner (server-controlled) public void SpawnEnemy(Vector3 position) { if (!IsServerStarted) return; NetworkObject nob = Instantiate(_playerPrefab, position, Quaternion.identity); ServerManager.Spawn(nob); // No owner } // Spawn projectile from owner request [ServerRpc] private void ServerSpawnProjectile(Vector3 position, Vector3 direction) { NetworkObject projectile = Instantiate(_projectilePrefab, position, Quaternion.LookRotation(direction)); ServerManager.Spawn(projectile, Owner); // Owner is the caller } // Despawn object public void DespawnObject(NetworkObject nob) { if (!IsServerStarted) return; // Despawn and destroy ServerManager.Despawn(nob, DespawnType.Destroy); // Or despawn and pool for reuse // ServerManager.Despawn(nob, DespawnType.Pool); } // Self-despawn public void SelfDestruct() { if (!IsServerStarted) return; Despawn(); // Despawns this NetworkObject } } ``` -------------------------------- ### Check Ownership Status on Server Source: https://context7.com/firstgeargames/fishnet/llms.txt Logs the ownership status and owner ID of a NetworkBehaviour when the server starts. Requires the server to be running. ```csharp using FishNet.Object; using FishNet.Connection; using UnityEngine; public class OwnershipExample : NetworkBehaviour { public override void OnStartServer() { // Check ownership status Debug.Log($ ``` -------------------------------- ### Transfer and Release Ownership Source: https://context7.com/firstgeargames/fishnet/llms.txt Provides methods for the server to explicitly give ownership of a NetworkBehaviour to a new client connection or to remove ownership entirely. Requires the server to be started. ```csharp // Server gives ownership public void TransferOwnership(NetworkConnection newOwner) { if (!IsServerStarted) return; GiveOwnership(newOwner); } // Server removes ownership public void ReleaseOwnership() { if (!IsServerStarted) return; RemoveOwnership(); } ``` -------------------------------- ### Retrieve Object from Pool Source: https://github.com/firstgeargames/fishnet/blob/main/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Types/ObjectPooling/README.txt Use `ObjectPool.Retrieve()` to get an object from the pool. This method mimics Unity's `Instantiate()` overrides. You can specify a type, such as a script, using `ObjectPool.Retrieve()`. ```csharp ObjectPool.Retrieve() ObjectPool.Retrieve() ``` -------------------------------- ### Manage Connection Events and Clients in C# Source: https://context7.com/firstgeargames/fishnet/llms.txt Demonstrates how to subscribe to server and client connection events, handle authentication, and perform client management tasks like kicking a user. ```csharp using FishNet.Managing; using FishNet.Managing.Server; using FishNet.Managing.Client; using FishNet.Connection; using FishNet.Transporting; using UnityEngine; public class ConnectionManager : MonoBehaviour { private NetworkManager _networkManager; private void Start() { _networkManager = FindObjectOfType(); // Server connection events _networkManager.ServerManager.OnServerConnectionState += OnServerConnectionState; _networkManager.ServerManager.OnRemoteConnectionState += OnRemoteConnectionState; _networkManager.ServerManager.OnAuthenticationResult += OnAuthenticationResult; // Client connection events _networkManager.ClientManager.OnClientConnectionState += OnClientConnectionState; _networkManager.ClientManager.OnAuthenticated += OnClientAuthenticated; } // Server started/stopped private void OnServerConnectionState(ServerConnectionStateArgs args) { switch (args.ConnectionState) { case LocalConnectionState.Starting: Debug.Log("Server starting..."); break; case LocalConnectionState.Started: Debug.Log("Server started!"); break; case LocalConnectionState.Stopping: Debug.Log("Server stopping..."); break; case LocalConnectionState.Stopped: Debug.Log("Server stopped."); break; } } // Client connected/disconnected from server private void OnRemoteConnectionState(NetworkConnection conn, RemoteConnectionStateArgs args) { if (args.ConnectionState == RemoteConnectionState.Started) { Debug.Log($"Client {conn.ClientId} connected"); // Access all connected clients foreach (var client in _networkManager.ServerManager.Clients.Values) { Debug.Log($"Connected client: {client.ClientId}"); } } else if (args.ConnectionState == RemoteConnectionState.Stopped) { Debug.Log($"Client {conn.ClientId} disconnected"); } } // Client authentication result private void OnAuthenticationResult(NetworkConnection conn, bool authenticated) { if (authenticated) Debug.Log($"Client {conn.ClientId} authenticated successfully"); else Debug.Log($"Client {conn.ClientId} failed authentication"); } // Local client connection state private void OnClientConnectionState(ClientConnectionStateArgs args) { switch (args.ConnectionState) { case LocalConnectionState.Starting: Debug.Log("Connecting to server..."); break; case LocalConnectionState.Started: Debug.Log("Connected to server!"); break; case LocalConnectionState.Stopping: Debug.Log("Disconnecting..."); break; case LocalConnectionState.Stopped: Debug.Log("Disconnected from server."); break; } } // Local client authenticated private void OnClientAuthenticated() { Debug.Log("Local client authenticated!"); Debug.Log($"My ClientId: {_networkManager.ClientManager.Connection.ClientId}"); } // Kick a client public void KickClient(NetworkConnection conn, string reason = "Kicked by server") { conn.Kick(KickReason.Unset, LoggingType.Common, reason); } private void OnDestroy() { if (_networkManager == null) return; _networkManager.ServerManager.OnServerConnectionState -= OnServerConnectionState; _networkManager.ServerManager.OnRemoteConnectionState -= OnRemoteConnectionState; _networkManager.ServerManager.OnAuthenticationResult -= OnAuthenticationResult; _networkManager.ClientManager.OnClientConnectionState -= OnClientConnectionState; _networkManager.ClientManager.OnAuthenticated -= OnClientAuthenticated; } } ``` -------------------------------- ### Implement Custom Player Spawner Logic Source: https://context7.com/firstgeargames/fishnet/llms.txt Use the PlayerSpawner component to automate player object instantiation and hook into spawn events for custom initialization. ```csharp using FishNet.Component.Spawning; using FishNet.Object; using UnityEngine; // PlayerSpawner is a built-in component that handles automatic player spawning // Simply add it to your NetworkManager GameObject and configure: // - Player Prefab: NetworkObject prefab to spawn for each player // - Spawns: Array of spawn point transforms // - Add To Default Scene: Whether to add players to active scene public class CustomPlayerSpawner : MonoBehaviour { [SerializeField] private PlayerSpawner _playerSpawner; private void Start() { // Listen for spawn events _playerSpawner.OnSpawned += OnPlayerSpawned; } private void OnPlayerSpawned(NetworkObject playerObject) { Debug.Log($"Player spawned: {playerObject.ObjectId}"); // Additional setup for spawned player if (playerObject.TryGetComponent(out var setup)) { setup.Initialize(); } } // Dynamically change the player prefab public void SetPlayerPrefab(NetworkObject newPrefab) { _playerSpawner.SetPlayerPrefab(newPrefab); } private void OnDestroy() { if (_playerSpawner != null) _playerSpawner.OnSpawned -= OnPlayerSpawned; } } public class PlayerSetup : NetworkBehaviour { public void Initialize() { } } ``` -------------------------------- ### Implement SyncDictionary for Synchronized Key-Value Pairs Source: https://context7.com/firstgeargames/fishnet/llms.txt Use SyncDictionary to synchronize dictionary collections across the network. Changes are tracked via the OnChange event, and modifications should be performed on the server. ```csharp using FishNet.Object; using FishNet.Object.Synchronizing; using UnityEngine; public class PlayerScoreboard : NetworkBehaviour { // SyncDictionary for player scores public readonly SyncDictionary PlayerScores = new(); // ClientId -> Score public readonly SyncDictionary PlayerDatabase = new(); public override void OnStartClient() { PlayerScores.OnChange += OnScoresChanged; } private void OnScoresChanged(SyncDictionaryOperation op, int clientId, int score, bool asServer) { switch (op) { case SyncDictionaryOperation.Add: Debug.Log($"Player {clientId} joined with score {score}"); break; case SyncDictionaryOperation.Set: Debug.Log($"Player {clientId} score updated to {score}"); break; case SyncDictionaryOperation.Remove: Debug.Log($"Player {clientId} removed from scoreboard"); break; case SyncDictionaryOperation.Clear: Debug.Log("Scoreboard cleared"); break; case SyncDictionaryOperation.Complete: UpdateScoreboardUI(); break; } } // Server updates scores public void AddScore(int clientId, int points) { if (!IsServerStarted) return; if (PlayerScores.TryGetValue(clientId, out int currentScore)) PlayerScores[clientId] = currentScore + points; else PlayerScores.Add(clientId, points); } public void RemovePlayer(int clientId) { if (!IsServerStarted) return; PlayerScores.Remove(clientId); } private void UpdateScoreboardUI() { } } [System.Serializable] public struct PlayerData { public string Name; public int Level; public int Kills; public int Deaths; } ``` -------------------------------- ### Handle Ownership Change on Server Source: https://context7.com/firstgeargames/fishnet/llms.txt Logs the previous and current owner's client ID when ownership of a NetworkBehaviour changes on the server. Handles cases where there was no previous owner or no current owner. ```csharp public override void OnOwnershipServer(NetworkConnection prevOwner) { Debug.Log($"Ownership changed: {prevOwner?.ClientId ?? -1} -> {Owner?.ClientId ?? -1}"); } ``` -------------------------------- ### Implement Networked Logic with NetworkBehaviour Source: https://context7.com/firstgeargames/fishnet/llms.txt Extend NetworkBehaviour to access lifecycle callbacks like OnStartServer and OnStartClient, and perform ownership checks. ```csharp using FishNet.Object; using FishNet.Connection; using UnityEngine; public class PlayerController : NetworkBehaviour { // Called when object spawns on server public override void OnStartServer() { Debug.Log($"Player spawned on server, ObjectId: {ObjectId}"); } // Called when object spawns on client public override void OnStartClient() { Debug.Log($"Player spawned on client, IsOwner: {IsOwner}"); // Only enable input for owner if (IsOwner) EnableInput(); } // Called when ownership changes (server-side) public override void OnOwnershipServer(NetworkConnection prevOwner) { Debug.Log($"Server: Ownership changed from {prevOwner?.ClientId} to {Owner?.ClientId}"); } // Called when ownership changes (client-side) public override void OnOwnershipClient(NetworkConnection prevOwner) { Debug.Log($"Client: Ownership changed, IsOwner: {IsOwner}"); } // Called when network initializes (before OnStartServer/OnStartClient) public override void OnStartNetwork() { Debug.Log("Network initialized for this object"); } // Called when network deinitializes public override void OnStopNetwork() { Debug.Log("Network deinitialized for this object"); } private void Update() { // Common ownership checks if (!IsOwner) return; // Only owner executes if (!IsServerStarted) return; // Only server executes if (!IsSpawned) return; // Only if spawned } private void EnableInput() { } } ``` -------------------------------- ### Handle Ownership Change on Client Source: https://context7.com/firstgeargames/fishnet/llms.txt Enables or disables player input on the client based on whether the local client now owns the NetworkBehaviour. This is called when ownership changes. ```csharp public override void OnOwnershipClient(NetworkConnection prevOwner) { // Enable/disable based on ownership if (IsOwner) { EnablePlayerInput(); } else { DisablePlayerInput(); } } ``` -------------------------------- ### Define and Use Custom Broadcast Messages in FishNet Source: https://context7.com/firstgeargames/fishnet/llms.txt Define structs implementing IBroadcast for custom messages. Register handlers on the server and client managers to receive and process these messages. Unregister handlers in OnDestroy to prevent memory leaks. ```csharp using FishNet.Broadcast; using FishNet.Managing; using FishNet.Connection; using FishNet.Transporting; using UnityEngine; // Define broadcast messages as structs implementing IBroadcast public struct ChatMessage : IBroadcast { public string Sender; public string Message; public int Channel; } public struct ServerAnnouncement : IBroadcast { public string Title; public string Content; } public class ChatSystem : MonoBehaviour { private NetworkManager _networkManager; private void Start() { _networkManager = FindObjectOfType(); // Register broadcast handlers // Server listens for client messages _networkManager.ServerManager.RegisterBroadcast(OnChatMessageReceived); // Client listens for server announcements _networkManager.ClientManager.RegisterBroadcast(OnAnnouncementReceived); _networkManager.ClientManager.RegisterBroadcast(OnChatBroadcastReceived); } private void OnDestroy() { // Unregister handlers if (_networkManager != null) { _networkManager.ServerManager.UnregisterBroadcast(OnChatMessageReceived); _networkManager.ClientManager.UnregisterBroadcast(OnAnnouncementReceived); _networkManager.ClientManager.UnregisterBroadcast(OnChatBroadcastReceived); } } // Server receives chat from client private void OnChatMessageReceived(NetworkConnection conn, ChatMessage msg, Channel channel) { Debug.Log($ ``` ```csharp [Server] {msg.Sender}: {msg.Message} ``` ```csharp // Rebroadcast to all clients _networkManager.ServerManager.Broadcast(msg); } // Client receives broadcast chat private void OnChatBroadcastReceived(ChatMessage msg, Channel channel) { Debug.Log($ ``` ```csharp [Chat] {msg.Sender}: {msg.Message} ``` ```csharp DisplayChatMessage(msg.Sender, msg.Message); } // Client receives server announcement private void OnAnnouncementReceived(ServerAnnouncement announcement, Channel channel) { Debug.Log($ ``` ```csharp [Announcement] {announcement.Title}: {announcement.Content} ``` ```csharp ) } // Client sends chat message public void SendChatMessage(string message) { ChatMessage msg = new() { Sender = "Player", Message = message, Channel = 0 }; _networkManager.ClientManager.Broadcast(msg); } // Server sends announcement to all public void SendAnnouncement(string title, string content) { ServerAnnouncement announcement = new() { Title = title, Content = content }; _networkManager.ServerManager.Broadcast(announcement); } // Server sends to specific client public void SendPrivateMessage(NetworkConnection target, string message) { ChatMessage msg = new() { Sender = "Server", Message = message, Channel = -1 // Private }; _networkManager.ServerManager.Broadcast(target, msg); } private void DisplayChatMessage(string sender, string message) { } } ``` -------------------------------- ### Implement SyncList for Synchronized Lists Source: https://context7.com/firstgeargames/fishnet/llms.txt Use SyncList to manage collections that require automatic synchronization of add, remove, and set operations. Requires subscribing to the OnChange event to handle collection updates. ```csharp using FishNet.Object; using FishNet.Object.Synchronizing; using System.Collections.Generic; using UnityEngine; public class Inventory : NetworkBehaviour { // SyncList automatically syncs add/remove/set operations public readonly SyncList Items = new(); // SyncList with custom comparer public readonly SyncList DetailedItems = new(); public override void OnStartClient() { // Subscribe to collection changes Items.OnChange += OnItemsChanged; } public override void OnStopClient() { Items.OnChange -= OnItemsChanged; } // Change callback with operation type private void OnItemsChanged(SyncListOperation op, int index, string oldItem, string newItem, bool asServer) { switch (op) { case SyncListOperation.Add: Debug.Log($"Item added: {newItem} at index {index}"); break; case SyncListOperation.RemoveAt: Debug.Log($"Item removed: {oldItem} from index {index}"); break; case SyncListOperation.Set: Debug.Log($"Item changed at {index}: {oldItem} -> {newItem}"); break; case SyncListOperation.Clear: Debug.Log("Inventory cleared"); break; case SyncListOperation.Complete: // All changes for this tick processed RefreshInventoryUI(); break; } } // Server-side inventory management public void AddItem(string itemName) { if (!IsServerStarted) return; Items.Add(itemName); } public void RemoveItem(string itemName) { if (!IsServerStarted) return; Items.Remove(itemName); } public void ClearInventory() { if (!IsServerStarted) return; Items.Clear(); } // Access like regular list public bool HasItem(string itemName) => Items.Contains(itemName); public int ItemCount => Items.Count; private void RefreshInventoryUI() { } } [System.Serializable] public struct InventoryItem { public string Name; public int Quantity; public int SlotIndex; } ``` -------------------------------- ### Server to All Clients Communication with ObserversRpc Source: https://context7.com/firstgeargames/fishnet/llms.txt Use ObserversRpc to broadcast method calls from the server to all observing clients. Configure exclusion rules like ExcludeOwner or BufferLast for specific behaviors. ```csharp using FishNet.Object; using FishNet.Transporting; using UnityEngine; public class GameEvents : NetworkBehaviour { // Called on server, executes on all observing clients [ObserversRpc] private void ObserversPlaySound(string soundName, Vector3 position) { // All clients play the sound AudioManager.PlaySound(soundName, position); } // Exclude owner from receiving the RPC [ObserversRpc(ExcludeOwner = true)] private void ObserversShowDamageEffect(Vector3 position, int damage) { // All observers except owner see the effect SpawnDamageNumber(position, damage); } // Buffer RPC for late-joining clients [ObserversRpc(BufferLast = true)] private void ObserversSetTeamColor(Color teamColor) { // Late joiners receive the last buffered call GetComponent().material.color = teamColor; } // Include server in execution (useful for host) [ObserversRpc(ExcludeServer = false)] private void ObserversAnnouncement(string message) { Debug.Log($ ``` ```csharp Announcement: {message} } // Server-side trigger public void OnPlayerScored(int points) { if (!IsServerStarted) return; // Broadcast to all clients ObserversPlaySound("score", transform.position); ObserversAnnouncement($"Player scored {points} points!"); } private void SpawnDamageNumber(Vector3 pos, int dmg) { } } public static class AudioManager { public static void PlaySound(string name, Vector3 pos) { } } ``` -------------------------------- ### Server to Specific Client Communication with TargetRpc Source: https://context7.com/firstgeargames/fishnet/llms.txt Use TargetRpc to send method calls from the server to a specific client. The `ExcludeServer` option can prevent the host client from receiving the call. ```csharp using FishNet.Object; using FishNet.Connection; using UnityEngine; public class PlayerNotifications : NetworkBehaviour { // Send to owner by default [TargetRpc] private void TargetShowMessage(NetworkConnection conn, string message) { // Only the targeted client executes this UIManager.ShowNotification(message); } // Server sends private message to specific player public void SendPrivateMessage(NetworkConnection targetPlayer, string message) { if (!IsServerStarted) return; TargetShowMessage(targetPlayer, message); } // Send reward notification to owner [TargetRpc] private void TargetReceiveReward(NetworkConnection conn, string itemName, int quantity) { Debug.Log($ ``` -------------------------------- ### Store Object in Pool Source: https://github.com/firstgeargames/fishnet/blob/main/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Types/ObjectPooling/README.txt Use `ObjectPool.Store()` to return an object to the pool. You can also store objects with a delay, similar to `Destroy(obj, delay)`. ```csharp ObjectPool.Store() Destroy(obj, delay) ``` -------------------------------- ### Client to Server Communication with ServerRpc Source: https://context7.com/firstgeargames/fishnet/llms.txt Use ServerRpc to allow clients to invoke methods on the server. The server executes the method body. Ensure correct ownership settings for security. ```csharp using FishNet.Object; using FishNet.Connection; using FishNet.Transporting; using UnityEngine; public class PlayerCombat : NetworkBehaviour { [SerializeField] private int _maxHealth = 100; private int _currentHealth; public override void OnStartServer() { _currentHealth = _maxHealth; } private void Update() { if (!IsOwner) return; // Client sends attack request to server if (Input.GetMouseButtonDown(0)) { Vector3 targetPosition = GetMouseWorldPosition(); ServerAttack(targetPosition); } } // [ServerRpc] - Called by client, executed on server // RequireOwnership: only owner can call (default true) [ServerRpc] private void ServerAttack(Vector3 targetPosition) { // Server validates and processes attack Debug.Log($ ``` ```csharp Server processing attack from {Owner.ClientId} at {targetPosition} // Perform server-side hit detection if (Physics.Raycast(transform.position, targetPosition - transform.position, out RaycastHit hit)) { if (hit.collider.TryGetComponent(out var target)) { target.TakeDamage(25, Owner); } } } // ServerRpc that any client can call (not just owner) [ServerRpc(RequireOwnership = false)] private void ServerRequestRespawn(NetworkConnection sender = null) { // sender is automatically populated with the calling client's connection Debug.Log($ ``` ```csharp Respawn requested by client {sender.ClientId} RespawnPlayer(sender); } // ServerRpc with specific channel [ServerRpc(RunLocally = true)] // Also runs on calling client private void ServerReliableAction() { Debug.Log("This runs on both server and calling client"); } private void TakeDamage(int damage, NetworkConnection attacker) { } private void RespawnPlayer(NetworkConnection conn) { } private Vector3 GetMouseWorldPosition() => Vector3.zero; } ``` -------------------------------- ### Implement Predicted Movement with Replicate and Reconcile Source: https://context7.com/firstgeargames/fishnet/llms.txt Uses TickNetworkBehaviour to handle input replication from client to server and state reconciliation from server to client. Requires a CharacterController component on the GameObject. ```csharp using FishNet.Object; using FishNet.Object.Prediction; using FishNet.Transporting; using FishNet.Utility.Template; using UnityEngine; public class PredictedMovement : TickNetworkBehaviour { // Replicate data sent from client to server public struct MoveData : IReplicateData { public Vector2 Movement; public bool Jump; private uint _tick; public MoveData(Vector2 movement, bool jump) { Movement = movement; Jump = jump; _tick = 0; } public void Dispose() { } public uint GetTick() => _tick; public void SetTick(uint value) => _tick = value; } // Reconcile data sent from server to client public struct ReconcileData : IReconcileData { public Vector3 Position; public Vector3 Velocity; private uint _tick; public ReconcileData(Vector3 position, Vector3 velocity) { Position = position; Velocity = velocity; _tick = 0; } public void Dispose() { } public uint GetTick() => _tick; public void SetTick(uint value) => _tick = value; } [SerializeField] private float _moveSpeed = 5f; [SerializeField] private float _jumpForce = 10f; private CharacterController _controller; private Vector3 _velocity; private void Awake() { _controller = GetComponent(); SetTickCallbacks(TickCallback.Tick | TickCallback.PostTick); } // Build input data (owner only) private MoveData BuildMoveData() { if (!IsOwner) return default; Vector2 movement = new( Input.GetAxisRaw("Horizontal"), Input.GetAxisRaw("Vertical") ); bool jump = Input.GetKeyDown(KeyCode.Space); return new MoveData(movement, jump); } // Called every network tick protected override void TimeManager_OnTick() { // Owner builds and sends input PerformReplicate(BuildMoveData()); } // Called after tick for reconciliation protected override void TimeManager_OnPostTick() { CreateReconcile(); } // Replicate method - runs on both client (prediction) and server (authority) [Replicate] private void PerformReplicate(MoveData md, ReplicateState state = ReplicateState.Invalid, Channel channel = Channel.Unreliable) { float delta = (float)TimeManager.TickDelta; // Apply gravity _velocity.y += Physics.gravity.y * delta; // Handle jump if (md.Jump && _controller.isGrounded) _velocity.y = _jumpForce; // Calculate movement Vector3 move = new Vector3(md.Movement.x, 0, md.Movement.y).normalized; move = move * _moveSpeed + new Vector3(0, _velocity.y, 0); // Move character _controller.Move(move * delta); } // Create reconcile data (server authoritative state) public override void CreateReconcile() { ReconcileData rd = new(transform.position, _velocity); PerformReconcile(rd); } // Reconcile method - resets client state to server state [Reconcile] private void PerformReconcile(ReconcileData rd, Channel channel = Channel.Unreliable) { // Disable controller to set position directly _controller.enabled = false; transform.position = rd.Position; _velocity = rd.Velocity; _controller.enabled = true; } } ``` -------------------------------- ### Check Ownership and Controller Status in Update Source: https://context7.com/firstgeargames/fishnet/llms.txt Checks if the local client owns the object (`IsOwner`) or if the object is controlled by the owner or the server (`IsController`) within the Update loop. This is useful for conditional logic based on who is controlling the object. ```csharp // Check ownership in gameplay private void Update() { // IsOwner - true if local client owns this object if (IsOwner) { HandleOwnerInput(); } // IsController - true if owner OR server with no owner if (IsController) { HandleControllerLogic(); } } ``` -------------------------------- ### Manage Network Timing and Ticks Source: https://context7.com/firstgeargames/fishnet/llms.txt Utilize TimeManager to subscribe to tick events and access synchronized network time, tick rates, and round-trip time. ```csharp using FishNet.Object; using FishNet.Managing.Timing; using UnityEngine; public class TimingExample : NetworkBehaviour { public override void OnStartNetwork() { // Subscribe to tick events TimeManager.OnTick += OnTick; TimeManager.OnPostTick += OnPostTick; TimeManager.OnPreTick += OnPreTick; } public override void OnStopNetwork() { TimeManager.OnTick -= OnTick; TimeManager.OnPostTick -= OnPostTick; TimeManager.OnPreTick -= OnPreTick; } private void OnPreTick() { // Called before physics simulation } private void OnTick() { // Main tick logic - runs at fixed rate (default 30 ticks/second) // Use for networked gameplay logic // Current tick number uint currentTick = TimeManager.Tick; // Time between ticks double tickDelta = TimeManager.TickDelta; // Current tick rate ushort tickRate = TimeManager.TickRate; Debug.Log($"Tick {currentTick}, Delta: {tickDelta}, Rate: {tickRate}"); } private void OnPostTick() { // Called after physics simulation } private void Update() { // For visual interpolation, use these: // Precise network time double networkTime = TimeManager.TicksToTime(TimeManager.Tick); // Round trip time to server (client only) if (IsClientStarted) { long rtt = TimeManager.RoundTripTime; Debug.Log($"RTT: {rtt}ms"); } } } ``` -------------------------------- ### Automatic State Synchronization with SyncVar Source: https://context7.com/firstgeargames/fishnet/llms.txt SyncVar automatically synchronizes values from the server to clients when they change. It supports custom settings for send rate, channel, and read permissions, including owner-only access. ```csharp using FishNet.Object; using FishNet.Object.Synchronizing; using UnityEngine; public class PlayerStats : NetworkBehaviour { // Basic SyncVar - syncs to all observers public readonly SyncVar Health = new(100); public readonly SyncVar PlayerName = new(""); public readonly SyncVar IsAlive = new(true); // SyncVar with custom settings public readonly SyncVar Stamina = new( new SyncTypeSettings( sendRate: 0.1f, // Sync every 0.1 seconds channel: Channel.Unreliable, // Use unreliable channel readPermission: ReadPermission.Observers // All observers can read ) ); // Owner-only SyncVar (private data) public readonly SyncVar Currency = new( new SyncTypeSettings(readPermission: ReadPermission.OwnerOnly) ); public override void OnStartServer() { // Set initial values on server Health.Value = 100; Stamina.Value = 100f; } public override void OnStartClient() { // Subscribe to change events Health.OnChange += OnHealthChanged; PlayerName.OnChange += OnNameChanged; } public override void OnStopClient() { // Unsubscribe from events Health.OnChange -= OnHealthChanged; PlayerName.OnChange -= OnNameChanged; } // Change callback signature: (previousValue, newValue, asServer) private void OnHealthChanged(int prev, int next, bool asServer) { Debug.Log($"Health changed from {prev} to {next}"); UpdateHealthBar(next); if (next <= 0 && prev > 0) OnPlayerDied(); } private void OnNameChanged(string prev, string next, bool asServer) { UpdateNameplate(next); } // Server modifies SyncVars public void TakeDamage(int damage) { if (!IsServerStarted) return; Health.Value = Mathf.Max(0, Health.Value - damage); if (Health.Value <= 0) IsAlive.Value = false; } public void Heal(int amount) { if (!IsServerStarted) return; Health.Value = Mathf.Min(100, Health.Value + amount); } private void UpdateHealthBar(int health) { } private void UpdateNameplate(string name) { } private void OnPlayerDied() { } } ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.