【问题标题】:How can I decide what objects should be detected and what should be ignored by raycast and how?我如何决定应该检测哪些对象以及光线投射应该忽略哪些对象以及如何?
【发布时间】:2019-05-27 19:17:16
【问题描述】:

在这种情况下我有两个主要问题:

  1. NAVI 是播放器 (FPSController) 的子级,因此 NAVI 与播放器一起移动。但是 NAVI 也位于玩家前面一点,结果是光线投射一直是第一次击中 NAVI 而不是击中 Interactable 对象。

  2. 如果玩家和一些可交互对象之间有一扇门,它会检测到该对象。但是如果玩家和可交互对象之间有一扇门和其他对象,它不应该检测到它们。玩家看不到它们,但它正在检测。

此脚本附加到 plaher (FPSController):

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class DetectInteractable : UnityEngine.MonoBehaviour
{
    public Camera cam;
    public float distanceToSee;
    public string objectHit;
    public bool interactableObject = false;
    public Transform parentToSearch;
    public Scaling scaling;
    public int spinX = 0;
    public int spinY = 0;
    public int spinZ = 0;
    public GameObject navi;
    public GameObject itemsDescriptionCanvas;
    public Text itemsDescriptionText;

    private RaycastHit whatObjectHit;
    private Transform[] childrenToSearhc;

    private void Start()
    {
        childrenToSearhc = parentToSearch.GetComponentsInChildren<Transform>();
    }

    private void Update()
    {
        if (cam.enabled == true)
        {
            if (Input.GetMouseButtonDown(0) && !scaling.scaleUp)
            {
                if (whatObjectHit.collider != null)
                    ExecuteActions(whatObjectHit.collider.gameObject);
            }

            Debug.DrawRay(cam.transform.position, cam.transform.forward * distanceToSee, Color.magenta);
            if (Physics.Raycast(cam.transform.position, cam.transform.forward, out whatObjectHit, distanceToSee))
            {
                objectHit = whatObjectHit.collider.gameObject.name;
                interactableObject = true;
                print("Hit ! " + whatObjectHit.collider.gameObject.name);

                if (scaling.objectToScale.transform.localScale == scaling.minSize)
                {
                    scaling.objectToScale.transform.Rotate(spinX, spinY, spinZ);
                }
                ProcessItemsDescripations();
                itemsDescriptionCanvas.SetActive(true);
            }
            else
            {
                if (scaling.objectToScale.transform.localScale == scaling.minSize)
                {
                    navi.transform.rotation = new Quaternion(0, 0, 0, 0);
                }
                itemsDescriptionCanvas.SetActive(false);
                print("Not Hit !");
            }
        }
    }

    private void ExecuteActions(GameObject go)
    {
        var ia = go.GetComponent<ItemAction>();
        if (ia != null)
        {
            ia.ItemMove();
        }
    }

    void ProcessItemsDescripations()
    {
        foreach (Transform child in childrenToSearhc)
        {
            if (child.GetComponent<ItemInformation>() != null)
            {
                ItemInformation iteminformation = child.GetComponent<ItemInformation>();
                if (child.name == objectHit)
                {
                    itemsDescriptionText.text = iteminformation.description;
                }
            }
        }
    }

    public class ViewableObject : UnityEngine.MonoBehaviour
    {
        public string displayText;
        public bool isInteractable;
    }
}

这个小脚本附加到每个 Interactable 对象:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ItemInformation : UnityEngine.MonoBehaviour
{
    [TextArea]
    public string description;
}

这是播放器(FPSController)Inspector设置的截图:

此屏幕截图是作为播放器子级的 NAVI: 您可以在左侧的游戏窗口和场景窗口看到 NAVI:

这是窗口的屏幕截图示例。窗口应该是 Interactable 对象并且应该被光线投射检测到:

窗口标签设置为无标签,图层设置为默认

这是游戏运行时的屏幕截图,玩家站在门后,门的另一侧有一个带有可交互窗口的房间。没有什么能阻止光线投射穿过门并检测到窗户。这不是逻辑。玩家看不到窗口。所以不应该检测到窗口:

在左上角,光线投射的洋红色穿过门到达窗户。在左下角,玩家站在门后,完全可以看到窗户,但它正在检测窗户:

但是当使用断点时,你可以看到真正被击中的是 NAVI,而不是门而不是窗户,而是 NAVI,因为 NAVI 总是站在玩家面前:

在此之前,我尝试在脚本中使用光线投射遮罩层,然后将窗口层更改为可交互,然后我看到它正在通过门进行检测。

最后的主要目标是:

为了检测游戏中的可交互对象而不是检测 NAVI,他不是可交互对象,所以以某种方式使 NAVI 不会阻挡光线投射!

并且只有在玩家对对象有逻辑视图时才能检测到对象。并且不检测来自门后或墙壁或其他物体的物体。

【问题讨论】:

  • Physics.Raycast 允许您传递它应该忽略的图层蒙版 (docs.unity3d.com/ScriptReference/Physics.Raycast.html),该链接还显示了如何使光线投射命中除应有帮助的特定图层之外的所有内容。
  • 只是出于好奇,您是否在使用某种预制资产?因为我向上帝发誓,我已经看到至少 3 个不同的 SO 帐户使用几乎相同的图片发布,这些图片看起来像是同一个项目。 No really.
  • @Draco18s 不,我猜是我在 * 的另一个帐户中
  • @yochile 你应该把它们合并,然后。真的没有理由不这样做。
  • @Draco18s 哈哈谢谢你的提问.. 回答herehere 后我已经有了同样的印象^^

标签: c# unity3d


【解决方案1】:

您可以使用Physics.Raycast的附加optional parameterlayerMask

Layer 遮罩,用于在投射光线时选择性地忽略碰撞器。

我会推荐使用LayerMask

public LayerMask targetLayer;

使用 Unity Inspector 配置想要的层(选择您想要命中的每个层),最后将其传递给 raycast,就像

if(Physics.Raycast(
    cam.transform.position, 
    cam.transform.forward, 
    out whatObjectHit, 
    distanceToSee, 
    targetLayer.value))
{
    ...
}

example Unity 中直接使用了位掩码(顺便说一句。在Update 中以一种非常低效的方式):

// Bit shift the index of the layer (8) to get a bit mask
int layerMask = 1 << 8;

// This would cast rays only against colliders in layer 8.
// But instead we want to collide against everything except layer 8. The ~ operator does this, it inverts a bitmask.
layerMask = ~layerMask;

如前所述,我不喜欢这种硬编码方式。此外,您现在只排除第 8 层,但点击其他所有内容

如果只包含一个特定层,请删除该行

layerMask = ~layerMask;

所以你只打第 8 层

无论如何,使用我以前的方式更加灵活,您宁愿选择您真正想要击中的那些层,而不是排除一个。使用LayerMask 比编写一些bitmask operations 代码来构建你想要的图层掩码要容易得多。

【讨论】:

  • 我使用了统一示例,并在更新中添加了这两行:int layerMask = 1
  • 现在,在我成功排除了 NAVI 被检测到之后,我怎样才能让只检测到特定对象而不检测其他所有对象?现在使用图层蒙版未检测到 NAVI,但检测到其他所有内容。我现在也想使用图层蒙版来检测特定项目而不是所有项目。
  • 在 pastebin 的这个链接中是脚本现在它与图层蒙版的样子以及我如何使用它来排除 NAVI 被检测到。现在我还想使用图层蒙版来仅检测特定对象而不是所有其他对象。 pastebin.com/u0AD2nx4
  • 这就是为什么我说你应该简单地使用public LayerMask targetLayers; 解决方案。它让您可以直接在 Unity 编辑器中配置它(您没看错吧?)。该示例所做的是使用layerMask = ~layerMask; 否定图层蒙版,它从光线投射中排除了一层。我的解决方案相反,您选择想要点击的那些层 - 而不是您想要排除的层