Amazonで商品を見るセール会場へ

Unity│PSDデータを階層保持でPrefab+PNGに一括出力する方法

PSDデータで作成したイラストなどの階層構造を維持したままPrefab化し、

レイヤーごとに個別PNG画像として一括出力する方法を解説します。

元記事の検証環境:Unity 6.1。以下の修正版コードは実機での再検証前です。

目次

Unity 2DでPSDデータを階層保持のままPrefabとPNGへ一括出力する手順

Unity標準のインポート機能とエディタ拡張を組み合わせることで、手動での書き出しや配置の手間を省けます。

PSDファイルの準備

テスト用としてsample.psdを用意します。

入れ子のレイヤーグループや日本語名を使う場合も、出力後に階層と表示を比較します。描画モードや調整レイヤーなど、Photoshopの合成結果を完全には再現できない要素があります。

編集用のPSD原稿を別に残し、Unityへ渡す複製でシェイプやスマートオブジェクトなどをラスタライズします。元の編集情報を失わないよう、原稿へ上書きしないでください。

レイヤーグループの入れ子やラスタライズ済みの要素を含むテスト用のPSDデータの構成を示す画面。

Unityプロジェクトの準備とフォルダ構成

Unityプロジェクトを2Dテンプレートで作成し、Package Managerで2D PSD Importerと2D Animationの導入を確認します。元記事にはパッケージのバージョン記録がないため、使用中の版のマニュアルも確認してください。

元記事の検証環境:Unity 6.1。以下の修正版コードは実機での再検証前です。

Assets配下に以下の作業フォルダを作成します。

親フォルダ子フォルダ用途
AssetsEditorエディタ拡張スクリプト(C#)を格納
Prefabs生成されたPrefabおよびPNG画像の出力先
PSDインポート対象のPSDファイルを配置

作成したPSDフォルダへsample.psdを配置します。

PSDインポート設定の変更

配置したsample.psdを選択し、Inspectorウィンドウでインポーターを

UnityEditor.TextureImporter (Default)からUnityEditor.U2D.PSD.PSDImporterへ切り替えます。

インスペクターでインポーターをデフォルトからPSDImporterへ切り替える操作の画面。

Texture TypeをSprite (2D and UI)、Sprite ModeをMultiple、Import ModeをIndividual Sprites (Mosaic)にします。Character RigのUse as Rigを有効にして階層付きPrefabを生成し、次のレイヤー設定を確認します。名称はパッケージの版により異なります。設定後はApplyを押してください。

非表示レイヤーの取り込みや階層構造の保持など、PSDImporterで有効にするオプション設定画面。

Layer Import設定

  • Include Hidden Layers:必要な場合のみ有効にする。非表示レイヤーも取り込むため、Photoshopで表示していた合成結果とは見え方が変わり得る
  • Use Layer Group(またはLayer GroupのAs Per Source File):レイヤーグループの階層構造を保持する

Layer Managementタブでは、出力したいレイヤーの取り込みチェックも確認します。グループをCollapseで結合すると、子グループ内も含めて1枚のSpriteになります。レイヤー別PNGが必要なら、対象グループをUncollapseに戻してからApplyしてください。後述の補助スクリプトは、選択したPSDのインポーターをメニューから切り替えるものです。Use as Rigやレイヤー設定は自動設定しません。

インポート設定をPreset(PSDImporter.preset)として保存し、Preset Managerのデフォルトに登録しておくと、PSD配置時の自動適用が可能です。

インポート設定をプリセットとして保存し、自動適用の設定を行う画面。

PrefabおよびPNG画像の一括出力手順

使用するスクリプトのコード全文は記事末尾に掲載しています。

Projectウィンドウで対象のsample.psdを選択します。

右クリックメニューから出力スクリプトを実行します。

プロジェクトウィンドウでPSDファイルを選択し、右クリックメニューから出力スクリプトを実行する様子。

Prefabsフォルダ内に実行ごとの専用フォルダが作成され、Prefabと連番付きのレイヤー別PNG画像が書き出されます。同名レイヤーや再実行による上書きを避ける構成です。

修正版はインポーターが生成した階層・Transformを複製し、元SpriteのPixels Per UnitとPivotをPNG側に引き継ぎます。元のPSDと同じ見た目になるかは出力後に比較してください。

出力処理によってフォルダ内に生成された、階層を再現したPrefabと各レイヤーのPNG画像。

以上で一括出力の基本フローは完了です。

以下は元記事のコードを修正した2つのエディタ拡張です。骨・ウェイトやアニメーション参照の移行は対象外です。静止したSpriteRendererの階層で試し、色・透明部分・重なり順・サイズ・基準点を比較してください。

  • PsdOverridePostprocessor.cs(選択したPSDのImporterをメニューからPSDImporterへ切り替え)
  • PsdToPrefabExporter.cs(PSDの階層構造を保持したままPrefabとPNG画像を一括生成)

プロジェクトの仕様に合わせて処理内容を調整することで、様々なアセット生成ワークフローに応用できます。

Unity関連の関連記事は一覧まとめから確認できます。

uGUIへ変換する方法については以下の記事を参照してください。

PsdOverridePostprocessor.cs

Assets/Editorフォルダに以下のスクリプトを配置します。

PSDを選択し、Assets → BG → PSD Importerに切り替えを実行します。Inspectorで手動切り替え済みなら、この補助スクリプトは不要です。旧版のOnPreprocessTextureによる自動切り替えコードは削除してから置き換えてください。

// Assets/Editor/PsdOverridePostprocessor.cs
using UnityEditor;
using UnityEditor.U2D.PSD;
using System;
using System.IO;

public static class PsdOverridePostprocessor
{
    [MenuItem("Assets/BG/PSD Importerに切り替え")]
    private static void SwitchSelected()
    {
        foreach (var asset in Selection.objects)
        {
            string path = AssetDatabase.GetAssetPath(asset);
            if (!string.Equals(Path.GetExtension(path), ".psd",
                               StringComparison.OrdinalIgnoreCase)) continue;
            if (AssetImporter.GetAtPath(path) is PSDImporter) continue;
            AssetDatabase.SetImporterOverride<PSDImporter>(path);
        }
    }
}

PsdToPrefabExporter.cs

こちらも同様にAssets/Editorフォルダへ配置します。

ProjectウィンドウでPSDファイルを右クリックし、コンテキストメニューから実行コマンドを選択します。

配置したスクリプトを実行するため、PSDファイルの右クリックメニューからコマンドを選択する操作画面。
// Assets/Editor/PsdToPrefabExporter.cs
using UnityEditor;
using UnityEngine;
using System.IO;
using System.Linq;

public static class PsdToPrefabExporter
{
    /// <summary>
    /// 選択 PSD → Prefab+PNG 一括変換
    /// </summary>
    [MenuItem("Assets/BG/PSD → Prefab+PNG (階層保持)", false, 30)]
    private static void ConvertSelectedPsd()
    {
        foreach (var psd in Selection.objects)
        {
            string psdPath = AssetDatabase.GetAssetPath(psd);
            if (Path.GetExtension(psdPath).ToLower() != ".psd") continue;

            // ---------- ① 出力ディレクトリ ----------
            string prefabRoot = "Assets/Prefabs";
            string dir        = prefabRoot + "/Export_" + System.Guid.NewGuid().ToString("N");
            Directory.CreateDirectory(dir);
            AssetDatabase.Refresh();

            // ---------- ② PSDImporter が生成した階層付き GO ----------
            var rootGo = AssetDatabase.LoadAllAssetsAtPath(psdPath)
                                       .OfType<GameObject>()
                                       .FirstOrDefault();

            if (rootGo == null)
            {
                Debug.LogWarning($"Sprite のルートが見つかりません : {psdPath}");
                continue;
            }

            // ---------- ③ 一時インスタンス化 ----------
            var instance = (GameObject)PrefabUtility.InstantiatePrefab(rootGo);
            try
            {
                PrefabUtility.UnpackPrefabInstance(instance, PrefabUnpackMode.Completely, InteractionMode.AutomatedAction);
                int index = 0;

                // ---------- ④ PNG 書き出し&置換 ----------
                foreach (var sr in instance.GetComponentsInChildren<SpriteRenderer>(true))
                {
                    if (sr.sprite == null) continue;
                    var sprite = sr.sprite;

                    // ④‑A: PNG 出力
                    string pngPath = dir + $"/layer_{index++:D4}.png";
                    SaveSpriteAsPng(sprite, pngPath);

                    // ④‑B: インポート設定を Sprite に
                    SetPngAsSprite(pngPath, sprite);

                    // ④‑C: SpriteRenderer が参照先を差し替え
                    var replacement = AssetDatabase.LoadAssetAtPath<Sprite>(pngPath);
                    if (replacement == null) throw new System.InvalidOperationException("PNGをSpriteとして読み込めません: " + pngPath);
                    sr.sprite = replacement;
                }

                if (index == 0)
                    throw new System.InvalidOperationException("出力できるSpriteがありません。Layer Managementの取り込み対象とUse as Rigを確認してください");

                // ---------- ⑤ Prefab 保存 ----------
                string prefabPath = dir + "/export.prefab";
                if (PrefabUtility.SaveAsPrefabAsset(instance, prefabPath) == null)
                    throw new System.InvalidOperationException("Prefabの保存に失敗しました");

                // ---------- ⑥ 後片付け ----------
                // 一時オブジェクトはfinallyで破棄します。
                AssetDatabase.Refresh();

                Debug.Log($"{psd.name} 変換完了 → {prefabPath}");
            }
            catch (System.Exception ex)
            {
                Debug.LogError($"変換失敗: {psdPath}\n途中の出力: {dir}\n{ex}");
            }
            finally
            {
                if (instance != null) Object.DestroyImmediate(instance);
            }
        }
    }

    // .psd が選択されていないとメニューを無効化
    [MenuItem("Assets/BG/PSD → Prefab+PNG (階層保持)", true)]
    private static bool Validate() =>
        Selection.objects.Any(o => Path.GetExtension(AssetDatabase.GetAssetPath(o)).ToLower() == ".psd");

    // ---------- Utility ----------

    /// <summary>Sprite 部分だけ切り取って PNG 書き出し</summary>
    private static void SaveSpriteAsPng(Sprite sprite, string pngPath)
    {
        if (sprite.packed) throw new System.NotSupportedException("AtlasにパックされたSpriteは対象外です");
        int w = (int)sprite.rect.width;
        int h = (int)sprite.rect.height;
        int x = (int)sprite.rect.x;
        int y = (int)sprite.rect.y;
        if (w <= 0 || h <= 0) throw new System.InvalidOperationException("Spriteの寸法が不正です");
        var prev = RenderTexture.active;
        bool previousSrgbWrite = GL.sRGBWrite;
        RenderTexture rt = null;
        Texture2D tex = null;
        try
        {
            rt = RenderTexture.GetTemporary(sprite.texture.width, sprite.texture.height,
                0, RenderTextureFormat.ARGB32, RenderTextureReadWrite.sRGB);
            GL.sRGBWrite = QualitySettings.activeColorSpace == ColorSpace.Linear;
            Graphics.Blit(sprite.texture, rt);
            RenderTexture.active = rt;
            tex = new Texture2D(w, h, TextureFormat.RGBA32, false, false);
            tex.ReadPixels(new Rect(x, y, w, h), 0, 0);
            tex.Apply();
            File.WriteAllBytes(pngPath, ImageConversion.EncodeToPNG(tex));
        }
        finally
        {
            RenderTexture.active = prev;
            GL.sRGBWrite = previousSrgbWrite;
            if (rt != null) RenderTexture.ReleaseTemporary(rt);
            if (tex != null) Object.DestroyImmediate(tex);
        }
    }

    /// <summary>PNG を Sprite として再インポート</summary>
    private static void SetPngAsSprite(string path, Sprite source)
    {
        AssetDatabase.ImportAsset(path);
        if (AssetImporter.GetAtPath(path) is TextureImporter ti)
        {
            ti.textureType          = TextureImporterType.Sprite;
            ti.spriteImportMode = SpriteImportMode.Single;
            ti.spritePixelsPerUnit = source.pixelsPerUnit;
            ti.sRGBTexture = true;
            ti.mipmapEnabled = false;
            ti.npotScale = TextureImporterNPOTScale.None;
            ti.maxTextureSize = 16384;
            var settings = new TextureImporterSettings();
            ti.ReadTextureSettings(settings);
            settings.spriteAlignment = (int)SpriteAlignment.Custom;
            settings.spritePivot = new Vector2(source.pivot.x / source.rect.width,
                                               source.pivot.y / source.rect.height);
            ti.SetTextureSettings(settings);
            ti.alphaIsTransparency  = true;
            ti.textureCompression   = TextureImporterCompression.Uncompressed;
            ti.isReadable           = false;
            ti.SaveAndReimport();
        }
    }
}

処理が完了すると、Prefabsフォルダ配下に実行ごとの専用フォルダが生成され、Prefabとレイヤー別PNG画像が出力されます。

スクリプト完了後にPrefabsフォルダ内へPrefabとレイヤー別PNG画像が出力された状態を示す画面。

出力後は元Prefabと並べ、Pixels Per Unitが100以外の場合や中央以外のPivotも含めて確認します。Gamma/Linear、透明部分、プラットフォーム別のテクスチャ上書き設定による縮小も比較してください。Spriteが割り当てられたSpriteRendererを1件も処理できない場合は、Prefab保存前にエラーで止めます。失敗時は専用フォルダに途中の出力が残ります。Consoleのエラーを確認し、完成したPrefabとして扱わないでください。

仕様の参照先:PSD Importerの設定、PSDのImporter切り替え、Sprite.pivot、Sprite.pixelsPerUnit。

元記事のコードは、GitHubのMOTOKI-LLC/cg-method-codeにもあります。この記事の修正版との同期は未確認なので、差分を確認して利用してください。

2Dとスプライトの記事は、ほかにもあります。

スポンサーリンク

よくある質問:PSDを階層保持でPrefabとPNGに一括出力する

この記事では、何ができますか?

PSDデータで作成したイラストなどの階層構造を維持したままPrefab化し、レイヤーごとに個別PNG画像として一括出力します。Unity標準のインポート機能とエディタ拡張を組み合わせることで、手動での書き出しや配置の手間を省けます。

どのバージョンで確認していますか?

元記事の検証環境はUnity 6.1です。今回の修正版コードは、Unityでのコンパイル・出力比較をまだ行っていません。お使いのバージョンでの最新の手順は、Unityの公式マニュアルで確認してください。

PSDは、どう準備しますか?

テスト用にsample.psdを用意しています。入れ子のレイヤーグループや日本語名を使う場合も、出力後に階層と表示を比較します。描画モードや調整レイヤーなど、Photoshopの合成結果を完全には再現できない要素があります。シェイプやスマートオブジェクトなどは、インポート時の崩れを防ぐため、事前にPhotoshop側でラスタライズしておきます。

Unityプロジェクトのフォルダ構成は?

Assets配下に、エディタ拡張スクリプト(C#)を格納するEditor、生成されたPrefabおよびPNG画像の出力先であるPrefabs、インポート対象のPSDファイルを配置するPSDの各フォルダを作成します。

PSDインポート設定では、何を変更しますか?

配置したsample.psdを選択し、Inspectorウィンドウでインポーターを切り替えます。Multiple、Individual Sprites (Mosaic)、Use as Rigと階層保持の設定を確認し、Applyを押します。Include Hidden Layersは非表示レイヤーも必要な場合だけ有効にします。

2つのスクリプトは、それぞれ何をしますか?

PsdOverridePostprocessor.csは、選択したPSDのImporterをメニュー操作でPSDImporterへ切り替えます。PsdToPrefabExporter.csは、PSDの階層構造を保持したままPrefabとPNG画像を一括生成します。どちらもAssets/Editorフォルダへ配置します。

どうやって一括出力しますか?

ProjectウィンドウでPSDファイル(sample.psd)を右クリックし、コンテキストメニューから実行コマンドを選択します。処理が完了すると、Prefabsフォルダ配下に実行ごとの専用フォルダが生成され、Prefabとレイヤー別PNG画像が出力されます。修正版はインポーターが生成した階層・Transformを複製し、元SpriteのPixels Per UnitとPivotをPNG側に引き継ぎます。元のPSDと同じ見た目になるかは出力後に比較してください。

コードはどこで確認できますか?

修正版はこの記事内に掲載しています。GitHubのMOTOKI-LLC/cg-method-codeは元記事の参照先で、今回の修正との同期は未確認です。処理内容は、プロジェクトの仕様に合わせて調整してください。

次に学ぶ・作業環境を選ぶ

学習を続けたい方や、作業環境を整えたい方は、目的に合うガイドをご覧ください。

Unityのおすすめ書籍

開発PCの選び方

周辺機器の優先度

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次