关于 unity-foundations
面向 Unity 初学者和开发者的基础知识技能,整理 GameObject、组件、Transform、场景和 Prefab 等核心概念。还介绍 ScriptableObject、常用 API、实践模式及易犯问题。
主要功能
- 01GameObject 与组件基础
- 02Transform 和父子层级
- 03场景与 Prefab 管理
- 04ScriptableObject 数据容器
- 05标签、图层与 LayerMask
- 06常用 API 和反模式
适用场景
- 01理解 Unity 场景对象的组成
- 02创建和复用 Prefab 配置
- 03用 ScriptableObject 管理共享数据
- 04查找对象及管理场景加载
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ nice-wolf-studio/ unity-claude-skills --skill unity-foundations安装后在 Claude Code 中输入/unity-foundations确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/component-lifecycle.mdgameobject-api.mdprefabs-and-scenes.md详细介绍
Unity 基础知识
核心概念
GameObject
GameObject 是 Unity 中的基本构建单元。场景中的每个对象——角色、道具、景物、摄像机、灯光——都是一个 GameObject。GameObject 是容器:它们无法独立运行,必须借助 组件 才能获得功能。每个 GameObject 都会自动包含一个无法移除的 Transform 组件。
组件
组件是每个 GameObject 的功能部件。Unity 采用 组合优于继承 的架构:通过将多个组件附加到 GameObject 上来构建行为,而不是继承深层类层次结构。每个 GameObject 必须且只能有一个 Transform 组件。其他组件(Rigidbody、Collider、MeshRenderer、自定义 MonoBehaviour)则决定对象的功能。
文档中的限制:
- 组件必须位于与目标 GameObject 相同的项目中
- 组件不能来自其他项目、未附加的脚本或未安装的软件包
Transform
Transform 组件用于存储 位置、旋转 和 缩放——这些值相对于父对象(局部坐标)或世界原点(世界坐标)。文档中的要点:
- 子 Transform 显示相对于父对象的值
- 根 GameObject(没有父对象)显示世界坐标
- 物理引擎假设 1 个单位 = 1 米
- 添加子对象前,将父对象的位置设为
(0,0,0),使局部坐标与全局坐标一致 - 避免在运行时调整 Transform 的 Scale;应让模型资源保持实际尺寸。不均匀缩放会导致 Collider、Light 和 Audio Source 出现问题
场景
场景是包含游戏或应用全部或部分内容的资源。新建场景默认包含一个 Camera 和一个方向光。项目可以使用单个场景,也可以使用多个场景(例如,每个关卡使用一个场景)。场景模板可用作创建新场景的蓝图。
Prefab
Prefab 是可复用的资源模板,用于存储完整的 GameObject 配置(所有组件、属性值和子 GameObject)。主要功能:
- 嵌套 Prefab:在其他 Prefab 中包含 Prefab 实例
- Prefab 变体:创建预定义变体,同时保留与基础 Prefab 的关联
- 覆盖项:修改特定实例上的组件或数据,而不影响模板
- 解包:将 Prefab 实例转换回独立的 GameObject
ScriptableObject
ScriptableObject 是一种可序列化的 Unity 类型,派生自 UnityEngine.Object,可作为独立于 GameObject 的数据容器。与 MonoBehaviour 不同,ScriptableObject 以项目级 .asset 文件的形式存在。主要用途:
- 共享数据容器(通过引用一个资源,而不是在多个 Prefab 中重复数据,从而减少内存占用)
- 编辑器工具的基础(EditorTool、EditorWindow 派生自 ScriptableObject)
- 运行时配置存储
重要:Unity 不会自动保存通过脚本在编辑模式下对 ScriptableObject 所做的更改。修改后必须调用 EditorUtility.SetDirty()。
标签
标签是分配给 GameObject 的引用标识符,供脚本使用。每个 GameObject 只能有一个标签,但多个 GameObject 可以使用同一个标签。内置标签:Untagged、Respawn、Finish、EditorOnly、MainCamera、Player、GameController。
MainCamera:编辑器会缓存这些对象;Camera.main返回第一个有效结果EditorOnly:构建时会销毁带有此标签的 GameObject- 标签创建后无法重命名
图层
图层可将 GameObject 分开,以便进行选择性处理,包括摄像机渲染、灯光、物理碰撞和自定义代码逻辑。Unity 最多支持 32 个图层。LayerMask 用于定义 API 调用会与哪些图层交互。
常见模式
创建和访问组件
using UnityEngine;
public class ComponentAccess : MonoBehaviour
{
void Start()
{
// Get a component on this GameObject
Rigidbody rb = GetComponent<Rigidbody>();
// Get a component on a child GameObject
Collider childCollider = GetComponentInChildren<Collider>();
// Get all components of a type on this and children
MeshRenderer[] renderers = GetComponentsInChildren<MeshRenderer>();
// Add a component at runtime
BoxCollider box = gameObject.AddComponent<BoxCollider>();
// Remove a component (destroys it)
Destroy(box);
}
}
查找 GameObject
using UnityEngine;
public class FindingObjects : MonoBehaviour
{
void Start()
{
// Find by tag (returns first match)
GameObject player = GameObject.FindWithTag("Player");
// Find all with tag
GameObject[] enemies = GameObject.FindGameObjectsWithTag("Enemy");
// Find by name (slow -- avoid in Update)
GameObject manager = GameObject.Find("GameManager");
// Compare tags efficiently (no GC allocation)
if (gameObject.CompareTag("Player"))
{
Debug.Log("This is the player");
}
}
}
实例化 Prefab
摘自 Unity 文档示例:
using UnityEngine;
public class InstantiationExample : MonoBehaviour
{
// Reference to the prefab. Drag a prefab into this field in the Inspector.
public GameObject myPrefab;
void Start()
{
// Instantiate at position (0, 0, 0) and zero rotation.
Instantiate(myPrefab, new Vector3(0, 0, 0), Quaternion.identity);
}
}
使用父级变换实例化
using UnityEngine;
public class SpawnWithParent : MonoBehaviour
{
public GameObject prefab;
public Transform parentTransform;
void SpawnChild()
{
// Instantiate as child of a parent transform
GameObject instance = Instantiate(prefab, parentTransform);
// Instantiate at specific world position under a parent
GameObject positioned = Instantiate(
prefab,
new Vector3(5, 0, 0),
Quaternion.identity,
parentTransform
);
}
}
ScriptableObject 数据容器
摘自 Unity 文档:
using UnityEngine;
[CreateAssetMenu(fileName = "Data", menuName = "ScriptableObjects/SpawnData", order = 1)]
public class SpawnDataScriptableObject : ScriptableObject
{
public GameObject prefab;
public int count;
public Vector3[] positions;
}
using UnityEngine;
public class SpawnManager : MonoBehaviour
{
public SpawnDataScriptableObject spawnData;
void Start()
{
for (int i = 0; i < spawnData.count; i++)
{
Instantiate(spawnData.prefab, spawnData.positions[i], Quaternion.identity);
}
}
}
场景管理
using UnityEngine;
using UnityEngine.SceneManagement;
public class SceneLoader : MonoBehaviour
{
void Start()
{
// Subscribe to scene loaded event
SceneManager.sceneLoaded += OnSceneLoaded;
}
void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
Debug.Log("Loaded: " + scene.name);
}
public void LoadLevel(string sceneName)
{
// Load scene by name (replaces current)
SceneManager.LoadScene(sceneName);
}
public void LoadAdditive(string sceneName)
{
// Load scene additively (keeps current scenes)
SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Additive);
}
public void UnloadLevel(string sceneName)
{
SceneManager.UnloadSceneAsync(sceneName);
}
public void GetSceneInfo()
{
Scene active = SceneManager.GetActiveScene();
Debug.Log("Active scene: " + active.name);
Debug.Log("Loaded scene count: " + SceneManager.loadedSceneCount);
}
}
基于标签生成对象
摘自 Unity 文档示例:
using UnityEngine;
public class RespawnSystem : MonoBehaviour
{
public GameObject respawnPrefab;
private GameObject respawn;
void Update()
{
if (respawn == null)
respawn = GameObject.FindWithTag("Respawn");
if (respawn != null)
{
Instantiate(respawnPrefab, respawn.transform.position,
respawn.transform.rotation);
}
}
}
激活和停用 GameObject
using UnityEngine;
public class ToggleVisibility : MonoBehaviour
{
public GameObject target;
public void Toggle()
{
// SetActive controls whether the GameObject is active
target.SetActive(!target.activeSelf);
// activeSelf: this object's own active state
// activeInHierarchy: effective state (considers parent chain)
Debug.Log("Self: " + target.activeSelf);
Debug.Log("InHierarchy: " + target.activeInHierarchy);
}
}
用于射线检测的层遮罩
using UnityEngine;
public class LayerRaycast : MonoBehaviour
{
void Update()
{
// Create a layer mask for layer named "Ground"
int groundLayer = LayerMask.NameToLayer("Ground");
int layerMask = 1 << groundLayer;
// Raycast only against the Ground layer
if (Physics.Raycast(transform.position, Vector3.down, out RaycastHit hit, 100f, layerMask))
{
Debug.Log("Hit ground at: " + hit.point);
}
// Use GetMask for multiple layers
int combinedMask = LayerMask.GetMask("Ground", "Water");
Physics.Raycast(transform.position, Vector3.forward, 50f, combinedMask);
}
}
反模式
1. 在 Update 中使用 GameObject.Find
// BAD: Find is expensive -- runs every frame with string lookup
void Update()
{
GameObject player = GameObject.Find("Player"); // Avoid!
}
// GOOD: Cache the reference
private GameObject player;
void Start()
{
player = GameObject.FindWithTag("Player");
}
2. 非均匀 Transform 缩放
摘自文档:“不要在 Transform 组件中调整 GameObject 的 Scale。”非均匀缩放(例如 2、4、2)会导致:
- Collider、Light、Audio Source 的行为异常
- 旋转后的子对象显示歪斜
- 实例化缩放对象时性能下降
应按现实尺寸制作模型资源。
3. 在 Prefab 中重复数据,而不是使用 ScriptableObject
// BAD: Every prefab instance duplicates this data
public class EnemyStats : MonoBehaviour
{
public int health = 100;
public float speed = 5f;
public string enemyName = "Goblin";
}
// GOOD: Use a ScriptableObject -- one asset, many references
[CreateAssetMenu(menuName = "ScriptableObjects/EnemyConfig")]
public class EnemyConfig : ScriptableObject
{
public int health = 100;
public float speed = 5f;
public string enemyName = "Goblin";
}
public class Enemy : MonoBehaviour
{
public EnemyConfig config; // All instances share the same asset
}
4. 修改 ScriptableObject 后忘记调用 EditorUtility.SetDirty
// BAD: Changes in Edit mode won't persist
settings.value += 10;
// GOOD: Mark asset dirty so Unity saves it
settings.value += 10;
EditorUtility.SetDirty(settings);
5. 基于字符串比较标签
// BAD: Allocates a new string for comparison (GC pressure)
if (gameObject.tag == "Player") { }
// GOOD: CompareTag avoids allocation
if (gameObject.CompareTag("Player")) { }
6. 忽略父子 Transform 关系
添加子对象前,将父对象的位置设为 (0,0,0)。否则子对象的局部坐标将与全局坐标不一致,导致对象定位时产生困惑。
关键 API 快速参考
| API | 描述 | 备注 |
|---|---|---|
GetComponent<T>() | 获取同一 GameObject 上的组件 | 找不到时返回 null |
GetComponentInChildren<T>() | 获取自身或子对象上的组件 | 按深度优先顺序搜索 |
GetComponentsInChildren<T>() | 获取层级结构中所有匹配的组件 | 返回数组 |
AddComponent<T>() | 在运行时添加新组件 | 返回新组件 |
Destroy(obj) | 销毁 GameObject 或 Component | 延迟到帧结束时执行 |
Instantiate(prefab, pos, rot) | 在指定位置和旋转角度克隆 Prefab | 返回克隆对象 |
GameObject.FindWithTag(tag) | 查找第一个处于激活状态且带有指定标签的 GO | 没有匹配项时返回 null |
GameObject.FindGameObjectsWithTag(tag) | 查找所有处于激活状态且带有指定标签的 GO | 返回数组 |
GameObject.Find(name) | 按名称查找(开销较大) | 避免在 Update 循环中使用 |
gameObject.SetActive(bool) | 激活或停用 | 禁用所有组件 |
gameObject.CompareTag(tag) | 比较标签且不产生 GC 分配 | 优于 == tag |
SceneManager.LoadScene(name) | 加载场景(替换当前场景) | 必须包含在 Build Settings 中 |
SceneManager.LoadSceneAsync(name, mode) | 异步加载场景 | Additive 或 Single 模式 |
SceneManager.UnloadSceneAsync(name) | 卸载已加载的场景 | 返回 AsyncOperation |
SceneManager.GetActiveScene() | 获取当前活动场景 | 返回 Scene 结构体 |
LayerMask.NameToLayer(name) | 根据名称获取层索引 | 返回 int |
LayerMask.GetMask(names) | 根据层名称获取组合遮罩 | 参数为字符串数组 |
Camera.main | 获取带有 MainCamera 标签的摄像机 | 由 Unity 缓存 |
相关技能
- unity-scripting -- C# 脚本模式、MonoBehaviour 生命周期、协程、事件
- unity-physics -- Rigidbody、Collider、物理层、射线检测、触发器
- unity-editor-tools -- 自定义检查器、编辑器窗口、Gizmo、构建流水线