文章目录
- 前言
- 添加相机
- 玩家添加对应组件
- 服务端权威(server authoritative)
- 客户端权威(client authoritative)
- 服务端同步位置
- 阅读与理解`PlayerTransformSync.cs`
- NetworkVariable
- UploadTransform
- SyncTransform
- 后话
前言
承接上篇,在Player上添加了移动脚本之后,还得同步每个玩家的位置。
添加相机
- 在
Main Camera
上添加一个CinemachineBrain
组件 - 新建一个空物体
Camera
,添加CinemachineVirtualCamera
与CinemachineCollider
这两个组件
先不手动绑定玩家,等后面在脚本控制摄像机的跟随对象。
玩家添加对应组件
- 确保玩家的预制体添加了
碰撞体
与其他两个Network
组件
在NetworkTransform
组件上选择要同步的轴,这里我只选择同步Position
与Rotation
的XYZ轴
服务端权威(server authoritative)
到目前位置,玩家位置已经是确实同步到了,但是还有一点是你移动的时候会把全部带有PlayerMove
脚本的角色都移动,下面要针对自己的角色才移动。所以得修改一下PlayerMove.cs
这个脚本
using System;
using Cinemachine;
using Cinemachine.Utility;
using UnityEngine;
using Unity.Netcode;
public class PlayerMove : NetworkBehaviour
{
public float Speed;
public float VelocityDamping;
public float JumpTime;
public enum ForwardMode
{
Camera,
Player,
World
};
public ForwardMode InputForward;
public bool RotatePlayer = true;
public Action SpaceAction;
public Action EnterAction;
Vector3 m_currentVleocity;
float m_currentJumpSpeed;
float m_restY;
private void Start()
{
if (IsOwner)
{
GameObject.Find("===Camera===/Camera").GetComponent<CinemachineVirtualCamera>().Follow = transform;
}
}
private void Reset()
{
Speed = 5;
InputForward = ForwardMode.Camera;
RotatePlayer = true;
VelocityDamping = 0.5f;
m_currentVleocity = Vector3.zero;
JumpTime = 1;
m_currentJumpSpeed = 0;
}
private void OnEnable()
{
m_currentJumpSpeed = 0;
m_restY = transform.position.y;
SpaceAction -= Jump;
SpaceAction += Jump;
}
void Update()
{
if (!IsOwner) return;
#if ENABLE_LEGACY_INPUT_MANAGER
Vector3 fwd;
switch (InputForward)
{
case ForwardMode.Camera:
fwd = Camera.main.transform.forward;
break;
case ForwardMode.Player:
fwd = transform.forward;
break;
case ForwardMode.World:
default:
fwd = Vector3.forward;
break;
}
fwd.y = 0;
fwd = fwd.normalized;
if (fwd.sqrMagnitude < 0.01f)
return;
Quaternion inputFrame = Quaternion.LookRotation(fwd, Vector3.up);
Vector3 input = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"));
input = inputFrame * input;
var dt = Time.deltaTime;
var desiredVelocity = input * Speed;
var deltaVel = desiredVelocity - m_currentVleocity;
m_currentVleocity += Damper.Damp(deltaVel, VelocityDamping, dt);
transform.position += m_currentVleocity * dt;
if (RotatePlayer && m_currentVleocity.sqrMagnitude > 0.01f)
{
var qA = transform.rotation;
var qB = Quaternion.LookRotation(
(InputForward == ForwardMode.Player && Vector3.Dot(fwd, m_currentVleocity) < 0)
? -m_currentVleocity
: m_currentVleocity);
transform.rotation = Quaternion.Slerp(qA, qB, Damper.Damp(1, VelocityDamping, dt));
}
// Process jump
if (m_currentJumpSpeed != 0)
m_currentJumpSpeed -= 10 * dt;
var p = transform.position;
p.y += m_currentJumpSpeed * dt;
if (p.y < m_restY)
{
p.y = m_restY;
m_currentJumpSpeed = 0;
}
transform.position = p;
if (Input.GetKeyDown(KeyCode.Space) && SpaceAction != null)
SpaceAction();
if (Input.GetKeyDown(KeyCode.Return) && EnterAction != null)
EnterAction();
#else
InputSystemHelper.EnableBackendsWarningMessage();
#endif
}
public void Jump()
{
m_currentJumpSpeed += 10 * JumpTime * 0.5f;
}
}
编译构建之后发现可以正常使用,但是还有一个问题就是,只能是Server
端可以移动,Client
端没办法移动,这是一个BUG吗?显然不是。
客户端权威(client authoritative)
因为NetworkTransform
默认是服务端的权威验证,客户端没办法更新自己的位置,只能通过告知服务器我的位置更新了,然后服务器在告诉别人他的位置更新了。
如果想用客户端权威
就需要添加ClientNetworkTransform
来代替NetworkTransform
这个组件
去包管理器添加Git URL
包,https://github.com/Unity-Technologies/com.unity.multiplayer.samples.coop.git?path=/Packages/com.unity.multiplayer.samples.coop#main
在Player
上添加ClientNetworkTransform
,删除NetworkTransform
其他保持不变,编译运行就可以实现客户端移动了。
服务端同步位置
新建个脚本PlayerTransformSync.cs
,挂载到Player
上。移除ClientNetworkTransform
PlayerTransformSync.cs
:
using Unity.Netcode;
using UnityEngine;
public class PlayerTransformSync : NetworkBehaviour
{
private NetworkVariable<Vector3> _syncPos = new();
private NetworkVariable<Quaternion> _syncRota = new();
private void Update()
{
if (IsLocalPlayer)
{
UploadTransform();
}
}
private void FixedUpdate()
{
if (!IsLocalPlayer)
{
SyncTransform();
}
}
private void SyncTransform()
{
transform.position = _syncPos.Value;
transform.rotation = _syncRota.Value;
}
private void UploadTransform()
{
if (IsServer)
{
_syncPos.Value = transform.position;
_syncRota.Value = transform.rotation;
}
else
{
UploadTransformServerRpc(transform.position, transform.rotation);
}
}
[ServerRpc]
private void UploadTransformServerRpc(Vector3 position, Quaternion rotation)
{
_syncPos.Value = position;
_syncRota.Value = rotation;
}
}
一般情况下,都是用的服务器权威这种方式做位置同步。
阅读与理解PlayerTransformSync.cs
该脚本可分为四部分去理解:
- 创建网络同步字段
- 如果是主机就不需要向
Server
发送信息可直接同步 - 如果是客户则需要向
Server
发送信息请求同步 - 同步其他人的位置信息
NetworkVariable
通过NetworkVariable
创建两个网络同步的字段,一个同步position
,另一个同步rotation
private NetworkVariable<Vector3> _syncPos = new();
private NetworkVariable<Quaternion> _syncRota = new();
然后本地玩家通过UploadTransform
方法,上传自己的位置信息,这里又有两种情况
- 为主机时
- 为客户时
UploadTransform
为主机时,直接同步Transform
即可
为客户时,向服务器发送信息,请求同步Transform
private void UploadTransform()
{
if (IsServer)
{
_syncPos.Value = transform.position;
_syncRota.Value = transform.rotation;
}
else
{
UploadTransformServerRpc(transform.position, transform.rotation);
}
}
[ServerRpc]
private void UploadTransformServerRpc(Vector3 position, Quaternion rotation)
{
_syncPos.Value = position;
_syncRota.Value = rotation;
}
SyncTransform
我自己上传位置就是在同步,别人就把位置信息同步到transform。
在Update()
与FixedUpdate()
可以确保优先级,先同步自己的,在同步他人的。
private void Update()
{
if (IsLocalPlayer)
{
UploadTransform();
}
}
private void FixedUpdate()
{
if (!IsLocalPlayer)
{
SyncTransform();
}
}
private void SyncTransform()
{
transform.position = _syncPos.Value;
transform.rotation = _syncRota.Value;
}
后话
这次的位置同步教程是服务端
与客户端
直接的基础通信,需要知道用法,还有搞清楚服务端与客户端之间的逻辑才是本次教程最大的重点。
NetworkVariable
这个用于字段网络同步有很大帮助,[ServerRpc]
与[ClientRpc]
之间的通信以后会经常用到,后面博文会继续深入讲解。