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 Asset(生成された 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 と動的 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 を使用してください。

ビルドパイプライン

Build Settings
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
    }
}

ベストプラクティス

✅ 推奨事項:

  • エディタースクリプトを使用して反復的な作業を自動化します
  • 複雑なコンポーネントにはカスタムインスペクターを実装します
  • マルチプラットフォームのプロジェクトには新しい Input System を使用します
  • 静的コンテンツと動的コンテンツでキャンバスを分け、UI を最適化します
  • 大規模なゲームやモバイル向けには Addressables を使用します
  • デザイナーやアーティスト向けのエディターツールを作成します
  • ビルド前にアセットを検証します

❌ 避けるべきこと:

  • エディターのパスをハードコードする(AssetDatabase を使用します)
  • Input System のアクションの購読解除を忘れる
  • Layout Groups を過度に組み合わせる(UI のパフォーマンスを低下させます)
  • 新しいプロジェクトで Resources フォルダーを使用する(Addressables を使用します)
  • Update ループ内で UI を作成する
  • 重要なスクリプトのエディター検証を省略する

黄金律:エディターツールでワークフローを自動化します。ツールの作成に費やした時間は、反復作業でそれをはるかに上回る時間の節約につながります。


これらのワークフロー最適化と最新のシステムを適用し、効率的でスケーラブルな Unity 開発を実現しましょう。

コメント