Best Practices for Developing a Client (with Networked Objects) =============================================================== This guide focuses on building smooth, bandwidth-friendly, and robust **clients** with this networking library (``ClientNetwork``, ``NetworkSync``, ``UCNetwork``). It assumes your game uses **networked objects** for continuous state and **RPCs** for discrete events. Goals ----- - Make the client **feel responsive** (latency hiding via interpolation/prediction). - Keep traffic **lean** (areas, lite sync, narrow RPC targets). - Respect **ownership** (only the owner sends authoritative updates). - Be **resilient** to disconnects, late joins, and object lifecycle changes. - Keep the **UI clear** about connection state and control/ownership. Scene & Setup ------------- - Create a ``Client`` Unity project/scene. - Add a GameObject with ``ClientNetwork`` and your client bootstrap MonoBehaviours. - Optional: add a simple status overlay to reflect callbacks (see below). **Minimal bootstrap** .. code-block:: csharp public class ClientBoot : MonoBehaviour { public ClientNetwork client; void Start() { client.Connect("127.0.0.1", 603, "Student01", "", "Player", 0); } // Status callbacks from UCNetwork void OnNetStatusConnected() => Debug.Log("Connected"); void OnNetStatusDisconnected() => Debug.Log("Disconnected"); } Ownership on the Client ----------------------- - **You may only sync objects you own.** The server enforces this. Violations can result in ownership loss messages. - Handle ownership transitions on every object that matters: .. code-block:: csharp public class PlayerController : MonoBehaviour { bool hasControl; void OnGainOwnership() { hasControl = true; /* enable input, cameras */ } void OnLoseOwnership() { hasControl = false; /* disable input, hand off */ } } Spawning & Prefabs (Resources) ------------------------------ - Instantiate networked objects with **``ClientNetwork.Instantiate``** (not Unity’s). - The **prefab name** must exist in a **Resources/** folder on *every* client. .. code-block:: csharp GameObject avatar = client.Instantiate("PlayerAvatar", spawnPos, Quaternion.identity); - Immediate local instantiate works because the client uses an **ID pool** from the server. - **Tip:** Centralize allowed prefab names in a shared enum or list to avoid typos and spoofing. Destroying ---------- - If you **own** the object, destroy it via the networking layer: .. code-block:: csharp client.Destroy(mySync.GetId()); - If you **don’t own** the object, request via an RPC to the **server**; never call Unity’s ``Destroy`` directly for networked entities. Areas (Interest Management) --------------------------- - Join the areas relevant to the local player to receive only the updates you need: .. code-block:: csharp client.AddToArea(AreaIds.Town); client.RemoveFromArea(AreaIds.Dungeon); - When you enter a new area, the server sends an **initialization snapshot** (instantiates current objects + latest sync). Be ready to instantiate and then render with interpolation (see below). - For per-object scoping (e.g., a pet that should only exist in a dungeon): .. code-block:: csharp client.AddObjectToArea(petSync.GetId(), AreaIds.Dungeon); RPCs (Use Narrow Targets) ------------------------- - Prefer **``ServerOnly``** for client → server intent (validation on server). - For “show to others” actions, prefer **``OtherClientsInArea``** over ``AllClients``: .. code-block:: csharp // Play jump animation on everyone *else* in my area mySync.CallRPC("PlayJump", UCNetwork.MessageReceiver.OtherClientsInArea); - To message a single client, route through the server (client uses ``ServerOnly`` and includes a target client ID as an argument; the server forwards with ``SingleClient``). Sync vs Lite Sync (Minimize Bandwidth) -------------------------------------- - Use **SyncUpdate** when you need to send **transform + data**. - Use **LiteSyncUpdate** when you need **data only** (e.g., ammo, anim state). **Sending extra custom bytes** .. code-block:: csharp // Example: send health as 4 bytes in a LiteSync void SendHealthLite(NetworkSync sync, int health) { byte[] buf = System.BitConverter.GetBytes(health); sync.SendLiteSyncData(buf); // wraps UCNetwork.LiteSyncNetworkData } Client-Side Interpolation (Hide Latency) ---------------------------------------- - Sync messages arrive at discrete intervals. Interpolate between the two most recent snapshots to avoid jitter. .. code-block:: csharp public class NetInterp : MonoBehaviour { Vector3 lastPos, nextPos; Quaternion lastRot, nextRot; float lastT, nextT; // timestamps sent by server or NetTime.Now stamps public void OnSync(Vector3 pos, Quaternion rot, float t) { lastPos = nextPos; lastRot = nextRot; lastT = nextT; nextPos = pos; nextRot = rot; nextT = t; } void Update() { float t = Mathf.InverseLerp(lastT, nextT, (float)Lidgren.Network.NetTime.Now); transform.position = Vector3.Lerp(lastPos, nextPos, t); transform.rotation = Quaternion.Slerp(lastRot, nextRot, t); } } - Keep an **interpolation buffer** (~100–200ms) to smooth irregular arrival times. - For fast movers, add **extrapolation** using last known velocity as a fallback. Input, Prediction & Reconciliation (Optional Pattern) ----------------------------------------------------- - For responsive movement, consider **client-side prediction**: - Locally apply input immediately. - Send input to server via RPC or Sync. - When the server’s authoritative state returns, **reconcile**: snap or smoothly correct small errors. - Keep correction thresholds small; prefer **smooth blending** over hard snaps. Throttling Sends (Don’t Spam) ----------------------------- - Don’t send Sync/RPC every frame. Throttle to a sensible rate (e.g., 10–20 Hz for transforms, higher only if needed). .. code-block:: csharp float syncInterval = 1f / 15f, acc; void Update() { acc += Time.deltaTime; if (!mySync.IsOwner()) return; if (acc >= syncInterval) { acc = 0f; mySync.SendFullSyncData(/* optional extra bytes */); } } Physics Considerations ---------------------- - Let the **owner** drive physics and send states; replicas interpolate. - Use **Rigidbody interpolation** for local visuals if you own the object. - Avoid sending physics every tick; send state on important changes + at a capped rate. UI & Feedback ------------- - Reflect connection state: .. code-block:: csharp void OnNetStatusConnected() { ShowToast("Connected"); } void OnNetStatusDisconnected() { ShowToast("Disconnected, retry?"); } - Reflect ownership on controllable objects (e.g., crosshair color, input lockouts). - Show **area changes** (“Entered: Dungeon 2F”). Error Handling & Resilience --------------------------- - Treat missing objects and late messages as normal in a networked world. Always null-check lookups by network ID. - Handle **duplicate instantiates** defensively (ignore if already spawned). - On disconnect: - Clear transient UI and input. - Optionally offer reconnect flow; upon reconnection, rejoin areas and let the server resync world state. Performance Tips (Client) ------------------------- - Avoid per-frame allocations in hot paths (recycle temp buffers, lists). - Cache component references (``GetComponent`` once). - Pool common networked prefabs to avoid GC and load spikes. - Keep **sequence channels** separate for continuous vs discrete streams (Sync on channel 10 as provided; don’t mix with unrelated high-freq RPCs). Security & Validation (Client Expectations) ------------------------------------------- - Assume the **server will validate** everything; the client should not rely on being trusted. - Never try to send Sync for objects you don’t own. - Send **minimal** intent; let the server compute results (e.g., send “fire” input, not “I hit player 7 for 500 dmg”). Common Patterns (Snippets) -------------------------- **Call an object-scoped RPC to others in area** .. code-block:: csharp // Broadcast a local emote without echoing to myself mySync.CallRPC("PlayEmote", UCNetwork.MessageReceiver.OtherClientsInArea, "wave"); **Send a private request to server** .. code-block:: csharp // Ask server to open a door I don't own client.CallRPC("RequestOpenDoor", UCNetwork.MessageReceiver.ServerOnly, -1, doorNetId); **Join a dungeon and receive state snapshot** .. code-block:: csharp client.AddToArea(AreaIds.Dungeon2F); // server will instantiate existing floor objects for me Summary ------- - Use ``ClientNetwork.Instantiate`` and **NetworkSync** on all networked objects. - **Only owners sync**; replicas interpolate. - Prefer **OtherClientsInArea / SingleClient** over global RPCs. - Use **LiteSync** for cheap data; **throttle** transform Sync. - Join **areas** to receive relevant updates and automatic snapshots. - Design for **latency** (interpolation, optional prediction) and **resilience** (clean handling of disconnects, late joins, and lifecycle events).