帧同步Demo环境初步搭建

最近在研究多人游戏的同步技巧,想要学习帧同步/状态同步相关概念,于是有了个demo。

初步导入

原素材用的是unity官方自带的第三人称视角游戏示例demo,这个demo里实现了人物最基本的运动控制器。

网络模块用的是NetCode

NetCode的官方文档事无巨细地讲了导入和使用,这里就先略过了,只需要了解netcode事先为我们提供了两个属性[ClientRpc]和[ServerRpc]即可。

public override void OnNetworkSpawn()
{
    //client和server连接后的调用
}

[ClientRpc]
void ClientRpc(param Param)
{
    //client收到消息后的函数调用
}

[ServerRpc]
public void ServerRpc(param Param)
{
    //server收到消息后的函数调用
}

netcode主要是维护一系列的NetworkBehaviour,在进行client/server连接的时候,会各自在两端生成一个networkBehaviour gameObject,如果是n个client和1个server连接,那么每个client和server都会有n个network gameObject。有IsClient/IsServer/IsOwner代表是否是客户端/服务器/代表自己的那个network.

客户端和服务器通信只在相同NetworkObjectID的networkBehaviour间传输,通过带ClientRpc和ServerRpc属性的函数进行沟通。

通信原理

如果只是想简单同步,我们只需要获取玩家输入,发送给服务器端,然后服务器端分发给各个客户端。这个第三人称demo里面有一个StarterAssetsInputs类专门管理玩家输入信息:

很简单,那我们就只需要在每帧把这个类信息传到服务器,然后服务器分发给所有客户端就行,具体操作即是:

ThirdPersonController m_selfPlayer = null;
StarterAssetsInputs m_selfInput = null;
public override void OnNetworkSpawn()
{
    if (!IsServer && IsOwner)
    {
        m_selfPlayer = FindSelfPlayer();
        m_selfInput = m_selfPlayer.GetComponent<StarterAssetsInputs>();
        ServerRpc(new StarterAssetsInputsInfo(m_selfInput), m_selfPlayer.GetMainCameraEulerAngles());
    }
}

public override void OnNetworkDespawn()
{
    if (!IsOwner && !IsServer && m_selfPlayer != null)
    {
        m_selfPlayer.DestroySelf();
        m_selfPlayer = null;
    }
}

//寻找自身的player组件
ThirdPersonController FindSelfPlayer()
{
    return GameObject.Find("PlayerArmature").GetComponent<ThirdPersonController>();
}

private void Update()
{
    //每一帧同步player的信息
    if (!IsServer && IsOwner && m_selfPlayer != null)
    {
        ServerRpc(new StarterAssetsInputsInfo(m_selfInput), m_selfPlayer.GetMainCameraEulerAngles());
    }
}

[ClientRpc]
void ClientRpc(StarterAssetsInputsInfo inputInfo, Vector3 mainCameraEulerAngles)
{
    if (m_selfPlayer == null)
    {
        GameObject goPlayer = Instantiate(AssetDatabase.LoadAssetAtPath<GameObject>("Assets//Prefabs//PlayerArmature.prefab"));
        m_selfPlayer = goPlayer.transform.GetComponent<ThirdPersonController>();
    }
    m_selfPlayer.UpdateByInput(inputInfo, mainCameraEulerAngles);
}

[ServerRpc]
public void ServerRpc(StarterAssetsInputsInfo inputInfo, Vector3 mainCameraEulerAngles)
{
    //只用来转发,所有客户端都会在服务器收到同步消息后同步
    ClientRpc(inputInfo, mainCameraEulerAngles);
}

netcode本身可以序列化一些基础类型的信息,比如Vector3,int这种数据类型,如上代码可以直接传输playerPos这种Vector3信息,上述说的input类本身还是比较复杂的,需要走一次netcode官方提供的接口做一次序列化转换,否则无法实现网络传输。

优化

人物转向相关代码有点问题,于是后期对代码进行了改良,将人物控制器代码原来用到单例相关的(比如mainCamera相关参数,实际上这种只能对自己操控的这个人有影响,不能对其他人物也有参数影响)都改为可变参数传到服务器再由服务器做同步。

因为之前处理回调的时候,操纵方的手感是很差的,动作并不是非常丝滑,有时候我一直按着移动键却迟迟没有看到人物移动,原因是我把input数据传到服务器再回传回来时直接覆盖了此时的input数据,导致目前操作被上一次的操作回调覆盖了。应该是input相关的数据依然让它保持客户端本地更新。

desktop 2023-12-10 16-44-58

优化后network代码(新增Despawn,断联时干掉相关player):

ThirdPersonController m_selfPlayer = null;
StarterAssetsInputs m_selfInput = null;
public override void OnNetworkSpawn()
{
    if (!IsServer && IsOwner)
    {
        m_selfPlayer = FindSelfPlayer();
        m_selfInput = m_selfPlayer.GetComponent<StarterAssetsInputs>();
        ServerRpc(new StarterAssetsInputsInfo(m_selfInput), m_selfPlayer.GetMainCameraEulerAngles());
    }
}

public override void OnNetworkDespawn()
{
    if (!IsOwner && !IsServer && m_selfPlayer != null)
    {
        m_selfPlayer.DestroySelf();
        m_selfPlayer = null;
    }
}

//寻找自身的player组件
ThirdPersonController FindSelfPlayer()
{
    return GameObject.Find("PlayerArmature").GetComponent<ThirdPersonController>();
}

private void Update()
{
    //每一帧同步player的信息
    if (!IsServer && IsOwner && m_selfPlayer != null)
    {
        ServerRpc(new StarterAssetsInputsInfo(m_selfInput), m_selfPlayer.GetMainCameraEulerAngles());
    }
}

[ClientRpc]
void ClientRpc(StarterAssetsInputsInfo inputInfo, Vector3 mainCameraEulerAngles)
{
    if (m_selfPlayer == null)
    {
        GameObject goPlayer = Instantiate(AssetDatabase.LoadAssetAtPath<GameObject>("Assets//Prefabs//PlayerArmature.prefab"));
        m_selfPlayer = goPlayer.transform.GetComponent<ThirdPersonController>();
    }
    m_selfPlayer.UpdateByInput(inputInfo, mainCameraEulerAngles);
}

[ServerRpc]
public void ServerRpc(StarterAssetsInputsInfo inputInfo, Vector3 mainCameraEulerAngles)
{
    //只用来转发,所有客户端都会在服务器收到同步消息后同步
    ClientRpc(inputInfo, mainCameraEulerAngles);
}

总结

多人游戏同步相关知识点还是非常多的,以上还只是在低延迟的情况下,要确保玩家在不同网络条件下均能够正常游戏,还需要考虑到网络延迟和抖动,并使用恰当的插值和平移技术,以使得游戏状态在不同客户端上看起来尽量一致。之后对这个demo的扩充就是进一步帧同步。

  • 11
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值