核心
- 1、我们客户端的时间与服务器的时间应该一致。
- 2、客户端获取时间的流程为:
- 1)客户端发起获取服务器时间;
- 1.1)网络通信时间
- 2)服务器得知客户端获取服务器时间;
- 3)发送服务器当前的时间;
- 3.1)网络通信时间
- 4)客户端获取服务器得之前的时间
思路
- 前提条件:假设客户端—>服务器以及服务器—>客户端两者通信的时间一致。
- 由于客户端获取服务器时间存在网络通信的时间,因此可以:
- 在客户端获取服务器时间时:把当前客户端时间发送到服务器上
- 在服务器拿到客户端时间后:将客户端时间与当前服务器时间打包发送
- 客户端最新获得的服务器返回的时间包(客户端之前时间,服务器之前时间)
- 当前服务器时间的计算公式:(客户端当前时间 - 客户端之前时间)/ 2 + 服务器之前时间;
- 其中 (客户端当前时间 - 客户端之前时间) 为完整的网络通信时间;
部分笔记以及核心代码
- 服务器与客户端时间同步,其中关键为GetServerTime()函数,其中客户端推算服务器的时间为 客户端的当前时间 + ClientServerDelta
- 而ClientServerDelta的计算公式为:
- ClientServerDelta = 当前服务器时间(推断) - 当前的客户端(真实)
- 当前服务器时间(推断)= 服务器RPC获取的当前时间 + 1 / 2 * 当前客户端到服务器再到客户端的同步时间。
- 服务器与客户端通信获取时间用于计算ClientServerDelta的流程为:
- 每次Tick触发同步Tick函数ABlasterPlayerController::CheckTimeSync
- 该函数将当前时间作为参数对服务器RPC调用ServerRequestServerTime_Implementation(float TimeOfClientRequest)
- TimeOfClientRequest 为当前客户端对服务器RPC调用时间
- 在服务器运行RPC函数ABlasterPlayerController::ServerRequestServerTime_Implementation(float TimeOfClientRequest)
- 获取当前服务器的时间,并使用RPC函数对客户端调用ABlasterPlayerController::ClientReportServerTime_Implementation(float TimeOfClientRequest, float TimeServerReceivedClientRequest)
- TimeServerReceivedClientRequest这个为当前服务器的时间
- TimeOfClientRequest为此前客户端时间
- 客户端RPC执行ABlasterPlayerController::ClientReportServerTime_Implementation(float TimeOfClientRequest, float TimeServerReceivedClientRequest)
- 计算客户端调用RPC前后之间的时间差 float RoundTripTime = GetWorld()->GetTimeSeconds() - TimeOfClientRequest;
- 推断当前服务器的时间 float CurrentServerTime = TimeServerReceivedClientRequest + (0.5f * RoundTripTime);
- 计算当前的ClientServerDelta = CurrentServerTime - GetWorld()->GetTimeSeconds();
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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67void ABlasterPlayerController::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
SetHUDTime();
CheckTimeSync(DeltaTime);
}
float ABlasterPlayerController::GetServerTime()
{
// 在服务器调用则使用当前时间;
// 在客户端调用则显示 当前时间 + 服务器与客户端延迟通信
if (HasAuthority()) return GetWorld()->GetTimeSeconds();
else return GetWorld()->GetTimeSeconds() + ClientServerDelta;
}
void ABlasterPlayerController::SetHUDTime()
{
uint32 SecondsLeft = FMath::CeilToInt(MatchTime - GetServerTime());
if (CountdownInt != SecondsLeft)
{
SetHUDMatchCountdown(MatchTime - GetServerTime());
}
CountdownInt = SecondsLeft;
}
// 每Tick检测时间同步的函数
void ABlasterPlayerController::CheckTimeSync(float DeltaTime)
{
TimeSyncRunningTime += DeltaTime;
if (IsLocalController() && TimeSyncRunningTime > TimeSyncFrequency)
{
ServerRequestServerTime(GetWorld()->GetTimeSeconds());
TimeSyncRunningTime = 0.f;
}
}
// 服务器RPC可靠传输标签
UFUNCTION(Server, Reliable)
void ServerRequestServerTime(float TimeOfClientRequest);
// 客户端RPC可靠传输标签
UFUNCTION(Client, Reliable)
void ClientReportServerTime(float TimeOfClientRequest, float TimeServerReceivedClientRequest);
void ABlasterPlayerController::ServerRequestServerTime_Implementation(float TimeOfClientRequest)
{
// 客户端RPC调用服务器运行函数,获取服务器的当前时间
float ServerTimeOfReceipt = GetWorld()->GetTimeSeconds();
ClientReportServerTime(TimeOfClientRequest, ServerTimeOfReceipt);
}
void ABlasterPlayerController::ClientReportServerTime_Implementation(float TimeOfClientRequest, float TimeServerReceivedClientRequest)
{
// 服务器调用客户端RPC函数,计算当前服务器到客户端TimeServerReceivedClientRequest以及客户端到服务器的时间TimeOfClientRequest,
float RoundTripTime = GetWorld()->GetTimeSeconds() - TimeOfClientRequest;
float CurrentServerTime = TimeServerReceivedClientRequest + (0.5f * RoundTripTime);
ClientServerDelta = CurrentServerTime - GetWorld()->GetTimeSeconds();
}
// 通知脚本我们被分配了一个有效的玩家,类似初始化
void ABlasterPlayerController::ReceivedPlayer()
{
Super::ReceivedPlayer();
if (IsLocalController())
{
ServerRequestServerTime(GetWorld()->GetTimeSeconds());
}
}
- 每次Tick触发同步Tick函数ABlasterPlayerController::CheckTimeSync