Files
Obsidian_Unity/Unity学习/Unity学习 4.21-4.27.md
T
2026-05-03 14:06:26 +08:00

15 KiB

一. 射线检测 (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<LineRenderer>();
}

常用代码操作:

  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 activeSelfactiveInHierarchy 的区别

  • gameObject.activeSelf: 表示该 GameObject 自身是否被设置为激活状态。如果它被设置为 false,即使其所有父对象都激活,它依然是非激活的。
  • gameObject.activeInHierarchy: 表示该 GameObject 在场景中是否真实处于激活状态。如果它自身 activeSelftrue,但其任何一个父对象 activeSelffalse (导致父对象 activeInHierarchyfalse),那么该 GameObject 的 activeInHierarchy 也会是 false

2. 组件的启用和禁用

2.1 激活组件

C#

// 对于当前脚本组件本身
this.enabled = true;

// 对于其他组件 (例如 Collider),需要先获取其引用
Collider myCollider = this.GetComponent<Collider>();
if (myCollider != null)
{
    myCollider.enabled = true;
}

2.2 禁用组件

C#

// 对于当前脚本组件本身
this.enabled = false;

// 对于其他组件
Collider myCollider = GetComponent<Collider>();
if (myCollider != null)
{
    myCollider.enabled = false;
}

2.3 检查组件的启用状态

C#

// 当前脚本组件的状态
bool isScriptEnabled = this.enabled;

// 其他组件的状态
Collider myCollider = GetComponent<Collider>();
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)

  • (内容待补充)