using System.Collections; using System.Collections.Generic; using System.IO; using FairyGUI; using FGUI.Arrow_game; using UnityEngine; namespace ChillConnect { public class ArrowGameUI : BaseUI { private ArrowGameUICtrl ctrl; private com_arrow_game ui; // FGUI界面根容器 private GComponent _viewContainer; #region FGUI 模板引用 private ArrorPoint _dotTemplate; private ArrowEnd _arrowTemplate; private LineTile _lineUnitTemplate; #endregion // 关卡总配置数据 private LevelConfig _levelConfig; // 全局固定参数 private const float _pointSpacing = 60f; // 点位间距 60px private const float _moveSpeed = 120f; // 移动速度 private List allArrowList = new List(); // 网格基础参数 private readonly int gridColNum = 10; private readonly float pointSpace = 60f; private readonly float outOffset = 200f; // 网格边界 private float gridLeft; private float gridRight; private readonly int gridRowNum = 10; private float gridTop; private float gridBottom; // 网格占用标记:true=该点位被占用,false=空闲 private bool[,] _gridOccupied; // 记录每个箭头占用的所有网格点位(行,列) private Dictionary> _arrowOccupiedCells = new Dictionary>(); // 爱心数量 private int _heartCount = 3; // 记录已经发生过碰撞的箭头对象(去重:同个箭头多次碰撞只扣一次血) private HashSet _collidedArrows = new HashSet(); // 统计场上还存在的箭头总数(用于判断全部移除=通关) private int _remainArrowCount = 0; // 记录当前UI所有由 CrazyAsyKit 启动的协程,用于统一停止 private List _activeCoroutines = new List(); // 是否开启删除道具模式 private bool _isDeleteMode = false; public ArrowGameUI(ArrowGameUICtrl ctrl) : base(ctrl) { uiName = UIConst.ArrowGameUI; this.ctrl = ctrl; } protected override void SetUIInfo(UIInfo uiInfo) { uiInfo.packageName = "Arrow_game"; uiInfo.assetName = "com_arrow_game"; uiInfo.layerType = UILayerType.Popup; uiInfo.isNeedOpenAnim = false; uiInfo.isNeedCloseAnim = false; uiInfo.isNeedUIMask = true; } #region 生命周期 protected override void OnInit() { } protected override void OnClose() { // ========= 第一步:统一停止所有协程(核心修复) ========= foreach (var co in _activeCoroutines) { if (co != null) { CrazyAsyKit.StopCoroutine(co); } } // 清空列表 _activeCoroutines.Clear(); // 保险========= 强制停止所有移动,终止对应协程 ========= if (_levelConfig != null) { foreach (var arrow in _levelConfig.arrows) { arrow.isMoving = false; } } // 销毁所有动态FGUI对象 ClearAllDynamicObj(); // 释放模板资源 if (_dotTemplate != null) _dotTemplate.Dispose(); if (_arrowTemplate != null) _arrowTemplate.Dispose(); if (_lineUnitTemplate != null) _lineUnitTemplate.Dispose(); _dotTemplate = null; _arrowTemplate = null; _lineUnitTemplate = null; _levelConfig = null; // 清空网格占用数据 _gridOccupied = null; _arrowOccupiedCells.Clear(); } protected override void OnBind() { ui = baseUI as com_arrow_game; } public override void OnUpdate() { if (_levelConfig == null) return; foreach (var arrow in _levelConfig.arrows) { UpdateArrowMove(arrow); } } protected override void OnOpenBefore(object args) { _viewContainer = ui.view_container; // 初始化FGUI模板 _dotTemplate = (ArrorPoint)UIPackage.CreateObject("Arrow_game", "ArrorPoint"); _arrowTemplate = (ArrowEnd)UIPackage.CreateObject("Arrow_game", "ArrowEnd"); _lineUnitTemplate = (LineTile)UIPackage.CreateObject("Arrow_game", "LineTile"); LoadLevelConfig(); InitGridBounds(); // 初始化网格占用数组 InitGridOccupied(); // ========== 新增:对局状态重置 ========== _heartCount = 3; _collidedArrows.Clear(); if(_levelConfig != null) _remainArrowCount = _levelConfig.arrows.Count; InitView(); } protected override void OnOpen(object args) { } protected override void OnHide() { } protected override void OnDisplay(object args) { } #endregion #region 页面初始化 & 关闭按钮 private void InitView() { // 关闭按钮 ui.btn_close.SetClick(() => { CtrlCloseUI(); }); ui.btn_clear.SetClick(OnClickDeleteItem); if (_levelConfig == null) return; // 1. 绘制网格圆点 DrawGridDots(_levelConfig); allArrowList = _levelConfig.arrows; // 2. 初始化所有箭头 + 路径线条 + 标记网格占用 foreach (var arrow in _levelConfig.arrows) { BuildArrowPath(arrow, _levelConfig); CreateArrowAndLines(arrow, _levelConfig); MarkArrowOccupiedCells(arrow, _levelConfig); } } #endregion private void InitGridBounds() { gridLeft = ui.point.x; gridRight = ui.point.x + gridColNum * pointSpace; gridTop = ui.point.y; gridBottom = ui.point.y + gridRowNum * pointSpace; } /// 初始化网格占用二维数组 private void InitGridOccupied() { int rows = _levelConfig.gridRows; int cols = _levelConfig.gridCols; _gridOccupied = new bool[rows, cols]; // 初始全部置为空闲 for (int r = 0; r < rows; r++) for (int c = 0; c < cols; c++) _gridOccupied[r, c] = false; _arrowOccupiedCells.Clear(); } /// 标记单个箭头占用的所有网格点 private void MarkArrowOccupiedCells(ArrowConfig arrow, LevelConfig cfg) { List<(int row, int col)> cellList = new List<(int row, int col)>(); int totalCol = cfg.gridCols; // 遍历箭头整条路径的所有网格点位 foreach (var pos in arrow.pathPointList) { int pointId = GetPointIdByPos(pos, cfg); PointIdToRowCol(pointId, totalCol, out int r, out int c); // 边界保护 if (r >= 0 && r < cfg.gridRows && c >= 0 && c < cfg.gridCols) { _gridOccupied[r, c] = true; cellList.Add((r, c)); } } _arrowOccupiedCells[arrow] = cellList; } /// 释放单个箭头占用的网格(箭头消失时调用) private void ReleaseArrowOccupied(ArrowConfig arrow) { if (!_arrowOccupiedCells.TryGetValue(arrow, out var cellList)) return; foreach (var (r, c) in cellList) { if (r >= 0 && r < _levelConfig.gridRows && c >= 0 && c < _levelConfig.gridCols) { _gridOccupied[r, c] = false; } } // 移除字典记录,避免冗余 _arrowOccupiedCells.Remove(arrow); } #region 配置加载 private void LoadLevelConfig() { _levelConfig = JsonHelper.LoadLevel("level_1001.json"); // 给 path 为空的箭头,设置默认方向 foreach (var arrow in _levelConfig.arrows) { if (arrow.path == null || arrow.path.Count == 0) { arrow.defaultDir = "up"; } else { arrow.defaultDir = null; } } } #endregion #region 【核心】网格坐标工具 /// 点位ID 转 行列索引 private void PointIdToRowCol(int pointId, int totalCol, out int row, out int col) { row = pointId / totalCol; col = pointId % totalCol; } /// 行列 → 实际像素坐标 private Vector2 RowColToPos(int row, int col) { float originX = ui.point.x; float originY = ui.point.y; float x = originX + col * _levelConfig.pointSpacing; float y = originY + row * _levelConfig.pointSpacing; return new Vector2(x, y); } /// 点位ID → 最终像素坐标 private Vector2 PointIdToPos(int pointId, int totalCol) { PointIdToRowCol(pointId, totalCol, out int r, out int c); return RowColToPos(r, c); } /// 像素坐标反向计算点位ID private int GetPointIdByPos(Vector2 pos, LevelConfig cfg) { float originX = ui.point.x; float originY = ui.point.y; int col = Mathf.RoundToInt((pos.x - originX) / cfg.pointSpacing); int row = Mathf.RoundToInt((pos.y - originY) / cfg.pointSpacing); return row * cfg.gridCols + col; } /// 根据FGUI旋转角度获取方向 private string GetDirByRotation(float rot) { rot = Mathf.Round(rot); if (rot == 0) return "right"; if (rot == 90) return "down"; if (rot == 180) return "left"; if (rot == 270) return "up"; return "right"; } /// 方向转旋转角度(箭头默认朝上) private float DirToRotation(string dir) { return dir switch { "up" => 0f, "right" => 90f, "down" => 180f, "left" => 270f, _ => 0f }; } #endregion #region 绘制网格圆点 private void DrawGridDots(LevelConfig cfg) { if (_dotTemplate == null || _viewContainer == null) return; _dotTemplate.visible = false; var rows = cfg.gridRows; var cols = cfg.gridCols; for (var r = 0; r < rows; r++) { for (var c = 0; c < cols; c++) { var dot = ArrorPoint.CreateInstance(); var pos = RowColToPos(r, c); dot.SetPosition(pos.x, pos.y, 0); _viewContainer.AddChild(dot); } } } #endregion #region 构建箭头路径点位 private void BuildArrowPath(ArrowConfig arrow, LevelConfig cfg) { arrow.pathPointList = new List(); int totalCol = cfg.gridCols; int curPointId = arrow.startPoint; Vector2 curPos = PointIdToPos(curPointId, totalCol); arrow.pathPointList.Add(curPos); foreach (string dir in arrow.path) { PointIdToRowCol(curPointId, totalCol, out int r, out int c); switch (dir) { case "up": r--; break; case "down": r++; break; case "left": c--; break; case "right": c++; break; } curPointId = r * totalCol + c; curPos = PointIdToPos(curPointId, totalCol); arrow.pathPointList.Add(curPos); } } #endregion #region 创建箭头 + 线条方块 private void CreateArrowAndLines(ArrowConfig arrow, LevelConfig cfg) { arrow.lineUnits = new List(); arrow.originalLinePos = new List(); arrow.lineTrackIndex = new List(); arrow.totalTrack = new List(); List pathPoints = arrow.pathPointList; // 生成整条连续行走轨迹 if (pathPoints.Count >= 2) { for (int i = 0; i < pathPoints.Count - 1; i++) { Vector2 segStart = pathPoints[i]; string dir = arrow.path[i]; float offX = 0, offY = 0; switch (dir) { case "right": offX = 10; break; case "left": offX = -10; break; case "down": offY = 10; break; case "up": offY = -10; break; } Vector2 cur = segStart; for (int k = 0; k < 6; k++) { arrow.totalTrack.Add(cur); cur.x += offX; cur.y += offY; } } if (arrow.path.Count > 0) arrow.finalMoveDir = arrow.path[arrow.path.Count - 1]; } // 路径只有单个点(纯箭头) if (pathPoints.Count < 2) { CreateArrowOnly(arrow, pathPoints[0]); // ========= 修复1:单点箭头也初始化轨迹索引 ========= arrow.arrowTrackIndex = 0; arrow.finalMoveDir = GetDirByRotation(arrow.arrowObj.rotation); // ========= 修复2:强制记录原始坐标(关键!)========= arrow.originalArrowPos = new Vector2(arrow.arrowObj.x, arrow.arrowObj.y); // 优化:给单点箭头填充一个轨迹点,和有线箭头逻辑完全一致 arrow.totalTrack.Add(pathPoints[0]); return; } // 生成线条图块 List tilePositions = new List(); for (int i = 0; i < pathPoints.Count - 1; i++) { Vector2 segStart = pathPoints[i]; string dir = arrow.path[i]; float offX = 0, offY = 0; switch (dir) { case "right": offX = 10; break; case "left": offX = -10; break; case "down": offY = 10; break; case "up": offY = -10; break; } Vector2 cur = segStart; for (int k = 0; k < 6; k++) { tilePositions.Add(cur); cur.x += offX; cur.y += offY; } } if (tilePositions.Count > 0) tilePositions.RemoveAt(tilePositions.Count - 1); ColorUtility.TryParseHtmlString(arrow.color, out Color lineColor); for (int i = 0; i < tilePositions.Count; i++) { Vector2 pos = tilePositions[i]; LineTile unit = LineTile.CreateInstance(); unit.SetPosition(pos.x, pos.y, 0); unit.GetChild("line").asGraph.color = lineColor; unit.visible = true; unit.touchable = true; unit.onClick.Add(() => OnPathClick(arrow)); _viewContainer.AddChild(unit); arrow.lineUnits.Add(unit); arrow.originalLinePos.Add(pos); arrow.lineTrackIndex.Add(i); } // 创建箭头 CreateArrowOnly(arrow, pathPoints[pathPoints.Count - 1]); arrow.originalArrowPos = new Vector2(arrow.arrowObj.x, arrow.arrowObj.y); arrow.arrowTrackIndex = arrow.totalTrack.Count - 1; } // 线条/箭头 统一点击回调 private void OnPathClick(ArrowConfig clickArrow) { if (clickArrow.isMoving) { Debug.Log($"[arrow] {clickArrow.id}正在移动,请勿点击其他箭头"); return; } // ========= 删除模式:直接删除箭头,不走移动逻辑 ========= if (_isDeleteMode) { DeleteArrowCompletely(clickArrow); return; } clickArrow.isMoving = true; bool hasBlock = CheckBlockByGrid(clickArrow); Debug.Log("[arrow] 点击箭头:" + clickArrow.id +$"hasBlock==={hasBlock}"); if (hasBlock) { MoveThenReset(clickArrow); } else { MoveToDisappear(clickArrow); } } /// 点击删除道具,开启/关闭删除模式 public void OnClickDeleteItem() { _isDeleteMode = !_isDeleteMode; // 可选:加提示,区分状态 if (_isDeleteMode) { Debug.Log("已开启删除模式,点击箭头即可删除"); // 可加UI提示:如图标高亮、文字提示 } else { Debug.Log("已关闭删除模式"); } } // 基于网格行列的阻挡检测 private bool CheckBlockByGrid(ArrowConfig curArrow) { if (_gridOccupied == null || _levelConfig == null) return false; // 当前箭头网格行列 Vector2 arrowPos = new Vector2(curArrow.arrowObj.x, curArrow.arrowObj.y); int curPointId = GetPointIdByPos(arrowPos, _levelConfig); PointIdToRowCol(curPointId, _levelConfig.gridCols, out int curRow, out int curCol); // 超出网格直接判定无阻挡 if (curRow < 0 || curRow >= _levelConfig.gridRows || curCol < 0 || curCol >= _levelConfig.gridCols) return false; // 获取移动方向 string moveDir; if (curArrow.path != null && curArrow.path.Count > 0) { moveDir = curArrow.path[curArrow.path.Count - 1]; } else if (!string.IsNullOrEmpty(curArrow.defaultDir)) { moveDir = curArrow.defaultDir; } else { moveDir = "up"; } bool isCollide = false; // 沿方向遍历前方网格 switch (moveDir) { case "up": for (int r = curRow - 1; r >= 0; r--) { if (_gridOccupied[r, curCol]) { isCollide = true; break; } } break; case "down": for (int r = curRow + 1; r < _levelConfig.gridRows; r++) { if (_gridOccupied[r, curCol]) { isCollide = true; break; } } break; case "left": for (int c = curCol - 1; c >= 0; c--) { if (_gridOccupied[curRow, c]) { isCollide = true; break; } } break; case "right": for (int c = curCol + 1; c < _levelConfig.gridCols; c++) { if (_gridOccupied[curRow, c]) { isCollide = true; break; } } break; } // ========== 新增:碰撞判重 + 扣爱心 ========== if (isCollide) { // 这条线第一次碰撞,才扣血 if (!_collidedArrows.Contains(curArrow)) { _collidedArrows.Add(curArrow); _heartCount--; if (_heartCount > 0) ui.HeartsPanel.GetChild($"xin_{_heartCount+1}").visible = false; // 爱心归零 → 失败结算 if (_heartCount <= 0) { ShowResult(false); } } } return isCollide; } /// 彻底删除指定箭头:销毁UI+清数据+停逻辑+移除列表 private void DeleteArrowCompletely(ArrowConfig arrow) { if (arrow == null) return; // 1. 停止移动标记,拦截Update和协程 arrow.isMoving = false; // 2. 销毁箭头中所有线条UI if (arrow.lineUnits != null) { foreach (var line in arrow.lineUnits) { if (line != null) { _viewContainer.RemoveChild(line); line.Dispose(); } } arrow.lineUnits.Clear(); } // 3. 销毁箭头本体UI if (arrow.arrowObj != null) { _viewContainer.RemoveChild(arrow.arrowObj); arrow.arrowObj.Dispose(); arrow.arrowObj = null; } // 4. 释放网格占用数据(和飞出边界逻辑保持一致) ReleaseArrowOccupied(arrow); // 5. 存活箭头计数-1 _remainArrowCount--; // 6. 从总列表移除该箭头(核心:删除数据源) if (_levelConfig != null && _levelConfig.arrows != null) { _levelConfig.arrows.Remove(arrow); } // 7. 可选:判断是否全部删除完成,触发结算(按需开启) if (_remainArrowCount <= 0) { ShowResult(true); } // 8. 退出删除模式 _isDeleteMode = false; Debug.Log("[arrow] 箭头已彻底删除"); } // 创建箭头 + 绑定点击 private void CreateArrowOnly(ArrowConfig arrow, Vector2 arrowPos) { ArrowEnd arrowIns = ArrowEnd.CreateInstance(); arrowIns.SetPosition(arrowPos.x, arrowPos.y, 0); arrowIns.touchable = true; if (arrow.path != null && arrow.path.Count > 0) { string lastDir = arrow.path[arrow.path.Count - 1]; arrowIns.rotation = DirToRotation(lastDir); } else if (!string.IsNullOrEmpty(arrow.defaultDir)) { // 纯单点箭头 使用默认方向旋转 arrowIns.rotation = DirToRotation(arrow.defaultDir); } ColorUtility.TryParseHtmlString(arrow.color, out Color arrowColor); GImage icon = arrowIns.GetChild("icon").asImage; if (icon != null) icon.color = arrowColor; arrowIns.visible = true; arrowIns.onClick.Add(() => OnPathClick(arrow)); _viewContainer.AddChild(arrowIns); arrow.arrowObj = arrowIns; arrow.isMoving = false; } // 移动后复位动画(回弹,不释放占用) private void MoveThenReset(ArrowConfig arrow) { RunCoroutine(MoveAndResetCoroutine(arrow)); } private IEnumerator MoveAndResetCoroutine(ArrowConfig arrow) { float frameDelay = 0.01f; int stepCount = 2; int moved = 0; int trackMaxIndex = arrow.totalTrack.Count - 1; string dir; if (arrow.path != null && arrow.path.Count > 0) { dir = arrow.path[arrow.path.Count - 1]; } else if (!string.IsNullOrEmpty(arrow.defaultDir)) { dir = arrow.defaultDir; } else { dir = "up"; } arrow.finalMoveDir = dir; while (moved < stepCount) { // 新增空对象防护 if (arrow == null || arrow.arrowObj == null) { Debug.Log($"[arrow] MoveAndResetCoroutine arrow 为空, 协程结束"); yield break; } for (int i = 0; i < arrow.lineUnits.Count; i++) { int curIdx = arrow.lineTrackIndex[i]; if (curIdx < trackMaxIndex) { curIdx++; arrow.lineUnits[i].SetPosition(arrow.totalTrack[curIdx].x, arrow.totalTrack[curIdx].y, 0); arrow.lineTrackIndex[i] = curIdx; } else { float dx = 0, dy = 0; switch (arrow.finalMoveDir) { case "right": dx = 10; break; case "left": dx = -10; break; case "down": dy = 10; break; case "up": dy = -10; break; } arrow.lineUnits[i].x += dx; arrow.lineUnits[i].y += dy; } } int arrowIdx = arrow.arrowTrackIndex; if (arrowIdx < trackMaxIndex) { arrowIdx++; arrow.arrowObj.SetPosition(arrow.totalTrack[arrowIdx].x, arrow.totalTrack[arrowIdx].y, 0); arrow.arrowTrackIndex = arrowIdx; } else { float dx = 0, dy = 0; switch (arrow.finalMoveDir) { case "right": dx = 10; break; case "left": dx = -10; break; case "down": dy = 10; break; case "up": dy = -10; break; } arrow.arrowObj.x += dx; arrow.arrowObj.y += dy; } moved++; yield return new WaitForSeconds(frameDelay); } // 复位位置 for (int i = 0; i < arrow.lineUnits.Count; i++) { arrow.lineUnits[i].SetPosition(arrow.originalLinePos[i].x, arrow.originalLinePos[i].y, 0); arrow.lineTrackIndex[i] = i; } if(arrow.arrowObj != null) { arrow.arrowObj.SetPosition(arrow.originalArrowPos.x, arrow.originalArrowPos.y, 0); } arrow.arrowTrackIndex = arrow.totalTrack.Count - 1; arrow.isMoving = false; } private void MoveToDisappear(ArrowConfig arrow) { RunCoroutine(MoveAndDisappearCoroutine(arrow)); } private IEnumerator MoveAndDisappearCoroutine(ArrowConfig arrow) { Debug.Log($"[arrow] arrow.totalTrack.Count =={arrow.totalTrack.Count}"); float frameDelay = 0.01f; int trackMaxIndex = arrow.totalTrack != null ? arrow.totalTrack.Count - 1 : 0; string dir; if (arrow.path != null && arrow.path.Count > 0) { dir = arrow.path[arrow.path.Count - 1]; } else if (!string.IsNullOrEmpty(arrow.defaultDir)) { dir = arrow.defaultDir; } else { dir = "up"; } arrow.finalMoveDir = dir; while (true) { // 兜底:对象已销毁,直接退出 if (arrow == null || arrow.arrowObj == null) { Debug.Log($"arrow 为空, 协程结束"); yield break; } // 线条移动 for (int i = 0; i < arrow.lineUnits.Count; i++) { int curIdx = arrow.lineTrackIndex[i]; if (curIdx < trackMaxIndex) { curIdx++; arrow.lineUnits[i].SetPosition(arrow.totalTrack[curIdx].x, arrow.totalTrack[curIdx].y, 0); arrow.lineTrackIndex[i] = curIdx; } else { float dx = 0, dy = 0; switch (arrow.finalMoveDir) { case "right": dx = 10; break; case "left": dx = -10; break; case "down": dy = 10; break; case "up": dy = -10; break; } arrow.lineUnits[i].x += dx; arrow.lineUnits[i].y += dy; } } // 箭头移动 int arrowIdx = arrow.arrowTrackIndex; if (arrowIdx < trackMaxIndex) { arrowIdx++; arrow.arrowObj.SetPosition(arrow.totalTrack[arrowIdx].x, arrow.totalTrack[arrowIdx].y, 0); arrow.arrowTrackIndex = arrowIdx; } else { float dx = 0, dy = 0; switch (arrow.finalMoveDir) { case "right": dx = 10; break; case "left": dx = -10; break; case "down": dy = 10; break; case "up": dy = -10; break; } arrow.arrowObj.x += dx; arrow.arrowObj.y += dy; } // ========= 边界判断:有线箭头取尾部轨迹,单点箭头取自身 ========= // 直接取箭头实时坐标,不再使用totalTrack静态点位 bool isOut = false; float checkX = 0; float checkY = 0; // 线段从起点生成,第 0 个元素才是尾巴 if (arrow.lineUnits != null && arrow.lineUnits.Count > 0) { var tailLine = arrow.lineUnits[0]; checkX = tailLine.x; checkY = tailLine.y; } else if (arrow.arrowObj != null) { // 纯单点箭头,使用自身坐标 checkX = arrow.arrowObj.x; checkY = arrow.arrowObj.y; } Debug.Log($"[arrow] arrow.finalMoveDir =={arrow.finalMoveDir}"); switch (arrow.finalMoveDir) { case "right": Debug.Log($"[arrow] checkX={checkX} | gridRight={gridRight} | gridRight + outOffset={gridRight + outOffset}"); if (checkX > gridRight + outOffset) isOut = true; break; case "left": Debug.Log($"[arrow] checkX={checkX} | gridLeft={gridLeft} | gridLeft - outOffset={gridLeft - outOffset}"); if (checkX < gridLeft - outOffset) isOut = true; break; case "down": Debug.Log($"[arrow] checkY={checkY} | gridBottom={gridBottom} | gridBottom + outOffset={gridBottom + outOffset}"); if (checkY > gridBottom + outOffset) isOut = true; break; case "up": Debug.Log($"[arrow] checkY={checkY} | gridTop={gridTop} | gridTop - outOffset={gridTop - outOffset}"); if (checkY < gridTop - outOffset) isOut = true; break; } // ========= 超出边界:隐藏 + 计数 + 终止协程 ========= Debug.Log($"[arrow] isOut =={isOut}"); if (isOut) { ReleaseArrowOccupied(arrow); // 销毁线条 foreach (var line in arrow.lineUnits) { if(line != null) { _viewContainer.RemoveChild(line); line.Dispose(); } } arrow.lineUnits.Clear(); // 销毁箭头 if(arrow.arrowObj != null) { _viewContainer.RemoveChild(arrow.arrowObj); arrow.arrowObj.Dispose(); arrow.arrowObj = null; } arrow.isMoving = false; _remainArrowCount--; if (_remainArrowCount <= 0) { ShowResult(true); } yield break; } yield return new WaitForSeconds(frameDelay); } } #endregion #region 点击 & 移动逻辑 private void OnArrowClick(ArrowConfig arrow) { if (arrow.isMoving) return; arrow.isMoving = true; } private void UpdateArrowMove(ArrowConfig arrow) { // 关键拦截:不允许移动 / 对象已销毁 → 直接退出 if (!arrow.isMoving || arrow.arrowObj == null) return; if (!arrow.isMoving) return; List path = arrow.pathPointList; if (arrow.targetIndex < 0) { arrow.isMoving = false; arrow.arrowObj.visible = false; foreach (var line in arrow.lineUnits) line.visible = false; return; } Vector2 targetPos = path[arrow.targetIndex]; Vector2 curPos = new Vector2(arrow.arrowObj.x, arrow.arrowObj.y); float distance = Vector2.Distance(curPos, targetPos); if (distance < 2f) { arrow.targetIndex--; } else { Vector2 dir = (targetPos - curPos).normalized; Vector2 newPos = curPos + dir * _moveSpeed * Time.deltaTime; arrow.arrowObj.SetPosition(newPos.x, newPos.y, 0); } RefreshLineVisible(arrow); } private void RefreshLineVisible(ArrowConfig arrow) { int segIndex = 0; int totalSeg = arrow.pathPointList.Count - 1; for (int s = 0; s < totalSeg; s++) { bool isShow = s < arrow.targetIndex; for (int i = 0; i < 6; i++) { arrow.lineUnits[segIndex + i].visible = isShow; } segIndex += 6; } } #endregion #region 资源清理 /// 全局启动协程 + 自动记录,关闭界面时统一停止 private Coroutine RunCoroutine(IEnumerator cor) { if (cor == null) return null; Coroutine co = CrazyAsyKit.StartCoroutine(cor); if (co != null) { _activeCoroutines.Add(co); } return co; } private void ClearAllDynamicObj() { if (_levelConfig == null || _viewContainer == null) return; foreach (var arrow in _levelConfig.arrows) { if (arrow.arrowObj != null) { _viewContainer.RemoveChild(arrow.arrowObj); arrow.arrowObj.Dispose(); arrow.arrowObj = null; } foreach (var line in arrow.lineUnits) { _viewContainer.RemoveChild(line); line.Dispose(); } arrow.lineUnits.Clear(); } } #endregion /// /// 弹出结算 /// isSuccess: true=成功,false=失败 /// private void ShowResult(bool isSuccess) { // 暂停所有交互/移动(可选) foreach (var arrow in _levelConfig.arrows) { arrow.isMoving = false; } uiCtrlDispatcher.Dispatch(UICtrlMsg.LevelSuccessUI_Open, isSuccess); } // 重新开始本局 private void OnRestartGame(object a = null) { if (a!=null && (bool)a) { _heartCount = 3; for (int i = _heartCount; i > 0; i--) { ui.HeartsPanel.GetChild($"xin_{i}").visible = true; } return; } // 先清空当前所有动态物体 ClearAllDynamicObj(); // 重新走初始化流程 InitGridBounds(); InitGridOccupied(); _heartCount = 3; _collidedArrows.Clear(); _remainArrowCount = _levelConfig.arrows.Count; DataMgr.ArrowResultLevel.Value = 1; InitView(); } #region 消息监听 protected override void AddListener() { GameDispatcher.Instance.AddListener(GameMsg.reset_game, OnRestartGame); } protected override void RemoveListener() { GameDispatcher.Instance.RemoveListener(GameMsg.reset_game, OnRestartGame); } #endregion } #region 配置解析 & 数据结构 public static class JsonHelper { public static LevelConfig LoadLevel(string fileName) { string path = Path.Combine(Application.streamingAssetsPath, fileName); return JsonUtility.FromJson(File.ReadAllText(path)); } } [System.Serializable] public class LevelConfig { public int levelId; public string levelName; public int gridRows; public int gridCols; public int pointSpacing; public List arrows; } [System.Serializable] public class ArrowConfig { public int id; public int startPoint; public int endPoint; public string color; public List path; public string defaultDir; public List pathPointList; public List lineUnits; public GComponent arrowObj; public bool isMoving; public List originalLinePos; public Vector2 originalArrowPos; public int targetIndex; public List totalTrack; public List lineTrackIndex; public int arrowTrackIndex; public string finalMoveDir; } #endregion }