// 渲染游戏板 using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using asap.core; using asap.core.common; using DG.Tweening; using EventBlocks.UI; using EventCubeMelt.Data; using EventCubeMelt.UI; using EventCubeMelt.Utils; using Game; using UnityEngine; using UnityEngine.Serialization; using UnityEngine.UI; using Debug = UnityEngine.Debug; using Quaternion = UnityEngine.Quaternion; using Vector2 = UnityEngine.Vector2; using Vector3 = UnityEngine.Vector3; namespace EventCubeMelt { /// /// 新手引导「落子判定」在棋盘上的 Ghost 格(行/列与 一致)。 /// [Serializable] public struct NewGuideGhostCheckCell { public int row; public int col; } public class BoardRenderer : MonoBehaviour { #region 组件引用 [Header("预制体")] [SerializeField] private GameObject _cellPrefab; [SerializeField] private GameObject _ghostCellPrefab; [SerializeField] private GameObject _emptyCellPrefab; //- [SerializeField] private RectTransform bgTransform; [SerializeField] private float boardPadding = 35f; // [SerializeField] private float cellSpacing = 0.1f; [SerializeField] private Transform _boardContainer; [SerializeField] private Transform _ghostContainer; [SerializeField] private PieceRenderer[] cellPiece; // 当前的Piece [SerializeField] private float putBeforeDisappearDuration = 0.2f; //- 消除特效 [SerializeField] private Transform fxUIEventCubemeltEliminatable; // 整行消除特效 [SerializeField] private Transform fxUIEventCubeMeltDisappear; [SerializeField] private Transform fxUIEventCubeMeltColDisappear; [SerializeField] private Transform fxUIEventCubeMeltDisappear6; [SerializeField] private Transform fxUIEventCubeMeltColDisappear6; [SerializeField] private Transform fxUIEventCubeMeltDisappear7; [SerializeField] private Transform fxUIEventCubeMeltColDisappear7; [Header("三行和三列消除特效")] [SerializeField] private Transform fxUIEventCubemeltDisappearColorful; [SerializeField] private Transform fxUIEventCubemeltDisappearColorfulVertical; [SerializeField] private Transform fxUIEventCubemeltDisappearColorful6; [SerializeField] private Transform fxUIEventCubemeltDisappearColorfulVertical6; [SerializeField] private Transform fxUIEventCubemeltDisappearColorful7; [SerializeField] private Transform fxUIEventCubemeltDisappearColorfulVertical7; [Header("跳字特效")] [SerializeField] private Transform fxInfoPiercing; // 飞行的对象Controller [Header("飞行特效")] [SerializeField] private RewardFlyBatchController _rewardFlyBatchCtrl; // [SerializeField] private BatchedRewardFly _rewardFly; [Header("延时")] [SerializeField] private float bombStartDelay = 0.2f; [SerializeField] private float bombFlyDelay = 0.2f; // [SerializeField] private Transform fxUIEventCubemeltBroke; [Header("新手引导")] [Tooltip("NewGuide1:放手时需被 Ghost 覆盖的棋盘格;在 Inspector 中填写行/列,长度 0 则判定始终失败")] [SerializeField] private NewGuideGhostCheckCell[] _newGuide1GhostCheckCells = { new NewGuideGhostCheckCell { row = 0, col = 3 }, new NewGuideGhostCheckCell { row = 1, col = 3 }, new NewGuideGhostCheckCell { row = 1, col = 2 }, new NewGuideGhostCheckCell { row = 0, col = 2 }, }; #endregion #region 私有成员 private TetrisBoardData _boardData; private CellRenderer[,] _cellRenderers; private GameObject[,] _emptyCellObjs; private Dictionary _materialCache = new(); // 特效Trans private Transform effectTrans; // 对象池 // // private readonly Queue _cellPool = new(); // 当前活跃的Cell private readonly List _activeCells = new(); // 特效对象池 private IObjectPoolService _objectPoolService; private IObjectPool _lineEffectPool; // 特效消除池 private IObjectPool _disappearEffectRowPool; private IObjectPool _disappearEffectColPool; // 当前渲染隐藏 private readonly List _ghostCells = new(); private EventCubeMeltManager _thisEventManager; private EventCubeMeltData _thisEventData; public float TargetCellSize { get; set; } = 100; // private Vector2 _cellSize; // private Vector2 _cellSpace; private float _contentWidth; private float _contentHeight; private Vector2 _contentZero; // private float TargetCellSpace { get; set; } = 3f; #endregion #region 周期函数 private void Awake() { effectTrans = transform.Find("EffectPanel"); } #endregion #region 初始化 // public void Initialize(int height, int width, List boardData) { ELog($"Initialize -> {height} {width}"); _objectPoolService = GContext.container.Resolve(); _lineEffectPool = _objectPoolService.CreatePool(fxUIEventCubemeltEliminatable, 0, 32); _disappearEffectRowPool = _objectPoolService.CreatePool(fxUIEventCubeMeltDisappear, 0, 32); _disappearEffectColPool = _objectPoolService.CreatePool(fxUIEventCubeMeltColDisappear, 0, 32); _thisEventManager = GContext.container.Resolve(); _thisEventData = _thisEventManager.EventData; _boardData = new TetrisBoardData(height, width); _boardData.LoadData(boardData); CreateGrid(); UpdateBgSize(height, width); } private void UpdateBgSize(int height, int width) { ELog("UpdateBgSize -> "); var layoutGroup = _boardContainer.GetComponent(); var cellSize = layoutGroup.cellSize; var spacing = layoutGroup.spacing; // _cellSize = cellSize; // _cellSpace = spacing; // var topLeft = -(cellSize.x + spacing.x) * width * 0.5; _contentWidth = cellSize.x * width + spacing.x * (width - 1); _contentHeight = cellSize.y * height + spacing.y * (height - 1); _contentZero = new Vector2(-_contentWidth * 0.5f, _contentHeight* 0.5f); bgTransform.sizeDelta = new Vector2(width * (cellSize.x + spacing.x) + boardPadding * 2 , height * (cellSize.y + spacing.y) + boardPadding * 2); } private void CreateGrid() { ELog("CreateGrid -> "); // 清空现有网格 foreach (Transform child in _boardContainer) Destroy(child.gameObject); foreach (Transform child in _ghostContainer) Destroy(child.gameObject); // 创建网格单元 _cellRenderers = new CellRenderer[_boardData.Height, _boardData.Width]; _emptyCellObjs = new GameObject[_boardData.Height, _boardData.Width]; for (var row = 0; row < _boardData.Height; row++) { for (var col = 0; col < _boardData.Width; col++) { var position = Vector3.zero; CreateCell(row, col, position); // 创建影子单元格 CreateEmptyCell(row, col, position); } } } private void CreateCell(int row, int col, Vector3 position) { // var parent = isGhost ? _ghostContainer : _boardContainer; // var cellName = isGhost ? $"GhostCell_{row}_{col}" : $"Cell_{row}_{col}"; var parent = _boardContainer; var cellName = $"Cell_{row}_{col}"; GameObject cellObj; // if (_cellPool.Count > 0) // { // cellObj = _cellPool.Dequeue().gameObject; // cellObj.name = cellName; // cellObj.transform.position = position; // cellObj.transform.parent = parent; // cellObj.SetActive(true); // } // else { cellObj = Instantiate(_cellPrefab, position, Quaternion.identity, parent); cellObj.name = cellName; } var cellRenderer = cellObj.GetComponent(); if (!cellRenderer) cellRenderer = cellObj.AddComponent(); // 没什么用 // 初始化单元格大小 // cellRenderer.SetSize(_cellSize); // if (isGhost) // { // _ghostRenderers[row, col] = cellRenderer; // // cellRenderer.SetMaterial(_materialCache[CellType.Ghost]); // } // else // { // _cellRenderers[row, col] = cellRenderer; // // cellRenderer.SetMaterial(_gridMaterial); // // cellRenderer.SetColor(_gridColor); // } _cellRenderers[row, col] = cellRenderer; cellRenderer.Row = row; cellRenderer.Col = col; // _activeCells.Add(cellRenderer); } private void CreateEmptyCell(int row, int col, Vector3 position) { var parent = _ghostContainer; var cellName = $"Empty_{row}_{col}"; var cellObj = Instantiate(_emptyCellPrefab, position, Quaternion.identity, parent); cellObj.name = cellName; _emptyCellObjs[row, col] = cellObj; } #endregion #region 更新渲染 public void UpdateBoard() { ELog("UpdateBoard-> "); if (_boardData.ReSizeWithData(_thisEventData.BoardViewSize.y , _thisEventData.BoardViewSize.x , _thisEventData.BoardData)) { RemakeBoard(); } UpdateGridCells(); } private void RemakeBoard() { ELog("ReMakeBoard-> "); UpdateBgSize(_boardData.Height, _boardData.Width); CreateGrid(); } public void ClearBoard() { for (var row = 0; row < _boardData.Height; row++) { for (var col = 0; col < _boardData.Width; col++) { var cellRenderer = _cellRenderers[row, col]; cellRenderer.UpdateVisual(CellType.Empty); } } } //- public void UpdateBoardWithAnim(Sequence sequence,float scaleDuration,float delayBetweenCells,out float delay) { ELog("UpdateBoardWithAnim"); UpdateBoard(); // _boardData.SetSize(); // _boardData.LoadData(_thisEventData?.BoardData); // UpdateGridCells(); delay = 0; var animationSequence = sequence; var hasIndex = 0; for (var row = 0; row < _boardData.Height; row++) { var has = false; for (var col = 0; col < _boardData.Width; col++) { var cellType = _boardData[row, col]; var cell = _cellRenderers[row, col]; if (cellType != CellType.Empty) { cell.transform.localScale = Vector3.zero; animationSequence.Insert(delay, cell.transform.DOScale(1.0f, scaleDuration) .SetEase(Ease.OutBack)); // 使用弹性缓动效果,看起来更自然 has = true; } } if (has) { hasIndex++; delay = (hasIndex)* delayBetweenCells; } } } private void UpdateGridCells() { ELog("UpdateGridCells-> "); // _activeCells.Clear(); for (var row = 0; row < _boardData.Height; row++) { for (var col = 0; col < _boardData.Width; col++) { var cellType = _boardData[row, col]; var cellRenderer = _cellRenderers[row, col]; cellRenderer.UpdateVisual(cellType,_boardData.GetGridValue(row,col-1),_boardData.GetGridValue(row +1,col - 1) ,_boardData.GetGridValue(row +1,col )); } } } // [Header("清除特效时长")] [SerializeField] private float disappearTimeDuration = 1f; private async Task DoGridClearAndAnims(List rows, List cols, List<(int row,int col,int value,int newRow,int newCol)> listBombs) { ELog("DoGridClearAndAnims-> "); var list = new List(); var countClear = rows.Count + cols.Count; foreach (var row in rows) { list.Add(DoRowDisappearEffect(row,countClear,listBombs)); } //ClearCol foreach (var col in cols) { list.Add(DoColDisappearEffect(col,countClear,listBombs)); } if (rows.Count + cols.Count > 0) { GContext.Publish(new EventUISound("audio_ui_block_cube_clear")); } await Task.WhenAll(list); RemoveDisappearEffect(); } private async Task DoColDisappearEffect(int col,int count,List<(int row,int col,int value,int newRow,int newCol)> listBombs) { await Awaiters.NextFrame; var list = new List(); list.Add(AddColDisappearEffect(col, count )); var ls = listBombs.Select(item => (item.row, item.col)).ToList(); for (var i = 0; i< _boardData.Height; i++) { var cellRenderer = _cellRenderers[i,col]; // _ = cellRenderer.RunClearAnimation(_boardData.Height - i ,count >= 3); if (!ls.Contains((i, col))) { // list.Add(cellRenderer.RunBombAnimation(_boardData.Height - i,true,2f)); // } // else // { list.Add(cellRenderer.RunClearAnimation(_boardData.Height - i ,count >= 3)); } } await Task.WhenAll(list); } private async Task DoRowDisappearEffect(int row,int count,List<(int row,int col,int value,int newRow,int newCol)> listBombs) { await Awaiters.NextFrame; var list = new List(); list.Add(AddRowDisappearEffect(row, count)); var ls = listBombs.Select(item => (item.row, item.col)).ToList(); for (var i = 0; i< _boardData.Width; i++) { var cellRenderer = _cellRenderers[row, i]; if (!ls.Contains((row, i))) { list.Add(cellRenderer.RunClearAnimation(i, count >= 3)); } } await Task.WhenAll(list); } private bool TryScreenToGrid(Vector2 screenPos, out Vector2Int grid) { var canvas = GetComponentInParent(); var boardRT = (RectTransform)_boardContainer; var cam = canvas.worldCamera; grid = default; if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(boardRT, screenPos, cam, out var local)) { #if UNITY_EDITOR if (visualCell) visualCell.SetActive(false); #endif return false; } // RectTransformUtility.ScreenPointToLocalPointInRectangle(boardRT, new Vector2(0,0), cam,out var p2); // RectTransformUtility.ScreenPointToLocalPointInRectangle(boardRT, new Vector2(100,100), cam,out var p3); ELog($"TryScreenToGrid -> {screenPos} {local}"); // var rect = boardRT.rect; // ELog($"rect -> {rect.width}x{rect.height}"); // /var topLeft = new Vector2(-rect.width * 0.5f, rect.height * 0.5f); var topLeft = _contentZero; // 将 local 改为以左上为原点、向右向下为正的坐标 var p = new Vector2(local.x - topLeft.x, topLeft.y - local.y); #if UNITY_EDITOR if (visualCell != null) { // var localFromP = new Vector2(topLeft.x + p.x, topLeft.y - p.y); // visualCell.transform.localPosition = local; // visualCell.SetActive(true); } #endif // var px = rect.width / _boardData.Width; // var py = rect.height / _boardData.Height; // var cellSize = new Vector2(100,100); var cellSize = new Vector2(_contentWidth / _boardData.Width, _contentHeight / _boardData.Height); var px = p.x / cellSize.x ; var py = p.y / cellSize.y; var x = Mathf.FloorToInt(px); var y = Mathf.FloorToInt(py); // ELog($"TryScreenToGrid -> zero = {p.x } {p.y} : {px} {py} : {x} {y}"); grid = new Vector2Int(x, y); // return x >= 0 && x < _boardData.Width && y >= 0 && y < _boardData.Height; return true; } public void UpdateVisual(PieceRenderer pieceRenderer, Vector2 screenPos) { // ELog($"UpdateVisual -> {pieceRenderer}"); var bRet = CanGetNewGhostGrid(pieceRenderer); // ELog($"UpdateVisual ->{bRet}"); if (!bRet) { return; } // 显示 RemoveLineEffect(); RemoveDisappearEffect(); if (IsCanPlaceDown(pieceRenderer)) { foreach (var cell in _ghostCells) { cell.UpdateGhostVisual(); } AddClearEffects(); } ELog($"UpdateVisual ->End"); } public void ClearGhostGrid(bool placed = false) { if (_ghostCells is not { Count: > 0 }) { return; } foreach (var cell in _ghostCells) { cell.UpdateVisual(CellType.Empty); } _ghostCells.Clear(); _boardData.ClearGhostGrid(); } public GameObject visualCell; private bool CanGetNewGhostGrid(PieceRenderer pieceRenderer) { // var zeroPos = pieceRenderer.GetPiecePosition(); if (!TryScreenToGrid(zeroPos, out var grid)) { ClearGhostGrid(); RemoveLineEffect(); RemoveDisappearEffect(); SavePieceGridPos(Vector2Int.zero); ELog("CanGetNewGhostGrid TryScreenToGrid Failed"); return false; } if (IsPieceGridNotChange(grid)) { return false; } this.SetCurrentPos($"row:{grid.y },col:{grid.x}"); ELog($"CanGetNewGhostGrid-> {_lastGrid} => {grid} "); SavePieceGridPos(grid); ClearGhostGrid(); var tetrominoData = pieceRenderer.Data; // 计算影子方块在网格中的位置 for (var row = 0; row < tetrominoData.Height; row++) { for (var col = 0; col < tetrominoData.Width; col++) { if (tetrominoData[row, col] != CellType.Empty) { var gridRow = grid.y + row; var gridCol = grid.x + col; if (gridCol >= 0 && gridCol < _boardData.Width && gridRow >= 0 && gridRow < _boardData.Height) { if (_boardData[gridRow, gridCol] == CellType.Empty) { _cellRenderers[gridRow, gridCol].CellGhostTypeValue = tetrominoData[row, col]; _ghostCells.Add(_cellRenderers[gridRow, gridCol]); _boardData.SetGhostValue(gridRow, gridCol, tetrominoData[row, col]); } } } } } return true; } private void SavePieceGridPos(Vector2Int grid) { _lastGrid = grid; } private Vector2Int _lastGrid = Vector2Int.zero; private bool IsPieceGridNotChange(Vector2Int grid) { return _lastGrid.Equals(grid); } // 检测是否显示可消除的行列 public void AddClearEffects() { var rows = _boardData.GetClearRows(withGhostCheck: true); if (rows.Count > 0) { foreach (var row in rows) { AddRowEffect(row); } } var cols = _boardData.GetClearCols(withGhostCheck: true); if (cols.Count > 0) { foreach (var col in cols) { AddColEffect(col); } } } public void ClearGridAndCollectItems(int pieceId,out List rows ,out List cols, out List<(int,int,int,int)> list, out List<(int row,int col,int value,int newR,int newC)> listBomb) { rows = _boardData.GetClearRows(); cols = _boardData.GetClearCols(); // 清空和收集道具 ClearAddCollectItems(pieceId,rows, cols, out list,out listBomb); if (ShouldBuildELog()) { var valList = list.Select(item => $"({item.Item1} : {item.Item2}) => {item.Item3}").ToList(); if (valList is { Count: > 0 } || rows is { Count: > 0 } || cols is { Count: > 0 }) ELog( $"LCM-> CheckLineClears -> rows : {string.Join(", ", rows)} cols {string.Join(", ", cols)} {string.Join(", ", valList)}"); } } private void ClearAddCollectItems(int pieceId, List rows, List cols, out List<(int row ,int col ,int value ,int count)> list, out List<(int row,int col,int value,int newR,int newC)> listBomb2) { _boardData.ClearRowsAndCols(rows, cols, out var myList ); list = DoCollectTransFilter(myList,out var listBomb); var bombValues =_thisEventManager.GetBombValues(listBomb.Select(item => item.value).ToList()); listBomb2 = _boardData.UpdateBombValue(listBomb,bombValues); var items = list .GroupBy(x => x.Item3) // 按第三个值分组 .ToDictionary( g => g.Key, // Key是第三个值 g => g.Sum(x => x.count) // 取每个组最后一个的第四个值 ); var itemCount = items.Sum(item => item.Value); switch (itemCount) { case > 3: GContext.Publish(new VibrationData(HapticTypes.Success)); break; case > 0: GContext.Publish(new VibrationData(HapticTypes.MediumImpact)); break; default: GContext.Publish(new VibrationData(HapticTypes.LightImpact)); break; } #if AGG using (var e = GEvent.GameEvent("event_cubemelt")) { e.AddContent("piece_id",pieceId); e.AddContent("clear_count", rows.Count + cols.Count); e.AddContent("collection_count", itemCount); e.AddContent("stage_id",_thisEventData.CurrentStagetId); } #else ELog($"event_cubemelt -> {pieceId} {rows.Count + cols.Count} {itemCount} {_thisEventData.CurrentStagetId}"); #endif _thisEventManager.AddCollect(items); } private string IconNameFromCellType(int cellType) { var items = _thisEventManager.GetCollectionItems(); if (items != null && items.TryGetValue(cellType, out var info)) return _thisEventManager.GetCollectIconById(info.TrueId); return _thisEventManager.GetCollectIconByType(cellType); } //- 清理 // private async Task RunCollectItems(List<(int,int,int,int)> listItems) // { // ELog("RunCollectItems"); // // if (listItems.Count <= 0) // return; // // PlayFxInfoPiercing(listItems.Count); // var tasks = new List(); // if (ShouldBuildELog()) // { // var valList = listItems.Select(item => $"({item.Item1} : {item.Item2}) => {item.Item3} {item.Item4}").ToList(); // ELog($"LCM-> RunCollectItems ->newListItems {string.Join(", ", valList)}"); // } // foreach (var item in listItems) // { // var cell = _cellRenderers[item.Item1, item.Item2]; // var req = new BatchedRewardFlyRequest // { // Icon = new BatchedRewardFlyRequestIcon { iconName = IconNameFromCellType(item.Item3) }, // StartPoint = new BatchedRewardFlyStartPoint((RectTransform)cell.transform), // EndPoint = new BatchedRewardFlyEndPoint(GetComponentInParent().GetEndRectTransform(item.Item3)), // IsDestinationRewardStash = false, // AnimationParamIndex = 0, // Quantity = item.Item4 // }; // tasks.Add(_rewardFlyBatchCtrl.OnRewardFlyRequestAsync(req)); // } // await Task.WhenAll(tasks); // // var list = new HashSet(listItems.Select(item => item.Item3)).ToList(); // var bRet = _thisEventManager.CheckCollectFinish(list); // GContext.Publish(new EventUISound("audio_ui_cubemelt_cube_collect")); // GContext.Publish(bRet ? new VibrationData(HapticTypes.Success) : new VibrationData(HapticTypes.SoftImpact)); // } private List<(int row ,int col ,int value ,int count )> DoCollectTransFilter( List<(int row , int col, int value )> listItems, out List<(int row,int col,int value)> listBomb) { // var list = _thisEventManager.GetRepValue(); var collectionItems = _thisEventManager.GetCollectionItems(); var list = new List<(int, int, int )>(); listBomb = new List<(int row, int col, int value)>(); foreach (var (row, col, value) in listItems) { if (_thisEventManager.IsUnCollectValue(value)) { listBomb.Add((row,col,value)); } else { var newValues = _thisEventManager.ReplaceValue(value); list.AddRange(newValues .Where(nv => collectionItems.ContainsKey(nv)) // 过滤出被集合包含的值 .Select(nv => (row, col, nv))); } } return list.GroupBy(t => t) .Select(g => (g.Key.Item1, g.Key.Item2, g.Key.Item3, g.Count())) .ToList(); // return list; } private bool IsCanPlaceDown(PieceRenderer pieceRenderer) { if (!pieceRenderer) return false; var num = _ghostCells?.Count; return num == pieceRenderer.Data.UsableCount; } #region 时间调试 // private Stopwatch sw; // private string scopeName; // private static Dictionary s_accum = new Dictionary(); // private ProfileScope ps = new (); // public void L_ProfileScope(string sectionName) // { // scopeName = sectionName; // sw = Stopwatch.StartNew(); // } // public void L_ProfileScopeEnd() // { // sw.Stop(); // long el = sw.ElapsedMilliseconds; // s_accum.TryGetValue(scopeName, out long old); // s_accum[scopeName] = old + el; // Log($"{scopeName} 本次 {el} ms 累计 {s_accum[scopeName]} ms"); // } #endregion public bool TryPlaceDown(PieceRenderer pieceRenderer) { //- if (_thisEventData.IsFinished ) { ClearGhostGrid(true); RemoveLineEffect(); RemoveDisappearEffect(); return false; } if (_thisEventData.TicketCount <= 0) { // GContext.Publish(new EventCubeMeltShowNeoNormalPack()); ClearGhostGrid(true); RemoveLineEffect(); RemoveDisappearEffect(); return false; } // if (!IsCanPlaceDown(pieceRenderer)) return false; #if UNITY_EDITOR _thisEventManager.SaveDataForPrev(); #endif // ps.L_ProfileScope("ClearGhostGrid"); // 移除Ghost ClearGhostGrid(true); // ps.L_ProfileScopeEnd(); // 删除特效 // ps.L_ProfileScope("RemoveLineEffect"); RemoveLineEffect(); RemoveDisappearEffect(); // ps.L_ProfileScopeEnd(); // ps.L_ProfileScope("UpdateCellData"); // 更新数值 UpdateCellData(pieceRenderer); // 更新显示 UpdateGridCells(); // // ps.L_ProfileScopeEnd(); return true; } public async Task OnGridAnimation(int pieceId, List rows, List cols, List<(int, int, int, int)> list, List<(int row, int col, int value, int newRow, int newCol)> listBomb, bool ignoreReqDelay) { // ELog($"OnGridAnimation-> {elapsedMilliseconds}"); // ps.L_ProfileScope("UpdateGridClearData"); UpdateGridClearData(pieceId); // ps.L_ProfileScopeEnd(); // ps.L_ProfileScope("Awaiters"); if (!ignoreReqDelay) { ELog("OnGridAnimation awaitPutBeforeDisappear"); await Awaiters.Seconds(putBeforeDisappearDuration ); } // ps.L_ProfileScopeEnd(); // ps.L_ProfileScope("RunGridClearAnimation"); await RunGridClearAnimation(rows,cols,list,listBomb); // ps.L_ProfileScopeEnd(); // ps.L_ProfileScope("UpdateGridCells"); UpdateGridCells(); // ps.L_ProfileScopeEnd(); _thisEventManager.SaveDataForNow(); } //更新格子数据 public void UpdateGridClearData(int pieceId // ,out List rows,out List cols, // out List<(int,int,int,int)> list, // out List<(int row,int col,int value,int newRow,int newCol)> listBomb ) { // ClearGridAndCollectItems( pieceId,out rows,out cols, out list,out listBomb); _thisEventManager.UpdateGridData(_boardData.GetBoardData()); if (_thisEventManager.IsThisCollectFinished()) { // GetComponentInParent().blocksRaycasts = false; GContext.Publish(new EventCubeMeltStageFinish()); } } // 执行格子清理动画 private async Task RunGridClearAnimation(List rows,List cols, List<(int,int,int,int)> list, List<(int row,int col,int value,int newRow,int newCol)> listBomb) { if (rows is { Count: <= 0 } && cols is { Count: <= 0 } && list is { Count: <= 0 }) return; RemoveDisappearEffect(); // 在EmptyCell 上添加 播放特效 _ = DoGridClearAndAnims(rows,cols,listBomb); _ = RunCollectItemsAndBombs(list, listBomb); _ = DoGridBombAnims(listBomb,bombStartDelay); await RunBombAnims(listBomb,bombFlyDelay); // //TODO:LCM // if (true) // { // // await RunCollectItemsAndBombs(list, listBomb); // } // else // { // await RunCollectItems(list); // } // // 全部完成之后,刷新数据 if(list is {Count: > 0}) GContext.Publish(new EventForUpdateCollect() ); } private async Task RunBombAnims(List<(int row, int col, int value, int newRow, int newCol)> listBomb,float delay) { await Awaiters.Seconds(delay); var tasks = new List(); foreach (var bomb in listBomb) { var cell = _cellRenderers[bomb.row, bomb.col]; if (bomb.newRow == -1 || bomb.newCol == -1) continue; var endCell = _cellRenderers[bomb.newRow, bomb.newCol]; var req = new BatchedRewardFlyRequest { Icon = new BatchedRewardFlyRequestIcon { iconName = IconNameFromCellType(bomb.value) }, StartPoint = new BatchedRewardFlyStartPoint((RectTransform)cell.transform), EndPoint = new BatchedRewardFlyEndPoint(endCell.GetAniTransform()), IsDestinationRewardStash = false, AnimationParamIndex = 1, }; tasks.Add(RunBombTask(req, endCell, bomb)); } await Task.WhenAll(tasks); async Task RunBombTask (BatchedRewardFlyRequest req, CellRenderer endCell, (int row, int col, int value, int endRow, int endCol) bomb) { await _rewardFlyBatchCtrl.OnRewardFlyRequestAsync(req); await endCell.RunBombAni(bomb.value); } } private async Task DoGridBombAnims(List<(int row, int col, int value, int newRow, int newCol)> listBombs,float delayTime) { await Awaiters.Seconds(delayTime); var list2 = new List(); foreach (var bomb in listBombs) { var cellRenderer = _cellRenderers[bomb.row ,bomb.col]; list2.Add(cellRenderer.RunBombAnimation(0)); } await Task.WhenAll(list2); } private async Task RunCollectItemsAndBombs( List<(int, int, int, int)> listItems, List<(int row,int col,int value,int newRow ,int newCol)> listBomb) { ELog("RunCollectItemsAndBombs"); var iCount = listItems.Count + listBomb.Count; if (iCount <= 0) return; // if (listItems.Count <= 0) // return; RectTransform GetEndRectTransform(int cellTypeValue) { var rootPanel = GetComponentInParent(); var root2Panel = GetComponentInParent(); if (rootPanel) { return rootPanel.GetEndRectTransform(cellTypeValue); } return root2Panel ? root2Panel.GetEndRectTransform(cellTypeValue) : null; } PlayFxInfoPiercing(iCount); var tasks = new List(); var valList = listItems.Select(item => $"({item.Item1} : {item.Item2}) => {item.Item3} {item.Item4}").ToList(); ELog($"LCM-> RunCollectItems ->newListItems {string.Join(", ",valList)}" ); foreach (var item in listItems) { var cell = _cellRenderers[item.Item1, item.Item2]; var req = new BatchedRewardFlyRequest { Icon = new BatchedRewardFlyRequestIcon { iconName = IconNameFromCellType(item.Item3) }, StartPoint = new BatchedRewardFlyStartPoint((RectTransform)cell.transform), EndPoint = new BatchedRewardFlyEndPoint(GetEndRectTransform(item.Item3)), IsDestinationRewardStash = false, AnimationParamIndex = 0, Quantity = item.Item4 }; tasks.Add(_rewardFlyBatchCtrl.OnRewardFlyRequestAsync(req)); } // // var listBombRepValues = GetBombEndPoint(listBomb); // foreach (var bomb in listBomb) // { // var cell = _cellRenderers[bomb.row, bomb.col]; // var endCell = _cellRenderers[bomb.newRow, bomb.newCol]; // var req = new BatchedRewardFlyRequest // { // Icon = new BatchedRewardFlyRequestIcon { iconName = IconNameFromCellType(bomb.value) }, // StartPoint = new BatchedRewardFlyStartPoint((RectTransform)cell.transform), // EndPoint = new BatchedRewardFlyEndPoint(endCell.GetAniTransform()), // IsDestinationRewardStash = false, // AnimationParamIndex = 0, // }; // tasks.Add(RunBombTask(req, endCell, bomb)); // // tasks.Add(_rewardFlyBatchCtrl.OnRewardFlyRequestAsync(req)); // } await Task.WhenAll(tasks); var list = new HashSet(listItems.Select(item => item.Item3)).ToList(); var bRet = _thisEventManager.CheckCollectFinish(list); // var list2 = new HashSet(listBomb.Select(item => )) // var bRet2 = _thisEventManager.CheckBombFinsh(listBomb); // GContext.Publish(new EventUISound("audio_ui_cubemelt_cube_collect")); GContext.Publish(bRet ? new VibrationData(HapticTypes.Success) : new VibrationData(HapticTypes.SoftImpact)); } private List<(int row, int col,int value,int endRow,int endCol)> GetBombEndPoint(List<(int row, int col, int value)> listBomb) { ELog("GetBombEndPoint"); var ls = new List<(int, int, int, int, int)>(); int m = _boardData.Height; int n = _boardData.Width; // 1. 先扫一遍,把所有 99 的坐标收集起来 var list = new List<(int, int)>(); for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { if (_boardData[i, j] == CellType.Ct99) list.Add((i, j)); } } // 2. 如果没有 99,返回无效坐标 if (list.Count == 0) return ls; var finder = new UniqueRandom99(_boardData.GetGrid()); foreach (var bomb in listBomb) { for (var i = 0; i < 4; ++i) { var (r, c) = finder.Next(); if (r >= 0 && r < m && c >= 0 && c < n) { ls.Add(new ValueTuple(bomb.row,bomb.col,bomb.value,r,c)); } } } return ls; // var randomValue = UnityEngine.Random.Range(0, list.Count); // var (newRow,newCol) = list[randomValue]; // ls.Add(new ValueTuple(1,2,3,newRow,newCol)) } private async void PlayFxInfoPiercing(int iCount) { if(iCount < 1) return; const string baseStr = "piercing_0"; var str = iCount > 5 ? $"{baseStr}5" : $"{baseStr}{iCount -1}"; foreach (Transform child in fxInfoPiercing) { child.gameObject.SetActive(false); } var p = fxInfoPiercing.Find(str); if (p) { p.gameObject?.SetActive(true); await Awaiters.Seconds(1f); p.gameObject.SetActive(false); } } //- 上一步 public void OnPrevStep() { _thisEventManager.LoadPrevData(); GContext.Publish(new EventForRefreshCubeMeltPanel()); } public void OnRestoreStep() { #if UNITY_EDITOR _thisEventManager.RestoreData(); GContext.Publish(new EventForRefreshCubeMeltPanel()); #endif } private void UpdateCellData(PieceRenderer piece) { var ghostPosition = piece.GetPiecePosition(); TryScreenToGrid(ghostPosition, out var ghostPos); _boardData.UpdateDataByPiece(piece.Data,ghostPos); _thisEventManager.UpdateGridData(_boardData.GetBoardData()); } #endregion #region Log private static bool ShouldBuildELog() { #if UNITY_EDITOR return EventCubeMeltLogPolicy.EnableELog; #else return false; #endif } private static void ELog(string message) { #if UNITY_EDITOR if (!EventCubeMeltLogPolicy.EnableELog) return; Debug.Log($"BoardRenderer => {message}"); #endif } #endregion #region 动画效果 private readonly List _disappearEffectList = new(); private async Task AddRowDisappearEffect(int row,int count) { if (_disappearEffectRowPool == null) return; var fx = GetSingleDisappearFx(); if (count >= 3) { fx = fxUIEventCubemeltDisappearColorful; if (_boardData.Width == 6) fx = fxUIEventCubemeltDisappearColorful6; else if (_boardData.Height == 7) fx = fxUIEventCubemeltDisappearColorful7; } var fxClone = Instantiate(fx.gameObject, effectTrans).transform; if (fxClone) { _disappearEffectList.Add(fxClone); fxClone.name = $"disappear_effect_row"; fxClone.localScale = Vector3.one; //* (1.05F * _boardData.Height / 8F); var cell = _cellRenderers[row, 0]; // 默认位置 x = 0; var position = new Vector3(0, cell.transform.position.y, cell.transform.position.z); var localPosition = new Vector3(0, cell.transform.localPosition.y, cell.transform.localPosition.z); fxClone.transform.localPosition = localPosition; if (ShouldBuildELog()) ELog($"AddRowDisappearEffect -> {cell.transform.position} {position} {fxClone.transform.localPosition}"); await Awaiters.Seconds(1F); } } private Transform GetSingleDisappearFx() { var fx = fxUIEventCubeMeltDisappear; if (_boardData.Width == 6) fx = fxUIEventCubeMeltDisappear6; else if(_boardData.Width == 7) fx = fxUIEventCubeMeltDisappear7; return fx; } private async Task AddColDisappearEffect(int col,int count) { if (_disappearEffectColPool == null) return; var fx = fxUIEventCubeMeltColDisappear; if (_boardData.Width == 6) fx = fxUIEventCubeMeltColDisappear6; else if(_boardData.Width == 7) fx = fxUIEventCubeMeltColDisappear7; if (count >= 3) { fx = fxUIEventCubemeltDisappearColorfulVertical; if (_boardData.Width == 6) fx = fxUIEventCubemeltDisappearColorfulVertical6; else if(_boardData.Width == 7) fx = fxUIEventCubemeltDisappearColorfulVertical7; } var fxClone = Instantiate(fx.gameObject, effectTrans).transform; if (fxClone) { _disappearEffectList.Add(fxClone); // fxClone.SetParent( transform); fxClone.name = $"disappear_effect_col"; fxClone.localScale = Vector3.one; //* (1.05F * _boardData.Height / 8F); // 1.1f; var cell = _cellRenderers[0,col]; var position = new Vector3(cell.transform.position.x, 0, cell.transform.position.z); var localPosition = new Vector3(cell.transform.localPosition.x, 0, cell.transform.localPosition.z); fxClone.transform.localPosition = localPosition; if (ShouldBuildELog()) ELog($"AddColDisappearEffect -> {cell.transform.position} {position} {fxClone.transform.localPosition}"); await Awaiters.Seconds(1F); } } public async void RemoveDisappearEffect() { ELog("RemoveDisappearEffect-> "); await Awaiters.NextFrame; foreach (var trans in _disappearEffectList) { Destroy(trans.gameObject); } _disappearEffectList.Clear(); } private async void AddRowEffect(int row) { if (_lineEffectPool == null) return; await Awaiters.NextFrame; // var fxClone = _lineEffectPool.SpawnObject() as Transform; var fx = fxUIEventCubemeltEliminatable; var fxClone = Instantiate(fx.gameObject,effectTrans).transform; if (fxClone) { _lineEffectList.Add(fxClone); fxClone.name = $"effect_row"; // fxClone.localScale = Vector3.one * 1.1f * (_boardData.Width / 8F); fxClone.localScale = new Vector3(1.12f * (_boardData.Height / 8F),1.1f,1 ); var cell = _cellRenderers[row, 0]; // 默认位置 x = 0; var position = new Vector3(0, cell.transform.position.y, cell.transform.position.z); // var localPosition = new Vector3(0, cell.transform.localPosition.y, cell.transform.localPosition.z); // fxClone.transform.localPosition = localPosition; if (ShouldBuildELog()) ELog($"AddRowEffect -> {cell.transform.position} {position} {fxClone.transform.localPosition}"); fxClone.transform.position = position; var pr = fxClone.transform.Find("1/fx_mat_ui_event_cubemelt_eliminatable_01") .GetComponent(); var main = pr.main; main.startRotation = 0; } } // 可以加个内存池子 private async void AddColEffect(int col) { if (_lineEffectPool == null) return; await Awaiters.NextFrame; var fx = fxUIEventCubemeltEliminatable; var fxClone = Instantiate(fx.gameObject, effectTrans).transform; if (fxClone) { _lineEffectList.Add(fxClone); // fxClone.SetParent(transform); fxClone.name = "effect_col"; fxClone.localScale = new Vector3(1.2f,1.12f * (_boardData.Height / 8F),1f ); var cell = _cellRenderers[0, col]; // 默认位置 x = 0; var position = new Vector3(cell.transform.position.x, 0, cell.transform.position.z); // fxClone.transform.position = position; // fxClone.transform.localRotation = Quaternion.Euler(0, 0, 90); var localPosition = new Vector3(cell.transform.localPosition.x, 0, cell.transform.localPosition.z); fxClone.transform.localPosition = localPosition; // fxClone.transform.position = position; if (ShouldBuildELog()) ELog($"AddColEffect -> {cell.transform.position} {position} {fxClone.transform.localPosition}"); var pr = fxClone.transform.Find("1/fx_mat_ui_event_cubemelt_eliminatable_01") .GetComponent(); var main = pr.main; main.startRotation = Mathf.Deg2Rad * 90f; // 第二组 var pr2 = fxClone.transform.Find("1/fx_mat_ui_event_cubemelt_eliminatable_02")?.GetComponent(); if (pr2?.main != null) { var main2 = pr2.main; main2.startRotation = Mathf.Deg2Rad * 90f; } } } private readonly List _lineEffectList = new(); public async void RemoveLineEffect() { ELog("RemoveLineEffect-> "); await Awaiters.NextFrame; foreach (var trans in _lineEffectList) { Destroy(trans.gameObject); } _lineEffectList.Clear(); } #endregion public bool SatisfyNewGuide1(PieceRenderer dragPiece) { ELog($"SatisfyNewGuide1 -> GetPiecePosition -> {dragPiece.GetPiecePosition()}"); if (_newGuide1GhostCheckCells == null || _newGuide1GhostCheckCells.Length == 0) { ELog("SatisfyNewGuide1: _newGuide1GhostCheckCells 未配置"); return false; } foreach (var cell in _newGuide1GhostCheckCells) { var v = _boardData.GetGhostValue(cell.row, cell.col); if (v == CellType.Empty || v == CellType.Illegal) return false; } return true; } #if UNITY_EDITOR private string _currentPosStr = string.Empty; private bool isShow = false; // private void OnGUI() { if (!isShow) { return; } int fontSize = 40; GUI.skin.label.fontSize = fontSize; GUILayout.Space(220); GUILayout.Label($" CurrentPosStr: {_currentPosStr}"); } #endif private void Update() { #if UNITY_EDITOR if (Input.GetKeyDown(KeyCode.K)) { isShow = !isShow; } #endif } public void SetCurrentPos(string posStr) { #if UNITY_EDITOR _currentPosStr = posStr; #endif } } }