mcpField
U
Unity Workflows

Unity Workflows

github.com/cryptorabea开发3103 次安装45 周前更新
代码仓库

关于 Unity Workflows

Unity Workflows 是面向 Unity 开发者的参考技能,涵盖编辑器脚本、输入、UI、资源和构建。内容结合实现示例与流程建议,帮助自动化编辑器任务和开发项目。

主要功能

  1. 01编辑器脚本与自定义工具
  2. 02新旧输入系统
  3. 03uGUI 与 UI Toolkit 指引
  4. 04Addressables 资源管理
  5. 05构建流程脚本

适用场景

  1. 01自动化重复的编辑器任务
  2. 02为项目选择输入系统
  3. 03构建运行时或编辑器 UI
  4. 04加载和整理游戏资源

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/cryptorabea/claude_unity_dev_plugin --skill unity-workflows

安装后在 Claude Code 中输入/unity-workflows确认已加载。

详细介绍

Unity 工作流和编辑器工具

Unity 编辑器脚本、输入系统、UI 开发和资源管理的核心工作流。

概述

高效的 Unity 工作流能够加快开发进度并减少错误。本技能涵盖编辑器自定义、现代输入处理、UI 系统和资源管线优化。

核心工作流领域:

  • 编辑器脚本和自定义工具
  • Input System(新版和旧版)
  • UI 开发(UI Toolkit、uGUI)
  • 资源管理和构建管线

编辑器脚本基础

菜单项

创建自定义菜单命令:

using UnityEditor;

public static class CustomMenu
{
    [MenuItem("Tools/My Tool")]
    private static void ExecuteTool()
    {
        Debug.Log("Custom tool executed");
    }

    [MenuItem("Tools/My Tool", true)]  // Validation
    private static bool ValidateTool()
    {
        return Selection.activeGameObject != null;
    }

    // Keyboard shortcut: Ctrl+Shift+T (Windows), Cmd+Shift+T (Mac)
    [MenuItem("Tools/Quick Action %#t")]
    private static void QuickAction()
    {
        // Action
    }
}

快捷键:

  • % = Ctrl(Mac 上为 Cmd)
  • # = Shift
  • & = Alt
  • _g = G 键
上下文菜单

为 GameObject/Component 添加右键上下文菜单:

[MenuItem("GameObject/My Custom Action", false, 10)]
private static void CustomAction()
{
    GameObject selected = Selection.activeGameObject;
    // Perform action
}

// Component context menu
public class MyComponent : MonoBehaviour
{
    [ContextMenu("Do Something")]
    private void DoSomething()
    {
        Debug.Log("Context menu action");
    }

    [ContextMenuItem("Reset Value", "ResetValue")]
    [SerializeField] private int value;

    private void ResetValue()
    {
        value = 0;
    }
}
自定义检查器

为自定义类型重写 Inspector:

[CustomEditor(typeof(MyScript))]
public class MyScriptEditor : Editor
{
    public override void OnInspectorGUI()
    {
        // Draw default inspector
        DrawDefaultInspector();

        MyScript script = (MyScript)target;

        // Custom button
        if (GUILayout.Button("Execute Action"))
        {
            script.ExecuteAction();
        }

        // Custom fields
        EditorGUILayout.LabelField("Custom Section", EditorStyles.boldLabel);
        script.customValue = EditorGUILayout.IntSlider("Custom Value", script.customValue, 0, 100);

        // Apply changes
        if (GUI.changed)
        {
            EditorUtility.SetDirty(target);
        }
    }
}
属性绘制器

在 Inspector 中自定义属性显示:

[System.Serializable]
public class Range
{
    public float min;
    public float max;
}

[CustomPropertyDrawer(typeof(Range))]
public class RangeDrawer : PropertyDrawer
{
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        EditorGUI.BeginProperty(position, label, property);

        position = EditorGUI.PrefixLabel(position, GUIUtility.GetControlID(FocusType.Passive), label);

        var minProp = property.FindPropertyRelative("min");
        var maxProp = property.FindPropertyRelative("max");

        float halfWidth = position.width / 2;

        Rect minRect = new Rect(position.x, position.y, halfWidth - 5, position.height);
        Rect maxRect = new Rect(position.x + halfWidth, position.y, halfWidth - 5, position.height);

        EditorGUI.PropertyField(minRect, minProp, GUIContent.none);
        EditorGUI.PropertyField(maxRect, maxProp, GUIContent.none);

        EditorGUI.EndProperty();
    }
}
EditorWindow

创建自定义编辑器窗口:

public class MyEditorWindow : EditorWindow
{
    private string textField = "";
    private int intField = 0;

    [MenuItem("Window/My Editor Window")]
    private static void ShowWindow()
    {
        var window = GetWindow<MyEditorWindow>();
        window.titleContent = new GUIContent("My Tool");
        window.minSize = new Vector2(300, 200);
        window.Show();
    }

    private void OnGUI()
    {
        GUILayout.Label("My Custom Tool", EditorStyles.boldLabel);

        textField = EditorGUILayout.TextField("Text Field", textField);
        intField = EditorGUILayout.IntField("Int Field", intField);

        if (GUILayout.Button("Execute"))
        {
            Execute();
        }
    }

    private void Execute()
    {
        Debug.Log($"Executed with: {textField}, {intField}");
    }
}
AssetDatabase 操作

以编程方式操作资源:

using UnityEditor;

public static class AssetUtilities
{
    [MenuItem("Assets/Create Prefab From Selection")]
    private static void CreatePrefab()
    {
        GameObject selected = Selection.activeGameObject;
        if (selected == null)
            return;

        string path = $"Assets/Prefabs/{selected.name}.prefab";

        // Create prefab
        PrefabUtility.SaveAsPrefabAsset(selected, path);

        AssetDatabase.Refresh();
    }

    public static T LoadAsset<T>(string path) where T : UnityEngine.Object
    {
        return AssetDatabase.LoadAssetAtPath<T>(path);
    }

    public static void CreateFolder(string path)
    {
        if (!AssetDatabase.IsValidFolder(path))
        {
            AssetDatabase.CreateFolder(System.IO.Path.GetDirectoryName(path), System.IO.Path.GetFileName(path));
        }
    }
}

Unity Input System

新 Input System 设置

安装:Window > Package Manager > Input System

创建 Input Actions:

  1. 选择 Create > Input Actions
  2. 添加 Action Maps(Player、UI 等)
  3. 添加 Actions(Move、Jump、Fire 等)
  4. 绑定输入(Keyboard、Gamepad 等)
  5. 生成 C# 类(Inspector > Generate C# Class)
使用 Input Actions
using UnityEngine.InputSystem;

public class PlayerController : MonoBehaviour
{
    private PlayerInput playerInput;
    private InputAction moveAction;
    private InputAction jumpAction;

    private void Awake()
    {
        playerInput = GetComponent<PlayerInput>();

        moveAction = playerInput.actions["Move"];
        jumpAction = playerInput.actions["Jump"];
    }

    private void OnEnable()
    {
        jumpAction.performed += OnJump;
    }

    private void OnDisable()
    {
        jumpAction.performed -= OnJump;
    }

    private void Update()
    {
        Vector2 moveInput = moveAction.ReadValue<Vector2>();
        Move(moveInput);
    }

    private void OnJump(InputAction.CallbackContext context)
    {
        Jump();
    }

    private void Move(Vector2 input) { }
    private void Jump() { }
}
Input Action 资源(生成的 C#)
// After generating C# class from Input Actions asset
private PlayerInputActions inputActions;

private void Awake()
{
    inputActions = new PlayerInputActions();
}

private void OnEnable()
{
    inputActions.Player.Enable();
    inputActions.Player.Jump.performed += OnJump;
}

private void OnDisable()
{
    inputActions.Player.Disable();
    inputActions.Player.Jump.performed -= OnJump;
}

private void Update()
{
    Vector2 move = inputActions.Player.Move.ReadValue<Vector2>();
}

优点:

  • 类型安全的动作访问
  • 自动补全
  • 编译时检查
旧版 Input System
// Still functional, simpler for basic games
private void Update()
{
    // Keyboard
    if (Input.GetKeyDown(KeyCode.Space))
        Jump();

    // Mouse
    if (Input.GetMouseButtonDown(0))
        Fire();

    // Axis (configured in Edit > Project Settings > Input Manager)
    float horizontal = Input.GetAxis("Horizontal");
    float vertical = Input.GetAxis("Vertical");

    Move(new Vector2(horizontal, vertical));
}

适用于:

  • 简单原型
  • 移动端触摸输入
  • 旧版项目

以下情况优先使用新版 Input System:

  • 多平台支持
  • 可重新绑定的控制
  • 复杂的输入处理

UI 系统

uGUI(基于 Canvas 的 UI)

Unity 标准 UI 系统:

using UnityEngine.UI;
using TMPro;  // TextMeshPro

public class UIManager : MonoBehaviour
{
    [SerializeField] private TextMeshProUGUI scoreText;
    [SerializeField] private Button playButton;
    [SerializeField] private Slider healthSlider;

    private void Start()
    {
        playButton.onClick.AddListener(OnPlayClicked);
    }

    private void OnDestroy()
    {
        playButton.onClick.RemoveListener(OnPlayClicked);
    }

    public void UpdateScore(int score)
    {
        scoreText.text = $"Score: {score}";
    }

    public void UpdateHealth(float health, float maxHealth)
    {
        healthSlider.value = health / maxHealth;
    }

    private void OnPlayClicked()
    {
        Debug.Log("Play button clicked");
    }
}

Canvas 渲染模式:

  • Screen Space - Overlay:UI 始终显示在最上层,无需摄像机(速度最快)
  • Screen Space - Camera:由摄像机渲染 UI,支持后处理
  • World Space:游戏世界中的 3D UI

UI 优化:

  • 将静态和动态 UI 分别放到不同的 Canvas 中
  • 对非交互元素禁用射线检测目标
  • 使用精灵图集
  • 尽量减少 Layout Groups 的使用(开销较大)
UI Toolkit(现代 UI)

运行时和编辑器 UI(Unity 2021+):

UXML(UI 结构):

<ui:UXML>
    <ui:VisualElement name="root">
        <ui:Label text="Score: 0" name="scoreLabel"/>
        <ui:Button text="Play" name="playButton"/>
    </ui:VisualElement>
</ui:UXML>

USS(样式):

.root {
    flex-grow: 1;
    background-color: rgb(50, 50, 50);
}

#scoreLabel {
    font-size: 24px;
    color: white;
}

C#(逻辑):

using UnityEngine.UIElements;

public class UIController : MonoBehaviour
{
    private UIDocument uiDocument;
    private Label scoreLabel;
    private Button playButton;

    private void Awake()
    {
        uiDocument = GetComponent<UIDocument>();

        var root = uiDocument.rootVisualElement;
        scoreLabel = root.Q<Label>("scoreLabel");
        playButton = root.Q<Button>("playButton");

        playButton.clicked += OnPlayClicked;
    }

    public void UpdateScore(int score)
    {
        scoreLabel.text = $"Score: {score}";
    }

    private void OnPlayClicked()
    {
        Debug.Log("Play clicked");
    }
}

优点:

  • 性能更快(保留模式)
  • 更适合复杂 UI
  • 现代化的类 Web 工作流(相当于 HTML/CSS)
  • 与编辑器 UI 共用

适用于:

  • 新项目(Unity 2021+)
  • 复杂 UI
  • 编辑器工具

资源管理

Addressables

异步资源加载和内存管理:

设置:

  1. Window > Asset Management > Addressables > Groups
  2. 将资源标记为 Addressable
  3. 将资源整理到组中

加载资源:

using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;

public class AssetLoader : MonoBehaviour
{
    private async void Start()
    {
        // Load asset asynchronously
        var handle = Addressables.LoadAssetAsync<GameObject>("Enemy");
        await handle.Task;

        if (handle.Status == AsyncOperationStatus.Succeeded)
        {
            GameObject prefab = handle.Result;
            Instantiate(prefab);
        }

        // Release when done
        Addressables.Release(handle);
    }

    // Instantiate directly
    private async void SpawnEnemy()
    {
        var handle = Addressables.InstantiateAsync("Enemy");
        await handle.Task;

        GameObject enemy = handle.Result;
        // Use enemy
    }
}

优点:

  • 异步加载(不会造成帧卡顿)
  • 内存管理
  • 远程内容更新
  • 构建优化

适用于:

  • 大型游戏
  • DLC/在线更新
  • 移动游戏(缩减安装包大小)
Resources 文件夹(旧版)

简单的资源加载:

// Load from Resources folder
GameObject prefab = Resources.Load<GameObject>("Prefabs/Enemy");

// All assets include in build - inefficient

避免使用: 新项目请改用 Addressables。

构建管线

构建设置
using UnityEditor;
using UnityEditor.Build.Reporting;

public static class BuildUtility
{
    [MenuItem("Build/Build Windows")]
    private static void BuildWindows()
    {
        BuildPlayerOptions options = new BuildPlayerOptions
        {
            scenes = new[] { "Assets/Scenes/MainMenu.unity", "Assets/Scenes/Game.unity" },
            locationPathName = "Builds/Windows/Game.exe",
            target = BuildTarget.StandaloneWindows64,
            options = BuildOptions.None
        };

        BuildReport report = BuildPipeline.BuildPlayer(options);

        if (report.summary.result == BuildResult.Succeeded)
        {
            Debug.Log($"Build succeeded: {report.summary.totalSize} bytes");
        }
        else
        {
            Debug.LogError($"Build failed: {report.summary.result}");
        }
    }
}
构建预处理
using UnityEditor.Build;
using UnityEditor.Build.Reporting;

public class BuildPreprocessor : IPreprocessBuildWithReport
{
    public int callbackOrder => 0;

    public void OnPreprocessBuild(BuildReport report)
    {
        Debug.Log("Preprocessing build...");

        // Validate assets
        // Update version number
        // Generate build info
    }
}

最佳实践

✅ 应该:

  • 使用 Editor 脚本自动执行重复性任务
  • 为复杂组件实现自定义检视面板
  • 在多平台项目中使用新的 Input System
  • 使用独立的画布显示静态和动态内容,优化 UI
  • 在大型游戏和移动项目中使用 Addressables
  • 为设计师和美术人员创建编辑器工具
  • 在构建前验证资源

❌ 不应该:

  • 将编辑器路径硬编码(使用 AssetDatabase)
  • 忘记取消订阅 Input System 操作
  • 过度混用 Layout Groups(会严重影响 UI 性能)
  • 在新项目中使用 Resources 文件夹(改用 Addressables)
  • 在 Update 循环中创建 UI
  • 跳过对关键脚本的编辑器验证

黄金法则:使用编辑器工具自动化工作流。花时间创建工具,能在迭代过程中节省更多时间,效果呈指数级增长。


采用这些工作流优化和现代系统,高效开发可扩展的 Unity 项目。

评论

支持 Markdown