Unity中的UGUI源码解析之事件系统(5)-RayCaster(上)

Unity中的UGUI源码解析之事件系统(5)-RayCaster(上)

今天要分享的是事件系统中的射线投射器(RayCaster).

Unity使用射线投射器来收集和鉴别被点击的游戏对象.

射线投射的原理很简单, 就是在屏幕点击的位置发射一条射线, 根据一些规则收集被射线穿透的对象, 然后再根据一些规则将这些对象排序, 选出距离屏幕最近的对象, 最后在这个对象上进行各种事件操作.

所以研究射线投射就是要围绕这个原理来展开, 有了方向, 我们才不会陷入各种代码中变得一头懵.

由于内容比较多, 我们分山下两篇文章来介绍.

相关的类

射线投射相关的类有下面几个:

  • Ray: 射线类
  • RaycastResult: 投射结果
  • RaycasterManager: 投射器管理器
  • BaseRaycaster: 射线投射基类
  • GraphicRaycaster : BaseRaycaster: 图形投射器
  • PhysicsRaycaster : BaseRaycaster: 针对3D物体的投射器, 需要对象上同时存在Camera组件
  • Physics2DRaycaster : PhysicsRaycaster: 针对2D物体的投射器, 需要对象上同时存在Camera组件

Ray

射线是从某一个点朝着某个方向发射的有限线段, Unity使用Camera的相关接口来发射射线.

Camera发射射线的原理很简单, 就是从Camera的近平面(不管是透视投影还是平行投影)的对应位置发送一条穿过指定点的射线.

发射点原则上是按照比例在视口上计算出来, 这个比例根据目标点在自己的坐标系中的比例来确定.

最后得出的射线使用世界坐标来描述.

  • public Ray ViewportPointToRay(Vector3 position): 从摄像机发送一条射线, 经过ViewRect(视口, 一般坐标是0到1之间)的指定点
  • public Ray ScreenPointToRay(Vector3 position): 从摄像机发送一条射线, 经过Screen坐标的指定点

Ray本身是一个结构体, 提供发射点坐标和发射方向的属性, 还提供了一个获取指定长度的射线终点的坐标接口, 注意这里说的坐标都是世界坐标.

public Vector3 origin
{
    get => this.m_Origin;
    set => this.m_Origin = value;
}

// 这个方向是标准化过后的
public Vector3 direction
{
    get => this.m_Direction;
    set => this.m_Direction = value.normalized;
}

public Vector3 GetPoint(float distance) => this.m_Origin + this.m_Direction * distance;

RaycastResult

投射结果, 本身是一个结构体, 封装投射相关的数据.

// 投射击中(射线穿过)的对象
private GameObject m_GameObject;

// 相关的投射器
public BaseRaycaster module;

// 击中距离(从射线起始点到击中点)
public float distance;

// 排序索引
public float index;

// 排序深度
public int depth;

// 排序sortingLayer
public int sortingLayer;

// 排序sortingOrder
public int sortingOrder;

// 击中点的世界坐标
public Vector3 worldPosition;

// 击中点的世界法线
public Vector3 worldNormal;

// 击中点的屏幕坐标
public Vector2 screenPosition;

// 是否有效
public bool isValid
{
    get { return module != null && gameObject != null; }
}

RaycasterManager

RaycasterManager是一个静态类, 通过静态列表维护所有的投射器, 在投射器的生命周期函数OnEnable/OnDisable时加入列表和从列表中移除.

internal static class RaycasterManager
{
    private static readonly List<BaseRaycaster> s_Raycasters = new List<BaseRaycaster>();

    public static void AddRaycaster(BaseRaycaster baseRaycaster)
    {
        if (s_Raycasters.Contains(baseRaycaster))
            return;

        s_Raycasters.Add(baseRaycaster);
    }

    public static List<BaseRaycaster> GetRaycasters()
    {
        return s_Raycasters;
    }

    // 这个名字明显不需要带"s", 看来Unity的程序大佬也喜欢复制黏贴嘛, 哈哈
    public static void RemoveRaycasters(BaseRaycaster baseRaycaster)
    {
        if (!s_Raycasters.Contains(baseRaycaster))
            return;
        s_Raycasters.Remove(baseRaycaster);
    }
}

代码不多, 也比较简单, 我就直接贴出来了.

BaseRaycaster

BaseRaycaster是一个抽象类, 继承于UIBehaviour, 提供一些基础行为, 最主要的就是定义投射接口, 摄像机属性加入和移除投射管理器的行为, 同时提供了一些基础属性供其它模块使用.

public abstract class BaseRaycaster : UIBehaviour
{
    // 核心的投射接口
    public abstract void Raycast(PointerEventData eventData, List<RaycastResult> resultAppendList);
    
    // 用于生成射线的摄像机
    public abstract Camera eventCamera { get; }
    
    protected override void OnEnable()
    {
        base.OnEnable();
        RaycasterManager.AddRaycaster(this);
    }

    protected override void OnDisable()
    {
        RaycasterManager.RemoveRaycaster(this);
        base.OnDisable();
    }
    
    // 这个也参与之前文章提到的信息展示
    public override string ToString()
    {
        return "Name: " + gameObject + "\n" +
            "eventCamera: " + eventCamera + "\n" +
            "sortOrderPriority: " + sortOrderPriority + "\n" +
            "renderOrderPriority: " + renderOrderPriority;
    }
    
    // 排序优先级, 用于射线排序比较, 在EventSystem组件中的比较函数中使用
    public virtual int sortOrderPriority
    {
        get { return int.MinValue; }
    }

    // 渲染顺序优先级, 用于射线排序比较, 在EventSystem组件中的比较函数中使用
    public virtual int renderOrderPriority
    {
        get { return int.MinValue; }
    }
}

EventSystem中用于投射的方法

在前面的文章中, 我们说过, EventSystem中提供了一些用于投射的方法, 不太清楚为什么放在这里, 个人感觉放在RaycasterManager更合适一点.

// 投射结果比较器, 用于排序RaycastResult
// 总体上是按照: 优先级越大, 深度越大, 越后渲染, 距离摄像机越近, 索引越小, 则越靠前
private static int RaycastComparer(RaycastResult lhs, RaycastResult rhs)
{
    // 优先比较投射器
    if (lhs.module != rhs.module)
    {
        var lhsEventCamera = lhs.module.eventCamera;
        var rhsEventCamera = rhs.module.eventCamera;
        
        // 两者摄像机都存在且深度不同时, 使用摄像机的深度排序
        if (lhsEventCamera != null && rhsEventCamera != null && lhsEventCamera.depth != rhsEventCamera.depth)
        {
            // need to reverse the standard compareTo
            if (lhsEventCamera.depth < rhsEventCamera.depth)
                return 1;
            if (lhsEventCamera.depth == rhsEventCamera.depth)
                return 0;

            return -1;
        }

        // sortOrderPriority排序
        if (lhs.module.sortOrderPriority != rhs.module.sortOrderPriority)
            return rhs.module.sortOrderPriority.CompareTo(lhs.module.sortOrderPriority);

        // renderOrderPriority排序
        if (lhs.module.renderOrderPriority != rhs.module.renderOrderPriority)
            return rhs.module.renderOrderPriority.CompareTo(lhs.module.renderOrderPriority);
    }

    // sortingLayer排序
    if (lhs.sortingLayer != rhs.sortingLayer)
    {
        // Uses the layer value to properly compare the relative order of the layers.
        var rid = SortingLayer.GetLayerValueFromID(rhs.sortingLayer);
        var lid = SortingLayer.GetLayerValueFromID(lhs.sortingLayer);
        return rid.CompareTo(lid);
    }

    // sortingOrder排序
    if (lhs.sortingOrder != rhs.sortingOrder)
        return rhs.sortingOrder.CompareTo(lhs.sortingOrder);

    // depth排序
    if (lhs.depth != rhs.depth)
        return rhs.depth.CompareTo(lhs.depth);

    // distance排序[从小到大]
    if (lhs.distance != rhs.distance)
        return lhs.distance.CompareTo(rhs.distance);

    // index排序[从小到大]
    return lhs.index.CompareTo(rhs.index);
}

private static readonly Comparison<RaycastResult> s_RaycastComparer = RaycastComparer;

// 使用事件数据对所有投射器进行投射, 获取排序后的投射结果, 主要使用位置属性
public void RaycastAll(PointerEventData eventData, List<RaycastResult> raycastResults)
{
    raycastResults.Clear();
    var modules = RaycasterManager.GetRaycasters();
    for (int i = 0; i < modules.Count; ++i)
    {
        var module = modules[i];
        if (module == null || !module.IsActive())
            continue;

        module.Raycast(eventData, raycastResults);
    }

    raycastResults.Sort(s_RaycastComparer);
}

总结

今天介绍了射线投射器的基础部分, 下一篇文章会介绍几个具体的投射器, 希望对大家所有帮助.

猜你喜欢

转载自blog.csdn.net/woodengm/article/details/123582306