Unity | 射线检测及EventSystem总结

发布于:2024-03-29 ⋅ 阅读:(24) ⋅ 点赞:(0)

目录

一、知识概述

1.Input.mousePosition

2.Camera.ScreenToWorldPoint

3.Camera.ScreenPointToRay

4.Physics2D.Raycast

二、射线相关

1.3D(包括UI)、射线与ScreenPointToRay

2.3D(包括UI)、射线与ScreenToWorldPoint

3.2D UI、射线与ScreenToWorldPoint

4.2D 检测视线中是否有障碍物

三、EventSystem

1.处理3D对象的交互事件

(1)利用EventSystem实现3D物体点击交互

(2)解决事件传递

2.处理2D对象(SpriteRender)的交互事件

3.处理UGUI的交互事件

4.利用EventTrigger组件代替接口来处理事件

5.总结


一、知识概述

1.Input.mousePosition

        在Unity中,Input.mousePosition 返回的是一个Vector3,它代表了鼠标在屏幕上的位置。这个位置的x和y分量代表了鼠标在屏幕上的水平和垂直坐标,而z分量默认情况下总是0,因为鼠标的位置是二维的,它没有深度信息。

2.Camera.ScreenToWorldPoint

        Camera.ScreenToWorldPoint 函数需要一个 Vector3 参数,其中 x 和 y 分别代表屏幕上的位置(通常是鼠标的位置),而 z 代表从摄像机到目标点的距离。在透视相机下,如果只传递了Input.mousePosition(鼠标的 x 和 y 坐标,而 z 坐标是默认的 0),那么转换出的世界坐标将会是摄像机自身的位置,因为从摄像机到它自己的距离是 0。 

        正确的做法是:设置 z 值为从摄像机到你感兴趣的对象的距离。这个距离不能是简单的摄像机的 z 值,因为摄像机的 z 值是相对于世界坐标系的,而 ScreenToWorldPoint 函数需要的是从摄像机到目标点的距离。 

        在正交视角(Orthographic)摄像机下,只传递了Input.mousePosition没问题,由于正交摄像机不具有透视变形,屏幕上的点转换到世界坐标的过程中不需要考虑深度信息,因为在正交投影中,深度(远近)不会影响物体的大小。

        其实,即使在正交视角下,ScreenToWorldPoint 依然需要一个 z 值,因为这个值会决定返回的世界坐标位于哪个深度上。 

         ScreenToWorldPoint通常用于在鼠标点击的位置放置一个对象,或者拖动物体等。

3.Camera.ScreenPointToRay

        Camera.ScreenPointToRay 方法用于从摄像机通过屏幕上的一个点创建一条射线。这个方法返回一个 Ray 对象,它包含了一个原点(摄像机的位置)和一个方向(从摄像机通过屏幕点的方向)。这个方法非常适合用于射线检测,因为你通常需要检测从摄像机出发,经过用户点击屏幕位置的直线上是否有任何物体。 

        常用于通过用户的鼠标点击或触摸来检测3D对象。

4.Physics2D.Raycast

        在Unity中,Physics2D.Raycast 是一个用于2D游戏的功能,它可以检测在给定的直线路径上是否有任何碰撞器(Collider)。这个功能常用于实现射线投射,比如检测玩家的视线中是否有障碍物,或者实现子弹的射击效果。

        对于UI元素的点击事件,常用Button或者使用【EventSystem + IPointerClickHandler接口】来处理。

二、射线相关

1.3D(包括UI)、射线与ScreenPointToRay

        对于3D物体,Unity中的鼠标点击射线检测需要依赖Camera。这主要通过使用Camera类的ScreenPointToRay方法来完成。该方法接收一个屏幕坐标并生成一条从相机位置出发,穿过屏幕点的射线。对于3D UI,依赖camera时,需要设置canvas render mode :Screen Space - Camera.

  • 摄像机为透视:Perspective。
  • 利用ScreenPointToRay。
  • 3D物体和3DUI都需要3D类型的Collider。
  • 实现鼠标点击到3D物体时,cube移动到点击位置。(后面也记录了利用射线+EventSystem实现该效果)
    void Update()
    {
        //3D
        if (Input.GetMouseButtonDown(0))
        {
            Debug.Log("Mouse Clicked:" + Input.mousePosition);
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            RaycastHit hit;
            int layerMask = 1 << 0 | 1 << 5;
            if (Physics.Raycast(ray, out hit, 1000, layerMask))
            {
                Debug.Log("Hit:" + hit.collider.gameObject.name);
                if (hit.collider.gameObject.name == "RawImage")//3D UI,需要添加3D Collider
                {
                    hit.collider.gameObject.GetComponent<RawImage>().color = new Color(Random.Range(0, 1.0f), Random.Range(0, 1.0f), Random.Range(0, 1.0f), 1);
                }
                else  //3D物体
                {
                    hitPos = hit.point;
                }
            }
        }
        if (cube.transform.position != hitPos)
        {
            cube.transform.LookAt(Camera.main.transform);
            cube.transform.position = Vector3.Lerp(cube.transform.position, hitPos, Time.deltaTime);
        }
    }

2.3D(包括UI)、射线与ScreenToWorldPoint

        ScreenToWorldPoint传入z值(10)后,结果mouseWorldPosition的z值=camera的z+传入的z值。以下代码透视相机没问题,但正交下需要修改传入的z值以便能够点击到目标。

        if (Input.GetMouseButtonDown(0))
        {
            Debug.Log(Camera.main.nearClipPlane);
            // 将鼠标位置的屏幕坐标转换为世界坐标
            Vector3 mouseWorldPosition = Camera.main.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, 10));

            // 从摄像机的位置到鼠标所在的世界坐标的方向
            Vector3 direction = mouseWorldPosition - Camera.main.transform.position;
            Debug.DrawRay(Camera.main.transform.position, direction, Color.red, 4);//Debug.DrawRay 方法的第二个参数应该是方向向量(即终点位置减去起始位置),而不是终点位置本身

            Debug.Log("mouseWorldPosition:" + mouseWorldPosition + "  direction:" + direction);
            RaycastHit hit;
            // 发射射线
            if (Physics.Raycast(Camera.main.transform.position, direction, out hit))
            {
                // 如果射线和游戏对象碰撞,hit变量就会包含碰撞信息
                Debug.Log("Hit " + hit.collider.gameObject.name);
                // 这里可以添加更多的逻辑,比如处理点击事件
            }
        }

         Debug.DrawRay如果第二个参数写成终点:

Debug.DrawRay( Camera.main.transform.position, mouseWorldPosition, Color.red, 4);

        Unity会将这个终点坐标解释为一个方向向量,这会导致射线从原点(通常是世界坐标的(0,0,0))指向你传入的“终点”坐标,这显然不是你想要的效果:

3.2D UI、射线与ScreenToWorldPoint

  • 利用ScreenToWorldPoint,并且参数为Input.mousePosition,没有设置z值,只在正交相机下有效。
  • 利用RaycastHit2D只检测2D Collider,所以只对UI有效。
        if (Input.GetMouseButtonDown(0))
        {
            // 将鼠标点击的屏幕坐标转换为射线
            Vector3 ray = Camera.main.ScreenToWorldPoint(Input.mousePosition);

            // 进行射线投射,检测2D Collider
            RaycastHit2D hit = Physics2D.Raycast(ray, Vector2.zero);

            // 检查是否有物体被射线击中
            if (hit.collider != null)
            {
                Debug.Log("Hit " + hit.collider.gameObject.name);
            }
        }

4.2D 检测视线中是否有障碍物

        if(Input.GetMouseButtonDown(0))
        {
            if (player2D != null)
            {
                Debug.Log(player2D.transform.forward);

                Debug.Log(player2D.transform.position + player2D.transform.forward);
                RaycastHit2D hit = Physics2D.Raycast(player2D.transform.position, player2D.transform.forward, 1000);
                Debug.DrawLine(player2D.transform.position, player2D.transform.position + player2D.transform.forward * 1000, Color.red, 4);
              
                if (hit.collider != null)
                {
                    Debug.Log("Hit " + hit.collider.gameObject.name);
                }
            }
        }

三、EventSystem

        Unity的EventSystem事件系统是用来处理交互事件的。它负责管理和处理所有的输入事件,例如鼠标点击、触摸事件、键盘输入等。它可以处理3D、2D和UI交互。

        在一个典型的Unity项目中,通常会有一个名为"EventSystem"的GameObject,它包含了一个名为"StandaloneInputModule"或者其他类型InputModule(如TouchInputModule)的组件。这个模块负责在每一帧收集用户输入并将其转化为特定事件。

1.处理3D对象的交互事件

        Unity的EventSystem可以处理3D对象的交互事件,但是需要配合Physics Raycaster(用于3D物体)组件进行检测鼠标或触摸输入。

  • Physics Raycaster组件会在每一帧发送一条从摄像机到鼠标位置的射线,如果这条射线碰到了带有Collider组件的3D物体,那么这个物体就可以接收到EventSystem的事件。
  • 要让3D物体能够响应EventSystem的事件,需要在物体上添加一个实现了EventSystem接口的脚本。例如,可以添加一个实现了IPointerClickHandler接口的脚本,然后在OnPointerClick方法中处理点击事件。

(1)利用EventSystem实现3D物体点击交互

  • 确保场景中有EventSystem组件,并保持活跃。
  • 确保摄像机上有一个Physics Raycaster组件。这个组件负责发送射线来检测物体。Physics Raycaster组件并不是射线检测的必需组件,它只是用于让EventSystem能够自动处理鼠标和触摸事件。
  • 确保3D物体上有一个Collider组件。
//下方代码需要挂在要交互的3D物体上
public class CubePointerClick : MonoBehaviour,IPointerClickHandler
{
    public void OnPointerClick(PointerEventData eventData)
    {
        Debug.Log("OnPointerClick");
        this.transform.localPosition += Vector3.forward;
    }
}

        Unity的事件系统在处理3D物体的点击事件时,会将事件传递给被点击物体的所有父物体,直到有一个物体处理了这个事件。所以如果3D物体上挂有上述脚本,并且有一个子物体(子物体没挂脚本,但是有collider组件),那么点击子物体时,也会触发父物体的OnPointClick。

(2)解决事件传递

方法一:检查被点击的物体是否是父物体

public class CubePointerClick : MonoBehaviour,IPointerClickHandler
{
    public void OnPointerClick(PointerEventData eventData)
    {
        Debug.Log(eventData.pointerCurrentRaycast.gameObject.name);//打印点击的物体名字
        if (eventData.pointerCurrentRaycast.gameObject == gameObject)
        {
            this.transform.localPosition += Vector3.forward;
        }
    }
}

方法二:在子物体上添加一个空的IPointerClickHandler接口,来阻止事件的传递

public class TestSubObjClick : MonoBehaviour, IPointerClickHandler
{
    public void OnPointerClick(PointerEventData eventData)
    {
        //不处理点击事件
    }
}

2.处理2D对象(SpriteRender)的交互事件

        Unity的EventSystem同样可以处理2D对象的交互事件,但是需要配合Physics 2D Raycaster组件(挂在摄像机上)进行检测鼠标或触摸输入。

  • Physics 2D Raycaster组件会在每一帧发送一条从摄像机到鼠标位置的射线,如果这条射线碰到了带有Collider2D组件的2D物体,那么这个物体就可以接收到EventSystem的事件。
  • 要让2D物体能够响应EventSystem的事件,你需要在物体上添加一个实现了EventSystem接口的脚本。例如,你可以添加一个实现了IPointerClickHandler接口的脚本,然后在OnPointerClick方法中处理点击事件。

3.处理UGUI的交互事件

        Unity的EventSystem同样可以处理UGUI的交互事件,但是需要配合GraphicRaycaster组件进行检测鼠标或触摸输入。

  • GraphicRaycaster组件通常挂载在Canvas组件上,它会处理所有子UI元素的射线投射。
  • 创建Canvas时,会自动创建带有EventSystem组件的物体。

4.利用EventTrigger组件代替接口来处理事件

        之前的代码实现OnPointerClick点击事件都继承了 IPointerClickHandler接口,我们也可以不继承各种接口,直接使用EventTrigger组件来实现各种事件。EventTrigger组件可以挂在任何物体上,包括3D、2D及UI。

public class CubePointerClick : MonoBehaviour
{
    public void OnPointerClick()
    {
        this.transform.localPosition += Vector3.forward;
    }
}

        EventTrigger组件依赖于Unity中的Event System。当你使用UI元素(例如按钮、滑块等)或者自定义游戏对象上添加了EventTrigger组件后,在这些元素接收到用户交互(点击、拖动、滚动等)时,由InputModule收集到这些信息并通过EventSystem传递给相应对象上的EventTrigger进行处理。

        当然,用EventTrigger组件时,也要依赖Physics Raycaster组件 或 Physics 2D Raycaster组件 或 GraphicRaycaster等组件。

EventTrigger组件是用来定义和处理事件的,但是它并不负责检测鼠标或触摸输入。这是由Physics RaycasterPhysics 2D RaycasterGraphicRaycaster等组件来完成的。

  • Physics RaycasterPhysics 2D Raycaster是用来处理3D和2D物体的射线投射的,它们可以使你的3D和2D物体响应EventTrigger定义的事件。

  • GraphicRaycaster则是用来处理UI元素的射线投射的,它可以使你的UI元素响应EventTrigger定义的事件。

所以,如果你希望你的游戏对象能够响应EventTrigger定义的事件,你需要在相应的CanvasCamera上添加一个射线投射器组件。

5.总结

        在Unity中,StandaloneInputModule、Physics Raycaster、Physics 2D Raycaster、GraphicRaycaster和EventTrigger都是事件系统的一部分,它们各自负责不同的任务。

  • StandaloneInputModule是一个输入模块,它处理基于独立输入的事件,如鼠标和键盘输入。它将这些输入转换为事件,如OnPointerClick、OnPointerDown、OnPointerUp等,并将这些事件发送到EventSystem。
  • Physics Raycaster、Physics 2D Raycaster和GraphicRaycaster是射线投射器,它们负责检测鼠标或触摸输入与游戏对象的交互。例如,当你点击一个游戏对象时,射线投射器会检测到这个交互,并将相关的事件(如OnPointerClick)发送到这个游戏对象。
  • EventTrigger是一个事件触发器,它可以在游戏对象上定义和处理各种事件。当射线投射器检测到一个交互并发送一个事件时,EventTrigger会调用与这个事件相关的处理器。

        所以,如果你希望你的游戏对象能够响应鼠标或触摸输入,并执行自定义的事件处理逻辑,你需要在EventSystem上添加一个StandaloneInputModule,在相应的Canvas或Camera上添加一个射线投射器,并在游戏对象上添加一个EventTrigger。这样,当你点击或触摸游戏对象时,StandaloneInputModule会将这个输入转换为事件,射线投射器会检测到这个交互并将事件发送到游戏对象,然后EventTrigger会调用与这个事件相关的处理器。

本文含有隐藏内容,请 开通VIP 后查看