目录
一、知识概述
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 Raycaster
、Physics 2D Raycaster
或GraphicRaycaster
等组件来完成的。
Physics Raycaster
和Physics 2D Raycaster
是用来处理3D和2D物体的射线投射的,它们可以使你的3D和2D物体响应EventTrigger
定义的事件。
GraphicRaycaster
则是用来处理UI元素的射线投射的,它可以使你的UI元素响应EventTrigger
定义的事件。所以,如果你希望你的游戏对象能够响应
EventTrigger
定义的事件,你需要在相应的Canvas
或Camera
上添加一个射线投射器组件。
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会调用与这个事件相关的处理器。