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);
}
总结
今天介绍了射线投射器的基础部分, 下一篇文章会介绍几个具体的投射器, 希望对大家所有帮助.