local TutorialConst = require "app/module/tutorial/tutorial_const" local TutorialUI = class("TutorialUI", LuaComponent) local MIN_DESC_HEIGHT = 100 local DESC_OFFSET_HEIGHT = 70 function TutorialUI:init() local uiMap = self.baseObject:genAllChildren() self.uiMap = uiMap self.mask = uiMap["tutorial_ui.mask"] self.maskMat = CS.UnityEngine.Object.Instantiate(RenderManager:getUITutorialMat()) self.mask:getComponent(GConst.TYPEOF_UNITY_CLASS.UI_IMAGE).material = self.maskMat self.clickArea = uiMap["tutorial_ui.click_area"] self.clickArea:addClickListener(function() self:onClickArea() end) self.clickAreaCallback = nil self.showClickSound = false self:setClickAreaEnable(false) self.clickAreaCS = self.clickArea:getComponent(GConst.TYPEOF_UNITY_CLASS.BF_TUTORIAL_CLICKAREA) self.clickAreaCS:ClearTargetTransform() self.isHaveClickBtnTutorial = false self:setClickAreaWithScreen() self.clickArea:setAnchoredPosition(0.2, 0.3) self.blockTouch = uiMap["tutorial_ui.block_touch"] self.blockTouch:addClickListener(function() if not self.isHaveClickBtnTutorial then return end local scale = self.guideFingerNode:fastGetLocalScale() if scale > 0.01 and self.guideFingerNode:getComponent(GConst.TYPEOF_UNITY_CLASS.ANIMATOR).enabled then self.guideFingerNode:getComponent(GConst.TYPEOF_UNITY_CLASS.ANIMATOR):Play("finger_click_prompt", -1, 0) end end) self:initTalk() self:initFinger() end function TutorialUI:initTalk() self.talkNode = self.uiMap["tutorial_ui.talk_node"] self.textBg = self.uiMap["tutorial_ui.talk_node.text_bg"] self.descTx = self.uiMap["tutorial_ui.talk_node.text_bg.desc_text"] self.roleImg = self.uiMap["tutorial_ui.talk_node.text_bg.role"] end function TutorialUI:initFinger() self.guideFingerNode = self.uiMap["tutorial_ui.finger_node"] self.guideFinger = self.uiMap["tutorial_ui.finger_node.finger"] self.fingerParentNode = self.uiMap["tutorial_ui.finger_node.finger_node"] self:hideGuideFinger() end -- 开始引导, 先检查一些通用的设置是否要显示 function TutorialUI:onTutorialStart() -- 设置是否屏蔽点击 self:setBlockTouch() -- 设置一些所有引导步骤都可能会用到的东西 -- 灰色遮罩 self:setMaskEnable(false) -- 清理点击 self:clearClickArea() -- 隐藏手指 self:hideGuideFinger() -- 隐藏剧情文本 self:hideTalk() self:clearScheduler() end function TutorialUI:hideAllTips() self:setBlockTouchEnabled(true) -- 灰色遮罩 self:setMaskEnable(false) -- 隐藏手指 self:hideGuideFinger() -- 隐藏剧情文本 self:hideTalk() end -- 停止引导 function TutorialUI:onTutorialStop() self:clear() end function TutorialUI:hideTalk() self.talkNode:setLocalScale(0, 0, 0) end function TutorialUI:setBlockTouch() local blockTouchEnabled = DataManager.TutorialData:getTouchEnabled() self:setBlockTouchEnabled(blockTouchEnabled) end function TutorialUI:setBlockTouchEnabled(enabled) local scale = enabled and 1 or 0 self.blockTouch:setLocalScale(scale, scale, scale) end function TutorialUI:getBlockTouchEnabled() local scale = self.blockTouch:fastGetLocalScale() return scale == 1 end function TutorialUI:setMaskEnable(enabled) local scale = enabled and 1 or 0 self.mask:setLocalScale(scale, scale, scale) end function TutorialUI:setClickAreaEnable(enabled) local scale = enabled and 1 or 0 self.clickArea:setLocalScale(scale, scale, scale) end function TutorialUI:setClickAreaWithScreen() self.clickArea:setAnchorMin(0, 0) self.clickArea:setAnchorMax(1, 1) self.clickArea:setAnchoredPosition(0, 0) self.clickArea:setSizeDelta(0, 0) end ---- 设定clickArea的区域 function TutorialUI:setClickAreaWithTarget(anchoredPosition, width, height) self.clickArea:setAnchorMin(0.5, 0.5) self.clickArea:setAnchorMax(0.5, 0.5) self.clickArea:setAnchoredPosition(anchoredPosition.x, anchoredPosition.y) self.clickArea:setSizeDelta(width, height) end function TutorialUI:showTutorialText() self.talkNode:setLocalScale(1, 1, 1) local text = DataManager.TutorialData:getTutorialText() self.descTx:setText(text) -- if DataManager.TutorialData:getTutorialDirLeft() then -- self.roleImg:setAnchoredPositionX(-212) -- self.roleImg:setLocalEulerAngles(0, 0, 0) -- self.descTx:setAnchoredPositionX(232) -- else -- self.roleImg:setAnchoredPositionX(211) -- self.roleImg:setLocalEulerAngles(0, 180, 0) -- self.descTx:setAnchoredPositionX(60) -- end local offset = DataManager.TutorialData:getTutorialOffset() or {0, 0} self.textBg:setAnchoredPosition(-5 + offset[1], 283.5 + offset[2]) local preferredHeight = self.descTx:getComponent(GConst.TYPEOF_UNITY_CLASS.UI_TEXT_MESH_PRO).preferredHeight if preferredHeight > MIN_DESC_HEIGHT then self.textBg:setSizeDeltaY(preferredHeight + DESC_OFFSET_HEIGHT) else self.textBg:setSizeDeltaY(MIN_DESC_HEIGHT + DESC_OFFSET_HEIGHT) end local showMask = DataManager.TutorialData:getShowMask() if showMask == 3 then -- 纯黑遮罩 self:setMaskEnable(true) self.maskMat:SetVector(self:getCenterPropertyID(), {x = 0, y = 0, z = 0, w = 0}) self.maskMat:SetFloat(self:getEllipsePropertyID(), 0) self.maskMat:SetFloat(self:getRectXPropertyID(), 0) self.maskMat:SetFloat(self:getRectYPropertyID(), 0) end local fingerOffset = DataManager.TutorialData:getFingerOffset() local targetName = DataManager.TutorialData:getTargetName() local fingerDir = DataManager.TutorialData:getFingerDir() if fingerDir and self.fingerParentNode then if fingerDir == 1 then self.fingerParentNode:setLocalEulerAngles(0, 0, 180) elseif fingerDir == 2 then self.fingerParentNode:setLocalEulerAngles(0, 0, 0) elseif fingerDir == 3 then self.fingerParentNode:setLocalEulerAngles(0, 0, 90) elseif fingerDir == 4 then self.fingerParentNode:setLocalEulerAngles(0, 0, -90) end else self.fingerParentNode:setLocalEulerAngles(0, 0, 0) end if fingerOffset then self.guideFingerNode:setLocalScale(1, 1, 1) self.guideFingerNode:getComponent(GConst.TYPEOF_UNITY_CLASS.ANIMATOR):Play("finger_click", -1, 0) self.guideFingerNode:getComponent(GConst.TYPEOF_UNITY_CLASS.ANIMATOR):Update(0) self.guideFingerNode:getComponent(GConst.TYPEOF_UNITY_CLASS.ANIMATOR).enabled = false self.guideFinger:setLocalPosition(0, 0, 0) else fingerOffset = {0, 0} end self.fingerParentNode:setLocalPosition(fingerOffset[1], fingerOffset[2], 0) if targetName or DataManager.TutorialData:getTargetHeroId() then local targetGo if targetName then targetGo = UIManager:getMainCanvasTransform():Find(targetName) else targetGo = self:getHeroCellByHeroId(DataManager.TutorialData:getTargetHeroId()) end local anchoredPosition = nil local setGuideFingerPosition = nil setGuideFingerPosition = function(interval) local rect = targetGo:GetComponent(GConst.TYPEOF_UNITY_CLASS.RECTTRANSFORM).rect self:updateMask(anchoredPosition, rect) self.guideFingerNode:setLocalPosition(anchoredPosition.x, anchoredPosition.y, 0) -- 如果ui在动的话就跟随着更新下位置 local time = 0 if self._findTargetSid then ModuleManager.TutorialManager:unscheduleGlobal(self._findTargetSid) end self._findTargetSid = ModuleManager.TutorialManager:scheduleGlobal(function(dt) if targetGo == nil or CS.BF.Utils.IsNull(targetGo) then -- 被干掉了 if self._findTargetSid then ModuleManager.TutorialManager:unscheduleGlobal(self._findTargetSid) self._findTargetSid = nil end return end local newAnchoredPosition = self:getTargetAnchoredPosition(targetGo) if math.abs(newAnchoredPosition.x - anchoredPosition.x) < 0.00001 and math.abs(newAnchoredPosition.y - anchoredPosition.y) < 0.00001 then time = time + dt if time > 0.1 then -- 有0.1秒没动了,但是部分ui可能会有一段时间不动,然后再动的情况,所以这里也不能算ui不动了,这里把检测间隔变长点,继续保持寻找坐标 ModuleManager.TutorialManager:unscheduleGlobal(self._findTargetSid) self._findTargetSid = nil setGuideFingerPosition(0.1) end else time = 0 anchoredPosition = newAnchoredPosition self.guideFingerNode:setLocalPosition(anchoredPosition.x, anchoredPosition.y, 0) local rect = targetGo:GetComponent(GConst.TYPEOF_UNITY_CLASS.RECTTRANSFORM).rect self:updateMask(anchoredPosition, rect) end end, interval) end if targetGo == nil then -- 没有找到说明需要找的ui还没打开 -- 每隔一定时间在重新找一次 if self._findTargetSid then ModuleManager.TutorialManager:unscheduleGlobal(self._findTargetSid) end self._findTargetSid = ModuleManager.TutorialManager:scheduleGlobal(function() if targetName then targetGo = UIManager:getMainCanvasTransform():Find(targetName) else targetGo = self:getHeroCellByHeroId(DataManager.TutorialData:getTargetHeroId()) end if targetGo then anchoredPosition = self:getTargetAnchoredPosition(targetGo) ModuleManager.TutorialManager:unscheduleGlobal(self._findTargetSid) self._findTargetSid = nil setGuideFingerPosition(0) end end, 0.1) else anchoredPosition = self:getTargetAnchoredPosition(targetGo) setGuideFingerPosition(0) end else self.guideFingerNode:setLocalPosition(fingerOffset[1], fingerOffset[2], 0) end end function TutorialUI:onClickArea() if self.clickAreaCallback == nil then return end if self.showClickSound then AudioManager:playEffect(AudioManager.EFFECT_ID.BUTTON) end self.clickAreaCallback() end -- 点击屏幕事件 function TutorialUI:registerClickScreenListener(callback) self:setClickAreaWithScreen() self:setClickAreaEnable(true) self.clickAreaCS:ClearTargetTransform() self.isHaveClickBtnTutorial = false self.clickAreaCallback = callback local showMask = DataManager.TutorialData:getShowMask() if showMask == 1 then -- 矩形遮罩 self:setMaskEnable(true) local squareOffset = DataManager.TutorialData:getMaskSquareOffset() local squareOffsetX = squareOffset and squareOffset[1] or 0 local squareOffsetY = squareOffset and squareOffset[2] or 0 self.maskMat:SetVector(self:getCenterPropertyID(), {x = squareOffsetX, y = squareOffsetY, z = 0, w = 0}) self.maskMat:SetFloat(self:getEllipsePropertyID(), 10) local squareSize = DataManager.TutorialData:getMaskSquareSize() local squareSizeW = squareSize and squareSize[1] or 0 local squareSizeH = squareSize and squareSize[2] or 0 self.maskMat:SetFloat(self:getRectXPropertyID(), squareSizeW) self.maskMat:SetFloat(self:getRectYPropertyID(), squareSizeH) elseif showMask == 2 then -- 圆形遮罩 self:setMaskEnable(true) local circleOffset = DataManager.TutorialData:getMaskCircleOffset() local squareOffsetX = circleOffset and circleOffset[1] or 0 local squareOffsetY = circleOffset and circleOffset[2] or 0 self.maskMat:SetVector(self:getCenterPropertyID(), {x = squareOffsetX, y = squareOffsetY, z = 0, w = 0}) self.maskMat:SetFloat(self:getEllipsePropertyID(), 2) local maskRadius = DataManager.TutorialData:getMaskRadius() or TutorialConst.DEFAULT_RADIUS self.maskMat:SetFloat(self:getRectXPropertyID(), maskRadius) self.maskMat:SetFloat(self:getRectYPropertyID(), maskRadius) end local fingerOffset = DataManager.TutorialData:getFingerOffset() if fingerOffset then self.guideFingerNode:setLocalScale(1, 1, 1) self.guideFingerNode:getComponent(GConst.TYPEOF_UNITY_CLASS.ANIMATOR).enabled = true self.guideFingerNode:getComponent(GConst.TYPEOF_UNITY_CLASS.ANIMATOR):Play("finger_click", -1, 0) self.guideFingerNode:setLocalPosition(fingerOffset[1], fingerOffset[2], 0) end local fingerDir = DataManager.TutorialData:getFingerDir() if fingerDir and self.fingerParentNode then if fingerDir == 1 then self.fingerParentNode:setLocalEulerAngles(0, 0, 180) elseif fingerDir == 2 then self.fingerParentNode:setLocalEulerAngles(0, 0, 0) elseif fingerDir == 3 then self.fingerParentNode:setLocalEulerAngles(0, 0, 90) elseif fingerDir == 4 then self.fingerParentNode:setLocalEulerAngles(0, 0, -90) end else self.fingerParentNode:setLocalEulerAngles(0, 0, 0) end end -- 清理点击屏幕相关 function TutorialUI:clearClickArea() self:setClickAreaEnable(false) self.clickAreaCallback = nil self.showClickSound = false self.clickAreaCS:ClearTargetTransform() self.isHaveClickBtnTutorial = false end function TutorialUI:hideGuideFinger() self.guideFingerNode:setLocalScale(0, 0, 0) self.guideFingerNode:getComponent(GConst.TYPEOF_UNITY_CLASS.ANIMATOR).enabled = false end function TutorialUI:registerClickBtnListener(callback, targetName, clickType) if targetName == nil then targetName = DataManager.TutorialData:getTargetName() end if targetName == nil and not DataManager.TutorialData:getTargetHeroId() then ModuleManager.TutorialManager:stopTutorial() return end local targetGo if targetName then targetGo = UIManager:getMainCanvasTransform():Find(targetName) else targetGo = self:getHeroCellByHeroId(DataManager.TutorialData:getTargetHeroId()) end if targetGo == nil then -- 没有找到说明需要找的ui还没打开 -- 每隔一定时间在重新找一次 if self._findTargetSid then ModuleManager.TutorialManager:unscheduleGlobal(self._findTargetSid) end self._findTargetSid = ModuleManager.TutorialManager:scheduleGlobal(function() if targetName then targetGo = UIManager:getMainCanvasTransform():Find(targetName) else targetGo = self:getHeroCellByHeroId(DataManager.TutorialData:getTargetHeroId()) end if targetGo then ModuleManager.TutorialManager:unscheduleGlobal(self._findTargetSid) self._findTargetSid = nil self.clickAreaCallback = callback self:setClickTarget(targetGo, clickType) end end, 0.1) else self.clickAreaCallback = callback self:setClickTarget(targetGo, clickType) end local fingerDir = DataManager.TutorialData:getFingerDir() if fingerDir and self.fingerParentNode then if fingerDir == 1 then self.fingerParentNode:setLocalEulerAngles(0, 0, 180) elseif fingerDir == 2 then self.fingerParentNode:setLocalEulerAngles(0, 0, 0) elseif fingerDir == 3 then self.fingerParentNode:setLocalEulerAngles(0, 0, 90) elseif fingerDir == 4 then self.fingerParentNode:setLocalEulerAngles(0, 0, -90) end else self.fingerParentNode:setLocalEulerAngles(0, 0, 0) end end function TutorialUI:setClickTarget(targetGo, clickType) self:setClickAreaEnable(true) if clickType == 1 then -- 点击指定区域就算完成事件 self.clickAreaCS:ClearTargetTransform() else self.clickAreaCS:SetTargetTransform(targetGo) end self.isHaveClickBtnTutorial = true local rectTransform = targetGo:GetComponent(GConst.TYPEOF_UNITY_CLASS.RECTTRANSFORM) local setGuideFingerPosition = nil local rect = rectTransform.rect local anchoredPosition = self:getTargetAnchoredPosition(targetGo) setGuideFingerPosition = function(interval) -- 如果ui在动的话就跟随着更新下位置 local time = 0 if self._findTargetSid then ModuleManager.TutorialManager:unscheduleGlobal(self._findTargetSid) end self._findTargetSid = ModuleManager.TutorialManager:scheduleGlobal(function(dt) if targetGo == nil or CS.BF.Utils.IsNull(targetGo) then -- 被干掉了 if self._findTargetSid then ModuleManager.TutorialManager:unscheduleGlobal(self._findTargetSid) self._findTargetSid = nil end return end local newAnchoredPosition = self:getTargetAnchoredPosition(targetGo) if math.abs(newAnchoredPosition.x - anchoredPosition.x) < 0.00001 and math.abs(newAnchoredPosition.y - anchoredPosition.y) < 0.00001 then time = time + dt if time > 0.1 then ModuleManager.TutorialManager:unscheduleGlobal(self._findTargetSid) self._findTargetSid = nil setGuideFingerPosition(0.1) end else time = 0 anchoredPosition = newAnchoredPosition self:updateClickFingerAndMask(anchoredPosition, rect) end end, interval) end setGuideFingerPosition(0) self:updateClickFingerAndMask(anchoredPosition, rect) end -- 更新点击区域表现 function TutorialUI:updateClickFingerAndMask(anchoredPosition, rect) self:setClickAreaWithTarget(anchoredPosition, rect.width, rect.height) local showMask = DataManager.TutorialData:getShowMask() if showMask == 1 then -- 矩形遮罩 self:setMaskEnable(true) local squareOffset = DataManager.TutorialData:getMaskSquareOffset() local addX = squareOffset and squareOffset[1] or 0 local addY = squareOffset and squareOffset[2] or 0 self.maskMat:SetVector(self:getCenterPropertyID(), {x = anchoredPosition.x + addX, y = anchoredPosition.y + addY, z = 0, w = 0}) self.maskMat:SetFloat(self:getEllipsePropertyID(), 10) local squareSize = DataManager.TutorialData:getMaskSquareSize() local addW = squareSize and squareSize[1] or 0 local addH = squareSize and squareSize[2] or 0 self.maskMat:SetFloat(self:getRectXPropertyID(), rect.width / 2 + addW) self.maskMat:SetFloat(self:getRectYPropertyID(), rect.height / 2 + addH) elseif showMask == 2 then -- 圆形遮罩 self:setMaskEnable(true) local circleOffset = DataManager.TutorialData:getMaskCircleOffset() local addX = circleOffset and circleOffset[1] or 0 local addY = circleOffset and circleOffset[2] or 0 self.maskMat:SetVector(self:getCenterPropertyID(), {x = anchoredPosition.x + addX, y = anchoredPosition.y + addY, z = 0, w = 0}) self.maskMat:SetFloat(self:getEllipsePropertyID(), 2) local maskRadius = DataManager.TutorialData:getMaskRadius() or TutorialConst.DEFAULT_RADIUS self.maskMat:SetFloat(self:getRectXPropertyID(), maskRadius) self.maskMat:SetFloat(self:getRectYPropertyID(), maskRadius) end local fingerOffset = DataManager.TutorialData:getFingerOffset() if fingerOffset then self.guideFingerNode:setLocalScale(1, 1, 1) if not self.guideFingerNode:getComponent(GConst.TYPEOF_UNITY_CLASS.ANIMATOR).enabled then self.guideFingerNode:getComponent(GConst.TYPEOF_UNITY_CLASS.ANIMATOR).enabled = true self.guideFingerNode:getComponent(GConst.TYPEOF_UNITY_CLASS.ANIMATOR):Play("finger_click_prompt", -1, 0) end self.guideFingerNode:setLocalPosition(anchoredPosition.x, anchoredPosition.y, 0) self.fingerParentNode:setLocalPosition(fingerOffset[1], fingerOffset[2], 0) end end -- 更新MASK function TutorialUI:updateMask(anchoredPosition, rect) local showMask = DataManager.TutorialData:getShowMask() if showMask == 1 then -- 矩形遮罩 self:setMaskEnable(true) local squareOffset = DataManager.TutorialData:getMaskSquareOffset() local addX = squareOffset and squareOffset[1] or 0 local addY = squareOffset and squareOffset[2] or 0 self.maskMat:SetVector(self:getCenterPropertyID(), {x = anchoredPosition.x + addX, y = anchoredPosition.y + addY, z = 0, w = 0}) self.maskMat:SetFloat(self:getEllipsePropertyID(), 10) local squareSize = DataManager.TutorialData:getMaskSquareSize() local addW = squareSize and squareSize[1] or 0 local addH = squareSize and squareSize[2] or 0 self.maskMat:SetFloat(self:getRectXPropertyID(), rect.width / 2 + addW) self.maskMat:SetFloat(self:getRectYPropertyID(), rect.height / 2 + addH) elseif showMask == 2 then -- 圆形遮罩 self:setMaskEnable(true) local circleOffset = DataManager.TutorialData:getMaskCircleOffset() local addX = circleOffset and circleOffset[1] or 0 local addY = circleOffset and circleOffset[2] or 0 self.maskMat:SetVector(self:getCenterPropertyID(), {x = anchoredPosition.x + addX, y = anchoredPosition.y + addY, z = 0, w = 0}) self.maskMat:SetFloat(self:getEllipsePropertyID(), 2) local maskRadius = DataManager.TutorialData:getMaskRadius() or TutorialConst.DEFAULT_RADIUS self.maskMat:SetFloat(self:getRectXPropertyID(), maskRadius) self.maskMat:SetFloat(self:getRectYPropertyID(), maskRadius) end end -- 这里先不ClearTargetTransform,避免在执行ExecuteEvents.Execute前target就先被清理了 function TutorialUI:unregisterClickBtn() self:setClickAreaEnable(false) self.clickAreaCallback = nil self.showClickSound = false end function TutorialUI:getCenterPropertyID() if not self._matCenterId then self._matCenterId = CS.UnityEngine.Shader.PropertyToID("_Center") end return self._matCenterId end function TutorialUI:getEllipsePropertyID() if not self._matEllipseId then self._matEllipseId = CS.UnityEngine.Shader.PropertyToID("_Ellipse") end return self._matEllipseId end function TutorialUI:getRectXPropertyID() if not self._matRectXId then self._matRectXId = CS.UnityEngine.Shader.PropertyToID("_Rect_X") end return self._matRectXId end function TutorialUI:getRectYPropertyID() if not self._matRectYId then self._matRectYId = CS.UnityEngine.Shader.PropertyToID("_Rect_Y") end return self._matRectYId end function TutorialUI:showTalk(callback) self:setClickAreaWithScreen() self:setClickAreaEnable(true) self.clickAreaCS:ClearTargetTransform() self.isHaveClickBtnTutorial = false self.clickAreaCallback = callback self.showClickSound = true local haveTutorialText = DataManager.TutorialData:getIsHaveTutorialText() if haveTutorialText then self:showTutorialText() else self:hideTalk() end end function TutorialUI:getTargetAnchoredPosition(targetGo) -- 这里是用transform.Find找到的组件,不是lua对象,所以直接用unity的api:GetComponent 而不是封装好的getComponent local rectTransform = targetGo:GetComponent(GConst.TYPEOF_UNITY_CLASS.RECTTRANSFORM) local rect = rectTransform.rect local localToWorldMatrix = rectTransform.localToWorldMatrix local tarCornerMid = localToWorldMatrix:MultiplyPoint3x4(BF.Vector3((rect.xMax + rect.x) / 2, (rect.yMax + rect.y) / 2, 0)) local screenPos = UIManager:getUICameraComponent():WorldToScreenPoint(tarCornerMid) local anchoredPosition = CS.BF.Utils.RectTransformScreenPointToLocalPointInRectangle(self.baseObject:getTransform(), screenPos.x, screenPos.y, UIManager:getUICameraComponent()) return anchoredPosition end function TutorialUI:clear() self:setBlockTouchEnabled(false) self:setMaskEnable(false) self:clearClickArea() self:hideTalk() self:hideGuideFinger() self:clearScheduler() self.baseObject:setActive(false) end function TutorialUI:clearScheduler() if self._findTargetSid then ModuleManager.TutorialManager:unscheduleGlobal(self._findTargetSid) self._findTargetSid = nil end end function TutorialUI:setVisible(visible) self.baseObject:setActive(visible) end function TutorialUI:getHeroCellByHeroId(heroId) local uiObj = UIManager:getUIByIndex(UIManager.UI_PATH.MAINCITY_UI) if not uiObj then return end local heroComp = uiObj:getHeroComp() if not heroComp then return end local cell = heroComp:getHeroCell(heroId) if not cell then return end return cell:getBaseObject():getTransform() end return TutorialUI