489 lines
15 KiB
C#
489 lines
15 KiB
C#
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 为原点)
|
|
/// <summary>点位ID 转 行列索引</summary>
|
|
private void PointIdToRowCol(int pointId, int totalCol, out int row, out int col)
|
|
{
|
|
row = pointId / totalCol;
|
|
col = pointId % totalCol;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 行列 → 实际像素坐标
|
|
/// 原点 = ui.point 组件坐标,向右+X,向下+Y,间距固定60
|
|
/// </summary>
|
|
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);
|
|
}
|
|
|
|
/// <summary>点位ID → 最终像素坐标</summary>
|
|
private Vector2 PointIdToPos(int pointId, int totalCol)
|
|
{
|
|
PointIdToRowCol(pointId, totalCol, out int r, out int c);
|
|
return RowColToPos(r, c);
|
|
}
|
|
|
|
/// <summary>方向转旋转角度(箭头默认朝上)</summary>
|
|
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<Vector2>();
|
|
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<GComponent>();
|
|
List<Vector2> path = arrow.pathPointList;
|
|
|
|
if (path.Count < 2)
|
|
{
|
|
CreateArrowOnly(arrow, path[0]);
|
|
return;
|
|
}
|
|
|
|
ColorUtility.TryParseHtmlString(arrow.color, out Color lineColor);
|
|
List<Vector2> allTilePositions = new List<Vector2>();
|
|
|
|
// 关键:直接用 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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 两点间距60px,使用10x10方块,生成6个,完整铺满从pStart到pEnd的线段。
|
|
/// 关键:覆盖完整区间,保证拐点无缝衔接。
|
|
/// </summary>
|
|
private void CreateLineUnitBetweenTwoPoint(Vector2 pStart, Vector2 pEnd, Color lineColor, List<GComponent> 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<Vector2> 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);
|
|
}
|
|
|
|
/// <summary>逐段控制显隐,每段固定6个方块</summary>
|
|
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<LevelConfig>(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<ArrowConfig> arrows;
|
|
}
|
|
|
|
[System.Serializable]
|
|
public class ArrowConfig
|
|
{
|
|
public int id;
|
|
public int startPoint;
|
|
public int endPoint;
|
|
public string color;
|
|
public List<string> path;
|
|
|
|
// 运行时数据
|
|
public List<Vector2> pathPointList; // 整条路径所有点位坐标
|
|
public GComponent arrowObj;
|
|
public List<GComponent> lineUnits; // 所有线条方块预制体
|
|
public bool isMoving;
|
|
public int targetIndex;
|
|
}
|
|
#endregion
|
|
} |