---@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 所有需要移动的 Actor MovingActorsManager.ActorMap = {}; ---@type table 同步用的表 MovingActorsManager.ActorNameMap = { }; ---@type boolean 是否启用移动 MovingActorsManager.EnableMoving = false; ---@type boolean 是否需要跟服务器校准 MovingActorsManager.bAdjustServer = false; ---@type AActor 拥有者 MovingActorsManager.Owner = nil; ---@type table 缓存服务器注册但是客户端未注册的 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;