399 lines
15 KiB
Lua
Raw Permalink Normal View History

2025-01-04 23:00:19 +08:00
---@class MovingActorsManager
---@field FrameTime float
---@field Frame float
---@type MovingActorsManager
local MovingActorsManager = {};
---@type float 一秒钟发多少次 RPC
MovingActorsManager.Frame = 3.;
MovingActorsManager.FrameTime = 1 / MovingActorsManager.Frame;
---@type table<string, AActor> 所有需要移动的 Actor
MovingActorsManager.ActorMap = {};
---@type table<string, MovingActorInfo> 同步用的表
MovingActorsManager.ActorNameMap = { };
---@type boolean 是否启用移动
MovingActorsManager.EnableMoving = false;
---@type boolean 是否需要跟服务器校准
MovingActorsManager.bAdjustServer = false;
---@type AActor 拥有者
MovingActorsManager.Owner = nil;
---@type table<string, float> 缓存服务器注册但是客户端未注册的 Actor
MovingActorsManager.CacheActorInfo = {};
---@type float 服务器客户端延迟,默认值较大,然后获取的值会比该值小
MovingActorsManager.Delay = 25;
MovingActorsManager.IsAlready = false;
--- 初始化,客户端,服务器同时进行
---@param InOwner AActor
function MovingActorsManager:Init(InOwner)
--- 计算客户端延迟
self.Owner = InOwner;
-- 直接开启
self.EnableMoving = true;
-- 创建一个 BP_MoveActorManager等待同步之后再一起注册需要保证是同步的
if not self:HasAuthority() then
local CurrTime = KismetSystemLibrary.GetGameTimeInSeconds(self.Owner)
local PC = STExtraGameplayStatics.GetFirstPlayerController(UGCGameSystem.GameState);
self:ClientSendRPC("AllReady", PC.PlayerKey, CurrTime);
end
end
MovingActorsManager.ClientTimes = {}
---@param InTime float 客户端准备就绪的时间
function MovingActorsManager:AllReady(InPlayerKey, InTime)
if self:HasAuthority() then
local CurrTime = KismetSystemLibrary.GetGameTimeInSeconds(self.Owner);
local Time = CurrTime - InTime;
UGCLogSystem.Log("[MovingActorsManager:ClientInit] 当前服务器,客户端延迟:%s", CurrTime - InTime);
self.ClientTimes[InPlayerKey] = Time;
self:ServerSendRPC("AllReady", nil, self.ClientTimes);
self.IsAlready = true;
else
self.ClientTimes = InTime;
local PC = STExtraGameplayStatics.GetFirstPlayerController(UGCGameSystem.GameState);
local Time = self.ClientTimes[PC.PlayerKey]
self.Delay = Time;
self.IsAlready = true;
end
end
---@param InActor AActor
---@param InDelay float
---@param IsMove bool
---@param InDir FVector
---@param InRot FRotator
function MovingActorsManager:RegisterMovingActorByMoveDirection(InActor, InDelay, IsMove, InDir, InRot)
if not UE.IsValid(InActor) then return false; end
local Name = UE.GetName(InActor);
local InActorPos = VectorHelper.ToLuaTable(InActor:K2_GetActorLocation());
local InActorRot = VectorHelper.RotToLuaTable(InActor:K2_GetActorRotation());
local CurrTime = KismetSystemLibrary.GetGameTimeInSeconds(self.Owner);
if self:HasAuthority() then
-- 判断当前是否客户端能否注册,不能注册,返回
local ActualTime = CurrTime - self.Delay - 0.5;
if ActualTime < 0 then return false; end
end
---@type MovingActorInfo
local Info = {
Delay = InDelay,
StartPos = InActorPos,
StartRot = InActorRot,
EndPos = nil,
CurrPos = VectorHelper.ToLuaTable(InActorPos),
DeltaPos = nil, -- 实时计算结果
StartMoveTime = InDelay + CurrTime,
TotalMoveTime = 0, -- 实时计算结果
MoveTime = 0,
IsMove = IsMove,
WaitServerMove = false,
MoveDirection = InDir,
Rotation = InRot,
Func = nil,
};
if self.ActorMap[Name] ~= nil then
Info.StartMoveTime = self.ActorNameMap[Name].StartMoveTime;
Info.WaitServerMove = true;
else
self.ActorMap[Name] = InActor;
end
self.ActorNameMap[Name] = Info;
if self:HasAuthority() then
local TimeTable = {
ServerTime = CurrTime;
ServerStartTime = Info.StartMoveTime;
Delay = InDelay;
MoveDir = Info.MoveDirection,
Rotation = Info.Rotation
}
UGCLogSystem.LogTree(string.format("[MovingActorsManager:RegisterMovingActor] TimeTable = "), TimeTable);
self:ServerSendRPC("ClientRegisterMovingActor", InActor, TimeTable);
else
-- 说明当前传过来了
if self.CacheActorInfo[Name] ~= nil then
self.ActorNameMap[Name].StartMoveTime = self.CacheActorInfo[Name].ClientMoveTime;
self.CacheActorInfo[Name] = nil;
self.ActorNameMap[Name].WaitServerMove = true; -- 此时已经不准了
UGCLogSystem.Log("[MovingActorsManager:RegisterMovingActor] 当前 Actor: %s 的移动是不准确的了", Name);
end
end
end
--- 通过控制判断当前移动,客户端服务器一起注册,然后一起执行
--- 注意:
--- 1.如果是在一开始就需要注册的:一定要先让服务器先注册(什么时候注册无所谓,但是要至少提前大约 23-25 秒注册),客户端后注册。且开始的时候一定要有延迟,以服务器为准
--- 2.如果是在后面注册的,那么不用担心,服务器比客户端提前 2-3 秒即可。且延迟可以随意加;还是以服务器为准。
--- S & C
---@param InActor AActor
---@param InDelay float
---@param Func fun(Actor: AActor, InDeltaTime:float):bool, FVector, FRotator return是否移动移动方向移动旋转是否显示 移动函数,可以每帧进行控制,但是最好不要这么做,因为每改变一次,都会发一次 RPC
function MovingActorsManager:RegisterMovingActor(InActor, InDelay, Func)
if not UE.IsValid(InActor) then return false; end
local Name = UE.GetName(InActor);
local InActorPos = VectorHelper.ToLuaTable(InActor:K2_GetActorLocation());
local InActorRot = VectorHelper.RotToLuaTable(InActor:K2_GetActorRotation());
local CurrTime = KismetSystemLibrary.GetGameTimeInSeconds(self.Owner);
-- 在客户端上进行一下处理
--if self:HasAuthority() then
-- -- 判断当前是否客户端能否注册,不能注册,返回
-- local ActualTime = CurrTime - self.Delay - 0.5;
-- if ActualTime < 0 then return false; end
--end
---@type MovingActorInfo
local Info = {
Delay = InDelay,
StartPos = InActorPos,
StartRot = InActorRot,
EndPos = nil,
CurrPos = VectorHelper.ToLuaTable(InActorPos),
DeltaPos = nil, -- 实时计算结果
StartMoveTime = InDelay + CurrTime,
TotalMoveTime = 0, -- 实时计算结果
MoveTime = 0,
IsMove = false,
WaitServerMove = false,
MoveDirection = nil,
Rotation = nil,
--IsShow = true,
Func = Func,
};
-- 此时是客户端先注册了,一定是正确的
if self.ActorMap[Name] ~= nil then
Info.StartMoveTime = self.ActorNameMap[Name].StartMoveTime;
UGCLogSystem.Log("[MovingActorsManager:RegisterMovingActor] zhixing")
Info.WaitServerMove = true;
end
self.ActorMap[Name] = InActor;
self.ActorNameMap[Name] = Info;
UGCLogSystem.LogTree("[MovingActorsManager:RegisterMovingActor] self.ActorNameMap = ", self.ActorNameMap);
if self:HasAuthority() then
local TimeTable = {
ServerTime = CurrTime;
ServerStartTime = Info.StartMoveTime;
Delay = InDelay;
}
UGCLogSystem.LogTree(string.format("[MovingActorsManager:RegisterMovingActor] TimeTable = "), TimeTable);
self:ServerSendRPC("ClientRegisterMovingActor", InActor, TimeTable);
else
-- 说明当前传过来了
if self.CacheActorInfo[Name] ~= nil then
self.ActorNameMap[Name].StartMoveTime = self.CacheActorInfo[Name].ClientExecTime;
self.ActorNameMap[Name].WaitServerMove = true; -- 此时已经不准了
self.CacheActorInfo[Name] = nil;
UGCLogSystem.Log("[MovingActorsManager:RegisterMovingActor] 当前 Actor: %s 的移动是不准确的了", Name);
end
end
return true;
end
--- C 客户端注册移动 Actor
---@param InTimeTable table 服务器执行时间
---@param InActor AActor 注册的 Actor
function MovingActorsManager:ClientRegisterMovingActor(InActor, InTimeTable)
local Name = UE.GetName(InActor);
local CurrTime = KismetSystemLibrary.GetGameTimeInSeconds(self.Owner);
UGCLogSystem.LogTree(string.format("[MovingActorsManager:ClientRegisterMovingActor] InTimeTable = "), InTimeTable)
--- 说明还没有值
if self.ActorMap[Name] == nil then
self.ActorMap[Name] = InActor;
self.ActorNameMap[Name] = {};
end
self.ActorNameMap[Name].StartMoveTime = InTimeTable.ServerStartTime - self.Delay + self:GetPing();
self.ActorNameMap[Name].WaitServerMove = true;
self.CacheActorInfo[Name] = {
ExecTime = InTimeTable.ServerStartTime,
ServerTime = InTimeTable.ServerTime;
ClientExecTime = InTimeTable.ServerStartTime - self.Delay + self:GetPing();
};
end
--- S & C
---@param InActor AActor 取消注册的 Actor
---@param RemoveCompletely boolean 是否是彻底取消
function MovingActorsManager:UnregisterMovingActor(InActor, RemoveCompletely)
local Name = UE.GetName(InActor);
UGCLogSystem.Log("[MovingActorsManager:UnregisterMovingActor] Actor: %s, 是否完全移除?:%s", Name, tostring(RemoveCompletely));
local Info = self.ActorNameMap[Name];
if Info ~= nil then
Info.WaitServerMove = false;
end
if RemoveCompletely then
self.ActorNameMap[Name] = nil;
self.ActorMap[Name] = nil;
end
if self:HasAuthority() then
self:ServerSendRPC("UnregisterMovingActor", InActor, RemoveCompletely);
end
end
---@param InDeltaTime float 当前帧执行的时间
function MovingActorsManager:Tick(InDeltaTime)
self:MoveAllActors1(InDeltaTime);
-- 同步位置
self:SyncLocation(InDeltaTime);
end
MovingActorsManager.TempSyncData = {
CountTime = 0.;
};
---@param InDeltaTime float
function MovingActorsManager:SyncLocation(InDeltaTime)
if not self.bAdjustServer then return end
if not self:HasAuthority() then return end
if table.isEmpty(self.ActorMap) then return end
if not self.EnableMoving then return end
if not self.IsAlready then return end
local CurrTime = KismetSystemLibrary.GetGameTimeInSeconds(self.Ownere);
self.TempSyncData.CountTime = self.TempSyncData.CountTime + InDeltaTime;
if self.TempSyncData.CountTime > self.FrameTime then
self.TempSyncData.CountTime = math.fmod(self.TempSyncData.CountTime, self.FrameTime);
-- 执行,组装 table
local Table = {};
for Name, Info in pairs(self.ActorNameMap) do
Table[Name] = {
Location = Info.CurrPos,
Rotation = Info.CurrRot;
ServerTime = CurrTime,
}
end
-- 发送过去
self:ServerSendRPC("Client_SyncLocation", Table);
end
end
function MovingActorsManager:Client_SyncLocation(InTable)
if table.isEmpty(self.ActorNameMap) then
return;
end
local CurrTime = KismetSystemLibrary.GetGameTimeInSeconds(self.Owner);
UGCLogSystem.LogTree(string.format("[MovingActorsManager:Client_SyncLocation] 同步, InTable = "), InTable)
-- 对比当前的位置
UGCLogSystem.LogTree(string.format("[MovingActorsManager:Client_SyncLocation] 客户端时间:%s, self.ActorNameMap = ", tostring(CurrTime)), self.ActorNameMap);
--UGCLogSystem.Log("[MovingActorsManager:Client_SyncLocation] Server - Client = %s", tostring(InTable.ServerTime - CurrTime));
end
MovingActorsManager.CurrMoveTime = 0.;
MovingActorsManager.bServerEverTick = true;
--- 重置一个 Actor
--- S
function MovingActorsManager:ResetActor(InActor)
local Name = UE.GetName(InActor);
local Actor = self.ActorMap[Name]
if Actor == nil then return end
Actor:K2_SetActorLocation(self.ActorNameMap[Name].StartPos);
Actor:K2_SetActorRotation(self.ActorNameMap[Name].StartRot);
self.ActorNameMap[Name].CurrPos = VectorHelper.ToLuaTable(self.ActorNameMap[Name].StartPos)
self.ActorNameMap[Name].CurrRot = VectorHelper.RotToLuaTable(self.ActorNameMap[Name].StartRot)
if self:HasAuthority() then
self:ServerSendRPC("ResetActor", InActor);
end
end
---@param InDeltaTime float
function MovingActorsManager:MoveAllActors1(InDeltaTime)
--UGCLogSystem.Log("[MovingActorsManager:MoveAllActors1] self.EnableMoving = %s, self.IsAlready = %s", tostring(self.EnableMoving), tostring(self.IsAlready));
if not self.EnableMoving then return end
if not self.IsAlready then return end
local CurrTime = KismetSystemLibrary.GetGameTimeInSeconds(self.Owner);
local NeedRemoveActors = {};
--UGCLogSystem.LogTree(string.format("[MovingActorsManager:MoveAllActors1] self.ActorNameMap ="), self.ActorNameMap)
for Name, Info in pairs(self.ActorNameMap) do
local Actor = self.ActorMap[Name];
if not UE.IsValid(Actor) then
NeedRemoveActors[#NeedRemoveActors + 1] = Name;
else
if Info.Func ~= nil and type(Info.Func) == 'function' then
local IsMove, MoveDir, MoveRotator = Info.Func(Actor, InDeltaTime);
if MoveRotator ~= nil and (MoveRotator.Pitch ~= nil or MoveRotator.Roll ~= nil or MoveRotator.Yaw ~= nil) then
-- 说明有这个
Info.Rotation = MoveRotator;
end
if MoveDir ~= nil then
-- 判断当前是什么
if MoveDir.X ~= nil or MoveDir.Y ~= nil or MoveDir.Z ~= nil then
Info.MoveDirection = VectorHelper.HandleLessVector(MoveDir);
end
if MoveDir.Pitch ~= nil or MoveDir.Yaw ~= nil or MoveDir.Roll ~= nil then
Info.Rotation = VectorHelper.HandleLessRotator(MoveDir);
end
end
Info.IsMove = IsMove;
else
-- 此时 Func 不存在,检查是否存在 MoveDirection
if Info.MoveDirection == nil and Info.Rotation == nil then
Info.IsMove = false;
end
end
if Info.IsMove then
if not table.isEmpty(Info.MoveDirection) then
local Delta = VectorHelper.ToLuaTable(VectorHelper.MulNumber(VectorHelper.HandleLessVector(Info.MoveDirection), InDeltaTime));
--UGCLogSystem.LogTree("[MovingActorsManager:MoveAllActors1] Delta = ", Delta);
local CurrPos = VectorHelper.Add(Actor:K2_GetActorLocation(), Delta);
Actor:K2_SetActorLocation(CurrPos);
Info.CurrPos = VectorHelper.ToLuaTable(CurrPos);
end
if not table.isEmpty(Info.Rotation) then
local Delta = VectorHelper.RotMulNumber(VectorHelper.HandleLessRotator(Info.Rotation), InDeltaTime);
local Rot = VectorHelper.RotToLuaTable( Actor:K2_GetActorRotation());
local CurrRot = VectorHelper.RotAdd(Delta, Rot);
Actor:K2_SetActorRotation(CurrRot);
Info.CurrRot = VectorHelper.RotToLuaTable(CurrRot);
end
end
end
end
end
function MovingActorsManager:ServerSendRPC(FuncName, ...)
UGCLogSystem.Log("[MovingActorsManager:ServerSendRPC] 执行函数:%s", FuncName);
self.Owner:SendMovingActorsManagerRPC(FuncName, ...);
end
function MovingActorsManager:ClientSendRPC(FuncName, ...)
UGCLogSystem.Log("[MovingActorsManager:ClientSendRPC] FuncName = %s", FuncName)
self.Owner:ClientMovingActorManagerSendRPC(FuncName, ...)
end
function MovingActorsManager:HandleCopy(InTable) return TableHelper.DeepCopy(self.ActorNameMap, "function"); end
--- 得到延迟(单位秒)
---@return float
function MovingActorsManager:GetPing()
local PC = UGCSystemLibrary.GetLocalPlayerController();
if UE.IsValid(PC) then return PC:GetPlayerNetworkPing() / 1000.; end
return 0.;
end
---@return bool 是否是服务器
function MovingActorsManager:HasAuthority() return UGCGameSystem.IsServer(); end
return MovingActorsManager;