网站中 点击出现登录框怎么做/外贸网站推广
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 compareToif (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);
}
总结
今天介绍了射线投射器的基础部分, 下一篇文章会介绍几个具体的投射器, 希望对大家所有帮助.