我该如何确定应该检测哪些对象,以及如何通过射线投射忽略哪些对象?

时间:2019-05-26 22:34:10

标签: c# unity3d

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

  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)检查器设置的屏幕截图:

Player Hierarchy and Inspector

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

Navi

这是窗口的屏幕截图示例。该窗口应该是可交互的对象,并且应该被射线广播检测到:

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

Windows Interactable object example

这是游戏运行时的屏幕快照,当玩家站在门另一侧的门后面时,有一个带有可交互窗口的房间。没有什么可以阻止光线从门上辐射到窗户并探测到窗户的。那不是逻辑。播放器看不到窗口。因此,不应检测到该窗口:

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

Detect

但是使用断点时,您可以看到真正碰到的是NAVI,而不是门而不是窗户,而是NAVI,因为NAVI始终站在播放器的前面:

What was hit ? NAVI

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

最后的主要目标是:

要检测游戏中的可交互对象但不能检测NAVI,他不是可交互对象,因此以某种方式使NAVI不会阻挡光线投射!

并且仅当播放器对对象具有逻辑视图时才能够检测到目标。并且不要从门,墙或其他物体后面检测物体。

1 个答案:

答案 0 :(得分:2)

您可以使用optional parameter中另外的Physics.Raycast layerMask

  

一个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层

无论如何,使用以前的方式都更加灵活,您宁愿选择实际要打的图层,而不是排除一个图层。与使用bitmask operations进行编码以构造所需的图层蒙版相比,使用LayerMask更容易。