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
}