如何使用EventSystem检测多个/重叠的游戏对象?

3
我想要实现的目标是:无论用户点击 A 中的哪个位置,包括 B 中的空间,牙刷都应该出现。但是显然,在 B 中单击不会显示牙刷(OnPointerDown 没有被触发)。
我尝试过的方法是:更改层的顺序。
当用户在框碰撞器 A 内单击时,牙刷就会显示出来,但是如果用户在框碰撞器 B 内单击,则牙刷不会显示出来,这意味着 OnPointerDown 没有被触发。
我认为问题是一个 BoxCollider2D 在另一个 BoxCollider2D 内部重叠造成的。在我的情况下,BA 中,我认为这是罪魁祸首,但我不知道如何解决它,或者是否有另一种实现 OnPointerDown 的方法?
我正在使用透视相机。但在此场景中,所有元素都在相同的 z 位置,即0。是否可能在每个相应的 BoxCollider2D 中触发 IPointerHander 事件?

enter image description here

DragableObject.cs

这个脚本附加在牙刷上。 BoxCollider2D A 也属于牙刷。

public void OnPointerDown(PointerEventData eventData)
{
    Debug.Log("pointer down");

    if (GetComponent<DragableObject>() == null)
        return;

    currentObject = GetComponent<DragableObject>();

    MeshRenderer renderer = GetComponent<MeshRenderer>();

    if (ShowOnTouch)
        ShowObject();

    // Store original state
    originalPosition = transform.position;
    originalOrderLayer = renderer.sortingOrder;
    // Snap to mouse
    Vector3 newPos = cam.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, 30));
    newPos.z = 30;
    transform.position = newPos;

    if (BringToFront)
    {
        if (renderer != null)
        {
            renderer.sortingOrder = 90;
        }
    }

    ObjectActive.Invoke();
}

TargetListener.cs

这个脚本附加到了 BoxCollider2D B 上。

public void OnPointerDown(PointerEventData eventData)
{
    for (int i = 0; i < Affectors.Count; i++) 
    {
        if (Affectors [i] == DragableObject.currentObject)
        {
            DragableObject.currentObject.OnEnterTarget(transform);

            ITriggerEffect[] iTrigger = GetComponents<ITriggerEffect>();

            for (int j = 0; j < iTrigger.Length; j++) 
            {
                Debug.Log("iTrigger enter");
                Debug.Log(iTrigger [j]);
                iTrigger [j].Execute(eventData, PointerState.Down);
            }
        }
        else
            continue;
    }
}

如果我点击 A,牙刷会出现,但当我在 B 内部点击时不会出现。下面是调试日志。

enter image description here

这是附加了 dragable.cs 脚本的 *Toothbrush 本身所连接的 BoxCollider2D。请注意,保留 HTML 标签。

enter image description here


更新:感谢其他回答者,问题对我来说变得更加清晰了。以下是BoxCollider2D A和BoxCollider2D B。它们都有脚本,其中大部分包含OnPointerHandler。我如何确保在各自的BoxCollider2D上触发所有OnPointerHandler

enter image description here

我遇到的问题:

  1. 当我的指针进入 B 时,A 上的 OnPointerExit 被触发。
  2. 如果在 B 内部点击,则只有在 B 上触发 OnPointerDown,而不是在 A 上。

在OnTriggerEnter中为两种情况添加你已有的代码。 - Ignacio Alorre
1
在附加到BoxCollider2D B的OnPointerDown代码中放置Debug.Log并让我们知道是否正在调用。如果可能,请禁用除BoxCollider2D B以外的其他碰撞器,并查看问题是否仍然存在。这是解决问题和帮助人们解决您的问题的方法,否则您将获得随机答案。 - Programmer
1
好的,这正是我想的。你的英语很好。只是你的问题比较复杂。稍等片刻再来检查。在发布之前进行测试。 - Programmer
1
@IgnacioAlorre 如果可能的话,应该使用EventSystem/OnPointerDown。这是一个新功能,使得编写一段代码可以在桌面和移动设备上运行,而不必为每个设备编写多个代码。此外,在移动设备上不应使用GetMouseButtonDown。请参见我的第一个答案,了解如何使用预处理器来分离移动和桌面输入,然后查看我的第二个答案,了解如何使用EventSystem的一段代码将适用于任何平台。是的,使用EventSystem会更加复杂,但这是因为它并非专门设计用于OP所要求的功能,但它确实可以胜任。 - Programmer
1
@程序员 我明白了。谢谢你的信息。 - Ignacio Alorre
显示剩余14条评论
2个回答

3
事件系统的一个好处是,事件不会通过游戏对象进行传递,而是返回被首先触发的那个物体。尽管如此,看起来您可能不想让这样发生。使事件系统返回多个游戏对象很复杂。

有两个解决方案:

1.去掉EventSystem(OnPointerDownIPointerDownHandler),使用旧的射线检测系统。

Physics2D.RaycastAllPhysics2D.RaycastNonAlloc可以实现。出于性能原因,本例将使用RaycastNonAlloc。非常简单。

仅附加到一个游戏对象(空游戏对象)

public class HitAll : MonoBehaviour
{
    //Detect up to 100 Objects
    const int raycastAmount = 100;
    RaycastHit2D[] result = new RaycastHit2D[raycastAmount];

    void Update()
    {
        #if UNITY_IOS || UNITY_ANDROID
        if (Input.touchCount > 0 && Input.GetTouch(0).phase == TouchPhase.Began)
        {
            checkRaycast(Input.GetTouch(0).position);
        }
        #else
        if (Input.GetMouseButtonDown(0))
        {
            checkRaycast(Input.mousePosition);
        }
        #endif
    }

    void checkRaycast(Vector2 mousePos)
    {
        Vector3 origin = Camera.main.ScreenToWorldPoint(mousePos);

        int hitCount = Physics2D.RaycastNonAlloc(origin, Vector2.zero, result, 200);
        Debug.Log(hitCount);

        for (int i = 0; i < hitCount; i++)
        {
            Debug.Log("Hit: " + result[i].collider.gameObject.name);
        }
    }
}

2. 继续使用 EventSystem,但重新抛出事件。

首先,你使用 EventSystem.current.RaycastAll 进行光线投射,然后使用 ExecuteEvents.Execute 手动调用事件。

将其附加到所有带有 2D 碰撞体的游戏对象上,并确保 Physics2DRaycaster 已附加到相机上:

public class ThroughEventScript : MonoBehaviour, IPointerDownHandler
{

    public void OnPointerDown(PointerEventData eventData)
    {
        rethrowRaycast(eventData, eventData.pointerCurrentRaycast.gameObject);

        //DO STUFF WITH THE OBJECT HIT BELOW
        Debug.Log("Hit: " + eventData.pointerCurrentRaycast.gameObject.name);
    }

    void rethrowRaycast(PointerEventData eventData, GameObject excludeGameObject)
    {
        PointerEventData pointerEventData = new PointerEventData(EventSystem.current);

        pointerEventData.position = eventData.pressPosition;
        //pointerEventData.position = eventData.position;}

        //Where to store Raycast Result
        List<RaycastResult> raycastResult = new List<RaycastResult>();

        //Rethrow the raycast to include everything regardless of their Z position
        EventSystem.current.RaycastAll(pointerEventData, raycastResult);

        //Debug.Log("Other GameObject hit");
        for (int i = 0; i < raycastResult.Count; i++)
        {
            //Debug.Log(raycastResult[i].gameObject.name);

            //Don't Rethrow Raycayst for the first GameObject that is hit
            if (excludeGameObject != null && raycastResult[i].gameObject != excludeGameObject)
            {
                //Re-simulate OnPointerDown on every Object hit
                simulateCallbackFunction(raycastResult[i].gameObject);
            }
        }
    }

    //This causes functions such as OnPointerDown to be called again
    void simulateCallbackFunction(GameObject target)
    {
        PointerEventData pointerEventData = new PointerEventData(EventSystem.current);
        //pointerEventData.ra
        RaycastResult res = new RaycastResult();
        res.gameObject = target;
        pointerEventData.pointerCurrentRaycast = res;
        ExecuteEvents.Execute(target, pointerEventData, ExecuteEvents.pointerDownHandler);
    }
}

1
这里的预期功能是什么?我怀疑您想让牙刷在用户单击口腔区域时随时显示?
如果是这样,解决方法之一是调整'Hierarchy'中'B'对象的顺序,使它们位于'A'对象上方,以便覆盖A碰撞区域。这将允许它们的交互阻止每个'B'碰撞器下定义的碰撞区域下面定义的区域的交互。
我们使用这种方法的目的是强制OnPointerDown事件在'B'碰撞器与'A'碰撞器发生碰撞之前发生。
请告诉我是否清楚,并随时提出有关该方法的后续问题。
我还不确定您的情况是否是我所认为的那样,但这是一个演示视频: Demo Video

牙刷应该在用户点击BoxCollider A内部(包括BoxCollider B)的任何地方显示,但如果用户点击BoxCollider B内部,则牙刷不会显示出来(OnPointerDown事件未触发)。最终,我尝试过更改层级顺序等方法,但仍然无法解决问题。谢谢。 - Tengku Fathullah
我明白了。如果您正确地叠加按钮(您正在使用带有按钮的UI画布,对吗?),则如果脚本已添加到“ A”和“ B”对象中,则应注册单击。那绝对不起作用吗? - Eissa
很抱歉,牙刷的显示/隐藏(OnPointerDown/OnPointerUp)是在dragable.cs脚本中控制的。如果用户在BoxCollider2D A内单击,则它会显示,但当用户在BoxCollider2D B内单击时,它不起作用。因为BoxCollider2D A和B都有OnPointerHandler。所以我猜可能是这个问题的原因? - Tengku Fathullah
1
我在我的答案中添加了一个视频作为演示,以说明我的意思。最后,这是一个分层的问题,导致物体 A 的碰撞器被先射线检测,而不是物体 B 的碰撞器。 - Eissa
谢谢分享这个视频,但是如果我想要的是点击B盒子触发OnPointerDown事件,同时也触发它后面的A盒子,我该怎么做呢?在OnPointerHandler的实现中是否支持这种操作?或者我应该使用其他方法? - Tengku Fathullah
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接