问题展示以及源码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
[RequireComponent(typeof(PlayerPawn))]
public class MoveSync:MonoBehaviour
{
private float time = 0;
[SerializeField]
private float SyncInternal = 0.5f;
private PlayerPawn playerPawn;
private void Start()
{
playerPawn= GetComponent<PlayerPawn>();
//playerPawn.OnDirectionChange += SyncMovement;
}
private void Update()
{
time += Time.deltaTime;
if (time > SyncInternal)
{
time -= SyncInternal;
SyncMovement();
}
}
//客户端->服务端 同步
private void SyncMovement()
{
var direction = playerPawn.Direction.ToCC();
var pos = transform.position.ToVec2().ToCC();
MovementInfo movementInfo = new MovementInfo() { Direction= direction, Position = pos };
SyncMovementToServer(movementInfo);
}
private void SyncMovementToServer(MovementInfo movementInfo)
{
Application.Instance.ClientService.ServerCaller.ServerProxy.AskSyncMovement(movementInfo);
}
}
//服务端 ->客户端 同步
public void OnSyncPlayerMove(PlayerMovementInfo playerMovementInfo)
{
//不处理自己的同步
if (playerMovementInfo.PlayerId == DataCenter.Instance.PlayerId)
{
Log.Debug("");
return;
}
var pawn = id2PlayerPawnDict[playerMovementInfo.PlayerId];
pawn.SetPosition(playerMovementInfo.Position.ToUnity());
pawn.SetDirection(playerMovementInfo.Direction.ToUnity());
}
问题观察以及分析
观察上图,我们可以发现玩家操作的角色会存在闪现,角色拉回等现象。
而导致这个问题出现的原因是 我们同时传递了 角色的位置信息 以及 角色的移动方向。
其中角色的位置信息是用于状态的及时更新,而角色的移动方向是用于状态的短期模拟。
观察代码,我们可以知道客户端向服务端同步的间隔是0.5f秒。
虽然移动是连续的,但这里我们传输数据必须是离散的。
因为如果你每时每刻都传输玩家的数据,那必然会占用大量的网络带宽。
但如果同步的频率间隔太长,会导致角色响应迟缓。
角色的移动方向用于模拟,这样我们可以不用一直发数据。
这里我们角色闪现,拉回无疑是同步太慢导致的。
比如角色在转角已经转弯,但是由于数据没有及时同步出去,模拟端由于信息缺失认为角色还在直行。
等到信源端再次同步的时候,就发生了模拟预测和源端数据不一致的现象。
问题解决以及结果展示
由于角色闪现是因为同步频率太少导致的,这里我们可以将角色的同步放到FixedUpdate提高同步的精度,接着提高同步的频率。就可以轻松解决问题。
读者也可以通过反复对比修改前后的源码来推敲细节以及其他可能的解决方案。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
[RequireComponent(typeof(PlayerPawn))]
public class MoveSync:MonoBehaviour
{
private float time = 0;
[SerializeField]
private float SyncInternal = 0.05f;
private PlayerPawn playerPawn;
private void Start()
{
playerPawn= GetComponent<PlayerPawn>();
}
private void FixedUpdate()
{
time += Time.fixedDeltaTime;
if (time > SyncInternal)
{
time -= SyncInternal;
SyncMovement();
}
}
// 客户端->服务端 同步
private void SyncMovement()
{
var direction = playerPawn.Direction.ToCC();
var pos = transform.position.ToVec2().ToCC();
MovementInfo movementInfo = new MovementInfo() { Direction= direction, Position = pos };
SyncMovementToServer(movementInfo);
}
private void SyncMovementToServer(MovementInfo movementInfo)
{
Application.Instance.ClientService.ServerCaller.ServerProxy.AskSyncMovement(movementInfo);
}
}
相关文章
https://www.bilibili.com/video/BV1Qb411h7ua
https://zhuanlan.zhihu.com/p/38468615
https://www.zhihu.com/question/323505671