Unity建造系统Raycast功能异常及实现优化咨询
建造系统问题分析与修复方案
问题描述
我想要实现如下建造系统:定义roundedPosition将鼠标位置取整为整数坐标,仅允许在这些特定位置建造物体;同时使用碰撞检测获取物体标签,在不可放置区域将选中物品设为红色。但当前代码对部分物品有效,调整物品位置后时好时坏;另外新建物体能被检测到,但对已有物体的检测有时有效有时无效。想了解问题原因、现有实现的错误/性能隐患,以及保留roundedPosition概念的替代实现方式。
现有预览代码
public GameObject prefab; public Camera Maincam; public GameObject selectedItem; private void Update() { Vector3 mousePosition = Input.mousePosition; Vector3 worldPosition = Camera.main.ScreenToWorldPoint(mousePosition); Vector3Int roundedPosition = new Vector3Int(Mathf.RoundToInt(worldPosition.x), Mathf.RoundToInt(worldPosition.y), Mathf.RoundToInt(worldPosition.z)); selectedItem.transform.position = roundedPosition; Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); RaycastHit2D hit = Physics2D.Raycast(ray.origin, ray.direction); Debug.Log(hit.collider.tag); if (hit.collider.tag == "Ground" && hit.collider != null) { selectedItem.GetComponent<SpriteRenderer>().color = Color.white; } else { selectedItem.GetComponent<SpriteRenderer>().color = Color.red; } }
现有建造代码
if (Input.GetMouseButtonDown(0)) { UserInterface ui = MouseData.interfaceMouseIsOver; if (ui == null) { if (hit.collider.tag != "Item" && hit.collider != null) { mousePosition = Input.mousePosition; worldPosition = Camera.main.ScreenToWorldPoint(mousePosition); roundedPosition = new Vector3Int(Mathf.RoundToInt(worldPosition.x), Mathf.RoundToInt(worldPosition.y), Mathf.RoundToInt(worldPosition.z)); GameObject newObject = Instantiate(prefab, roundedPosition, Quaternion.identity); } } }
问题原因分析
- 空引用顺序错误:原代码先判断
hit.collider.tag == "Ground"再检查hit.collider != null,当无碰撞体时访问hit.collider.tag会直接抛出空引用异常,导致逻辑中断,这是功能时好时坏的核心原因。 - 检测位置不匹配:用屏幕射线检测鼠标原始位置,而非预览物体所在的
roundedPosition,导致检测结果和实际可放置位置脱节。 - 复用旧检测结果:建造代码复用Update帧中的
hit变量,而非点击时刻的实时检测结果,导致已有物体检测时灵时不灵。
性能隐患说明
- 重复调用
Camera.main:每帧多次调用该方法会触发全局查找,开销较大。 - 每帧组件查找:
selectedItem.GetComponent<SpriteRenderer>()每帧执行,属于不必要的性能消耗。 - 每帧Debug输出:
Debug.Log(hit.collider.tag)会持续占用IO资源,拖慢帧率。
保留roundedPosition的改进实现
优化后的完整代码
public GameObject prefab; public Camera mainCam; public GameObject selectedItem; private SpriteRenderer selectedItemRenderer; private void Awake() { // 缓存相机引用,避免每帧全局查找 if (mainCam == null) { mainCam = Camera.main; } // 缓存SpriteRenderer组件,减少每帧查找开销 if (selectedItem != null) { selectedItemRenderer = selectedItem.GetComponent<SpriteRenderer>(); } } private void Update() { // 空值保护,避免组件未赋值时出错 if (selectedItem == null || selectedItemRenderer == null || mainCam == null) { return; } Vector3 mousePosition = Input.mousePosition; // 2D场景中指定z轴为相机近裁剪面,避免坐标转换错误 Vector3 worldPosition = mainCam.ScreenToWorldPoint(new Vector3(mousePosition.x, mousePosition.y, mainCam.nearClipPlane)); Vector3Int roundedPosition = new Vector3Int(Mathf.RoundToInt(worldPosition.x), Mathf.RoundToInt(worldPosition.y), Mathf.RoundToInt(worldPosition.z)); // 更新预览物体位置 selectedItem.transform.position = roundedPosition; // 直接检测预览位置的碰撞体,确保检测结果与放置位置一致 Collider2D hitCollider = Physics2D.OverlapPoint(roundedPosition); // 先判空再判断标签,避免空引用 if (hitCollider != null && hitCollider.CompareTag("Ground")) { selectedItemRenderer.color = Color.white; } else { selectedItemRenderer.color = Color.red; } // 处理建造逻辑 if (Input.GetMouseButtonDown(0)) { UserInterface ui = MouseData.interfaceMouseIsOver; if (ui == null) { // 点击时刻重新检测,确保结果实时准确 Collider2D clickHit = Physics2D.OverlapPoint(roundedPosition); if (clickHit != null && !clickHit.CompareTag("Item")) { GameObject newObject = Instantiate(prefab, roundedPosition, Quaternion.identity); // 给新建物体添加Item标签,确保后续检测能识别 newObject.tag = "Item"; } } } }
关键改进点
- 缓存核心组件:在
Awake中缓存相机和SpriteRenderer,消除每帧的查找开销。 - 检测位置对齐:用
Physics2D.OverlapPoint直接检测roundedPosition,确保检测结果与预览位置完全匹配。 - 空值保护优先:所有涉及组件/碰撞体的操作先判空,避免空引用异常。
- 实时检测建造:点击时重新执行碰撞检测,确保用的是当前时刻的位置数据。
- 移除冗余输出:删除每帧的Debug.Log,提升运行效率。
内容的提问来源于stack exchange,提问作者erdem.61
相关产品推荐
相关产品推荐

