1181 lines
40 KiB
C#
1181 lines
40 KiB
C#
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<ArrowConfig> allArrowList = new List<ArrowConfig>();
|
|
|
|
// 网格基础参数
|
|
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<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;
|
|
|
|
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;
|
|
}
|
|
|
|
/// <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");
|
|
// 给 path 为空的箭头,设置默认方向
|
|
foreach (var arrow in _levelConfig.arrows)
|
|
{
|
|
if (arrow.path == null || arrow.path.Count == 0)
|
|
{
|
|
arrow.defaultDir = "up";
|
|
}
|
|
else
|
|
{
|
|
arrow.defaultDir = 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);
|
|
}
|
|
}
|
|
}
|
|
#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;
|
|
}
|
|
|
|
// 线条/箭头 统一点击回调
|
|
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);
|
|
}
|
|
}
|
|
|
|
/// <summary>点击删除道具,开启/关闭删除模式</summary>
|
|
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;
|
|
}
|
|
|
|
/// <summary>彻底删除指定箭头:销毁UI+清数据+停逻辑+移除列表</summary>
|
|
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<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);
|
|
}
|
|
|
|
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 资源清理
|
|
|
|
/// <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 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
|
|
|
|
/// <summary>
|
|
/// 弹出结算
|
|
/// isSuccess: true=成功,false=失败
|
|
/// </summary>
|
|
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<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 int targetIndex;
|
|
|
|
public List<Vector2> totalTrack;
|
|
public List<int> lineTrackIndex;
|
|
public int arrowTrackIndex;
|
|
public string finalMoveDir;
|
|
}
|
|
#endregion
|
|
} |