You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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);
        }
    }                   
}

问题原因分析

  1. 空引用顺序错误:原代码先判断hit.collider.tag == "Ground"再检查hit.collider != null,当无碰撞体时访问hit.collider.tag会直接抛出空引用异常,导致逻辑中断,这是功能时好时坏的核心原因。
  2. 检测位置不匹配:用屏幕射线检测鼠标原始位置,而非预览物体所在的roundedPosition,导致检测结果和实际可放置位置脱节。
  3. 复用旧检测结果:建造代码复用Update帧中的hit变量,而非点击时刻的实时检测结果,导致已有物体检测时灵时不灵。

性能隐患说明

  1. 重复调用Camera.main:每帧多次调用该方法会触发全局查找,开销较大。
  2. 每帧组件查找:selectedItem.GetComponent<SpriteRenderer>()每帧执行,属于不必要的性能消耗。
  3. 每帧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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 13:10:16