[Unity3D]利用Raycast实现物体的选择与操作

时间:2023-03-09 17:32:16
[Unity3D]利用Raycast实现物体的选择与操作

本文系作者原创 转载请注明出处

如果是一个2D的平面项目或者说需要在三维空间选择一个物体时(经常表现为抓取物件),我们需要用到Raycast事件

那么首先先说说什么是Raycast 按照字面上来理解的话,就是投射射线=.=的确也是这样

Raycast有几种重载函数,所以按照惯例,先看看官方文档怎么说的

-------------------------------------------------------------------

Physics.Raycast 光线投射

static function Raycast (origin : Vector3, direction : Vector3, distance : float = Mathf.Infinity, layerMask : int = kDefaultRaycastLayers) : bool

Parameters参数

  • origin
    The starting point of the ray in world coordinates.
    在世界坐标,射线的起始点。
  • direction
    The direction of the ray.
    射线的方向。
  • distance
    The length of the ray
    射线的长度。
  • layerMask
    A Layer mask that is used to selectively ignore colliders
    when casting a ray.
    只选定Layermask层内的碰撞器,其它层内碰撞器忽略。

Returns返回

bool - True when the ray intersects any collider, otherwise false.

当光线投射与任何碰撞器交叉时为真,否则为假。

Description描述

Casts a ray against all colliders in the scene.

在场景中投下可与所有碰撞器碰撞的一条光线。

Note: This function will return false if you cast a ray from inside a sphere to the outside; this in an intended behaviour.

注意:如果从一个球型体的内部到外部用光线投射,返回为假。

static function Raycast (origin : Vector3, direction : Vector3, out hitInfo : RaycastHit, distance : float = Mathf.Infinity, layerMask : int = kDefaultRaycastLayers) : bool

Parameters参数

  • origin
    The starting point of the ray in world coordinates.
    在世界坐标,射线的起始点。
  • direction
    The direction of the ray.
    射线的方向。
  • distance
    The length of the ray
    射线的长度。
  • hitInfo
    If true is returned, hitInfo will contain more information
    about where the collider was hit (See Also: RaycastHit).
    如果返回true,hitInfo将包含碰到器碰撞的更多信息。
  • layerMask
    A Layer mask that is used to selectively ignore colliders
    when casting a ray.
    只选定Layermask层内的碰撞器,其它层内碰撞器忽略。

Returns

bool - True when the ray intersects any collider, otherwise false.

当光线投射与任何碰撞器交叉时为真,否则为假。

Description描述

Casts a ray against all colliders in the scene and returns detailed
information on what was hit.

在场景中投下可与所有碰撞器碰撞的一条光线,并返回碰撞的细节信息。

static function Raycast (ray : Ray, distance : float = Mathf.Infinity, layerMask : int = kDefaultRaycastLayers) : bool

Parameters参数

  • ray
    The starting point and direction of the ray.
    射线的起点和方向
  • distance
    The length of the ray
    射线的长度。
  • layerMask
    A Layer mask that is used to selectively ignore colliders
    when casting a ray.
    只选定Layermask层内的碰撞器,其它层内碰撞器忽略。

Returns

bool - True when the ray intersects any collider, otherwise false.

当光线投射与任何碰撞器交叉时为真,否则为假。

Description描述

Same as above using ray.origin and ray.direction instead of origin and
direction.

使用ray.origin和ray.direction同上,替代origin和direction。

static function Raycast (ray : Ray, out hitInfo : RaycastHit, distance : float = Mathf.Infinity, layerMask : int = kDefaultRaycastLayers) : bool

Parameters参数

  • ray
    The starting point and direction of the ray.
    射线的起点和方向
  • distance
    The length of the ray
    射线的长度
  • hitInfo
    If true is returned, hitInfo will contain more information
    about where the collider was hit (See Also: RaycastHit).
    如果返回true,hitInfo将包含碰到器碰撞的更多信息。
  • layerMask
    A Layer mask that is used to selectively ignore colliders
    when casting a ray.
    只选定Layermask层内的碰撞器,其它层内碰撞器忽略。

Returns

bool - True when the ray intersects any collider, otherwise false.

当光线投射与任何碰撞器交叉时为真,否则为假。

Description描述

Same as above using ray.origin and ray.direction instead of origin and
direction.

使用ray.origin和ray.direction同上,替代origin和direction

-------------------------------------------------------------------

其中,提到了几个很关键的参数:距离、hitInfo、layerMask

距离,可以用来实现“有效抓取距离”,比如说人的手臂假如说是0.5米长,那我们不能抓到0.5米以外的东西

Layer Mask可以理解为我们用来做标记的物体

而重中之重的是hitInfo,它用来储存我们所投出去的射线的关键信息

比如说射线射到了哪个物体?距离多远?物体的位置?

那么怎么获取它呢?

我们仔细看看Physics.Raycast函数,里面有个参数是out hitInfo,也就是说我们在投射这个射线的时候可以直接利用函数所给的参数获取hitInfo,也就是投出去的射线的关键信息

有了这些背景知识,我们就可以随时用射线实现“选择物体”、“抓取物体”这些动作了(二位尤其需要)

那么现在又出现一个问题,我们怎么去发出这样一条射线??

众所周知,射线从一个固定端点出发,向一个方向无限延伸,我们首先就要找到这个固定端点,然后表示出这个方向简单的是,我们可以用当前的渲染相机作为这个固定端点,因为我们实时在用它观察四周,那么我们所要做的只是找到一个好的投射方向。

请看官方文档里面的一个函数及其解释

-----------------------------------------------------------------------------------------------------------------------------

Camera.ScreenPointToRay 屏幕位置转射线

function ScreenPointToRay (position : Vector3) : Ray

Description描述

Returns a ray going from camera through a screen point.

返回一条从相机发射的,穿过屏幕位置上一点的射线

Resulting
ray is in world space, starting on the near plane of the camera and
going through position's (x,y) pixel coordinates on the screen
(position.z is ignored).

产生的射线是在世界空间中,从相机的近裁剪面发出并穿过屏幕position(x,y)像素坐标(position.z被忽略)。

Screenspace is defined in pixels. The bottom-left of the screen is (0,0); the right-top is (pixelWidth,pixelHeight).

屏幕空间以像素定义。屏幕的左下为(0,0);右上是(pixelWidth,pixelHeight)。

-----------------------------------------------------------------------------------------------------------------------------

请注意特意标出来的红色字体

我们可以看出,它能够返回一条从相机发射的射线,并且函数返回的是一个Ray对象,也就是说我们必须创建一个对象来容纳它

特别注意的是最后几句关于屏幕空间的定义!!

那么现在步骤就清晰了

利用Raycast事件选择一个物体并实现对其的相关操作的步骤:

1.找到一个作为投射起点的相机

2.分别创建两个对象,一个用来容纳返回的射线,一个用来容纳返回的这条射线的所有相关信息(如位置、距离、投射到的物体等)

3.选择这个被射线投射到的物体,之后可以进行相关操作

于是乎放代码了

using UnityEngine;
using System.Collections;

[AddComponentMenu("Raycast/CastObjects")]
public class Raycast_CastObj : MonoBehaviour {

    private GameObject selectedObj=null;
    private Camera validCam;

    //有效触摸距离
    public float validTouchDistance = 5.0F;
    //用户选择的Layer Mask
    public string selectedLayerMask = "Default";

    // Use this for initialization
    void Start ()
    {
        //寻找一个合理的相机
        validCam = FindObjectOfType<Camera>();
        if (validCam == null)
            Debug.LogWarning("Warning!\nFailed to find camera!");
        else
            Debug.Log("Find camera\nName:" + validCam.name);
    }

    // Update is called once per frame
    void Update ()
    {
        var raycast = validCam.ScreenPointToRay(Input.mousePosition);
        RaycastHit raycastInfo;

        if(Physics.Raycast(raycast, out raycastInfo, validTouchDistance, LayerMask.GetMask(selectedLayerMask)))
        {
            //选择这个被射线投射到的物体
            selectedObj = raycastInfo.collider.gameObject;
            Debug.Log("Ray cast to one OBJ\nName:" + selectedObj.name);
        }

        ) && selectedObj)
        {
            //如果射线没有(在用户设置的有效范围内)投射到任何物体
            if (selectedObj == null)
            {
                Debug.Log("Didn't select any Obj\nCheck if it's out of Valid Touch Distance");
            }
            //如果射线(在用户设置的有效范围内)投射到了物体
            else
            {
                Debug.Log("Selected Obj Position in World\n" + selectedObj.transform.position);
            }
        }

        ))
        {
            //必须清空
            selectedObj = null;
        }
    }
}