Unity3D 进阶教程 - 16 Netcode for GameObjects

概述

Netcode for GameObjects 是 Unity 官方推出的多人游戏网络库,取代了旧版的 UNET(已废弃)。它基于 客户端-服务器(Client-Server)模型——一个权威服务器处理所有关键逻辑,客户端负责输入和渲染。

本篇覆盖 Netcode 的核心概念:NetworkObject、NetworkVariable、RPC、客户端-服务器同步、玩家权限控制。

1. 核心概念

1.1 客户端-服务器模型

1
2
3
4
5
6
7
8
9
10
11
服务器(权威):
├── 验证玩家输入
├── 更新游戏状态
├── 同步到所有客户端
└── 处理延迟补偿

客户端(非权威):
├── 收集玩家输入
├── 发送给服务器
├── 接收服务器状态
└── 渲染画面

1.2 安装

1
Package Manager -> Unity Registry -> Netcode for GameObjects

2. NetworkObject 与 NetworkVariable

2.1 NetworkObject

任何需要网络同步的 GameObject 必须附加 NetworkObject 组件:

1
2
3
4
5
6
7
8
9
10
11
using Unity.Netcode;

public class Bullet : NetworkBehaviour
{
[ServerRpc]
private void SpawnBulletServerRpc(Vector3 origin, Vector3 direction)
{
var bullet = Instantiate(bulletPrefab, origin, Quaternion.LookRotation(direction));
bullet.GetComponent<NetworkObject>().Spawn();
}
}

2.2 NetworkVariable

自动同步的状态变量:

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
32
public class PlayerHealth : NetworkBehaviour
{
public NetworkVariable<int> Health = new(100,
NetworkVariableReadPermission.Everyone,
NetworkVariableWritePermission.Server);

public NetworkVariable<bool> IsDead = new(false,
NetworkVariableReadPermission.Everyone,
NetworkVariableWritePermission.Server);

private void Awake()
{
Health.OnValueChanged += OnHealthChanged;
}

private void OnHealthChanged(int oldValue, int newValue)
{
UpdateHealthUI(newValue);
}

[ServerRpc]
public void TakeDamageServerRpc(int damage)
{
if (IsDead.Value) return;
Health.Value -= damage;
if (Health.Value <= 0)
{
Health.Value = 0;
IsDead.Value = true;
}
}
}

3. RPC 调用

3.1 ServerRpc(客户端 -> 服务器)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class PlayerController : NetworkBehaviour
{
[ServerRpc]
private void SubmitInputServerRpc(Vector3 movement, bool jump)
{
transform.position += movement * Time.deltaTime * _moveSpeed;
}

private void Update()
{
if (!IsOwner) return;
var move = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"));
if (move != Vector3.zero)
SubmitInputServerRpc(move, Input.GetButtonDown("Jump"));
}
}

3.2 ClientRpc(服务器 -> 客户端)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class GameManager : NetworkBehaviour
{
[ClientRpc]
private void ShowExplosionClientRpc(Vector3 position, float radius)
{
Instantiate(_explosionPrefab, position, Quaternion.identity);
}

[ClientRpc(RequireOwnership = false)]
private void UpdateScoreClientRpc(int playerId, int newScore)
{
ScoreUI.Instance.UpdatePlayerScore(playerId, newScore);
}
}

4. 玩家管理

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 GameManager : NetworkBehaviour
{
[SerializeField] private GameObject _playerPrefab;

public override void OnNetworkSpawn()
{
NetworkManager.Singleton.OnClientConnectedCallback += OnPlayerConnected;
NetworkManager.Singleton.OnClientDisconnectCallback += OnPlayerDisconnected;
}

private void OnPlayerConnected(ulong clientId)
{
if (!IsServer) return;

var spawnPos = new Vector3(Random.Range(-10f, 10f), 0, Random.Range(-10f, 10f));
var player = Instantiate(_playerPrefab, spawnPos, Quaternion.identity);
var networkPlayer = player.GetComponent<NetworkObject>();
networkPlayer.SpawnAsPlayerObject(clientId);
}

private void OnPlayerDisconnected(ulong clientId)
{
if (!IsServer) return;

if (NetworkManager.Singleton.ConnectedClients.TryGetValue(clientId, out var client))
{
client.PlayerObject?.Despawn();
}
}
}

5. 网络位置同步

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class NetworkTransform : NetworkBehaviour
{
private NetworkVariable<Vector3> _networkPosition = new();
[SerializeField] private float _lerpSpeed = 10f;

private void Update()
{
if (IsOwner)
{
if (IsServer)
_networkPosition.Value = transform.position;
}
else
{
transform.position = Vector3.Lerp(
transform.position, _networkPosition.Value, Time.deltaTime * _lerpSpeed);
}
}
}

6. 测试与调试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class NetworkTester : MonoBehaviour
{
private void OnGUI()
{
if (!NetworkManager.Singleton.IsClient && !NetworkManager.Singleton.IsServer)
{
if (GUILayout.Button("Host (Server + Client)"))
NetworkManager.Singleton.StartHost();

if (GUILayout.Button("Client"))
NetworkManager.Singleton.StartClient();
}
}
}

总结

  • NetworkBehaviour 替代 MonoBehaviour,启用网络功能
  • NetworkVariable 自动同步状态,服务器写入、客户端读取
  • ServerRpc 客户端到服务器,ClientRpc 服务器到客户端
  • NetworkObject.Spawn() 在服务器上生成网络对象
  • 位置同步需要插值处理,减少网络延迟影响

下一篇将搭建 多人游戏架构实战,包括房间系统、同步策略和网络性能优化。

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