mcpField
U
unity-foundations

unity-foundations

github.com/nice-wolf-studio开发4072 次安装6 周前更新
代码仓库

关于 unity-foundations

面向 Unity 初学者和开发者的基础知识技能,整理 GameObject、组件、Transform、场景和 Prefab 等核心概念。还介绍 ScriptableObject、常用 API、实践模式及易犯问题。

主要功能

  1. 01GameObject 与组件基础
  2. 02Transform 和父子层级
  3. 03场景与 Prefab 管理
  4. 04ScriptableObject 数据容器
  5. 05标签、图层与 LayerMask
  6. 06常用 API 和反模式

适用场景

  1. 01理解 Unity 场景对象的组成
  2. 02创建和复用 Prefab 配置
  3. 03用 ScriptableObject 管理共享数据
  4. 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.md
gameobject-api.md
prefabs-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、构建流水线

Additional Resources

评论

支持 Markdown