# 一. 射线检测 (Ray Detection) ## 1. 射线对象 (Ray Object) ### 1.1 3D世界中的射线 (Ray in a 3D World) 射线是一条具有**初始点**和**方向**的直线(注意:方向不是终点)。 声明射线时,需要传入初始点和方向。 C# ``` // Ray(Vector3 origin, Vector3 direction) Ray ray = new Ray(origin, direction); // 示例:从原点 (0,0,0) 指向右方 (1,0,0) 的射线 Ray rayFromOriginToRight = new Ray(Vector3.zero, Vector3.right); ``` ### 1.2 摄像机发出的射线 (Ray from Camera) 这是从屏幕上的一个点(例如鼠标位置)作为起点,沿着摄像机视口方向发出的一条射线。 其初始点即为摄像机位置,方向由屏幕坐标转换而来。 C# ``` // 从主摄像机发出,基于当前鼠标在屏幕上的位置 Ray rayFromCamera = Camera.main.ScreenPointToRay(Input.mousePosition); ``` > **注意**: 单独的射线没有实际作用,需要结合射线检测才有意义。 --- ## 2. 射线检测 (Raycasting) ### 2.1 最原始的射线检测 (Basic Raycast) 这种检测方法返回一个布尔值 (`bool`),用于判断射线是否碰撞到任何物体。 **参数说明:** 1. `ray`: 发出的射线。 2. `maxDistance`: 检测的最大距离。 3. `layerMask`: 指定检测的层级 (Layer)。 4. `queryTriggerInteraction`: 是否与触发器 (Trigger) 交互。 - `UseGlobal`: 使用全局设置。 - `Collide`: 检测触发器。 - `Ignore`: 忽略触发器 (默认值,如果不填,则使用 `UseGlobal`)。 C# ``` // 示例:检测名为 "Player" 的层级,最大距离1000,忽略触发器 if (Physics.Raycast(ray, 1000f, 1 << LayerMask.NameToLayer("Player"), QueryTriggerInteraction.Ignore)) { // 射线碰到了 "Player" 层上的物体 } ``` ### 2.2 获得相交的单个物体的信息 (Getting Information about a Single Hit) 此方法同样返回 `bool` 值,但能通过 `out` 参数获取碰撞到的单个物体的详细信息。 **参数说明:** 1. `ray`: 发出的射线。 2. `out RaycastHit hitInfo`: `out` 关键字表示此参数会传出碰撞信息。 3. `maxDistance`: 检测的最大距离。 4. `layerMask`: 指定检测的层级。 5. `queryTriggerInteraction`: 是否与触发器交互。 C# ``` RaycastHit hit; // 用来存储碰撞信息 if (Physics.Raycast(ray, out hit, 1000f, 1 << LayerMask.NameToLayer("Enemy"), QueryTriggerInteraction.Collide)) { // 射线碰到了物体,碰撞信息存储在 'hit' 中 // hit.collider: 碰到的碰撞体 // hit.point: 碰撞点坐标 // hit.normal: 碰撞点处的法线向量 // hit.distance: 射线起点到碰撞点的距离 // GameObject hitObject = hit.collider.gameObject; // 获取碰撞到的游戏对象 } ``` ### 2.3 获得相交的多个物体 (Getting Information about Multiple Hits) 此方法返回一个 `RaycastHit` 类型的数组。如果射线没有碰撞到任何物体,则数组长度为0。 **参数说明:** 1. `ray`: 发出的射线。 2. `maxDistance`: 检测的最大距离。 3. `layerMask`: 指定检测的层级。 4. `queryTriggerInteraction`: 是否与触发器交互。 C# ``` RaycastHit[] hits; hits = Physics.RaycastAll(ray, 1000f, 1 << LayerMask.NameToLayer("Obstacle")); if (hits.Length > 0) { foreach (RaycastHit hit in hits) { // 处理每一个碰撞到的物体 // Debug.Log("Hit: " + hit.collider.name); } } ``` > **`RaycastHit` 结构体**: 在射线检测中非常关键。它记录了大量信息,例如: > > - `collider`: 射线命中的碰撞体,通过它可以获取整个游戏对象。 > - `point`: 射线检测到的碰撞点。 > - `normal`: 射线与表面相交点的法线向量。 > - `distance`: 射线起点到相交点的距离。 --- # 二. 范围检测 (Overlap Detection) 范围检测是在一个固定的区域内,检测其中所有携带碰撞体 (Collider) 的物体。 Unity 中主要有三种范围检测: - 盒状范围检测 (`OverlapBox`) - 球形范围检测 (`OverlapSphere`) - 胶囊体范围检测 (`OverlapCapsule`) 这些方法都存储在 `Physics` 类中,参数大致相同,只是根据形状不同,需要传入不同的点来构造形状。 ## 1. 盒状范围检测 (OverlapBox) `Physics.OverlapBox()` **参数说明:** 1. `center`: 立方体的中心点。 2. `halfExtents`: 立方体三个轴向的半尺寸 (大小的一半)。 3. `orientation`: 立方体的旋转角度 (四元数 `Quaternion`)。 4. `layerMask`: **检测指定层级**。 5. `queryTriggerInteraction`: 是否忽略触发器。 **返回值**: 范围内的所有碰撞体 (`Collider[]`) 数组。 ### 重点参数四:检测指定层级 (LayerMask) Unity 中的 Layer共有32个 (0-31),这刚好可以用一个32位的二进制数来表示。 `0000 0000 0000 0000 0000 0000 0000 0000` **使用 `1 << LayerMask.NameToLayer("LayerName")` 创建层掩码:** `LayerMask.NameToLayer("LayerName")` 函数会返回指定层名称在 Layer 中的索引序号。 将数字 `1` 左移这个索引号的位数,会在32位数字中得到一个全新的数字。例如,如果 "UI" 层是第 5 层 (索引为5): `1 << 5` 结果为 `0000 0000 0000 0000 0000 0000 0010 0000` (第5位为1) 在这个32位的二进制掩码中,`1` 代表需要检测该层级,`0` 代表不需要。 **检测多个层级:** 使用按位或 (`|`) 操作可以方便地组合多个层级进行检测。 例如,要检测第0层、第2层和第5层: `0000 0000 0000 0000 0000 0000 0010 0101` (二进制表示) C# ``` int layerMask = (1 << LayerMask.NameToLayer("第一层")) | (1 << LayerMask.NameToLayer("第二层")) | (1 << LayerMask.NameToLayer("第五层")); // 或者,如果知道层索引: // int layerMask = (1 << 0) | (1 << 2) | (1 << 5); ``` ### 位运算在 LayerMask 中的应用 - **按位与 (`&`)**: - 规则:两个位都为 `1` 时,结果才为 `1`。 - 用途:检测某个特定层是否存在于当前的层掩码中。 - 示例: - 掩码: `0000 ... 0010 0101` (检测0, 2, 5层) - 待检测层 (例如第0层): `0000 ... 0000 0001` (1 << 0) - `(掩码 & 待检测层)` 结果不为0,证明该层在掩码中。 - **按位或 (`|`)**: - 规则:只要有一个位为 `1`,结果就为 `1`。 - 用途:将多个层添加到层掩码中。 - **按位异或 (`^`)**: - 规则:两个位相同为 `0`,不同为 `1`。 - 用途:动态切换某个层级的检测状态(添加或移除)。 - 示例: - 原始掩码 (检测第1层和第3层): `0000 ... 1010` - 操作层 (第3层): `0000 ... 1000` - `原始掩码 ^ 操作层` 结果: `0000 ... 0010` (第3层的状态被翻转,现在只检测第1层) ### Layer 和 LayerMask 的区别是什么? - **Layer**: 指的是 Unity 编辑器中为 GameObject 分配的单个层级(例如 "Player", "Enemy", "UI")。每个 GameObject 只能属于一个 Layer。Layer 本身是一个整数索引 (0-31)。 - **LayerMask**: 是一个32位的整数,用作位掩码 (bitmask)。它的每一位对应一个 Layer。通过设置 LayerMask 中的特定位为1,可以指定射线检测、范围检测等操作应该作用于哪些 Layer。一个 LayerMask 可以同时代表多个 Layer。 --- # 三. RPG游戏学习 (RPG Game Study) ## 1. 导航系统 (Navigation System) - (内容待补充) ## 2. 鼠标点击, 角色移动 (Mouse Click, Character Movement) - (内容待补充) --- # 四. 对象池技术 (Object Pooling) 目前构造的对象池比较简单。这个对象池是一个单例类。我们需要明确: - 对象池存储的物体是什么类型。 - 对象池存储的物体初始个数是多少。 然后构造一个队列 (Queue) 来管理这些对象。 一个典型的对象池实现: 1. 在 `Awake()` 方法中完成单例类的构造。 2. 实现三个核心方法: - **初始化填充对象池**: 在开始时创建并存储一定数量的对象。 - **从对象池中获取对象**: 当需要对象时,从池中取出一个。 - **将对象返回对象池**: 当对象不再使用时,将其返还给池中以备复用。 --- # 五. LineRenderer (画线组件) `LineRenderer` 组件允许在 Unity 场景中绘制线段。它主要包含编辑器设置和代码控制两大部分。代码控制是重点,通常在脚本中动态实现。 ## 1. 编辑器部分 (Editor Properties) _(图片引用)_ 1. **Loop**: 线段的开头和结束是否闭合形成循环。 2. **Positions**: 控制线段的顶点个数和每个顶点的坐标。 3. **Width**: 线段的粗细 (可以是一个曲线,使粗细沿长度变化)。 4. **Color**: 线段的颜色 (可以使用渐变色)。 5. **Corner Vertices**: 角顶点个数。数值越大,线段转角处越圆润。 6. **End Cap Vertices**: 末端顶点个数。数值越大,线段末端越圆润。 7. **Alignment**: 对齐方式 (View, TransformZ)。 8. **Texture Mode**: 纹理模式 (Stretch, Tile, DistributePerSegment, Shape)。 9. **Generate Lighting Data**: 是否生成光照数据,即光照是否对线段产生影响。 10. **Use World Space**: 是否使用世界坐标。 - 若勾选,移动 LineRenderer 所在的 GameObject 时,线段本身不会随之移动。 - 若不勾选 (使用本地坐标),线段会随 GameObject 移动。 ## 2. 代码部分 (Scripting) 首先,需要获取 `LineRenderer` 组件的引用。 C# ``` LineRenderer lineRenderer; void Start() { lineRenderer = GetComponent(); } ``` **常用代码操作:** 1. **首尾相连 (Looping)**: C# ``` lineRenderer.loop = true; // 或者 false ``` 2. **设置开始和结束宽度**: C# ``` lineRenderer.startWidth = 0.1f; lineRenderer.endWidth = 0.5f; // 也可以使用曲线来控制宽度 // lineRenderer.widthCurve = new AnimationCurve(new Keyframe(0, 0.1f), new Keyframe(1, 0.5f)); ``` 3. **设置开始和结束颜色**: C# ``` lineRenderer.startColor = Color.red; lineRenderer.endColor = Color.blue; // 也可以使用 Gradient 来控制颜色 // Gradient gradient = new Gradient(); // gradient.SetKeys( // new GradientColorKey[] { new GradientColorKey(Color.red, 0.0f), new GradientColorKey(Color.blue, 1.0f) }, // new GradientAlphaKey[] { new GradientAlphaKey(1.0f, 0.0f), new GradientAlphaKey(1.0f, 1.0f) } // ); // lineRenderer.colorGradient = gradient; ``` 4. **设置材质 (Material)**: C# ``` public Material lineMaterial; // 在 Inspector 中指定 // ... lineRenderer.material = lineMaterial; ``` 5. **设置顶点 (Positions)**: - 设置顶点数量: C# ``` lineRenderer.positionCount = 2; // 例如,一条直线需要2个点 ``` - 设置单个顶点坐标: C# ``` // lineRenderer.SetPosition(int index, Vector3 position); lineRenderer.SetPosition(0, new Vector3(0, 0, 0)); lineRenderer.SetPosition(1, new Vector3(1, 1, 0)); ``` - 通过数组一次性设置所有顶点坐标: C# ``` Vector3[] positions = new Vector3[] { new Vector3(0, 0, 0), new Vector3(1, 1, 0), new Vector3(2, 0, 0) }; lineRenderer.SetPositions(positions); ``` --- # 六. 四元数 (Quaternions) - (内容待补充,用于表示旋转) --- # 七. 组件和物体的启用和禁用 (Component and GameObject Enabling/Disabling) ## 1. GameObject 的启用和禁用 ### 1.1 激活 GameObject C# ``` // 'this' 指向当前脚本所在的 GameObject this.gameObject.SetActive(true); ``` ### 1.2 禁用 GameObject C# ``` this.gameObject.SetActive(false); ``` ### 1.3 查看 GameObject 状态 C# ``` // 自身是否被设置为激活 (不受父物体影响) bool isActiveSelf = gameObject.activeSelf; // 在层级视图中实际的激活状态 (受父物体影响) bool isActiveInHierarchy = gameObject.activeInHierarchy; ``` ### 1.4 `activeSelf` 和 `activeInHierarchy` 的区别 - `gameObject.activeSelf`: 表示该 GameObject 自身是否被设置为激活状态。如果它被设置为 `false`,即使其所有父对象都激活,它依然是非激活的。 - `gameObject.activeInHierarchy`: 表示该 GameObject 在场景中是否真实处于激活状态。如果它自身 `activeSelf` 为 `true`,但其任何一个父对象 `activeSelf` 为 `false` (导致父对象 `activeInHierarchy` 为 `false`),那么该 GameObject 的 `activeInHierarchy` 也会是 `false`。 ## 2. 组件的启用和禁用 ### 2.1 激活组件 C# ``` // 对于当前脚本组件本身 this.enabled = true; // 对于其他组件 (例如 Collider),需要先获取其引用 Collider myCollider = this.GetComponent(); if (myCollider != null) { myCollider.enabled = true; } ``` ### 2.2 禁用组件 C# ``` // 对于当前脚本组件本身 this.enabled = false; // 对于其他组件 Collider myCollider = GetComponent(); if (myCollider != null) { myCollider.enabled = false; } ``` ### 2.3 检查组件的启用状态 C# ``` // 当前脚本组件的状态 bool isScriptEnabled = this.enabled; // 其他组件的状态 Collider myCollider = GetComponent(); bool isColliderEnabled = false; if (myCollider != null) { isColliderEnabled = myCollider.enabled; } ``` ## 3. 相关的生命周期事件 (Lifecycle Events) ### 3.1 组件相关的生命周期方法: C# ``` void OnEnable() { // 当组件被启用时调用。 // 这也包括其所在的 GameObject 从非激活状态变为激活状态时。 // 注意: 如果 GameObject 首次激活,OnEnable 会在 Awake 和 Start 之后(对于脚本而言)或之前(对于某些内置组件)被调用, // 具体取决于脚本执行顺序和组件类型。通常在 Awake 之后,Start 之前或之后。 // 严格来说,是 Awake -> OnEnable -> Start } void OnDisable() { // 当组件被禁用时调用。 // 这也包括其所在的 GameObject 从激活状态变为非激活状态时。 // 不会触发 OnDestroy()。 } ``` ### 3.2 GameObject 相关的生命周期影响: - 调用 `gameObject.SetActive(true)`: - 会触发该 GameObject 及其所有激活的子物体上所有激活组件的 `OnEnable()` 方法。 - 如果该 GameObject 是首次被激活 (之前从未激活过或被实例化后首次激活),还会先调用其上所有组件的 `Awake()`,然后是 `OnEnable()`,接着是 `Start()`。 - 调用 `gameObject.SetActive(false)`: - 会触发该 GameObject 及其所有激活的子物体上所有激活组件的 `OnDisable()` 方法。 - **不会**触发 `OnDestroy()`。 --- # 八. MonoBehaviour 中的重要内容和 GameObject 的重要内容 (Key Aspects of MonoBehaviour and GameObject) - (内容待补充)