Files
ArrowBeatTap-Gp/Assets/Scripts/ModuleUI/ArrowGame/ArrowGameUI.cs
T

1693 lines
59 KiB
C#
Raw Normal View History

using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
2026-06-14 19:25:04 +08:00
using DG.Tweening;
using FairyGUI;
using FGUI.Arrow_game;
using FGUI.Common_01;
using IgnoreOPS;
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 readonly int gridColNum = 10;
private readonly float pointSpace = 60f;
private readonly float outOffset = 200f;
// 网格边界
2026-06-14 16:25:04 +08:00
private Vector2 _gridWorldCenter; // 网格世界中心点
private float gridLeft;
private float gridRight;
private readonly int gridRowNum = 10;
private float gridTop;
private float gridBottom;
2026-06-14 16:25:04 +08:00
// 网格行列、单个格子尺寸(你原有网格参数)
private int _gridRow;
private int _gridCol;
private float _cellSize;
// 网格占用标记:true=该点位被占用,false=空闲
private bool[,] _gridOccupied;
// 记录每个箭头占用的所有网格点位(行,列)
private Dictionary<ArrowConfig, List<(int row, int col)>> _arrowOccupiedCells = new Dictionary<ArrowConfig, List<(int row, int col)>>();
// 爱心数量
private int _heartCount = 3;
// 记录已经发生过碰撞的箭头对象(去重:同个箭头多次碰撞只扣一次血)
private HashSet<ArrowConfig> _collidedArrows = new HashSet<ArrowConfig>();
// 统计场上还存在的箭头总数(用于判断全部移除=通关)
private int _remainArrowCount = 0;
// 记录当前UI所有由 CrazyAsyKit 启动的协程,用于统一停止
private List<Coroutine> _activeCoroutines = new List<Coroutine>();
// 是否开启删除道具模式
private bool _isDeleteMode = false;
// 手指提示图标
private com_finger _fingerTipObj;
// 当前正在提示的箭头
private ArrowConfig _currentTipArrow;
// 手指图标偏移(微调位置)
private readonly Vector2 _fingerOffset = new Vector2(-60, -60);
// 所有网格圆点实例列表,用于批量改色/清理
private List<ArrorPoint> _gridDotList = new List<ArrorPoint>();
#region 缩放、拖拽、滑动条 全局变量
// 缩放阈值(滑动条左右边界对应这两个值)
private readonly float _minScale = 0.6f;
private readonly float _maxScale = 1.5f;
// FGUI 滑动条组件
private GSlider _zoomSlider;
// 双指缩放手势
private float _lastTwoFingerDistance;
private bool _isTwoFingerTouch;
// 单指拖拽平移
private Vector2 _lastTouchPos;
private bool _isDraging;
// 拖拽边界留边(防止完全移出屏幕)
private readonly float _dragBorder = 100f;
#endregion
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;
2026-06-14 16:25:04 +08:00
uiInfo.isTickUpdate = 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();
HideFingerTip();
// 释放模板资源
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;
}
2026-06-14 19:25:04 +08:00
// 新增坐标转换方法
private Vector2 UnityMouseToFGUIPos(Vector2 unityPos)
{
float x = unityPos.x;
float y = Screen.height - unityPos.y;
return new Vector2(x, y);
}
public override void OnUpdate()
{
2026-06-14 16:25:04 +08:00
// if (_levelConfig == null) return;
// foreach (var arrow in _levelConfig.arrows)
// {
// UpdateArrowMove(arrow);
// }
//
// ========= 编辑器模拟触摸(仅编辑模式生效) =========
2026-06-14 19:25:04 +08:00
// ========== 编辑器鼠标拖拽 ==========
// 初始化未完成,直接跳过拖拽
if (!_initComplete)
return;
2026-06-14 16:25:04 +08:00
#if UNITY_EDITOR
2026-06-14 19:25:04 +08:00
float moveThreshold = 1f; // 移动阈值,大于这个值才算有效拖动
// 鼠标按下:仅初始化状态和起点
if (InputHelper.Instance.GetMouseButtonDown(0))
2026-06-14 16:25:04 +08:00
{
2026-06-14 19:25:04 +08:00
Vector2 unityPos = InputHelper.Instance.MousePosition;
_lastTouchPos = UnityMouseToFGUIPos(unityPos);
2026-06-14 16:25:04 +08:00
_isDraging = true;
2026-06-14 19:25:04 +08:00
Debug.Log("鼠标按下,开始拖拽");
return; // 本帧直接返回,不执行拖动逻辑
2026-06-14 16:25:04 +08:00
}
2026-06-14 19:25:04 +08:00
// 拖拽中:按住左键 + 拖拽标记为true
if (_isDraging && InputHelper.Instance.GetMouseButton(0))
2026-06-14 16:25:04 +08:00
{
2026-06-14 19:25:04 +08:00
Vector2 unityPos = InputHelper.Instance.MousePosition;
Vector2 curMousePos = UnityMouseToFGUIPos(unityPos);
2026-06-14 16:25:04 +08:00
Vector2 offset = curMousePos - _lastTouchPos;
2026-06-14 19:25:04 +08:00
Debug.Log($"curMousePos=={curMousePos} _lastTouchPos=={_lastTouchPos} offset=={offset}");
// 只有偏移超过阈值才移动 & 更新基准点
if (offset.magnitude > moveThreshold)
{
MoveContainer(offset);
_lastTouchPos = curMousePos; // 移动后再刷新起点
}
2026-06-14 16:25:04 +08:00
}
2026-06-14 19:25:04 +08:00
// 鼠标抬起,结束拖拽
if (InputHelper.Instance.GetMouseButtonUp(0))
{
2026-06-14 16:25:04 +08:00
_isDraging = false;
2026-06-14 19:25:04 +08:00
Debug.Log("鼠标抬起,结束拖拽");
}
2026-06-14 16:25:04 +08:00
#endif
// ========= 双指 = 缩放(优先级最高) =========
2026-06-14 19:25:04 +08:00
if (InputHelper.Instance.TouchCount == 2)
{
_isDraging = false;
2026-06-14 19:25:04 +08:00
Touch t0 = InputHelper.Instance.GetTouch(0);
Touch t1 = InputHelper.Instance.GetTouch(1);
float currentDis = Vector2.Distance(t0.position, t1.position);
if (!_isTwoFingerTouch)
{
_lastTwoFingerDistance = currentDis;
_isTwoFingerTouch = true;
}
else
{
float delta = currentDis - _lastTwoFingerDistance;
float curScale = _viewContainer.scaleX;
// 缩放灵敏度,按需微调
curScale += delta * 0.001f;
curScale = Mathf.Clamp(curScale, _minScale, _maxScale);
// 应用缩放
_viewContainer.scaleX = curScale;
_viewContainer.scaleY = curScale;
// 关键:双指缩放后,同步更新滑动条位置
_zoomSlider.value = GetSliderValueByScale(curScale);
_lastTwoFingerDistance = currentDis;
}
}
// ========= 单指 = 拖拽平移 =========
2026-06-14 19:25:04 +08:00
else if (InputHelper.Instance.TouchCount == 1)
{
2026-06-14 19:25:04 +08:00
Touch touch = InputHelper.Instance.GetTouch(0);
Vector2 curTouchPos = touch.position;
if (touch.phase == TouchPhase.Began)
{
_lastTouchPos = curTouchPos;
_isDraging = true;
_isTwoFingerTouch = false;
}
else if (touch.phase == TouchPhase.Moved && _isDraging)
{
Vector2 offset = curTouchPos - _lastTouchPos;
MoveContainer(offset);
_lastTouchPos = curTouchPos;
}
else if (touch.phase == TouchPhase.Ended || touch.phase == TouchPhase.Canceled)
{
_isDraging = false;
}
}
// 无触摸,重置状态
else
{
_isTwoFingerTouch = false;
_isDraging = false;
}
}
protected override void OnOpenBefore(object args)
{
//加载配置
InitAllLevelData();
_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 消息监听
protected override void AddListener()
{
GameDispatcher.Instance.AddListener(GameMsg.reset_game, OnRestartGame);
GameDispatcher.Instance.AddListener(GameMsg.Update102, SetTopCurr);
GameDispatcher.Instance.AddListener(GameMsg.Update101, SetTopCurr);
}
protected override void RemoveListener()
{
GameDispatcher.Instance.RemoveListener(GameMsg.reset_game, OnRestartGame);
GameDispatcher.Instance.RemoveListener(GameMsg.Update102, SetTopCurr);
GameDispatcher.Instance.RemoveListener(GameMsg.Update101, SetTopCurr);
}
#endregion
#region 页面初始化
private bool _initComplete = false;
private void InitView()
{
// 重置手势状态
_isTwoFingerTouch = false;
_isDraging = false;
SetModel();
ui.text_level.SetVar("lv", GameHelper.GetLevel().ToString()).FlushVars();
// 按钮点击事件绑定
OnBtnClickBindEvent();
_fingerTipObj = com_finger.CreateInstance();
_viewContainer.AddChild(_fingerTipObj);
2026-06-14 16:25:04 +08:00
_fingerTipObj.visible = false;
_currentTipArrow = null;
_zoomSlider = ui.com_bottom.zoomSlide;
// 2. 绑定滑动条拖动回调
_zoomSlider.onChanged.Add(OnZoomSliderChange);
// 3. 容器初始缩放、位置
_viewContainer.scaleX = 1f;
_viewContainer.scaleY = 1f;
// 滑动条初始居中(对应 1.0 缩放)
float initValue = GetSliderValueByScale(1f);
_zoomSlider.value = initValue;
// 先兜底重置为默认锚点
_viewContainer.pivot = new Vector2(0, 0);
if (_levelConfig == null)
{
_initComplete = true;
return;
}
// 第一步:绘制网格 → 内部会计算网格中心、修改pivot为网格中心
DrawGridDots(_levelConfig);
// ========== 核心:把网格中心钉在屏幕中心 ==========
// _gridWorldCenter 是 DrawGridDots 里 CalculateGridCenter 算出的值
float screenCenterX = Screen.width * 0.5f;
float screenCenterY = Screen.height * 0.5f;
// 计算容器需要移动多少,才能让网格中心正好对准屏幕中心
float targetContainerX = screenCenterX - _gridWorldCenter.x;
float targetContainerY = screenCenterY - _gridWorldCenter.y;
// 直接设置容器位置
_viewContainer.SetPosition(targetContainerX, targetContainerY, 0);
// ==================================================
foreach (var arrow in _levelConfig.arrows)
{
BuildArrowPath(arrow, _levelConfig);
CreateArrowAndLines(arrow, _levelConfig);
MarkArrowOccupiedCells(arrow, _levelConfig);
}
_initComplete = true;
SetTopCurr();
}
private void SetTopCurr(object a = null)
{
if (ui.com_money is com_money btnMoney)
{
btnMoney.text_gold.text = DataMgr.Ticket.Value.ToString("0.00");
btnMoney.SetClick(PopMakeup);
}
}
private void PopMakeup()
{
var makeupTaskData = DataMgr.MakeupTaskHistory.Value.Last();
var vo = ConfigSystem.GetConfig<MakeupModel>().GetData(makeupTaskData.tableId);
if (vo == null)
{
return;
}
GameHelper.showGameUI = false;
uiCtrlDispatcher.Dispatch(UICtrlMsg.MakeupConfirmUI_Open, makeupTaskData);
}
#endregion
#region UI相关
private void OnBtnClickBindEvent()
{
ui.btn_close.SetClick(CtrlCloseUI);
ui.com_bottom.btn_clear.SetClick(OnClickDeleteItem);
ui.com_bottom.btn_hint.SetClick(OnClickHint);
ui.com_bottom.btn_skin.SetClick(() => { uiCtrlDispatcher.Dispatch(UICtrlMsg.ArrowThemeUI_Open); });
ui.btn_petty.SetClick(() => { uiCtrlDispatcher.Dispatch(UICtrlMsg.PettyAwardUI_Open); });
ui.btn_signin.SetClick(() => { uiCtrlDispatcher.Dispatch(UICtrlMsg.SignInUI_Open); });
ui.btn_statement.SetClick(() => { uiCtrlDispatcher.Dispatch(UICtrlMsg.StatementViewUI_Open); });
ui.btn_saveingpot.SetClick(() => { uiCtrlDispatcher.Dispatch(UICtrlMsg.SaveingPotUI_Open); });
}
/// <summary>
/// 切换模式 默认黑夜模式
/// </summary>
/// <param name="mod">true:白天 false:黑夜</param>
private void SetModel(object mod = null)
{
int idx = 0;
if (mod != null)
{
idx = (bool)mod? 1 : 0;
}
ui.mode.selectedIndex = idx;
// 同步更新网格圆点颜色
bool isDayMode = idx == 1;
UpdateGridDotColor(isDayMode);
}
/// <summary>
/// 批量设置网格圆点颜色(随昼夜模式切换)
/// </summary>
/// <param name="isDay">true=白天模式,false=黑夜模式</param>
private void UpdateGridDotColor(bool isDay)
{
if (_gridDotList == null || _gridDotList.Count == 0)
return;
// 色值映射:白天#2c3a62,黑夜#d4e3f2
string colorHex = isDay ? "#2c3a62" : "#d4e3f2";
ColorUtility.TryParseHtmlString(colorHex, out Color targetColor);
foreach (var dot in _gridDotList)
{
if (dot == null) continue;
dot.point.color = targetColor;
}
}
#endregion
private void InitGridBounds()
{
gridLeft = ui.point.x;
gridRight = ui.point.x + gridColNum * pointSpace;
gridTop = ui.point.y;
gridBottom = ui.point.y + gridRowNum * pointSpace;
}
/// <summary>初始化网格占用二维数组</summary>
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();
}
/// <summary>标记单个箭头占用的所有网格点</summary>
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;
}
/// <summary>释放单个箭头占用的网格(箭头消失时调用)</summary>
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");
// 示例:加载第1关 levelId=1001
int currentLevelId = GameHelper.GetLevel();
_levelConfig = LoadLevelById(currentLevelId);
// 给 path 为空的箭头,设置默认方向
foreach (var arrow in _levelConfig.arrows)
{
if (arrow.path == null || arrow.path.Count == 0)
{
arrow.defaultDir = "up";
}
else
{
arrow.defaultDir = null;
}
}
}
/// <summary>
/// 从 all_levels.json 中按 levelId 读取指定关卡
/// </summary>
/// <param name="targetLevelId">目标关卡ID 如 1001</param>
/// <returns>找到返回关卡数据,没找到返回null</returns>
///
private static AllLevelRoot _allLevelData;
// 游戏启动时调用一次
public static void InitAllLevelData()
{
string path = Path.Combine(Application.streamingAssetsPath, "all_levels.json");
string json = File.ReadAllText(path);
_allLevelData = JsonUtility.FromJson<AllLevelRoot>(json);
}
public static LevelConfig LoadLevelById(int targetLevelId)
{
AllLevelRoot root = _allLevelData;
if (root == null || root.levels == null) return null;
// 遍历查找对应关卡
foreach (var lv in _allLevelData.levels)
{
if (lv.levelId == targetLevelId)
{
return lv;
}
}
Debug.LogWarning($"未找到关卡ID: {targetLevelId}");
return null;
}
#endregion
#region 【核心】网格坐标工具
/// <summary>点位ID 转 行列索引</summary>
private void PointIdToRowCol(int pointId, int totalCol, out int row, out int col)
{
row = pointId / totalCol;
col = pointId % totalCol;
}
/// <summary>行列 → 实际像素坐标</summary>
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);
}
/// <summary>点位ID → 最终像素坐标</summary>
private Vector2 PointIdToPos(int pointId, int totalCol)
{
PointIdToRowCol(pointId, totalCol, out int r, out int c);
return RowColToPos(r, c);
}
/// <summary>像素坐标反向计算点位ID</summary>
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;
}
/// <summary> 根据FGUI旋转角度获取方向 </summary>
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";
}
/// <summary>方向转旋转角度(箭头默认朝上)</summary>
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);
// 加入列表统一管理
_gridDotList.Add(dot);
}
}
2026-06-14 16:25:04 +08:00
// ========= 网格绘制完成后,设置居中缩放轴心 =========
2026-06-14 16:25:04 +08:00
CalculateGridCenter(cfg);
SetViewPivotToGridCenter();
}
/// <summary>计算整个网格圆点区域的世界中心点</summary>
private void CalculateGridCenter(LevelConfig cfg)
{
int rows = cfg.gridRows;
int cols = cfg.gridCols;
// 取四个角点位,算出整体中心
Vector2 topLeft = RowColToPos(0, 0);
Vector2 bottomRight = RowColToPos(rows - 1, cols - 1);
// 几何中心
_gridWorldCenter.x = (topLeft.x + bottomRight.x) * 0.5f;
_gridWorldCenter.y = (topLeft.y + bottomRight.y) * 0.5f;
}
/// <summary>设置_viewContainer缩放轴心 = 网格中心,实现居中缩放</summary>
private void SetViewPivotToGridCenter()
{
if (_viewContainer == null) return;
// FGUI pivot 取值范围 0~1,是容器内部相对比例
float pivotX = _gridWorldCenter.x / _viewContainer.width;
float pivotY = _gridWorldCenter.y / _viewContainer.height;
// 限制在合法区间
pivotX = Mathf.Clamp01(pivotX);
pivotY = Mathf.Clamp01(pivotY);
// 设置轴心,此后所有缩放都围绕网格中心
_viewContainer.pivot = new Vector2(pivotX, pivotY);
}
#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>();
arrow.originalLinePos = new List<Vector2>();
arrow.lineTrackIndex = new List<int>();
arrow.totalTrack = new List<Vector2>();
List<Vector2> 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<Vector2> tilePositions = new List<Vector2>();
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;
// 在 CreateArrowAndLines 方法最后,补充这段初始化代码
// ========== 新增:初始化距离偏移 ==========
arrow.movedDistance = 0f;
// 轨迹总长度 = (点数-1) * 点间距
arrow.trackTotalLength = (arrow.totalTrack.Count - 1) * 10f;
// 箭头初始在轨迹末端
arrow.arrowInitOffset = arrow.trackTotalLength;
// 每个线条的初始偏移 = 索引 * 点间距
arrow.lineInitOffsets = new List<float>();
for (int i = 0; i < arrow.lineUnits.Count; i++)
{
arrow.lineInitOffsets.Add(i * 10f);
}
}
#endregion
#region 道具(删除和提示)
/// <summary>点击删除道具,开启/关闭删除模式</summary>
private void OnClickDeleteItem()
{
_isDeleteMode = !_isDeleteMode;
// 可选:加提示,区分状态
if (_isDeleteMode)
{
Debug.Log("已开启删除模式,点击箭头即可删除");
// 可加UI提示:如图标高亮、文字提示
}
else
{
Debug.Log("已关闭删除模式");
}
}
private void OnClickHint()
{
ShowRandomArrowTip();
}
/// <summary>随机选中有效箭头,显示静态手指提示(不跟随移动)</summary>
private void ShowRandomArrowTip()
{
// 先隐藏上一次提示
HideFingerTip();
if (_levelConfig == null || _levelConfig.arrows == null || _levelConfig.arrows.Count == 0)
{
Debug.Log("暂无可提示的箭头");
return;
}
// 筛选有效箭头
List<ArrowConfig> validArrows = _levelConfig.arrows
.Where(arrow => arrow != null && arrow.arrowObj != null)
.ToList();
if (validArrows.Count == 0)
{
Debug.Log("没有有效箭头可提示");
return;
}
// 随机选一个
int randomIndex = Random.Range(0, validArrows.Count);
_currentTipArrow = validArrows[randomIndex];
// 设置手指位置(仅初始化一次,不再刷新)
float x = _currentTipArrow.arrowObj.x + _fingerOffset.x;
float y = _currentTipArrow.arrowObj.y + _fingerOffset.y;
_fingerTipObj.SetPosition(x, y, 0);
_fingerTipObj.visible = true;
}
/// <summary>隐藏手指提示并清空标记</summary>
private void HideFingerTip()
{
if (_fingerTipObj != null)
{
_fingerTipObj.visible = false;
}
_currentTipArrow = null;
}
#endregion
#region 缩放
/// <summary>根据缩放值,计算滑动条 value(0~1)</summary>
private float GetSliderValueByScale(float scale)
{
2026-06-14 16:25:04 +08:00
float validScale = Mathf.Clamp(scale, _minScale, _maxScale);
float ratio = (validScale - _minScale) / (_maxScale - _minScale);
float sliderVal = ratio * 100f;
return Mathf.Clamp(sliderVal, 0f, 100f);
}
/// <summary>根据滑动条 value(0~1),计算实际缩放值</summary>
private float GetScaleBySliderValue(float sliderValue)
{
2026-06-14 16:25:04 +08:00
// 先把滑块值钳位在 0~100 内
float val = Mathf.Clamp(sliderValue, 0f, 100f);
// 线性映射:0→0.6100→1.5
float ratio = val / 100f;
float scale = _minScale + ratio * (_maxScale - _minScale);
return Mathf.Clamp(scale, _minScale, _maxScale);
}
/// <summary>滑动条数值变化回调</summary>
private void OnZoomSliderChange()
{
float scale = GetScaleBySliderValue((float)_zoomSlider.value);
_viewContainer.scaleX = scale;
_viewContainer.scaleY = scale;
}
/// <summary>移动容器 + 边界限制,防止完全拖出屏幕</summary>
private void MoveContainer(Vector2 offset)
{
2026-06-14 19:25:04 +08:00
Debug.Log($"[arrow] 移动容器:{offset}");
float newX = _viewContainer.x + offset.x;
float newY = _viewContainer.y + offset.y;
float viewW = _viewContainer.width * _viewContainer.scaleX;
float viewH = _viewContainer.height * _viewContainer.scaleY;
// 左右边界
float minX = -viewW + _dragBorder;
float maxX = Screen.width - _dragBorder;
newX = Mathf.Clamp(newX, minX, maxX);
// 上下边界
float minY = -viewH + _dragBorder;
float maxY = Screen.height - _dragBorder;
newY = Mathf.Clamp(newY, minY, maxY);
_viewContainer.SetPosition(newX, newY,0);
}
#endregion
#region 点击 & 移动逻辑
// 线条/箭头 统一点击回调
private void OnPathClick(ArrowConfig clickArrow)
{
if (clickArrow.isMoving)
{
Debug.Log($"[arrow] {clickArrow.id}正在移动,请勿点击其他箭头");
return;
}
// 点击的是当前提示箭头,直接隐藏手指
if (_currentTipArrow == clickArrow)
{
HideFingerTip();
}
// ========= 删除模式:直接删除箭头,不走移动逻辑 =========
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);
}
}
// 基于网格行列的阻挡检测
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;
}
/// <summary>彻底删除指定箭头:销毁UI+清数据+停逻辑+移除列表</summary>
private void DeleteArrowCompletely(ArrowConfig arrow)
{
if (arrow == null) return;
// 被删除的是提示箭头,隐藏手指
if (_currentTipArrow == arrow)
{
HideFingerTip();
}
// 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);
}
else
{
// 消除每条线小概率触发
DisappearLineGetReward();
}
// 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 float _bounceSpeed = 180f;
private IEnumerator MoveAndResetCoroutine(ArrowConfig arrow)
{
arrow.movedDistance = 0f;
arrow.finalMoveDir = GetFinalMoveDir(arrow);
// 向前移动的总距离(对应原逻辑2步=20px,可自行调整)
float forwardTotal = 20f;
float timer = 0f;
float duration = forwardTotal / _bounceSpeed;
// 第一段:向前移动
while (timer < duration)
{
if (arrow == null || arrow.arrowObj == null) yield break;
timer += Time.deltaTime;
arrow.movedDistance = _bounceSpeed * timer;
UpdateAllElementsPosition(arrow);
yield return null;
}
// 第二段:回弹到初始位置
timer = 0f;
float startDis = arrow.movedDistance;
while (timer < duration)
{
if (arrow == null || arrow.arrowObj == null) yield break;
timer += Time.deltaTime;
float t = timer / duration;
arrow.movedDistance = Mathf.Lerp(startDis, 0f, t);
UpdateAllElementsPosition(arrow);
yield return null;
}
// 强制复位到初始状态
arrow.movedDistance = 0f;
UpdateAllElementsPosition(arrow);
arrow.isMoving = false;
}
/// <summary>统一更新箭头+所有线条的位置</summary>
private void UpdateAllElementsPosition(ArrowConfig arrow)
{
for (int i = 0; i < arrow.lineUnits.Count; i++)
{
float curDist = arrow.lineInitOffsets[i] + arrow.movedDistance;
Vector2 pos = GetPositionByDistance(arrow, curDist);
arrow.lineUnits[i].SetPosition(pos.x, pos.y, 0);
}
float arrowCurDist = arrow.arrowInitOffset + arrow.movedDistance;
Vector2 arrowPos = GetPositionByDistance(arrow, arrowCurDist);
arrow.arrowObj.SetPosition(arrowPos.x, arrowPos.y, 0);
}
private void MoveToDisappear(ArrowConfig arrow)
{
RunCoroutine(MoveAndDisappearCoroutine(arrow));
}
// 新增:统一移动速度(像素/秒),数值越大移动越快,可自由修改
[SerializeField] private float _moveSpeed = 800f;
private IEnumerator MoveAndDisappearCoroutine(ArrowConfig arrow)
{
arrow.movedDistance = 0f;
arrow.finalMoveDir = GetFinalMoveDir(arrow);
while (true)
{
// 空对象兜底保护
if (arrow == null || arrow.arrowObj == null)
{
yield break;
}
// 每帧累计移动距离:速度 * 时间增量
float deltaDis = _moveSpeed * Time.deltaTime;
arrow.movedDistance += deltaDis;
// 更新所有线条位置
for (int i = 0; i < arrow.lineUnits.Count; i++)
{
float curDist = arrow.lineInitOffsets[i] + arrow.movedDistance;
Vector2 pos = GetPositionByDistance(arrow, curDist);
arrow.lineUnits[i].SetPosition(pos.x, pos.y, 0);
}
// 更新箭头位置
float arrowCurDist = arrow.arrowInitOffset + arrow.movedDistance;
Vector2 arrowPos = GetPositionByDistance(arrow, arrowCurDist);
arrow.arrowObj.SetPosition(arrowPos.x, arrowPos.y, 0);
// 边界检测:以尾部线条为准(和原逻辑一致)
Vector2 tailPos = GetPositionByDistance(arrow, arrow.movedDistance);
if (CheckIsOutOfBounds(tailPos, arrow.finalMoveDir))
{
// ========== 以下销毁逻辑和原代码保持一致 ==========
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);
}
else
{
DisappearLineGetReward();
}
yield break;
}
yield return null; // 每帧执行一次,平滑移动
}
}
#region 移动工具方法
/// <summary>根据距离,计算沿轨迹的实际坐标</summary>
private Vector2 GetPositionByDistance(ArrowConfig arrow, float distance)
{
int trackCount = arrow.totalTrack.Count;
const float segmentLen = 10f; // 轨迹点固定间距
// 距离在轨迹范围内:两点插值得到精确位置
if (distance <= arrow.trackTotalLength)
{
float indexFloat = distance / segmentLen;
int index0 = Mathf.FloorToInt(indexFloat);
int index1 = Mathf.Min(index0 + 1, trackCount - 1);
float t = indexFloat - index0;
Vector2 p0 = arrow.totalTrack[index0];
Vector2 p1 = arrow.totalTrack[index1];
return Vector2.Lerp(p0, p1, t);
}
// 距离超出轨迹:沿最终方向直线延伸
else
{
float extraDis = distance - arrow.trackTotalLength;
Vector2 endPos = arrow.totalTrack[trackCount - 1];
Vector2 dir = DirToVector2(arrow.finalMoveDir);
return endPos + dir * extraDis;
}
}
/// <summary>方向字符串转单位向量</summary>
/// <summary>方向字符串转单位向量(适配 FGUI 坐标系:y 轴向下)</summary>
private Vector2 DirToVector2(string dir)
{
return dir switch
{
"up" => new Vector2(0, -1), // FGUI 向上 = y 减小
"down" => new Vector2(0, 1), // FGUI 向下 = y 增大
"left" => new Vector2(-1, 0),
"right" => new Vector2(1, 0),
_ => new Vector2(0, -1)
};
}
/// <summary>统一获取箭头最终移动方向</summary>
private string GetFinalMoveDir(ArrowConfig arrow)
{
if (arrow.path != null && arrow.path.Count > 0)
return arrow.path[arrow.path.Count - 1];
if (!string.IsNullOrEmpty(arrow.defaultDir))
return arrow.defaultDir;
return "up";
}
/// <summary>统一边界检测</summary>
private bool CheckIsOutOfBounds(Vector2 pos, string dir)
{
switch (dir)
{
case "right": return pos.x > gridRight + outOffset;
case "left": return pos.x < gridLeft - outOffset;
case "down": return pos.y > gridBottom + outOffset;
case "up": return pos.y < gridTop - outOffset;
default: return false;
}
}
#endregion
/// <summary>
/// 消除每条线时概率获得奖励
/// </summary>
private void DisappearLineGetReward()
{
bool isGet = false;
int money_rate = ConfigSystem.GetConfig<CommonModel>().rewardrate;
var randomNum = UnityEngine.Random.Range(0, 100);
// Debug.Log($"[creat] money_rate------------ {randomNum}==={money_rate}");
if (GameHelper.IsGiftSwitch() && randomNum < money_rate)
{
isGet = true;
// 显示大额奖励
float[] cash_array = GameHelper.GetRewardValue(1);
var temp = new SuccessData();
temp.IsWin = true;
temp.cash_number = cash_array[0];
temp.rate = (int)cash_array[1];
temp.IsLevelSuccess = false;
temp.IsH5Reward = false;
temp.boost_array = GameHelper.GetRewardBoost(1);
DOVirtual.DelayedCall(0.55f, () =>
{
UICtrlDispatcher.Instance.Dispatch(UICtrlMsg.LevelSuccessUI_Open, temp);
});
}
if (!isGet && Random.Range(0, 100) < ConfigSystem.GetConfig<CommonModel>().Smallrewardsrate)
{
var rewardData = new RewardData();
Vector2 fguiPosition = new Vector2(ui.samll_point.x, ui.samll_point.y);
var start = fguiPosition;
var end = GameHelper.GetUICenterPosition(ui.com_money.GetChild("text_gold"));
float[] cash_array = GameHelper.GetRewardValue(0);
Debug.Log($"小额奖励:{cash_array[0]}");
var rewardSingleData = new RewardSingleData(102, (decimal)cash_array[0], RewardOrigin.AdTask)
{
startPosition = start,
endPosition = new Vector2(end.x - 135, end.y - 135)
};
rewardData.AddReward(rewardSingleData);
rewardData.displayType = RewardDisplayType.RewardFly | RewardDisplayType.ValueChange;
GameDispatcher.Instance.Dispatch(GameMsg.GetReward, rewardData);
}
}
#endregion
#region 资源清理
/// <summary>全局启动协程 + 自动记录,关闭界面时统一停止</summary>
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 dot in _gridDotList)
{
if (dot != null)
{
_viewContainer.RemoveChild(dot);
dot.Dispose();
}
}
_gridDotList.Clear();
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 结算
/// <summary>
/// 弹出结算
/// isSuccess: true=成功,false=失败
/// </summary>
private void ShowResult(bool isSuccess)
{
// 暂停所有交互/移动(可选)
foreach (var arrow in _levelConfig.arrows)
{
arrow.isMoving = false;
}
2026-06-14 19:25:04 +08:00
// uiCtrlDispatcher.Dispatch(UICtrlMsg.LevelSuccessUI_Open, isSuccess);
if (isSuccess)
{
GameHelper.SetLevel(GameHelper.GetLevel() + 1);
//通关成功
float[] cash_array = GameHelper.GetRewardValue(2);
var temp = new SuccessData();
temp.IsWin = true;
temp.cash_number = cash_array[0];
temp.rate = (int)cash_array[1];
temp.IsLevelSuccess = true;
temp.IsH5Reward = false;
temp.boost_array = GameHelper.GetRewardBoost(2);
DOVirtual.DelayedCall(0.5f, () =>
{
UICtrlDispatcher.Instance.Dispatch(UICtrlMsg.LevelSuccessUI_Open, temp);
});
}
else
{
//通关失败
float[] cash_array = GameHelper.GetRewardValue(2);
var temp = new SuccessData();
temp.IsWin = false;
temp.cash_number = cash_array[0];
temp.IsLevelSuccess = true;
temp.IsH5Reward = false;
temp.boost_array = GameHelper.GetRewardBoost(2);
DOVirtual.DelayedCall(0.5f, () =>
{
UICtrlDispatcher.Instance.Dispatch(UICtrlMsg.LevelSuccessUI_Open, temp);
});
}
}
/// <summary>
/// 重新开始本局
/// </summary>
/// <param name="a">true: 重生 false:重开一局或者下一局 </param>
private void OnRestartGame(object a = null)
{
Debug.Log($"[ArrowGameUI] 重新开始本局 a:{(bool)a}");
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();
}
#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));
}
}
[System.Serializable]
public class LevelConfig
{
public int levelId;
public string levelName;
public int gridRows;
public int gridCols;
public int pointSpacing;
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 string defaultDir;
public List<Vector2> pathPointList;
public List<GComponent> lineUnits;
public GComponent arrowObj;
public bool isMoving;
public List<Vector2> originalLinePos;
public Vector2 originalArrowPos;
public List<Vector2> totalTrack;
public List<int> lineTrackIndex;
public int arrowTrackIndex;
public string finalMoveDir;
// ========== 新增:距离驱动移动所需字段 ==========
public float movedDistance; // 当前整体累计移动距离
public float trackTotalLength; // 整条轨迹的总像素长度
public List<float> lineInitOffsets; // 每个线条单元的初始距离偏移
public float arrowInitOffset; // 箭头的初始距离偏移
}
[System.Serializable]
public class AllLevelRoot
{
public List<LevelConfig> levels;
}
#endregion
}