1.3 场景 (Scene) 与游戏对象 (GameObject)


文档摘要

1.3 场景 (Scene) 与游戏对象 (GameObject) 1.3 场景 (Scene) 与游戏对象 (GameObject) 1.3.1 场景 (Scene):游戏的舞台 想象一下舞台剧或电影拍摄。不同的场景代表着故事发生的地点和时间的变化。在 Unity 中,场景 (Scene) 就如同一个独立的舞台或关卡。它包含了游戏世界中所有可视的内容和互动元素,例如角色、环境、道具、灯光、摄像机等等。 场景的主要作用: 组织游戏内容: 将游戏世界划分为逻辑上的独立区域。例如,你可以为游戏的每个关卡、主菜单、设置界面等分别创建不同的场景。 资源管理: 场景只加载当前关卡或区域所需的资源,有助于优化内存使用和加载时间。 协同开发: 多个开发者可以同时编辑不同的场景,提高开发效率。

1.3 场景 (Scene) 与游戏对象 (GameObject)

1.3 场景 (Scene) 与游戏对象 (GameObject)

1.3.1 场景 (Scene):游戏的舞台

想象一下舞台剧或电影拍摄。不同的场景代表着故事发生的地点和时间的变化。在 Unity 中,场景 (Scene) 就如同一个独立的舞台或关卡。它包含了游戏世界中所有可视的内容和互动元素,例如角色、环境、道具、灯光、摄像机等等。

场景的主要作用:

  • 组织游戏内容: 将游戏世界划分为逻辑上的独立区域。例如,你可以为游戏的每个关卡、主菜单、设置界面等分别创建不同的场景。

  • 资源管理: 场景只加载当前关卡或区域所需的资源,有助于优化内存使用和加载时间。

  • 协同开发: 多个开发者可以同时编辑不同的场景,提高开发效率。

  • 游戏流程控制: 通过场景的切换,可以控制游戏的流程和进度。

场景的构成:

一个场景本质上是一个文件,通常以 .unity 为扩展名。当你打开一个场景文件时,Unity 编辑器会加载该场景中包含的所有游戏对象、资源和设置。场景文件存储了以下关键信息:

  • 游戏对象 (GameObject) 及其层级关系: 场景中包含的所有游戏对象,以及它们之间的父子关系。

  • 组件 (Component) 数据: 每个游戏对象上附加的组件及其属性值。

  • 资源引用: 场景中使用的资源,例如材质、纹理、模型、音频剪辑等。

  • 场景设置: 例如,灯光设置、物理设置、环境光照等。

场景管理 (Scene Management):

Unity 提供了 SceneManager 类来管理场景的加载、卸载和切换。常用的场景管理操作包括:

  • 创建场景: 在 Unity 编辑器中,可以通过 "File" -> "New Scene" 创建一个新的场景。

  • 保存场景: 使用 "File" -> "Save Scene" 或 "Save Scene As..." 保存当前场景。

  • 打开场景: 双击项目窗口中的场景文件,或使用 SceneManager.LoadScene() 在运行时加载场景。

  • 切换场景: 使用 SceneManager.LoadScene() 方法加载新的场景,从而实现场景的切换。

  • 异步加载场景: 使用 SceneManager.LoadSceneAsync() 可以异步加载场景,避免加载过程中游戏卡顿。

  • 获取当前场景: 使用 SceneManager.GetActiveScene() 获取当前激活的场景。

代码实践 1:场景加载与切换

以下代码示例演示了如何使用 SceneManager 加载和切换场景。你需要创建一个新的 C# 脚本,例如命名为 SceneLoader.cs,并将其附加到一个场景中的任意游戏对象上(例如,创建一个空的 GameObject 并附加脚本)。

using UnityEngine; using UnityEngine.SceneManagement; public class SceneLoader : MonoBehaviour { public void LoadSceneByName(string sceneName) { SceneManager.LoadScene(sceneName); Debug.Log("加载场景: " + sceneName); } public void LoadSceneByIndex(int sceneIndex) { SceneManager.LoadScene(sceneIndex); Debug.Log("加载场景索引: " + sceneIndex); } public void LoadSceneAsyncByName(string sceneName) { StartCoroutine(LoadSceneAsyncCoroutine(sceneName)); } System.Collections.IEnumerator LoadSceneAsyncCoroutine(string sceneName) { AsyncOperation asyncLoad = SceneManager.LoadSceneAsync(sceneName); while (!asyncLoad.isDone) { float progress = Mathf.Clamp01(asyncLoad.progress / 0.9f); // progress 范围 0-0.9 Debug.Log("异步加载进度: " + (progress * 100) + "%"); yield return null; // 等待下一帧 } Debug.Log("异步加载场景完成: " + sceneName); } public void ReloadCurrentScene() { SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex); Debug.Log("重新加载当前场景"); } public void QuitGame() { #if UNITY_EDITOR UnityEditor.EditorApplication.isPlaying = false; // 在编辑器中停止运行 #else Application.Quit(); // 打包后退出应用 #endif Debug.Log("退出游戏"); } }

代码详解:

  • using UnityEngine.SceneManagement;: 引入场景管理相关的命名空间。

  • SceneManager.LoadScene(string sceneName): 根据场景名称加载场景。场景名称需要在 "Build Settings" 中添加到场景列表。

  • SceneManager.LoadScene(int sceneIndex): 根据场景索引加载场景。场景索引是场景在 "Build Settings" 场景列表中的序号。

  • SceneManager.LoadSceneAsync(string sceneName): 异步加载场景,返回一个 AsyncOperation 对象,可以用来监测加载进度。

  • StartCoroutine(LoadSceneAsyncCoroutine(sceneName)): 使用协程来处理异步加载,以便在加载过程中更新进度信息。

  • asyncLoad.progress: 获取异步加载的进度,范围是 0 到 0.9。需要除以 0.9 才能得到 0 到 1 的完整进度。

  • yield return null: 在协程中暂停执行,等待下一帧。

  • SceneManager.GetActiveScene(): 获取当前激活的场景。

  • SceneManager.GetActiveScene().buildIndex: 获取当前激活场景在 "Build Settings" 中的索引。

  • ReloadCurrentScene(): 重新加载当前场景,常用于游戏重启或重置关卡。

  • QuitGame(): 退出游戏。需要区分编辑器环境和打包后的环境。

在 Unity 编辑器中使用脚本:

  1. 创建一个新的 Unity 项目。

  2. 创建至少两个场景,例如 "SceneA" 和 "SceneB",并保存。

  3. 打开 "Build Settings" (File -> Build Settings...),将 "SceneA" 和 "SceneB" 拖拽到 "Scenes In Build" 列表中。确保场景被添加到列表中,否则无法通过名称或索引加载。

  4. 在 "SceneA" 中创建一个空的 GameObject,并附加 SceneLoader.cs 脚本。

  5. 在 Inspector 窗口中,你会看到 SceneLoader 组件。

  6. 在场景中创建一些 UI 按钮,例如 "Load SceneB", "Load SceneA Async", "Reload Scene", "Quit"。

  7. 为每个按钮的 "OnClick()" 事件添加 SceneLoader 组件上的相应函数,并根据需要设置场景名称或索引参数。例如,"Load SceneB" 按钮的 "OnClick()" 事件调用 LoadSceneByName("SceneB")

  8. 运行游戏,点击按钮测试场景加载和切换功能。在 Console 窗口中查看 Debug.Log 输出的信息。

Mermaid 图 1:场景结构

图表解释:

  • Scene: 代表一个场景,是游戏世界的容器。

  • GameObjects: 场景中包含多个游戏对象。

  • Components: 每个游戏对象可以拥有多个组件。

  • Scene Hierarchy: 场景层级关系组织和管理场景中的游戏对象。

  • 图表展示了场景、游戏对象和组件之间的包含关系,以及场景层级关系对游戏对象的组织作用。

1.3.2 游戏对象 (GameObject):万物皆对象

在 Unity 中,游戏对象 (GameObject) 是所有实体 (Entities) 的基本单元。无论是角色、道具、特效、UI 元素,甚至是摄像机和灯光,都以游戏对象的形式存在于场景中。你可以将游戏对象视为场景中的一个“容器”或“实体”,它本身是空的,没有任何实际功能。游戏对象的功能完全由附加在其上的组件 (Component) 决定。

游戏对象的主要作用:

  • 组织场景内容: 作为场景中所有元素的容器,用于组织和管理场景中的各种实体。

  • 组件的载体: 承载各种组件,通过组件赋予游戏对象不同的功能和行为。

  • 层级关系的构建: 通过父子关系构建场景的层级结构,方便管理和控制复杂的游戏元素。

  • 脚本控制: 可以通过脚本控制游戏对象的行为和属性,实现游戏的逻辑和互动。

游戏对象的构成:

一个游戏对象主要由以下部分组成:

  • 名称 (Name): 游戏对象在编辑器中显示的名称,用于标识和查找游戏对象。

  • 标签 (Tag): 用于对游戏对象进行分类和标记,方便在脚本中通过标签查找游戏对象。

  • 层 (Layer): 用于控制游戏对象的渲染顺序和碰撞检测,实现分层渲染和碰撞过滤。

  • Transform 组件 (Transform Component): 每个游戏对象都默认拥有一个 Transform 组件,用于控制游戏对象在世界空间中的位置、旋转和缩放。

  • 其他组件 (Components): 根据需要,可以向游戏对象添加各种组件,例如 Mesh Filter、Mesh Renderer、Collider、Rigidbody、Audio Source、脚本组件等等,赋予游戏对象不同的功能。

游戏对象的操作:

在 Unity 编辑器中,你可以通过以下方式操作游戏对象:

  • 创建游戏对象: 在 Hierarchy 窗口中,点击 "+" 按钮,选择 "Create" -> "Empty" 创建一个空的游戏对象,或者选择其他预设类型创建特定类型的游戏对象(例如 3D Object, UI, Light 等)。

  • 选择游戏对象: 在 Hierarchy 窗口或 Scene 视图中点击游戏对象,选中游戏对象。

  • 重命名游戏对象: 双击 Hierarchy 窗口中的游戏对象名称,或者在 Inspector 窗口中修改 "Name" 属性。

  • 移动、旋转、缩放游戏对象: 使用 Scene 视图中的移动、旋转、缩放工具,或者在 Inspector 窗口中修改 Transform 组件的 Position, Rotation, Scale 属性。

  • 添加组件: 在 Inspector 窗口中,点击 "Add Component" 按钮,选择要添加的组件类型。

  • 删除组件: 在 Inspector 窗口中,点击组件右上角的齿轮图标,选择 "Remove Component"。

  • 激活/禁用游戏对象: 在 Inspector 窗口中,勾选/取消勾选游戏对象名称左侧的复选框,或者在脚本中使用 gameObject.SetActive(bool) 方法。

  • 设置父子关系: 在 Hierarchy 窗口中,将一个游戏对象拖拽到另一个游戏对象下方,建立父子关系。

代码实践 2:游戏对象创建、组件操作与层级关系

以下代码示例演示了如何使用脚本创建游戏对象,添加和访问组件,以及设置父子关系。你需要创建一个新的 C# 脚本,例如命名为 GameObjectManipulator.cs,并将其附加到一个场景中的任意游戏对象上。

using UnityEngine; public class GameObjectManipulator : MonoBehaviour { void Start() { // 1. 创建一个空的游戏对象 GameObject emptyGameObject = new GameObject("MyEmptyGameObject"); Debug.Log("创建空游戏对象: " + emptyGameObject.name); // 2. 创建一个带有 MeshRenderer 的立方体游戏对象 GameObject cubeGameObject = GameObject.CreatePrimitive(PrimitiveType.Cube); cubeGameObject.name = "MyCubeGameObject"; Debug.Log("创建立方体游戏对象: " + cubeGameObject.name); // 3. 获取游戏对象的 Transform 组件 Transform cubeTransform = cubeGameObject.GetComponent<Transform>(); if (cubeTransform != null) { cubeTransform.position = new Vector3(2, 0, 0); // 设置位置 Debug.Log("立方体位置设置为: " + cubeTransform.position); } // 4. 添加自定义组件 (假设已创建 MyCustomComponent.cs 脚本) MyCustomComponent customComponent = cubeGameObject.AddComponent<MyCustomComponent>(); if (customComponent != null) { customComponent.message = "Hello from MyCustomComponent!"; Debug.Log("为立方体添加自定义组件 MyCustomComponent"); } // 5. 获取并访问自定义组件 MyCustomComponent retrievedComponent = cubeGameObject.GetComponent<MyCustomComponent>(); if (retrievedComponent != null) { Debug.Log("从立方体获取自定义组件,消息: " + retrievedComponent.message); } // 6. 设置父子关系 cubeGameObject.transform.SetParent(emptyGameObject.transform); // 将立方体设置为空对象的子对象 Debug.Log("将立方体设置为空对象的子对象"); // 7. 激活/禁用游戏对象 cubeGameObject.SetActive(false); // 禁用立方体 Debug.Log("禁用立方体"); cubeGameObject.SetActive(true); // 重新激活立方体 Debug.Log("重新激活立方体"); // 8. 通过标签查找游戏对象 (假设立方体已添加 "MyCubeTag" 标签) GameObject taggedCube = GameObject.FindGameObjectWithTag("MyCubeTag"); if (taggedCube != null) { Debug.Log("通过标签找到游戏对象: " + taggedCube.name); } else { Debug.LogWarning("未找到标签为 MyCubeTag 的游戏对象"); } } }

代码详解:

  • GameObject emptyGameObject = new GameObject("MyEmptyGameObject"): 创建一个新的空游戏对象,并设置名称。

  • GameObject cubeGameObject = GameObject.CreatePrimitive(PrimitiveType.Cube): 使用 GameObject.CreatePrimitive() 创建一个预设的立方体游戏对象。Unity 提供了多种预设的 PrimitiveType,例如 Cube, Sphere, Capsule, Plane 等。

  • cubeGameObject.GetComponent<Transform>(): 获取游戏对象上的 Transform 组件。GetComponent<T>() 方法用于获取指定类型的组件。

  • cubeTransform.position = new Vector3(2, 0, 0): 修改 Transform 组件的 position 属性,设置游戏对象的位置。

  • cubeGameObject.AddComponent<MyCustomComponent>(): 向游戏对象添加一个自定义组件 MyCustomComponent。你需要先创建 MyCustomComponent.cs 脚本并定义该组件。

  • cubeGameObject.GetComponent<MyCustomComponent>(): 获取游戏对象上的 MyCustomComponent 组件。

  • cubeGameObject.transform.SetParent(emptyGameObject.transform): 设置游戏对象的父对象,建立父子关系。

  • cubeGameObject.SetActive(false)cubeGameObject.SetActive(true): 禁用和激活游戏对象。禁用后的游戏对象将不会被渲染,也不会参与物理和脚本更新。

  • GameObject.FindGameObjectWithTag("MyCubeTag"): 通过标签查找场景中第一个带有指定标签的游戏对象。你需要先在 Unity 编辑器中为立方体游戏对象添加 "MyCubeTag" 标签。

创建自定义组件 MyCustomComponent.cs (示例):

using UnityEngine; public class MyCustomComponent : MonoBehaviour { public string message = "Default Message"; void Start() { Debug.Log("MyCustomComponent Start() called on GameObject: " + gameObject.name + ", Message: " + message); } }

在 Unity 编辑器中使用脚本:

  1. 创建一个新的 C# 脚本 MyCustomComponent.cs,并将上述代码复制粘贴进去。

  2. 在 "SceneA" 或 "SceneB" 中创建一个空的 GameObject,并附加 GameObjectManipulator.cs 脚本。

  3. 在 Hierarchy 窗口中选择 "MyCubeGameObject" (脚本创建的立方体),在 Inspector 窗口中,点击 "Tag" 下拉菜单,选择 "Add Tag...",点击 "+" 按钮创建一个新的 Tag,命名为 "MyCubeTag",保存。然后再次选择 "MyCubeGameObject",将 Tag 设置为 "MyCubeTag"。

  4. 运行游戏,在 Console 窗口中查看 Debug.Log 输出的信息,观察游戏对象在场景中的变化。

Mermaid 图 2:游戏对象结构

图表解释:

  • GameObject: 代表一个游戏对象,是场景中的基本实体。

  • Transform: 每个游戏对象都必须有的 Transform 组件,负责位置、旋转和缩放。

  • Component1ComponentN: 游戏对象可以拥有多个组件,例如 Mesh Renderer, Collider, Script 等。

  • Components: 组件集合,赋予游戏对象各种功能。

  • Functionality: 组件定义了游戏对象的功能和行为。

  • 图表展示了游戏对象由 Transform 组件和多个其他组件构成,组件赋予游戏对象功能。

1.3.3 场景与游戏对象的协同工作

场景和游戏对象是紧密协作的。场景是游戏对象的容器,游戏对象存在于场景中,并共同构建起游戏世界。

  • 场景加载时,场景中包含的所有游戏对象也会被加载。

  • 场景卸载时,场景中包含的所有游戏对象也会被卸载(除非使用了 DontDestroyOnLoad)。

  • 游戏对象之间的层级关系,以及游戏对象上的组件,共同定义了场景的结构和行为。

  • 通过场景的切换,可以实现游戏流程的控制和关卡之间的转换。

跨场景的游戏对象:DontDestroyOnLoad

在场景切换时,默认情况下,当前场景中的所有游戏对象都会被销毁,然后加载新场景中的游戏对象。但有时我们希望某些游戏对象在场景切换后仍然存在,例如游戏管理器、背景音乐播放器等。这时可以使用 DontDestroyOnLoad(gameObject) 方法。

代码实践 3:DontDestroyOnLoad 的使用

创建一个新的 C# 脚本,例如命名为 PersistentObject.cs,并将其附加到一个场景中的游戏对象上。

using UnityEngine; public class PersistentObject : MonoBehaviour { private static PersistentObject instance; void Awake() { if (instance != null && instance != this) { Destroy(gameObject); // 如果已存在实例,销毁自身 return; } instance = this; DontDestroyOnLoad(gameObject); // 场景切换时不销毁 Debug.Log("PersistentObject 已创建,场景切换时不销毁"); } void OnDestroy() { if (instance == this) { Debug.Log("PersistentObject 即将被销毁 (但由于 DontDestroyOnLoad,通常不会发生)"); instance = null; } } }

代码详解:

  • private static PersistentObject instance;: 使用静态变量 instance 来存储单例实例。

  • Awake(): 在游戏对象初始化时调用。

  • if (instance != null && instance != this): 检查是否已存在实例。如果已存在,则销毁自身,确保只有一个实例存在。

  • instance = this;: 将当前实例赋值给静态变量 instance

  • DontDestroyOnLoad(gameObject): 核心方法,将当前游戏对象标记为在场景切换时不销毁。

  • OnDestroy(): 在游戏对象被销毁时调用。通常由于 DontDestroyOnLoad 的存在,这个方法在场景切换时不会被调用。

使用方法:

  1. 创建一个新的场景,例如 "ScenePersistent"。

  2. 在 "ScenePersistent" 中创建一个空的 GameObject,并附加 PersistentObject.cs 脚本。

  3. 创建另一个场景,例如 "SceneTest"。

  4. 在 "ScenePersistent" 或 "SceneTest" 中创建一个按钮,使用之前创建的 SceneLoader.cs 脚本,添加按钮点击事件来加载 "SceneTest" 和 "ScenePersistent" 场景,实现场景切换。

  5. 运行 "ScenePersistent" 场景,点击按钮切换到 "SceneTest" 场景,再切换回 "ScenePersistent" 场景。观察 Console 窗口的输出信息。你会发现 "PersistentObject 已创建,场景切换时不销毁" 只会输出一次,说明 PersistentObject 游戏对象在场景切换后仍然存在。

Mermaid 图 3:场景与游戏对象关系

图表解释:

  • SceneASceneB: 代表不同的场景。

  • GameObjectA1, GameObjectA2, GameObjectB1, GameObjectB2: 代表不同场景中的游戏对象。

  • SceneManager: 场景管理器,负责管理场景的加载、卸载和切换。

  • 图表展示了场景包含游戏对象,场景管理器管理多个场景的关系。

1.3.4 总结

场景 (Scene) 和游戏对象 (GameObject) 是 Unity3D 中最基础也是最重要的概念。

  • 场景 (Scene) 是游戏内容的容器,用于组织和管理游戏世界,实现关卡划分和流程控制。

  • 游戏对象 (GameObject) 是场景中的基本实体,是所有游戏元素的载体,通过附加组件来赋予功能和行为。

  • 场景和游戏对象协同工作,共同构建起丰富多彩的互动游戏世界。

理解和熟练掌握场景和游戏对象的使用,是成为 Unity 开发者的必要条件。通过本章节的学习和实践,相信你已经对这两个核心概念有了深入的理解。在后续的学习中,你将更深入地探索如何利用场景和游戏对象来构建更复杂、更精彩的游戏体验。


发布者: 作者: 转发
评论区 (0)
U