Unity Workflows について
Unity Workflowsは、Unity開発者向けにエディタースクリプト、入力、UI、アセット、ビルドを扱うスキルです。実装例とワークフローの指針を組み合わせ、エディター作業の自動化や開発を支援します。
主な機能
- 01エディタースクリプトとカスタムツール
- 02新旧の入力システム
- 03uGUIとUI Toolkitの解説
- 04Addressablesによるアセット管理
- 05ビルドパイプラインのスクリプト
活用シーン
- 01繰り返しのエディター作業の自動化
- 02プロジェクトに適した入力方式の選択
- 03ランタイムまたはエディターUIの構築
- 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 を作成します。
- Create > Input Actions
- Action Maps(Player、UI など)を追加します
- Actions(Move、Jump、Fire など)を追加します
- 入力(Keyboard、Gamepad など)をバインドします
- 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
アセットの非同期読み込みとメモリ管理を行います。
セットアップ:
- Window > Asset Management > Addressables > Groups
- アセットを Addressable としてマークします
- グループに整理します
アセットの読み込み:
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 開発を実現しましょう。