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
ServerNetworkand 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
maxConnectionssensibly; 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:
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);
}
}
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
FindNewOwnersfor objects that do notfollowsClient; otherwise it destroys or persists them.When transferring ownership, the server emits
OwnershipLost/OwnershipGained; clients getOnLoseOwnership/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: -
ServerOnlyfor client → server requests/validation -SingleClientfor private replies -AllClientsInArea/OtherClientsInAreafor local events -AllClientsonly for truly global eventsKeep 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: -
ReliableOrderedfor important or stateful events (spawns, destroys, RPCs). -UnreliableSequencedfor time-series state (movement, aim).Sequence channels: -
SyncSequenceChannel = 10for sync/lite sync streams. -VoiceSequenceChannel = 11reserved (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.Login hot paths; prefer aggregated periodic logs.
Performance Patterns
Avoid per-frame allocations in hotspots: - Reuse
List<NetConnection>and buffers; pool if necessary. - ReuseNetOutgoingMessagewhen 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 serverUpdate()lean; schedule heavier tasks ontimers 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 inOnDestroy()) and persist any needed state in yourOnDestroyNetworkObjector 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
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
VoIPManagerstub; 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.