2.3 Unity API 常用类 (GameObject, Transform, Component, Time) 二、C# 脚本编程 2.3 Unity API 常用类 (GameObject, Transform, Component, Time) 在 Unity3D 游戏开发中,C# 脚本扮演着至关重要的角色,它是连接游戏逻辑与 Unity 引擎的桥梁。而要高效地进行 C# 脚本编程,深入理解 Unity 提供的应用程序编程接口 (API) 是必不可少的。本章节将聚焦于 Unity API 中最常用且核心的四个类: 、 、 和 。掌握这四个类,你将能够构建出复杂的游戏世界,控制游戏对象的行为,并实现各种游戏机制。 2.3.
在 Unity3D 游戏开发中,C# 脚本扮演着至关重要的角色,它是连接游戏逻辑与 Unity 引擎的桥梁。而要高效地进行 C# 脚本编程,深入理解 Unity 提供的应用程序编程接口 (API) 是必不可少的。本章节将聚焦于 Unity API 中最常用且核心的四个类:GameObject、Transform、Component 和 Time。掌握这四个类,你将能够构建出复杂的游戏世界,控制游戏对象的行为,并实现各种游戏机制。
GameObject 是 Unity 场景中最基础、也是最重要的类之一。你可以将它视为游戏世界中的实体或对象。无论是角色、道具、场景物件,甚至是空物体,都是 GameObject 的实例。理解 GameObject 的概念,是理解 Unity 场景结构和对象组织方式的关键。
场景中的实体: GameObject 代表着场景中存在的任何物体,它可以是看得见的模型、听得见的声音源,也可以是仅仅用于逻辑控制的空对象。
容器: GameObject 本身并不具备特定的功能,它的强大之处在于它可以作为 组件 (Component) 的容器。通过附加不同的组件,GameObject 可以拥有各种各样的行为和属性。
层级结构: GameObject 可以通过父子关系构建场景的层级结构。这种层级结构对于组织和管理场景中的对象,以及实现对象间的相对运动至关重要。
标识符: 每个 GameObject 都有一个名称 (Name)、标签 (Tag) 和层 (Layer),用于在脚本中识别和管理不同的游戏对象。
生命周期管理: GameObject 的创建、激活、禁用和销毁都受到 Unity 引擎的管理,了解其生命周期对于编写高效且健壮的游戏逻辑至关重要。
在 C# 脚本中,我们可以通过 GameObject 类提供的 API 来操作和管理游戏对象。以下是一些常用的 API 及其详细说明和代码示例:
1. 名称 (Name)
name 属性 (string): 获取或设置 GameObject 的名称。名称主要用于在编辑器和脚本中识别对象,不会影响游戏运行逻辑。// 获取当前 GameObject 的名称 string objectName = gameObject.name; Debug.Log("当前游戏对象的名称是: " + objectName); // 设置当前 GameObject 的名称 gameObject.name = "PlayerCharacter"; Debug.Log("游戏对象名称已更改为: " + gameObject.name);
2. 标签 (Tag)
tag 属性 (string): 获取或设置 GameObject 的标签。标签是一种轻量级的标识符,用于在运行时快速识别一类游戏对象。Unity 预定义了一些常用标签,你也可以自定义标签。// 获取当前 GameObject 的标签 string objectTag = gameObject.tag; Debug.Log("当前游戏对象的标签是: " + objectTag); // 设置当前 GameObject 的标签为 "Enemy" gameObject.tag = "Enemy"; // 检查 GameObject 是否拥有特定标签 if (gameObject.CompareTag("Player")) { Debug.Log("这是一个玩家对象"); } else if (gameObject.CompareTag("Enemy")) { Debug.Log("这是一个敌人对象"); }
3. 层 (Layer)
layer 属性 (int): 获取或设置 GameObject 所属的层。层主要用于控制渲染顺序、碰撞检测以及光照效果。Unity 提供了 32 个层,可以根据项目需求进行配置。// 获取当前 GameObject 的层级索引 int objectLayerIndex = gameObject.layer; Debug.Log("当前游戏对象的层级索引是: " + objectLayerIndex); // 获取当前 GameObject 的层级名称 string objectLayerName = LayerMask.LayerToName(gameObject.layer); Debug.Log("当前游戏对象的层级名称是: " + objectLayerName); // 设置当前 GameObject 的层级为 "Ignore Raycast" 层 (假设该层索引为 2) gameObject.layer = LayerMask.NameToLayer("Ignore Raycast"); // 使用 LayerMask 进行层级检测 int layerMask = 1 << LayerMask.NameToLayer("Ground"); // 创建一个只包含 "Ground" 层的 LayerMask if ((layerMask & gameObject.layer) != 0) { Debug.Log("游戏对象在 Ground 层"); }
4. 激活状态 (Active State)
activeSelf 属性 (bool): 获取或设置 GameObject 自身是否激活。即使父对象未激活,activeSelf 为 true 的对象也可能不显示或不参与游戏逻辑。
activeInHierarchy 属性 (bool): 获取 GameObject 在层级结构中是否处于激活状态。只有当自身和所有父对象都激活时,activeInHierarchy 才为 true。
// 检查 GameObject 自身是否激活 if (gameObject.activeSelf) { Debug.Log("GameObject 自身处于激活状态"); } else { Debug.Log("GameObject 自身处于禁用状态"); } // 检查 GameObject 在层级结构中是否激活 if (gameObject.activeInHierarchy) { Debug.Log("GameObject 在层级结构中处于激活状态"); } else { Debug.Log("GameObject 在层级结构中处于禁用状态"); } // 激活 GameObject gameObject.SetActive(true); // 禁用 GameObject gameObject.SetActive(false);
5. 组件操作
GetComponent<T>() 方法: 获取附加到 GameObject 的指定类型的组件。如果不存在该组件,则返回 null。// 获取 Rigidbody 组件 Rigidbody rb = GetComponent<Rigidbody>(); if (rb != null) { Debug.Log("找到了 Rigidbody 组件"); // 可以通过 rb 对象访问 Rigidbody 组件的属性和方法 rb.useGravity = false; // 关闭重力 } else { Debug.Log("未找到 Rigidbody 组件"); } // 获取 Renderer 组件 Renderer renderer = GetComponent<Renderer>(); if (renderer != null) { Debug.Log("找到了 Renderer 组件"); renderer.material.color = Color.red; // 将材质颜色设置为红色 }
AddComponent<T>() 方法: 动态地为 GameObject 添加指定类型的组件。返回新添加的组件实例。// 添加 Rigidbody 组件 Rigidbody newRigidbody = gameObject.AddComponent<Rigidbody>(); Debug.Log("成功添加 Rigidbody 组件"); newRigidbody.mass = 10f; // 设置刚体的质量 // 添加 自定义脚本组件 (假设脚本名为 "MyScript") MyScript myScript = gameObject.AddComponent<MyScript>(); Debug.Log("成功添加 MyScript 组件"); myScript.speed = 5f; // 设置 MyScript 组件中的 speed 变量
GetComponents<T>() 方法: 获取附加到 GameObject 的所有指定类型的组件,返回一个组件数组。// 获取所有 Renderer 组件 Renderer[] renderers = GetComponents<Renderer>(); Debug.Log("找到 " + renderers.Length + " 个 Renderer 组件"); foreach (Renderer r in renderers) { r.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off; // 关闭所有 Renderer 组件的阴影投射 }
Destroy(Component component) 和 Destroy(GameObject targetObject) 方法: 销毁指定的组件或 GameObject。需要注意的是,Destroy 方法只是标记对象为销毁,实际销毁会延迟到帧末尾。可以使用 DestroyImmediate 方法立即销毁,但通常不推荐,因为它可能导致性能问题。// 获取 Rigidbody 组件 Rigidbody rbToDestroy = GetComponent<Rigidbody>(); if (rbToDestroy != null) { Destroy(rbToDestroy); // 销毁 Rigidbody 组件 Debug.Log("Rigidbody 组件已被标记为销毁"); } // 销毁当前 GameObject Destroy(gameObject); Debug.Log("GameObject 已被标记为销毁");
6. 查找 GameObject
FindGameObjectWithTag(string tag) 静态方法: 在场景中查找第一个拥有指定标签的 GameObject。如果找到,返回该 GameObject;否则返回 null。// 查找标签为 "Player" 的 GameObject GameObject playerObject = GameObject.FindGameObjectWithTag("Player"); if (playerObject != null) { Debug.Log("找到了玩家对象: " + playerObject.name); // 可以对 playerObject 进行操作 } else { Debug.LogError("未找到标签为 'Player' 的游戏对象"); }
Find(string name) 静态方法: 在场景中按名称查找 GameObject (包括层级结构中的子对象)。 搜索是广度优先的,效率可能较低,不推荐频繁使用,尤其是在性能敏感的代码中。// 查找名为 "Enemy_01" 的 GameObject GameObject enemyObject = GameObject.Find("Enemy_01"); if (enemyObject != null) { Debug.Log("找到了敌人对象: " + enemyObject.name); } else { Debug.LogError("未找到名为 'Enemy_01' 的游戏对象"); }
using UnityEngine; public class GameObjectExample : MonoBehaviour { void Start() { // 创建一个新的空 GameObject GameObject newGameObject = new GameObject("MyNewObject"); // 设置新 GameObject 的标签和层级 newGameObject.tag = "InteractiveObject"; newGameObject.layer = LayerMask.NameToLayer("Default"); // 获取新 GameObject 的 Transform 组件 Transform newTransform = newGameObject.transform; // 设置新 GameObject 的位置 newTransform.position = new Vector3(0, 1, 0); // 为新 GameObject 添加一个 MeshRenderer 组件,使其可见 (需要手动设置 Mesh 和 Material) MeshRenderer meshRenderer = newGameObject.AddComponent<MeshRenderer>(); // 为新 GameObject 添加一个自定义脚本组件 (假设脚本名为 "ObjectBehavior") ObjectBehavior behaviorScript = newGameObject.AddComponent<ObjectBehavior>(); behaviorScript.speed = 2.5f; // 设置脚本组件中的 speed 变量 Debug.Log("成功创建并配置新的 GameObject: " + newGameObject.name); } } // 假设的自定义脚本组件 public class ObjectBehavior : MonoBehaviour { public float speed = 1f; void Update() { // 在每一帧中移动 GameObject transform.Translate(Vector3.forward * speed * Time.deltaTime); } }
GameObject 与 Transform: 每个 GameObject 都有一个关联的 Transform 组件,用于控制其在场景中的位置、旋转和缩放。Transform 是 GameObject 的核心组成部分,负责空间变换。
GameObject 与 Component: GameObject 可以包含多个 Component 组件。组件赋予 GameObject 各种功能和行为。Transform 本身也是一种组件。
Component 与 MonoBehaviour: MonoBehaviour 是 Unity 中所有脚本组件的基类。我们自定义的脚本通常都继承自 MonoBehaviour。因此,自定义脚本也是一种 Component。
Transform 组件是 Unity 中用于控制 GameObject 在 3D 空间中位置、旋转和缩放的关键组件。每个 GameObject 都有且只有一个 Transform 组件。理解 Transform 的工作原理和 API,是实现对象移动、旋转、缩放以及构建层级结构的基础。
位置 (Position): 定义 GameObject 在世界坐标系中的位置。
旋转 (Rotation): 定义 GameObject 在世界坐标系中的旋转角度。可以使用欧拉角 (Euler angles) 或四元数 (Quaternion) 表示。
缩放 (Scale): 定义 GameObject 在局部坐标系中的缩放比例。
父子关系 (Parent-Child Hierarchy): 通过 Transform 组件可以建立 GameObject 之间的父子关系,子对象的变换会受到父对象的影响。
局部坐标系和世界坐标系: Transform 组件提供了局部坐标系和世界坐标系的概念,理解两者之间的区别对于精确控制对象变换至关重要。
1. 位置 (Position)
position 属性 (Vector3): 获取或设置 GameObject 在 世界坐标系 中的位置。// 获取当前 GameObject 的世界坐标位置 Vector3 worldPosition = transform.position; Debug.Log("世界坐标位置: " + worldPosition); // 设置当前 GameObject 的世界坐标位置 transform.position = new Vector3(5, 0, 5);
localPosition 属性 (Vector3): 获取或设置 GameObject 在 局部坐标系 (相对于父对象) 中的位置。如果没有父对象,则局部坐标系与世界坐标系重合。// 获取当前 GameObject 的局部坐标位置 Vector3 localPosition = transform.localPosition; Debug.Log("局部坐标位置: " + localPosition); // 设置当前 GameObject 的局部坐标位置 transform.localPosition = new Vector3(1, 2, 0);
2. 旋转 (Rotation)
rotation 属性 (Quaternion): 获取或设置 GameObject 在 世界坐标系 中的旋转,使用 四元数 (Quaternion) 表示。 四元数是一种更高效且避免万向节锁的旋转表示方法。// 获取当前 GameObject 的世界坐标旋转 (四元数) Quaternion worldRotationQuaternion = transform.rotation; Debug.Log("世界坐标旋转 (四元数): " + worldRotationQuaternion); // 设置当前 GameObject 的世界坐标旋转 (四元数) transform.rotation = Quaternion.Euler(0, 45, 0); // 使用欧拉角转换为四元数
localRotation 属性 (Quaternion): 获取或设置 GameObject 在 局部坐标系 (相对于父对象) 中的旋转,使用 四元数 (Quaternion) 表示。// 获取当前 GameObject 的局部坐标旋转 (四元数) Quaternion localRotationQuaternion = transform.localRotation; Debug.Log("局部坐标旋转 (四元数): " + localRotationQuaternion); // 设置当前 GameObject 的局部坐标旋转 (四元数) transform.localRotation = Quaternion.Euler(30, 0, 0);
eulerAngles 属性 (Vector3): 获取或设置 GameObject 在 世界坐标系 中的旋转,使用 欧拉角 (Euler angles - X, Y, Z 轴旋转角度) 表示。 欧拉角更容易理解,但在进行复杂旋转时可能出现万向节锁问题。// 获取当前 GameObject 的世界坐标旋转 (欧拉角) Vector3 worldRotationEuler = transform.eulerAngles; Debug.Log("世界坐标旋转 (欧拉角): " + worldRotationEuler); // 设置当前 GameObject 的世界坐标旋转 (欧拉角) transform.eulerAngles = new Vector3(0, 90, 0); // 绕 Y 轴旋转 90 度
localEulerAngles 属性 (Vector3): 获取或设置 GameObject 在 局部坐标系 中的旋转,使用 欧拉角 (Euler angles) 表示。// 获取当前 GameObject 的局部坐标旋转 (欧拉角) Vector3 localRotationEuler = transform.localEulerAngles; Debug.Log("局部坐标旋转 (欧拉角): " + localRotationEuler); // 设置当前 GameObject 的局部坐标旋转 (欧拉角) transform.localEulerAngles = new Vector3(0, 0, 45); // 绕 Z 轴旋转 45 度
3. 缩放 (Scale)
localScale 属性 (Vector3): 获取或设置 GameObject 在 局部坐标系 中的缩放比例。// 获取当前 GameObject 的局部缩放 Vector3 localScale = transform.localScale; Debug.Log("局部缩放: " + localScale); // 设置当前 GameObject 的局部缩放 transform.localScale = new Vector3(2, 2, 2); // 放大两倍
4. 父子关系 (Parenting)
parent 属性 (Transform): 获取或设置 GameObject 的父 Transform 组件。设置为 null 表示取消父对象。// 获取当前 GameObject 的父 Transform Transform parentTransform = transform.parent; if (parentTransform != null) { Debug.Log("父对象名称: " + parentTransform.gameObject.name); } else { Debug.Log("没有父对象"); } // 设置当前 GameObject 的父对象 (假设 parentObject 是一个 GameObject) Transform newParentTransform = parentObject.transform; transform.parent = newParentTransform; Debug.Log("已设置父对象为: " + parentObject.name); // 取消父对象 transform.parent = null; Debug.Log("已取消父对象");
SetParent(Transform parent) 方法: 设置 GameObject 的父 Transform 组件。可以选择是否保持世界坐标。// 设置父对象,保持世界坐标 transform.SetParent(newParentTransform, true); // 第二个参数为 true 表示保持世界坐标 // 设置父对象,不保持世界坐标 (默认行为) transform.SetParent(newParentTransform); // 等同于 transform.SetParent(newParentTransform, false);
5. 移动、旋转和缩放方法
Translate(Vector3 translation, Space relativeTo = Space.Self) 方法: 在指定的坐标空间中移动 GameObject。Space.Self 表示局部坐标系,Space.World 表示世界坐标系。// 在局部坐标系中向前移动 1 个单位 transform.Translate(Vector3.forward * Time.deltaTime); // 帧率独立移动 // 在世界坐标系中向右移动 0.5 个单位 transform.Translate(Vector3.right * 0.5f, Space.World);
Rotate(Vector3 eulerAngles, Space relativeTo = Space.Self) 方法: 在指定的坐标空间中旋转 GameObject,使用欧拉角表示旋转角度。// 在局部坐标系中绕 Y 轴旋转 1 度 transform.Rotate(Vector3.up * 1f * Time.deltaTime); // 在世界坐标系中绕 Z 轴旋转 45 度 transform.Rotate(Vector3.forward * 45f, Space.World);
LookAt(Transform target, Vector3? worldUp = null) 方法: 使 GameObject 的 Z 轴指向目标对象。可以指定世界坐标系中的 Up 向量。// 使当前 GameObject 指向目标对象 (targetTransform) transform.LookAt(targetTransform); // 使当前 GameObject 指向目标位置 (targetPosition),并指定世界 Up 向量为 Vector3.up transform.LookAt(targetPosition, Vector3.up);
using UnityEngine; public class TransformExample : MonoBehaviour { public Transform targetObject; // 在 Inspector 面板中拖拽目标对象 public float moveSpeed = 5f; public float rotateSpeed = 100f; void Update() { // 移动到目标对象 float step = moveSpeed * Time.deltaTime; transform.position = Vector3.MoveTowards(transform.position, targetObject.position, step); // 旋转朝向目标对象 Vector3 targetDirection = targetObject.position - transform.position; Vector3 newDirection = Vector3.RotateTowards(transform.forward, targetDirection, rotateSpeed * Time.deltaTime, 0.0f); transform.rotation = Quaternion.LookRotation(newDirection); // 缩放 (每秒放大 0.1 倍) transform.localScale += Vector3.one * 0.1f * Time.deltaTime; } }
Transform 与 GameObject: Transform 组件依附于 GameObject 存在,是 GameObject 的一部分,负责其空间变换。
Transform 与 Transform (Parenting): Transform 可以拥有一个父 Transform,形成层级结构。子 Transform 的变换会受到父 Transform 的影响。
Transform 与 Vector3/Quaternion: Transform 使用 Vector3 结构体存储位置、欧拉角和缩放信息,使用 Quaternion 结构体存储旋转信息。
Component 是 Unity 中实现 组件化 编程的核心概念。它是所有附加到 GameObject 的功能模块的基类。通过组合不同的组件,我们可以赋予 GameObject 各种各样的行为和属性,例如渲染、物理、动画、音频以及自定义逻辑等。
功能模块化: Component 将复杂的游戏对象分解为多个独立的、可重用的功能模块。
组合性: 通过添加和移除组件,可以灵活地组合和修改 GameObject 的功能。
继承性: Unity 提供的各种内置组件 (如 Renderer, Collider, Rigidbody) 以及我们自定义的脚本组件都继承自 Component 类。
依附于 GameObject: Component 必须依附于 GameObject 才能存在和工作。一个 GameObject 可以拥有多个 Component。
消息传递: Component 可以通过 Unity 的消息系统 (例如 SendMessage, BroadcastMessage) 与其他组件或脚本进行通信。
由于我们自定义的脚本组件通常继承自 MonoBehaviour (而 MonoBehaviour 又继承自 Component),所以这里主要介绍 MonoBehaviour 中常用的 API,这些 API 实际上也是 Component 类提供的 (或扩展的)。
1. GameObject 访问
gameObject 属性 (GameObject): 获取当前组件所依附的 GameObject 实例。// 在组件脚本中获取依附的 GameObject GameObject currentGameObject = gameObject; Debug.Log("当前组件依附的 GameObject 名称: " + currentGameObject.name);
2. Transform 访问
transform 属性 (Transform): 获取当前组件所依附的 GameObject 的 Transform 组件。这是一个常用的快捷方式,因为大多数组件都需要访问 Transform 来进行位置、旋转和缩放操作。// 在组件脚本中获取依附 GameObject 的 Transform 组件 Transform currentTransform = transform; Debug.Log("当前组件依附 GameObject 的 Transform 组件位置: " + currentTransform.position);
3. 组件启用/禁用
enabled 属性 (bool): 获取或设置组件是否启用。禁用组件会使其停止执行 Update, FixedUpdate, LateUpdate 等生命周期函数,但组件仍然存在于 GameObject 上。// 检查组件是否启用 if (enabled) { Debug.Log("组件已启用"); } else { Debug.Log("组件已禁用"); } // 禁用当前组件 enabled = false; // 启用当前组件 enabled = true;
4. 获取其他组件
GetComponent<T>(), GetComponents<T>(), GetComponentInChildren<T>(), GetComponentInParent<T>() 方法: 这些方法与 GameObject 类中同名的方法功能相同,用于在当前组件所依附的 GameObject 或其子对象/父对象上查找其他组件。// 在当前 GameObject 上获取 Rigidbody 组件 Rigidbody rb = GetComponent<Rigidbody>(); // 在当前 GameObject 的所有子对象中获取 Renderer 组件 Renderer[] childRenderers = GetComponentsInChildren<Renderer>(); // 在当前 GameObject 的父对象中获取 AudioSource 组件 AudioSource parentAudioSource = GetComponentInParent<AudioSource>();
5. 组件类型信息
GetType() 方法: 获取当前组件的运行时类型。// 获取当前组件的类型 System.Type componentType = GetType(); Debug.Log("当前组件的类型是: " + componentType.FullName); // 输出完整的类型名称 (包括命名空间) Debug.Log("当前组件的类型名称是: " + componentType.Name); // 输出类型名称 (不包括命名空间)
using UnityEngine; public class ComponentExample : MonoBehaviour { void Start() { // 获取当前 GameObject 的 Renderer 组件 Renderer renderer = GetComponent<Renderer>(); if (renderer != null) { // 禁用 Renderer 组件 renderer.enabled = false; Debug.Log("Renderer 组件已禁用"); } // 获取当前 GameObject 的 Transform 组件 Transform currentTransform = transform; Debug.Log("当前 GameObject 的位置: " + currentTransform.position); // 添加一个新的 Light 组件 Light newLight = gameObject.AddComponent<Light>(); newLight.type = LightType.Point; newLight.color = Color.yellow; Debug.Log("成功添加 Point Light 组件"); } void Update() { // 每帧旋转当前 GameObject transform.Rotate(Vector3.up * 30f * Time.deltaTime); } }
Component 与 GameObject: Component 必须依附于 GameObject,通过 gameObject 属性可以访问到依附的 GameObject。
Component 与 MonoBehaviour: 自定义脚本组件通常继承自 MonoBehaviour,MonoBehaviour 本身也是一种 Component。
Component 与其他内置组件: Unity 提供了各种内置组件,例如 Renderer (用于渲染),Collider (用于碰撞检测),Rigidbody (用于物理模拟),AudioSource (用于音频播放),Light (用于灯光) 等,它们都继承自 Component。
Component 与 Transform: Component 可以通过 gameObject.transform 访问到依附 GameObject 的 Transform 组件。
Time 类是一个静态类,提供了访问时间相关信息的接口,用于实现帧率独立的游戏逻辑、控制游戏时间流速以及进行各种时间相关的操作。理解 Time 类对于编写流畅且稳定的游戏至关重要。
帧率独立性: Time 类提供的 deltaTime 属性可以帮助我们实现帧率独立的游戏逻辑,确保游戏在不同帧率下运行速度一致。
游戏时间控制: Time 类可以获取游戏运行时间、帧数等信息,也可以控制游戏时间缩放,实现慢动作、快进等效果。
物理更新控制: Time 类提供了与物理引擎更新相关的属性,例如 fixedDeltaTime,用于控制物理模拟的步长。
性能优化: 合理使用 Time 类可以帮助我们优化游戏性能,例如避免在每一帧中进行不必要的时间计算。
1. 帧率相关
deltaTime 属性 (float): 自上一帧完成到现在所用的时间 (以秒为单位)。这是实现帧率独立运动和动画的关键。// 帧率独立移动示例 float speed = 5f; transform.Translate(Vector3.forward * speed * Time.deltaTime); // 无论帧率高低,每秒移动速度都接近 5 个单位 // 帧率独立旋转示例 float rotationSpeed = 90f; // 每秒旋转 90 度 transform.Rotate(Vector3.up * rotationSpeed * Time.deltaTime);