概述
上一篇掌握了 Netcode for GameObjects 的核心 API。这一篇从 系统架构 的角度出发,讨论一个完整的多人游戏项目应该怎么组织——房间管理、状态同步策略、网络性能优化、以及常见的坑。
1. 多人游戏架构分层
1 2 3 4
| Presentation(表现层) <- Prefabs、动画、特效(客户端本地执行) Network(网络层) <- RPC、NetworkVariable、消息序列化 Game Logic(逻辑层) <- 状态机、游戏规则(服务器权威) Data(数据层) <- 玩家数据、房间状态、排行榜
|
2. 房间管理系统
2.1 房间数据结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| [System.Serializable] public struct RoomData : INetworkSerializable { public FixedString64Bytes RoomName; public FixedString64Bytes HostName; public int MaxPlayers; public int CurrentPlayers; public RoomState State;
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter { serializer.SerializeValue(ref RoomName); serializer.SerializeValue(ref HostName); serializer.SerializeValue(ref MaxPlayers); serializer.SerializeValue(ref CurrentPlayers); serializer.SerializeValue(ref State); } }
public enum RoomState : byte { Waiting, Starting, Playing, Finished }
|
2.2 房间管理器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| public class RoomManager : NetworkBehaviour { public static RoomManager Instance { get; private set; } private readonly Dictionary<ulong, RoomData> _rooms = new(); public NetworkList<RoomData> RoomList => _roomList; private NetworkList<RoomData> _roomList;
private void Awake() { Instance = this; _roomList = new NetworkList<RoomData>(); }
[ServerRpc(RequireOwnership = false)] public void CreateRoomServerRpc(FixedString64Bytes roomName, FixedString64Bytes playerName, ServerRpcParams rpcParams = default) { var senderId = rpcParams.Receive.SenderClientId; var room = new RoomData { RoomName = roomName, HostName = playerName, MaxPlayers = 4, CurrentPlayers = 1, State = RoomState.Waiting }; _rooms[senderId] = room; _roomList.Add(room); } }
|
3. 同步策略
3.1 完全同步(服务器权威)
1 2 3 4 5 6 7 8 9 10 11 12
| public class FullySyncedPlayer : NetworkBehaviour { private NetworkVariable<Vector3> Position = new(); private NetworkVariable<float> Health = new();
[ServerRpc] public void SubmitInputServerRpc(Vector2 moveInput, bool fire) { Move(moveInput); if (fire) Fire(); } }
|
优点:防止作弊。缺点:延迟感明显。
3.2 客户端预测
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| public class PredictedPlayer : NetworkBehaviour { private Vector3 _localPosition;
private void Update() { if (!IsOwner) return;
var move = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical")); _localPosition += move * Time.deltaTime * _speed; transform.position = _localPosition;
SubmitMoveServerRpc(move); }
[ServerRpc] public void SubmitMoveServerRpc(Vector3 move) { transform.position += move * Time.deltaTime * _speed; ConfirmPositionClientRpc(transform.position); }
[ClientRpc] public void ConfirmPositionClientRpc(Vector3 serverPosition) { if (!IsOwner) return; var error = Vector3.Distance(_localPosition, serverPosition); if (error > 0.1f) _localPosition = Vector3.Lerp(_localPosition, serverPosition, 0.5f); } }
|
优点:操作零延迟。缺点:需要实现纠错逻辑。
4. 网络性能优化
4.1 减少带宽
1 2 3 4 5 6 7 8 9
| private float _lastSendTime; private const float SendInterval = 0.05f;
private void FixedUpdate() { if (Time.time - _lastSendTime < SendInterval) return; _lastSendTime = Time.time; SendPositionServerRpc(transform.position); }
|
4.2 增量更新
1 2 3 4 5 6 7 8 9 10 11 12 13
| private Vector3 _lastSentPosition; private const float DeltaThreshold = 0.01f;
private void Update() { if (!IsOwner) return; var currentPos = transform.position; if (Vector3.Distance(currentPos, _lastSentPosition) > DeltaThreshold) { _lastSentPosition = currentPos; SyncPositionServerRpc(currentPos); } }
|
总结
- 三层架构:数据层 + 逻辑层 + 网络层 + 表现层,职责分离
- 房间系统:用 NetworkList 维护房间列表
- 三种同步:完全同步(防作弊)、客户端预测(低延迟)、插值同步(平滑)
- 优化手段:位置增量同步、可变速率、带宽压缩
下一篇将讲解 新版 Input System 深入,从 Input Manager 迁移到现代化的输入处理方案。