--控制单位进行 直行 -> 升轨 -> 圆周的运动 local base = require("Battle/Logic/AI/Decision/Decision_Base") local Decision_DetourMove = class("Decision_DetourMove", base) local EPhase = { STRAIGHT_MOVE = 1, ORBITAL_TRANSFER = 2, --升轨运动 ROUND_MOVE = 3, --环绕目标运动 } local ORBITAL_TRANSFER_PATH_POINT_COUNT = 24 --目前写死圆周运动的路径的点的数量, 这个数量太大了会有问题(跑得贼慢),太小了也会有问题(原地打转)。感觉得根据半径和速度去算出一个合适的值 local _tempV2 = FP.newV2(0, 0) local ROUND_MOVE_PER_POINT_MAX_FRAME = 20 --圆周运动时每个目标点最大的持续时间,防止一直到不了某个目标点时会卡住 function Decision_DetourMove:initialize(entity) --类型 self.Type = LuaConst.DecisionType.DetourMove --是否是瞬时 self.IsOnce = false end local ROUND_MOVE_STRIDE = FP.Const.PI / 8 --目前写死环绕点间隔为45度的弧度 local _tempV1_radStride = FP.newV1(0) function Decision_DetourMove:GetRoundMoveRadStride(decisionRecordDic) _tempV1_radStride:Reset(ROUND_MOVE_STRIDE):Mul(decisionRecordDic.isClockWise and 1 or -1) -- 策略1:目前返回定值,以后可能需要根据半径、entity速度算出一个自适应值 --_tempV1_radStride:Reset(ROUND_MOVE_STRIDE):Mul(LogicRandom.GetRandom01(entity , 456454) < FP.Const.Half and 1 or -1) --策略2:返回随机方向也别有风味, 但是马头会摆 -- ↓↓↓--策略3: 策略2的优化,定一个概率,如果低于这个概率才换clockwise,这样动作会更平滑,马不会疯狂摆头 --概率低则袭扰频率低,概率高则袭扰概率高。当为0.5时,经常能袭扰,但是马头狂摆,考虑做一个袭扰Decision -- if LogicRandom.GetRandom01(entity , 456454) < FP.newV1(0.01) then -- decisionRecordDic.isRoundMoveClockwise = not decisionRecordDic.isRoundMoveClockwise -- end --_tempV1_radStride:Reset(ROUND_MOVE_STRIDE):Mul(decisionRecordDic.isRoundMoveClockwise and 1 or -1) -- ↑↑↑-策略3 return _tempV1_radStride end function Decision_DetourMove:RecalculateRoundMovePathPoint(decisionRecordDic, entity, decisionData) local target = entity.components.target.Target local pathComponent = entity.components.path if target then local center = target:GetLogicPos():cloneV2() _tempV2:Reset(entity:GetLogicPos()):Sub(center):Normalize() -- 该方法内 _tempV2是方向 _tempV2:Reset(FP.V2.RotateAroundOrigin(_tempV2, self:GetRoundMoveRadStride(decisionRecordDic))) _tempV2:Mul(decisionRecordDic.roundMoveRadius) pathComponent.Path = {_tempV2:Add(center):cloneV2()} pathComponent.CurPathPosIndex = 1 else if #pathComponent.Path > 0 then pathComponent.Path = {} end end end --[[ 进入该Decision --]] function Decision_DetourMove:DecisionIn(entity, decisionData) local decisionRecordDic = entity.components.aiBase:GetDecisionRecordDic(decisionData) decisionRecordDic.startFrame = LogicUpdateManager._clientHandleFrame decisionRecordDic.straightMoveTotalDist = FP.newV1(decisionData.IntParam1) decisionRecordDic.roundMoveRadius = FP.newV1(decisionData.IntParam2) decisionRecordDic.maxRoundMoveRadius = FP.newV1(decisionData.IntParam2) decisionRecordDic.isClockWise = decisionData.IntParam4 == 0 self:CommonConditionalSkillStringParamParse(decisionData, decisionRecordDic, entity) self:ChangePhase(EPhase.STRAIGHT_MOVE, decisionRecordDic, entity) end function Decision_DetourMove:ChangePhase(newPhase, decisionRecordDic, entity, decisionData) if decisionRecordDic.phase ~= newPhase then decisionRecordDic.phase = newPhase decisionRecordDic.phaseFrameEla = 0 if newPhase == EPhase.STRAIGHT_MOVE then decisionRecordDic.phaseMoveDist = FP.newV1(0) elseif newPhase == EPhase.ORBITAL_TRANSFER then --↓↓↓升轨阶段跳过,因为发现升轨阶段的设计有问题,没有考虑移动中的目标(直行阶段甚至都可以跳过, 如果不需要U turn的话) do self:ChangePhase(EPhase.ROUND_MOVE, decisionRecordDic, entity, decisionData) return end --↑↑↑ self:OnOrbitalTransferPhaseStart(decisionRecordDic, entity, decisionData) elseif newPhase == EPhase.ROUND_MOVE then self:OnRoundMovePhaseStart(decisionRecordDic, entity, decisionData) end else logging.error(LTAG.ERROR, "HYW :警告! 试图重复设置phase @Decision_DetourMove(程序调试代码,测试可以忽略)") end end function Decision_DetourMove:OnRoundMovePhaseStart(decisionRecordDic, entity, decisionData) entity.components.aiBase:SetAiRecord("DetourRoundMoveTimeEla", FP.newV1(0)) decisionRecordDic.isRoundMoveClockwise = decisionRecordDic.isClockWise self:RecalculateRoundMovePathPoint(decisionRecordDic, entity, decisionData) end function Decision_DetourMove:OnOrbitalTransferPhaseStart(decisionRecordDic, entity, decisionData) --升轨阶段数据初始化 local pathPointList = {} local center = entity:GetLogicPos():cloneV2() local radius = decisionRecordDic.roundMoveRadius / 2 --升轨运动的半径,是圆周半径的一半 local tangentLineDegree = decisionData.IntParam3 --切线方向角度 local tangetLineRad = FP.newV1(tangentLineDegree):Mul(FP.Const.Deg2Rad) --切线方向弧度 _tempV2 = FP.V2.RotateAroundOrigin(entity.components.move.LogicRotateDir, tangetLineRad:Add(FP.Const.PI_HALF)) _tempV2:Normalize() --此时_tempV2是切线的右方向 center:Add(_tempV2:Mul(radius)) local radStride = FP.Const.PI / ORBITAL_TRANSFER_PATH_POINT_COUNT --升轨阶段只运动半个圆,所以弧度为PI if not decisionRecordDic.isClockWise then radStride:Mul(-1) end local startOffsest = entity:GetLogicPos() - center local tempV1_rad = FP.newV1(0) for i=1, ORBITAL_TRANSFER_PATH_POINT_COUNT do table.insert(pathPointList, FP.V2.RotateAroundOrigin(startOffsest, tempV1_rad:Reset(radStride):Mul(i)):Add(center)) end local pathComponent = entity.components.path pathComponent.Path = pathPointList pathComponent.CurPathPosIndex = 1 -- logging.error(LTAG.ERROR, "HYW : startPos : ", entity:GetLogicPos()) -- logging.error(LTAG.ERROR, "HYW : center : ", center) -- logging.error(LTAG.ERROR, "HYW : -------------所有点---------------") -- for i,v in ipairs(pathPointList) do -- logging.error(LTAG.ERROR, "HYW : ", v) -- end end --[[ 退出该Decision --]] function Decision_DetourMove:DecisionOut(entity, decisionData) base.DecisionOut(self, entity, decisionData) entity.components.aiBase:SetAiRecord("DetourRoundMoveTimeEla", nil) end --[[ 能否进入 --]] function Decision_DetourMove:CanEnter(entity, decisionData, command) return true end --[[ 能否退出 --]] function Decision_DetourMove:CanExit(entity, decisionData, command) local can = false local decisionRecordDic = entity.components.aiBase:GetDecisionRecordDic(decisionData) if decisionRecordDic.phase == EPhase.ROUND_MOVE then can = entity.components.target.Target == nil end return can end --local _tempV1 = FP.newV1(0) --↓↓↓↓ 环绕阶段防止撞墙的代码 ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ --目前下面的参数,都是程序凭感觉调的一版,策划觉得效果上没问题,并且写死在代码里的 local MIN_ROUND_MOVE_DIST = FP.newV1(1) local MAX_WALL_DIST = FP.newV1(5) --环绕半径衰减最大距离墙的距离,这个值越大,越不容易撞墙 local _tempV2Probe = FP.newV2(0,0) local _tempV2Dir = FP.newV2(1,0) local _tempV1WallDist = FP.newV1(0) local _factor = FP.newV1(0) function Decision_DetourMove:GetRoundMoveRadiusFactor(entity) _tempV2Dir:Reset(entity.components.move.LogicRotateDir) _tempV2Dir:Normalize() _tempV1WallDist:Reset(999999) --先设置一个极大的值 local const_entityPos = entity:GetLogicPos() --只用检测MAX_WALL_DIST + 1以内的格子有没有墙, +1是为了多搜寻一格,防止边界情况 for i=1, FP.tonumberV1(MAX_WALL_DIST) + 1 do _tempV2Probe:Reset(_tempV2Dir):Mul(i):Add(const_entityPos) if not LuaShoveManager.IsMovablePos(_tempV2Probe) then _tempV1WallDist:Reset(i) break end end if _tempV1WallDist > MAX_WALL_DIST then _factor:Reset(1.08) --环绕半径增长时的系数就填定值,因为环绕半径增长时表现比较好,不需要那么精确 --↑ 这个值必须大于1。 越接近1,表示半径增长得越慢,升轨持续时间越长,升轨的轨迹越平缓 else _factor:Reset(1):Div((FP.tonumberV1(MAX_WALL_DIST) + 6 - FP.tonumberV1(_tempV1WallDist))):Mul(3) --环绕半径减小时的系数需要更大一点,这样更能避免撞墙 end return _factor end --这个方法非常消耗性能,目前仅用于于吕布Boss function Decision_DetourMove:ChangeRoundMoveRadiusByDistToWall(entity, decisionRecordDic) local factor = self:GetRoundMoveRadiusFactor(entity) if decisionRecordDic.roundMoveRadius then if factor > FP.Const.One then if decisionRecordDic.roundMoveRadius < decisionRecordDic.maxRoundMoveRadius / factor then decisionRecordDic.roundMoveRadius:Mul(factor) end else if decisionRecordDic.roundMoveRadius > MIN_ROUND_MOVE_DIST then --这里其实有点问题,但是为了省性能就这么写了(实际应该写为MIN_ROUND_MOVE_DIST * factor) decisionRecordDic.roundMoveRadius:Mul(factor) end end end end --↑↑↑↑↑↑↑↑↑↑↑ 环绕阶段防止撞墙的代码↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ --[[ 修饰指令(修饰command) --]] function Decision_DetourMove:SetCommand(entity, decisionData, command) local decisionRecordDic = entity.components.aiBase:GetDecisionRecordDic(decisionData) local deltaFrame = 0 --两次SetCommand之间的间隔 if decisionRecordDic.lastSetCommandFrame then deltaFrame = LogicUpdateManager._clientHandleFrame - decisionRecordDic.lastSetCommandFrame end decisionRecordDic.lastSetCommandFrame = LogicUpdateManager._clientHandleFrame decisionRecordDic.phaseFrameEla = decisionRecordDic.phaseFrameEla + deltaFrame if decisionRecordDic.phase == EPhase.STRAIGHT_MOVE then command.MoveDir:Reset(entity.components.move.LogicRotateDir) command.RotateDir:Reset(command.MoveDir) if decisionRecordDic.lastPos then _tempV2:Reset(decisionRecordDic.lastPos) _tempV2:Sub(entity:GetLogicPos()) decisionRecordDic.phaseMoveDist:Add(_tempV2:Magnitude()) else decisionRecordDic.lastPos = FP.newV2(0, 0) end decisionRecordDic.lastPos:Reset(entity:GetLogicPos()) if decisionRecordDic.phaseMoveDist >= decisionRecordDic.straightMoveTotalDist then self:ChangePhase(EPhase.ORBITAL_TRANSFER, decisionRecordDic, entity, decisionData) end elseif decisionRecordDic.phase == EPhase.ORBITAL_TRANSFER then if entity.components.path.CurPathPosIndex >= ORBITAL_TRANSFER_PATH_POINT_COUNT then self:ChangePhase(EPhase.ROUND_MOVE, decisionRecordDic, entity, decisionData) command.MoveDir:Reset(entity.components.move.LogicRotateDir) else self:RunToPathPoint(entity, command, decisionData) end elseif decisionRecordDic.phase == EPhase.ROUND_MOVE then local aiBase = entity.components.aiBase self:ChangeRoundMoveRadiusByDistToWall(entity, decisionRecordDic) --新增一个步骤,在环绕阶段根据离墙的距离来调整环绕半径,这个方法很费,目前仅用于吕布。如果性能还是扛不住,考虑增加调用帧间隔以及距离衰减系数 local detourRoundMoveTimeEla = aiBase:GetAiRecord("DetourRoundMoveTimeEla") if not detourRoundMoveTimeEla then detourRoundMoveTimeEla = FP.newV1(0) end detourRoundMoveTimeEla:Reset(decisionRecordDic.phaseFrameEla):Mul(LuaConst.TimeSpan) aiBase:SetAiRecord("DetourRoundMoveTimeEla", detourRoundMoveTimeEla) if entity.components.path.CurPathPosIndex == 2 or decisionRecordDic.phaseFrameEla >= ROUND_MOVE_PER_POINT_MAX_FRAME then self:RecalculateRoundMovePathPoint(decisionRecordDic, entity, decisionData) end self:RunToPathPoint(entity, command, decisionData) self:CommonIntervalUseConditionalSkill(decisionRecordDic, entity, deltaFrame * LuaConst.TimeSpanMs, command) end return command end return Decision_DetourMove