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; // 移动速度 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() { // 销毁所有动态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; } 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(); 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(); }); if (_levelConfig == null) return; // 1. 以 ui.point 为原点生成整张网格圆点 DrawGridDots(_levelConfig); // 2. 初始化所有箭头 + 路径线条 foreach (var arrow in _levelConfig.arrows) { BuildArrowPath(arrow, _levelConfig); CreateArrowAndLines(arrow, _levelConfig); } } #endregion #region 配置加载 private void LoadLevelConfig() { _levelConfig = JsonHelper.LoadLevel("level_1001.json"); } #endregion #region 【核心】网格坐标工具(基于 ui.point 为原点) /// 点位ID 转 行列索引 private void PointIdToRowCol(int pointId, int totalCol, out int row, out int col) { row = pointId / totalCol; col = pointId % totalCol; } /// /// 行列 → 实际像素坐标 /// 原点 = ui.point 组件坐标,向右+X,向下+Y,间距固定60 /// private Vector2 RowColToPos(int row, int col) { // 取UI上point组件的坐标作为网格原点 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); } /// 方向转旋转角度(箭头默认朝上) private float DirToRotation(string dir) { return dir switch { "up" => 0f, "right" => 90f, "down" => 180f, "left" => 270f, _ => 0f }; } #endregion #region 绘制网格圆点(以 ui.point 为起点) 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(); List path = arrow.pathPointList; if (path.Count < 2) { CreateArrowOnly(arrow, path[0]); return; } ColorUtility.TryParseHtmlString(arrow.color, out Color lineColor); List allTilePositions = new List(); // 关键:直接用 path 数组判断方向,和配置表完全一致 for (int i = 0; i < path.Count - 1; i++) { Vector2 pStart = path[i]; Vector2 pEnd = path[i + 1]; string dir = arrow.path[i]; // 直接从配置里取方向 float offsetX = 0f; float offsetY = 0f; // 严格按 path 方向设置步长 switch (dir) { case "right": offsetX = 10f; break; case "left": offsetX = -10f; break; case "down": offsetY = 10f; break; case "up": offsetY = -10f; break; } Vector2 curPos = pStart; // 生成6个方块,从pStart一直覆盖到pEnd for (int k = 0; k < 6; k++) { allTilePositions.Add(curPos); curPos.x += offsetX; curPos.y += offsetY; } } // 移除最后一个点(终点位置的方块,避免挡住箭头) if (allTilePositions.Count > 0) { allTilePositions.RemoveAt(allTilePositions.Count - 1); } // 创建所有方块 foreach (var pos in allTilePositions) { LineTile unit = LineTile.CreateInstance(); unit.SetPosition(pos.x, pos.y, 0); unit.GetChild("line").asGraph.color = lineColor; unit.visible = true; _viewContainer.AddChild(unit); arrow.lineUnits.Add(unit); } // 创建箭头 CreateArrowOnly(arrow, path[path.Count - 1]); } private void CreateArrowOnly(ArrowConfig arrow, Vector2 arrowPos) { ArrowEnd arrowIns = ArrowEnd.CreateInstance(); arrowIns.SetPosition(arrowPos.x, arrowPos.y, 0); if (arrow.path.Count > 0) { string lastDir = arrow.path[arrow.path.Count - 1]; arrowIns.rotation = DirToRotation(lastDir); } if (ColorUtility.TryParseHtmlString(arrow.color, out Color arrowColor)) { GImage icon = arrowIns.GetChild("icon").asImage; if (icon != null) icon.color = arrowColor; } arrowIns.visible = true; _viewContainer.AddChild(arrowIns); arrowIns.onClick.Add(() => OnArrowClick(arrow)); arrow.arrowObj = arrowIns; arrow.isMoving = false; arrow.targetIndex = arrow.pathPointList.Count - 1; } /// /// 两点间距60px,使用10x10方块,生成6个,完整铺满从pStart到pEnd的线段。 /// 关键:覆盖完整区间,保证拐点无缝衔接。 /// private void CreateLineUnitBetweenTwoPoint(Vector2 pStart, Vector2 pEnd, Color lineColor, List lineList) { if (_lineUnitTemplate == null) return; float offsetX = 0f; float offsetY = 0f; // 判断方向 if (Mathf.Abs(pEnd.x - pStart.x) > 1f) { offsetX = 10f; } else { offsetY = 10f; } Vector2 curPos = pStart; // 循环6次,生成6个方块,从pStart一直覆盖到pEnd for (int i = 0; i < 6; i++) { LineTile unit = LineTile.CreateInstance(); unit.SetPosition(curPos.x, curPos.y, 0); unit.GetChild("line").asGraph.color = lineColor; unit.visible = true; _viewContainer.AddChild(unit); lineList.Add(unit); curPos.x += offsetX; curPos.y += offsetY; } } #endregion #region 点击 & 移动逻辑 private void OnArrowClick(ArrowConfig arrow) { if (arrow.isMoving) return; arrow.isMoving = true; } private void UpdateArrowMove(ArrowConfig arrow) { 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); } /// 逐段控制显隐,每段固定6个方块 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 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 #region 消息监听 protected override void AddListener() { } protected override void RemoveListener() { } #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)); } } #endregion #region 配置解析 & 数据结构(保留原有定义) [System.Serializable] public class LevelConfig { public int levelId; public string levelName; public int gridRows; public int gridCols; public int pointSpacing; // 配置内此字段仅保留兼容,实际间距以代码 60px 为准 public List arrows; } [System.Serializable] public class ArrowConfig { public int id; public int startPoint; public int endPoint; public string color; public List path; // 运行时数据 public List pathPointList; // 整条路径所有点位坐标 public GComponent arrowObj; public List lineUnits; // 所有线条方块预制体 public bool isMoving; public int targetIndex; } #endregion }