Best Practices for Developing a Server ====================================== This guide collects practical patterns and guardrails for building an authoritative game server using this library (Lidgren-based, Unity-hosted). It assumes you’re working with ``ServerNetwork`` / ``ClientNetwork``, ``UCNetwork``, and the ``NetworkSync`` component. Goals ----- - Keep the **server authoritative** over game state. - Minimize bandwidth via **areas**, **lite sync**, and targeted RPCs. - Validate all client input; never trust the client. - Make behavior **predictable** and **observable** (logging/metrics). - Design for **disconnects**, **ownership transfer**, and **hot joins**. Project & Scene Structure ------------------------- - **One server scene, one root**: Create a dedicated “Server” scene with a single GameObject that has ``ServerNetwork`` and your server logic MonoBehaviours. - **Headless build** (preferred): Run the Unity server as a headless build on your host. (Editor play works for development, but headless is closer to prod.) - **No physics authority on clients**: Simulate authoritative physics on the server; clients render/interpolate. Server Startup & Config ----------------------- ``ServerNetwork`` config is created in ``Awake()``: - **App ID**: ``NetPeerConfiguration("UpsilonCircuit")`` — keep this stable; use a new string if you fork the protocol. - **Port**: Default is ``UCNetwork.port`` (603). Expose a CLI arg or config file if you need variants. - **Max connections**: Set ``maxConnections`` sensibly; reject overflow cleanly. - **Connection approval**: Already enabled; you must implement the callback. Example: connection approval hook you own ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ``ServerNetwork`` sends your script a ``ConnectionRequestInfo`` via ``SendMessage("ConnectionRequest", data)``. Approve/deny explicitly: .. code-block:: csharp public class AuthGate : MonoBehaviour { public ServerNetwork server; // Called by ServerNetwork via SendMessage void ConnectionRequest(ServerNetwork.ConnectionRequestInfo info) { bool ok = Validate(info.username, info.password, info.clientType, info.uniqueId, info.ip); if (ok) server.ConnectionApproved((long)info.id); else server.ConnectionDenied((long)info.id); } bool Validate(string user, string pass, string type, ulong uid, string ip) { // TODO: check banlists, versions, tokens, rate limits, etc. return !string.IsNullOrEmpty(user); } } Authoritative Mindset --------------------- - **All state changes** must be **validated** server-side. - **Never accept** client SyncUpdate for objects they do not own (the server already enforces this and replies with ``OwnershipLost``). - Prefer **server-driven RPCs** to inform clients of results rather than letting clients decide outcomes. Areas (Interest Management) --------------------------- - Use **areas** to send updates **only** to relevant clients. - Joining an area triggers a **state snapshot** via ``SendInitializationData`` (Instantiate + latest Sync/LiteSync). - Keep areas **coarse** (rooms/floors/zones) rather than tiny cells to avoid bookkeeping overhead. - For features like **team chat**, put players in **overlapping areas** (location area + team area) and target RPCs to that area. Ownership & Object Lifecycle ---------------------------- - **Default ownership**: - Server-spawned → server owns. - Client-spawned → that client owns (server confirms). - On **disconnect**: the server calls ``FindNewOwners`` for objects that **do not** ``followsClient``; otherwise it destroys or persists them. - When transferring ownership, the server emits ``OwnershipLost`` / ``OwnershipGained``; clients get ``OnLoseOwnership`` / ``OnGainOwnership``. - Treat **scene objects** (with ``NetworkSync``) as server-owned, persistent simulation elements. ID Allocation & Spawning ------------------------ - Server **issues ID ranges** to each client (default block = 500). - Immediate local instantiate is possible on the client while preserving **global uniqueness**. - If your game is server-spawn-heavy, prefer **server-created** objects to centralize authority and avoid client-side abuse. RPCs: Targeting & Design ------------------------ - Pick the **narrowest recipient**: - ``ServerOnly`` for client → server requests/validation - ``SingleClient`` for private replies - ``AllClientsInArea`` / ``OtherClientsInArea`` for local events - ``AllClients`` only for truly global events - **Keep payloads small**. Send identifiers, not blobs. - **Whitelist** function names / validate arguments server-side. Reflection is powerful—don’t allow arbitrary method reachability on sensitive scripts. - Don’t spam RPCs in ``Update()``; use **Sync** or **LiteSync** for frequent data. Sync & LiteSync --------------- - Use **SyncUpdate** for position/rotation + custom bytes. - Use **LiteSyncUpdate** for **custom bytes only** (animation state, ammo count, cooldowns). - **Rate-limit** and **batch** where possible; prioritize objects near players. - Clients should **interpolate** to hide latency; server sends the ground truth. Delivery, Channels & Reliability -------------------------------- - **Delivery methods**: - ``ReliableOrdered`` for important or stateful events (spawns, destroys, RPCs). - ``UnreliableSequenced`` for time-series state (movement, aim). - **Sequence channels**: - ``SyncSequenceChannel = 10`` for sync/lite sync streams. - ``VoiceSequenceChannel = 11`` reserved (voice chat is stubbed / optional). - Don’t overload a single channel with unrelated high-frequency traffic. Validation, Security & Abuse Prevention --------------------------------------- - **Sanity-check** all client input: - Is the sender **owner** of the object they are syncing? - Is the **prefab name** allowed? (Use a whitelist.) - Are **area transitions** legal? (Prevent “teleporting” by spoof.) - Is the **RPC** allowed for this GameObject? - **Rate-limit** noisy actions per-connection (RPCs, spawns, chat). - **Disconnect** on repeated protocol violations. - Add **version checks** (client build/protocol) in connection approval. - Implement **admin kick/ban** tools using ``serverNetwork.Kick(clientId)``. Logging, Metrics & Observability -------------------------------- - Enable file logging: ``server.EnableLogging("log.txt")``. - Use existing logs for **RPC counts**, **message types**, and **per-client** traffic stats. - Expose a simple console/endpoint to emit ``GetStatsText()`` for operational insight (connections, objects, areas, ownership lists). - Avoid excessive ``Debug.Log`` in hot paths; prefer **aggregated** periodic logs. Performance Patterns -------------------- - **Avoid per-frame allocations** in hotspots: - Reuse ``List`` and buffers; pool if necessary. - Reuse ``NetOutgoingMessage`` when possible (create, write, send, recycle). - **Batch** operations: - Build a single outgoing message for many recipients instead of per-client duplicates when feasible. - **Guard your Update()**: - The library’s ``UCNetwork.Update()`` processes inbound queues. - Keep your own server ``Update()`` lean; schedule heavier tasks on timers or coroutines. - **Tick consistency**: - Consider a fixed simulation step for server-side gameplay systems. - Translate to client-friendly “visual ticks” via interpolation. Clean Shutdown & Fault Handling ------------------------------- - On shutdown: call ``connection.Shutdown("Peace out")`` (already done in ``OnDestroy()``) and **persist** any needed state in your ``OnDestroyNetworkObject`` or related hooks. - Be resilient to **mid-flight disconnects**: - Always wrap ownership reassignment and area cleanup in null/exists checks. - Protect persistence calls (e.g., hooks like ``PersistInactiveSavedObject`` / ``UpdateSaveObject``) with try/catch and minimal I/O on the main thread. Example: Minimal Server Orchestration ------------------------------------- .. code-block:: csharp public class GameServer : MonoBehaviour { public ServerNetwork server; // Fired by ServerNetwork void OnClientConnected(long clientId) { Debug.Log($"Client connected: {clientId}"); // Place in default area server.AddToArea(clientId, 1); // e.g., Town } // Fired by ServerNetwork void OnClientDisconnected(long clientId) { Debug.Log($"Client disconnected: {clientId}"); // Ownership transfers & cleanup are handled by ServerNetwork } // RPC from clients: server validates and reroutes public void RequestOpenDoor(int doorNetId) { if (!ValidateDoor(doorNetId)) return; // Broadcast to area only var areaIds = server.GetNetObjById(doorNetId)?.areaIds; if (areaIds != null && areaIds.Count > 0) server.CallRPCToArea("OpenDoor", UCNetwork.MessageReceiver.AllClientsInArea, areaIds, doorNetId); } bool ValidateDoor(int netId) { var obj = server.GetNetObjById(netId); return obj != null && obj.prefabName == "Door"; } } Testing & Tooling ----------------- - Create **simulated clients** that spam typical actions at realistic rates. - Add **protocol fuzzers** to try bad arguments, wrong ownership, and invalid area transitions; confirm the server rejects them gracefully. - Use **loopback testing** first, then LAN, then WAN to evaluate latency, packet loss, and bandwidth headroom. Voice Chat (Optional / Stubbed) ------------------------------- - The codebase includes **VoiceData** message handling and a ``VoIPManager`` stub; treat this as **opt-in** and complete it only if needed. It should use separate channels and its own rate limiting. Summary ------- A reliable server with this library is **authoritative, minimal, and selective**. Validate everything, send only what’s needed (areas, lite sync, narrow RPC targets), and build strong observability. Your clients will be smoother, your bandwidth lower, and your game harder to cheat.