Unity3D 进阶教程 - 17 多人游戏架构实战

概述

上一篇掌握了 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 迁移到现代化的输入处理方案。

ByteFisher
分享编程技术 · 记录钓鱼乐趣
扫码关注
▸ 扫码关注 ◂
分享: