local GoldenSpyHookCtrl = class("GoldenSpyHookCtrl", BaseCtrl) local LoopType = CS.DG.Tweening.LoopType GoldenSpyHookCtrl._mapNodeConfig = { trHookPivot = { sNodeName = "HookPivot", sComponentName = "RectTransform" }, trLine = { sNodeName = "hook_line", sComponentName = "RectTransform" }, trHookEnd = { sNodeName = "hook", sComponentName = "RectTransform" }, rtMaxLengthArea = { sNodeName = "MaxLengthArea", sComponentName = "RectTransform" }, itemParent = { sNodeName = "itemParent", sComponentName = "RectTransform" }, hook_open = {}, hook_normal = {}, hook_catched = {} } GoldenSpyHookCtrl.DefaultLineSize = 100 GoldenSpyHookCtrl.DefaultHookPosY = -90 GoldenSpyHookCtrl.HOOK_SWING_EASE = Ease.Linear GoldenSpyHookCtrl.HOOK_EXTEND_EASE = Ease.Linear GoldenSpyHookCtrl.HOOK_RETRACT_EASE = Ease.Linear local HookStage = { STATE_SWINGING = 1, STATE_EXTENDING = 2, STATE_RETRACTING = 3 } function GoldenSpyHookCtrl:Init(levelId, floorId, floorCtrl, levelData, floorData) self.levelId = levelId self.floorId = floorId self.floorCtrl = floorCtrl self.levelData = levelData self.floorData = floorData self:DestroyItemInHook() local levelCfg = ConfigTable.GetData("GoldenSpyLevel", self.levelId) local configCfg = ConfigTable.GetData("GoldenSpyConfig", levelCfg.ConfigId) self.HOOK_SWING_LEFT_ANGLE = -configCfg.MaxAngle self.HOOK_SWING_RIGHT_ANGLE = configCfg.MaxAngle self.HOOK_SWING_DURATION = configCfg.AngleTime self._swingTweener = nil self._extendTweener = nil self._retractTweener = nil self._hookLength = 0 self._hookState = 0 self._onExtendComplete = nil self._onRetractComplete = nil self._swingGoingRight = true self._swingAtRight = false self.curCatchedItem = nil self._mapNode.trHookPivot.localEulerAngles = Vector3(0, 0, 0) self._mapNode.trHookEnd.localPosition = Vector3(0, self.DefaultHookPosY, 0) self:ApplyHookLength() self._mapNode.hook_open:SetActive(false) self._mapNode.hook_normal:SetActive(true) self._mapNode.hook_catched:SetActive(false) end function GoldenSpyHookCtrl:DestroyItemInHook() local itemParent = self._mapNode.itemParent if itemParent == nil then return end local nChildCount = itemParent.transform.childCount for i = 1, nChildCount do local child = itemParent.transform:GetChild(i - 1) destroy(child.gameObject) end end function GoldenSpyHookCtrl:ApplyHookLength() local tr = self._mapNode.trHookEnd if tr then tr.localPosition = Vector3(0, -self._hookLength + self.DefaultHookPosY, 0) end local rtLine = self._mapNode.trLine if rtLine and not rtLine:IsNull() then rtLine.sizeDelta = Vector2(rtLine.sizeDelta.x, self._hookLength + self.DefaultLineSize) end end function GoldenSpyHookCtrl:SetHookLength(length) self._hookLength = math.max(0, length) self:ApplyHookLength() end function GoldenSpyHookCtrl:_DoSwingPhase(fromAngle, toAngle, onPhaseComplete) local pivot = self._mapNode.trHookPivot if pivot == nil or onPhaseComplete == nil then return end self._mapNode.hook_open:SetActive(false) self._mapNode.hook_normal:SetActive(true) self._mapNode.hook_catched:SetActive(false) self._swingGoingRight = toAngle == self.HOOK_SWING_RIGHT_ANGLE pivot.localEulerAngles = Vector3(0, 0, fromAngle) local realAngle = math.abs(toAngle - fromAngle) local realDuration = realAngle / 1.0 / math.abs(self.HOOK_SWING_RIGHT_ANGLE * 2) * self.HOOK_SWING_DURATION if self._swingTweener ~= nil then self._swingTweener:Kill() self._swingTweener = nil end self._swingTweener = pivot:DOLocalRotate(Vector3(0, 0, toAngle), realDuration, RotateMode.Fast):SetEase(self.HOOK_SWING_EASE):SetUpdate(true):OnComplete(function() onPhaseComplete() end) end function GoldenSpyHookCtrl:_ChainSwing() local pivot = self._mapNode.trHookPivot if pivot == nil then return end local leftAngle = self.HOOK_SWING_LEFT_ANGLE local rightAngle = self.HOOK_SWING_RIGHT_ANGLE if self._swingAtRight then self._swingGoingRight = false self:_DoSwingPhase(rightAngle, leftAngle, function() self._swingAtRight = false self:_ChainSwing() end) else self._swingGoingRight = true self:_DoSwingPhase(leftAngle, rightAngle, function() self._swingAtRight = true self:_ChainSwing() end) end end function GoldenSpyHookCtrl:StartSwing(leftAngle, rightAngle) if self._hookState == HookStage.STATE_EXTENDING or self._hookState == HookStage.STATE_RETRACTING then return end self:StopSwing() self:StopExtend() self:StopRetract() self._mapNode.hook_open:SetActive(false) self._mapNode.hook_normal:SetActive(true) self._mapNode.hook_catched:SetActive(false) local pivot = self._mapNode.trHookPivot if pivot == nil then return end self._hookLength = 0 self:ApplyHookLength() self._hookState = HookStage.STATE_SWINGING leftAngle = leftAngle or self.HOOK_SWING_LEFT_ANGLE rightAngle = rightAngle or self.HOOK_SWING_RIGHT_ANGLE self._swingAtRight = false self._swingGoingRight = true self:_DoSwingPhase(0, rightAngle, function() self._swingAtRight = true self:_ChainSwing() end) EventManager.Hit("GoldenSpyHookResumeSwing") end function GoldenSpyHookCtrl:PauseSwing() self:StopSwing() if self._hookState == HookStage.STATE_SWINGING then end end function GoldenSpyHookCtrl:ResumeSwing(fromCurrent) local pivot = self._mapNode.trHookPivot if pivot == nil then return end local leftAngle = self.HOOK_SWING_LEFT_ANGLE local rightAngle = self.HOOK_SWING_RIGHT_ANGLE if fromCurrent then self:StopExtend() self:StopRetract() self:StopSwing() local curZ = self:_NormalizeAngleZ(pivot.localEulerAngles.z) if self._swingGoingRight then self._swingAtRight = false self._swingGoingRight = true self:_DoSwingPhase(curZ, rightAngle, function() self._swingAtRight = true self:_ChainSwing() end) else self._swingAtRight = true self._swingGoingRight = false self:_DoSwingPhase(curZ, leftAngle, function() self._swingAtRight = false self:_ChainSwing() end) end else self:StartSwing() return end self._hookState = HookStage.STATE_SWINGING EventManager.Hit("GoldenSpyHookResumeSwing") end function GoldenSpyHookCtrl:_NormalizeAngleZ(z) while 180 < z do z = z - 360 end while z < -180 do z = z + 360 end return z end function GoldenSpyHookCtrl:StopSwing() if self._swingTweener ~= nil then self._swingTweener:Kill() self._swingTweener = nil end end function GoldenSpyHookCtrl:StartExtend(speed, radius, factor, onComplete, onCatched, onCatchedComplete) self:StopSwing() self:StopExtend() self:StopRetract() EventManager.Hit("GoldenSpyHookStartExtend") local tr = self._mapNode.trHookEnd if tr == nil then return end local maxLength = self:GetCurrentMaxLength() self._onExtendComplete = onComplete self._hookState = HookStage.STATE_EXTENDING local fromLen = self._hookLength local duration = (maxLength - fromLen) / speed if duration <= 0 then self._hookLength = maxLength self:ApplyHookLength() self._hookState = 0 if self._onExtendComplete then self._onExtendComplete() self._onExtendComplete = nil end return end self._mapNode.hook_open:SetActive(true) self._mapNode.hook_normal:SetActive(false) self._mapNode.hook_catched:SetActive(false) self._extendTweener = DOTween.To(function() return self._hookLength end, function(v) self._hookLength = v self:ApplyHookLength() local items = self.floorCtrl.tbItem for _, item in ipairs(items) do local worldPos = self:GetHookEndWorldPosition() if self:CheckCatchIntersect(worldPos, radius, item.Ctrl) then if onCatched then onCatched(item.Ctrl) end local itemCfg = item.Ctrl:GetItemCfg() if itemCfg.ItemType == GameEnum.GoldenSpyItem.Boom then item.Ctrl:Boom(nil) self:StartRetract(speed, function() self:ResumeSwing(true) if onCatchedComplete then onCatchedComplete(item.Ctrl) end end) return end self.curCatchedItem = item.Ctrl if itemCfg.ItemType == GameEnum.GoldenSpyItem.Companion or itemCfg.ItemType == GameEnum.GoldenSpyItem.Patrol then item.Ctrl:onCatch() end self.curCatchedItem.gameObject.transform:SetParent(self._mapNode.itemParent) self.curCatchedItem.gameObject.transform.localPosition = Vector3.zero local itemNormalWeight = item.Ctrl:GetWeight() for _, v in ipairs(self:GetBuffData()) do local buffCfg = ConfigTable.GetData("GoldenSpyBuffCard", v.buffId) if buffCfg ~= nil and buffCfg.EffectType == GameEnum.GoldenSpyBuffEffect.ReduceItemWeight and self.levelData:CheckBuffActive(v) then itemNormalWeight = itemNormalWeight - buffCfg.Params[2] end end itemNormalWeight = math.max(0, itemNormalWeight) local nbackSpeed = speed + factor * itemNormalWeight if NovaAPI.IsEditorPlatform() then print("GoldenSpyLevelCtrl:backSpeed=", nbackSpeed) end self:StartRetract(nbackSpeed, function() self._mapNode.hook_open:SetActive(false) self._mapNode.hook_normal:SetActive(true) self._mapNode.hook_catched:SetActive(false) if onCatchedComplete then onCatchedComplete(self.curCatchedItem) end self.curCatchedItem.gameObject:SetActive(false) self.curCatchedItem = nil end) self:StopExtend() break end end end, maxLength, duration):SetEase(self.HOOK_EXTEND_EASE):SetUpdate(true):OnComplete(function() self._extendTweener = nil self._hookState = 0 self:StartRetract(speed, function() self:ResumeSwing(true) if onCatchedComplete then onCatchedComplete(self.curCatchedItem) end self.curCatchedItem = nil end) if self._onExtendComplete then self._onExtendComplete() self._onExtendComplete = nil end end) end function GoldenSpyHookCtrl:GetHookEndWorldPosition() local rootTr = self.gameObject.transform local trEnd = self._mapNode.trHookEnd local localPt = rootTr:InverseTransformPoint(trEnd.position) return Vector3(localPt.x, localPt.y, 0) end function GoldenSpyHookCtrl:StopExtend() if self._extendTweener ~= nil then self._extendTweener:Kill() self._extendTweener = nil end if self._hookState == HookStage.STATE_EXTENDING then self._hookState = 0 end self._onExtendComplete = nil end function GoldenSpyHookCtrl:PauseExtend() if self._extendTweener ~= nil then self._extendTweener:Pause() end if self._hookState == HookStage.STATE_EXTENDING then end end function GoldenSpyHookCtrl:ResumeExtend() if self._extendTweener ~= nil then self._extendTweener:Play() end if self._hookState == HookStage.STATE_EXTENDING then end end function GoldenSpyHookCtrl:StartRetract(speed, onComplete) self:StopSwing() self:StopExtend() self:StopRetract() local tr = self._mapNode.trHookEnd if tr == nil then if onComplete then onComplete() end return end self._mapNode.hook_open:SetActive(false) self._mapNode.hook_normal:SetActive(false) self._mapNode.hook_catched:SetActive(true) self._onRetractComplete = onComplete self._hookState = HookStage.STATE_RETRACTING local fromLen = self._hookLength local duration = fromLen / speed if duration <= 0 then self._hookLength = 0 self:ApplyHookLength() self._hookState = 0 if self._onRetractComplete then self._onRetractComplete() self._onRetractComplete = nil end return end self._retractTweener = DOTween.To(function() return self._hookLength end, function(v) self._hookLength = v self:ApplyHookLength() end, 0, duration):SetEase(self.HOOK_RETRACT_EASE):SetUpdate(true):OnComplete(function() self._retractTweener = nil self._hookState = 0 if self._onRetractComplete then self._onRetractComplete() self._onRetractComplete = nil end end):OnKill(function() self._retractTweener = nil if self._hookState == HookStage.STATE_RETRACTING then self._hookState = 0 end end) end function GoldenSpyHookCtrl:StopRetract() if self._retractTweener ~= nil then self._retractTweener:Kill() self._retractTweener = nil end if self._hookState == HookStage.STATE_RETRACTING then self._hookState = 0 end self._onRetractComplete = nil end function GoldenSpyHookCtrl:SetRetractSpeed(newSpeed) if not self:IsRetracting() then return end local onComplete = self._onRetractComplete self:StopRetract() self:StartRetract(newSpeed, onComplete) end function GoldenSpyHookCtrl:PauseRetract() if self._retractTweener ~= nil then self._retractTweener:Pause() end if self._hookState == HookStage.STATE_RETRACTING then end end function GoldenSpyHookCtrl:ResumeRetract() if self._retractTweener ~= nil then self._retractTweener:Play() end if self._hookState == HookStage.STATE_RETRACTING then end end function GoldenSpyHookCtrl:GetHookAngle() local pivot = self._mapNode.trHookPivot if pivot == nil then return 0 end return pivot.localEulerAngles.z end function GoldenSpyHookCtrl:GetMaxLengthForAngle(angleDeg) local rect = self._mapNode.rtMaxLengthArea local trLine = self._mapNode.trLine if rect == nil or rect:IsNull() or trLine == nil or trLine:IsNull() then return 0 end local lineLocalPos = rect:InverseTransformPoint(trLine.position) local sx, sy = lineLocalPos.x, lineLocalPos.y local left = 0 local bottom = 0 local right = rect.sizeDelta.x local top = rect.sizeDelta.y local rad = math.rad(angleDeg) local worldDir = Vector3(math.sin(rad), -math.cos(rad), 0) local localDir = rect:InverseTransformDirection(worldDir) local dx, dy = localDir.x, localDir.y local lenSq = dx * dx + dy * dy if lenSq < 1.0E-12 then return 0 end local invLen = 1 / math.sqrt(lenSq) dx, dy = dx * invLen, dy * invLen local tMin if math.abs(dx) > 1.0E-6 then local tL = (left - sx) / dx if 0 < tL then local y = sy + tL * dy if bottom <= y and top >= y then tMin = tMin == nil and tL or math.min(tMin, tL) end end local tR = (right - sx) / dx if 0 < tR then local y = sy + tR * dy if bottom <= y and top >= y then tMin = tMin == nil and tR or math.min(tMin, tR) end end end if math.abs(dy) > 1.0E-6 then local tB = (bottom - sy) / dy if 0 < tB then local x = sx + tB * dx if left <= x and right >= x then tMin = tMin == nil and tB or math.min(tMin, tB) end end local tT = (top - sy) / dy if 0 < tT then local x = sx + tT * dx if left <= x and right >= x then tMin = tMin == nil and tT or math.min(tMin, tT) end end end return math.max(0, tMin or 0) end function GoldenSpyHookCtrl:GetCurrentMaxLength() return self:GetMaxLengthForAngle(self:GetHookAngle()) end function GoldenSpyHookCtrl:_CircleCircleIntersect(hx, hy, hookR, ix, iy, itemR) local dx, dy = ix - hx, iy - hy local dist2 = dx * dx + dy * dy local sumR = hookR + (itemR or 0) return dist2 <= sumR * sumR end function GoldenSpyHookCtrl:_CircleRectIntersect(hx, hy, hookR, cx, cy, width, height) local hw = (width or 0) * 0.5 local hh = (height or 0) * 0.5 local px = hx if px < cx - hw then px = cx - hw elseif px > cx + hw then px = cx + hw end local py = hy if py < cy - hh then py = cy - hh elseif py > cy + hh then py = cy + hh end local dx, dy = hx - px, hy - py local dist2 = dx * dx + dy * dy return dist2 <= hookR * hookR end function GoldenSpyHookCtrl:CheckCatchIntersect(hookWorldPos, hookRadius, item) if hookWorldPos == nil or not item then return false end local hx, hy = hookWorldPos.x, hookWorldPos.y local hitArea = item:GetHitArea() local ix, iy = hitArea.center.x, hitArea.center.y if hitArea.nType == AllEnum.GoldenSpyHitAreaType.Circle then return self:_CircleCircleIntersect(hx, hy, hookRadius, ix, iy, (hitArea.width or 0) / 2) elseif hitArea.nType == AllEnum.GoldenSpyHitAreaType.Rectangle then return self:_CircleRectIntersect(hx, hy, hookRadius, ix, iy, hitArea.width or 0, hitArea.height or 0) end return self:_CircleCircleIntersect(hx, hy, hookRadius, ix, iy, item.radius or 0) end function GoldenSpyHookCtrl:CheckCatchInRadius(hookWorldPos, radius, items) if not (hookWorldPos ~= nil and items) or #items == 0 then return nil end for i = 1, #items do local item = items[i] if self:CheckCatchIntersect(hookWorldPos, radius, item) then return item end end return nil end function GoldenSpyHookCtrl:GetHookLength() return self._hookLength end function GoldenSpyHookCtrl:GetState() return self._hookState end function GoldenSpyHookCtrl:IsSwinging() return self._hookState == HookStage.STATE_SWINGING end function GoldenSpyHookCtrl:IsExtending() return self._hookState == HookStage.STATE_EXTENDING end function GoldenSpyHookCtrl:IsRetracting() return self._hookState == HookStage.STATE_RETRACTING end function GoldenSpyHookCtrl:DropItem() self.curCatchedItem = nil end function GoldenSpyHookCtrl:OnDestroy() self:StopSwing() self:StopExtend() self:StopRetract() end function GoldenSpyHookCtrl:GetBuffData() return self.levelData:GetBuffData() end function GoldenSpyHookCtrl:Pause() if self:IsSwinging() then self:PauseSwing() end if self:IsExtending() then self:PauseExtend() end if self:IsRetracting() then self:PauseRetract() end end function GoldenSpyHookCtrl:Continue() if self:IsSwinging() then self:ResumeSwing(true) end if self:IsExtending() then self:ResumeExtend() end if self:IsRetracting() then self:ResumeRetract() end end function GoldenSpyHookCtrl:Exit() self:StopSwing() self:StopExtend() self:StopRetract() end return GoldenSpyHookCtrl