Unity类俄罗斯方块游戏:Physics.RaycastAll方块行检测异常问题
类俄罗斯方块游戏行检测异常:漏检与延迟问题
问题概述
开发类俄罗斯方块游戏时,核心需求是检测落地方块所在行是否集齐10个方块,满行则消除并加分,但当前实现存在两个关键问题:
- 检测延迟:刚落下的方块能立即触发检测,此前已落下的方块触发检测时存在明显延迟
- RaycastAll漏检:多数情况下无法正确捕获整行10个方块,仅偶尔随机成功(截图显示实际10个方块仅检测到8个)
相关代码
行检测脚本(CheckBlocksRow)
using System.Collections.Generic; using UnityEngine; public class CheckBlocksRow : MonoBehaviour { public bool check; public List<float> blocksHeight = new List<float>(); public int blocksInRow; public GameObject[] caughtBlocks; public LayerMask layerMask; RaycastHit[] hittedBlocks; void Update() { if (check) { blocksHeight.Sort(); for (int i = 0; i < blocksHeight.Count; i++) { if (SendLaser(blocksHeight[i])) CheckBlocks(); } } } bool SendLaser(float heightToCheck) { RaycastHit[] hits = Physics.RaycastAll(new Vector2(-5.78f, heightToCheck), transform.TransformDirection(Vector3.right), Mathf.Infinity, layerMask); blocksInRow = hits.Length; if (blocksInRow >= 1) { hittedBlocks = hits; return true; } else { check = false; return false; } } void CheckBlocks() { caughtBlocks = new GameObject[0]; caughtBlocks = new GameObject[hittedBlocks.Length]; for (int i = 0; i < caughtBlocks.Length; i++) { caughtBlocks[i] = hittedBlocks[i].transform.gameObject; } if (caughtBlocks.Length == 10) print("Full row: " + caughtBlocks[0].transform.position.y); check = false; } }
检测触发脚本
void SetToCheck() { CheckBlocksRow _checkBlocksRow = GameObject.FindGameObjectWithTag("Board").transform.GetChild(1).GetComponent<CheckBlocksRow>(); _checkBlocksRow.blocksHeight.Clear(); for (int i = 0; i < transform.childCount; i++) { if(transform.GetChild(i).name != "Center") _checkBlocksRow.blocksHeight.Add(transform.GetChild(i).transform.position.y); } _checkBlocksRow.check = true; }
问题根源
- RaycastAll漏检原因
- 浮点精度误差:直接使用方块的
position.y作为射线高度,可能因碰撞盒位置偏移、浮点计算误差,导致射线未穿过部分方块的碰撞盒 - 射线方向错误:
transform.TransformDirection(Vector3.right)会受检测脚本所在物体的旋转影响,导致射线偏离水平方向 - 射线范围不足:起点x=-5.78f可能未覆盖最左侧方块的左侧区域,导致边缘方块无法被检测到
- 浮点精度误差:直接使用方块的
- 延迟与未触发早期检测原因
- 触发逻辑仅收集当前落下方块的子方块高度,仅检测这些行,早期已填满的行不会被触发检测
CheckBlocks方法中直接设置check=false,导致Update中的循环只执行一次就终止,同一批传入的多个高度仅检测第一个
修复方案
1. 解决RaycastAll漏检问题
- 修正射线高度:对方块Y坐标取整(假设行高度为整数间隔),用行的基准高度作为射线高度,避免浮点误差
- 固定射线方向:使用世界空间的
Vector3.right,而非受物体旋转影响的转换方向 - 调整射线范围:将起点设为足够靠左的位置(如x=-6),射线长度设为覆盖整个棋盘宽度(如12),确保覆盖所有方块
- 验证LayerMask:在Inspector中确认LayerMask仅勾选方块所在层,无误排除情况
2. 解决检测延迟与未触发问题
- 扩展触发逻辑:每次方块落地后,收集所有已存在方块的行高度,而非仅当前方块的子方块,确保所有可能满行的行都被检测
- 修正循环逻辑:将
check=false移到Update循环遍历完所有高度后,避免循环提前终止 - 去重优化:对收集的行高度去重,避免重复检测同一行
修复后代码示例
优化后的行检测脚本
using System.Collections.Generic; using UnityEngine; using System.Linq; public class CheckBlocksRow : MonoBehaviour { public bool check; public List<float> blocksHeight = new List<float>(); public LayerMask layerMask; void Update() { if (check) { // 去重并排序,避免重复检测同一行 var uniqueHeights = blocksHeight.Distinct().OrderBy(h => h).ToList(); foreach (var height in uniqueHeights) { CheckRowAtHeight(height); } check = false; } } void CheckRowAtHeight(float heightToCheck) { // 取整消除浮点误差,用世界空间水平射线覆盖整个棋盘 float adjustedHeight = Mathf.Round(heightToCheck); Ray ray = new Ray(new Vector3(-6f, adjustedHeight, 0f), Vector3.right); RaycastHit[] hits = Physics.RaycastAll(ray, 12f, layerMask); if (hits.Length == 10) { Debug.Log($"消除满行:Y={adjustedHeight}"); // 添加消除方块、加分逻辑 foreach (var hit in hits) { Destroy(hit.transform.gameObject); } } } }
优化后的触发脚本
void SetToCheck() { CheckBlocksRow checkRow = GameObject.FindGameObjectWithTag("Board").transform.GetChild(1).GetComponent<CheckBlocksRow>(); checkRow.blocksHeight.Clear(); // 收集所有已存在方块的行高度 GameObject[] allBlocks = GameObject.FindGameObjectsWithTag("Block"); foreach (var block in allBlocks) { float rowHeight = Mathf.Round(block.transform.position.y); if (!checkRow.blocksHeight.Contains(rowHeight)) { checkRow.blocksHeight.Add(rowHeight); } } checkRow.check = true; }
内容的提问来源于stack exchange,提问作者Drunkiel
相关产品推荐
相关产品推荐

