YAMADA TAISHI’s diary

ゲームについてとか私の日記とか。このブログのあらゆるコードは好きにどうぞ。利用規約があるものは記事内のGitHubのRepositoryのリンクで貼られていると思うので、そちらを参照ください。

Affinity DesignerのPSDファイルをUnityのuGUIとして取り込んでみた

こんにちは、やまだたいし( やまだ たいし (@OrotiYamatano) | Twitter )です。
普段、個人制作でAffinity Designerを使っており、Affinity DesignerのPSDファイルを取り込めたら作業工数削減できるのでは?と思いやってみました。
今回はその時の知見を共有したいと思い記事化しました。

目次


なぜ、PSDファイルを読み込もうと思ったのか


Affinity DesignerはAdobe Illustratorのように使える買い切りの商品です。
(↓これがAffinity Designer)
affinity.serif.com

スマホゲームの制作会社ではAdobe Illustratorを使い、UIを作るということが行われています。
そしてIllustratorを使った場合は、以下ライブラリ(Baum2)などを使いPSDファイルをUnity用に変換して使うことが出来ます。
かなり便利です。

github.com

Affinity DesignerでもBaum2を使いPSDをインポート出来たら良かったのですが、
Affinity DesignerにはScript機能が無いため、同じ様にエクスポート/インポートは出来ません。

そこで、UnityでAffinity DesignerのPSDをインポート出来る機能が出来れば作業効率化できて欲しいのでは?と思い今回挑戦してみました。
PSD to uGUIができれば一々Unity側でレイアウトをしなくても済むし超嬉しい!

(因みに、こんな↓UIとかをAffinity Designerで作ったりしてUI勉強しています)

出来たのか?


結論を先にいうと、
Affinity DesignerのPSDはレイヤー2層までのものなら、Unityに取り込みが出来た
という感じです。
(今回はuGUIのImageのみ対応)

なぜ、このような中途半端な感じになってしまったのかというと
Affinity DesignerのPSD出力が完全なものではなくレイヤー2層より下は、どんな設定をしてもラスタライズされレイヤーが統合されてしまうためです。(悲しい)
(なにか設定があるなら誰か教えて欲しい)

もしレイヤー階層の深いPSDを出力してUnityに取り込んで使いたいのなら、
Affinity Designerが対応してくれるのを待つか、
素直に他のソフトを使うしかないと思います。

動作環境


今回は、以下の条件でAffinity DesignerのPSDファイルを取り込む機能を作りました。

Unity:2019.4.16f1
Affinity Designer:1.9.2.1035
OS:Windows10

今回の内容はPSDを読み込み、uGUIのImageとして出力するものです。
スライダーやボタンの差し替えの対応までは正直な所、面倒くさかったので対応してません。
(そもそもレイヤーが2層までしか動かないので作るモチベーションが下がってしまった……)

Unity公式の2D PSD Importerが動くバージョンなら、どのバージョンのUnityでも動くとは思いますが、
全てのバージョンで私が使用したUnityのスクリプトが挙動するかまでは確認してないので
私が作った環境のバージョン以降で使用することをオススメします。

また、Macでも動くとは思いますが、もしかしたらpngを生成するscriptは環境に依存する部分があり書き換える必要があるかも知れません。
詳細はスクリプトの解説部分で話します。

リポジトリ


github.com

内容の解説


今回Githubリポジトリを公開しますので、使い方の解説を行った後、
順を追って説明したい思います。
手っ取り早く使いたい人はコードの説明は飛ばしてもらって大丈夫です。

今回のコードの内容の説明は分類すると3つになります。

  1. PSDの取り込み
  2. SpriteRendererで参照しているPSDのテクスチャのpng
  3. SpriteRendererのuGUIへの変換とImport設定

利用方法


github.com

こちらのリポジトリの内容を既存のプロジェクトに反映します。
(入っているPSDとシーンファイルは動作確認で利用したものなので消して構いません)
中に入っているので2D PSD Importerは入っていると思いますが、もし2D PSD Importerがインポートされなかった場合、
Unityのパーケージマネージャーから2D PSD Importerのインストールをお願いします。


1.まずはPSDをUnityに取り込み f:id:OrotiYamatano:20210530212950p:plain (プロジェクト名がPDFtoUnityになってるのは気にしないでw)
Defaultで読み込まれます。
このままでは、1枚の画像としか使えないので、変換します。


2.インポートしたアセットを右クリック、[2D Importer]→[Change PSD File Importer] f:id:OrotiYamatano:20210530213221p:plain ↓以下のように変換される

f:id:OrotiYamatano:20210530212655p:plain

このまま、PSDファイルをuGUIのImageとして使いたいところですが、
取り込んでみると分かると思いますが、SpriteRendererとして読み込まれます。

そのためuGUIで使えるようにSpriteRenderer→Imageへ変換をします。
後、ついでにpsdファイルの画像参照を使い続けるのは個人的に気持ちが悪いので、
psdの画像をpngとしてpsdのアセットの置いてあるフォルダに出力します。

(インポートされるときはSpriteRendererの構成のままインポートされるように作ってあるので、
描画順が気になる方はスクリプトをイジって、Order in Layer順になるようにGameObjectの順番を入れ替えるように変更を加えてください)


3.一旦、変換するためにシーン上に読み込み f:id:OrotiYamatano:20210530214558p:plain


4.右クリック、[SpriteRendereToUGUI]を選択
(すみませんが、一つずつ選択するのしか対応してません)
f:id:OrotiYamatano:20210530214641p:plain

↓uGUI用のアセットが出来上がる

f:id:OrotiYamatano:20210530214714p:plain


5.後は、Canvasを追加して、GameObjectの順番を整えたらインポート完了です! f:id:OrotiYamatano:20210530214914p:plain

Scriptの解説


1. PSDの取り込み


PSDの取り込み自体はUnityで対応してますが、1枚の画像として取り込まれてしまいます。
その場合、2D PSD Importerを使ってPSDを取り込みたいところですが、2D PSD ImporterはPSD Importerという名前のくせして、
PSBファイルしかデフォルトで対応していません。

そこで、PSDファイルをPSBファイルとして読み込ませるように変換取り込み機能を実装します。
実はPSBとPSDは同じようなファイル構成になっており、PSB→PSDは出来ないですが、PSD→PSBは、ほぼほぼ互換性があり、
拡張子を変えるだけでPSBとして読み込まれます。

とは言いつつも一々拡張子を変えて保存するのも億劫です。
スクリプトで変換できるのならスクリプトで対応してしまったほうが楽だなということで、スクリプトで対応しました。

Asset>Editor>PSDImporterOverride.csと、Asset>Editor>PSDImporterOverrideEditor.csがその変換スクリプトにあたります。

実はUnity公式でほぼ同じスクリプトが公開されていたので、それを丸パクリしてきた感じです。

↓パクリ元
docs.unity3d.com

コードを見ていただければ、解説するまでも無いとは思いますが、ざっくり簡単に説明すると、
PSBファイルインポート処理であるPSDImporterの処理をOverrideし、指定したアセットをPSDImporterOverrideでインポートする処理を走らせているだけです。

2.SpriteRendererで参照しているPSDのテクスチャのpng


画像はCreateReadabeTexture2DでTexture2Dで参照しているspriteをrectで切り抜いてpng化しています。

    /// <summary>
    /// Texture2Dから切り取って画像を生成
    /// </summary>
    /// <param name="texture2d"></param>
    /// <param name="rect"></param>
    /// <returns></returns>
    private static Texture2D CreateReadabeTexture2D(Texture2D texture2d,Rect rect)
    {
        RenderTexture renderTexture = RenderTexture.GetTemporary(
            texture2d.width,
            texture2d.height,
            0,
            RenderTextureFormat.Default,
            RenderTextureReadWrite.Linear);
        

        Graphics.Blit(texture2d, renderTexture);
        
        RenderTexture previous = RenderTexture.active;
        RenderTexture.active = renderTexture;
        Texture2D readableTextur2D = new Texture2D((int) rect.width, (int) rect.height);
        readableTextur2D.ReadPixels(new Rect((int) rect.xMin, texture2d.height-rect.yMax, rect.width, rect.height),  0, 0);
        readableTextur2D.Apply();
        RenderTexture.active = previous;
        RenderTexture.ReleaseTemporary(renderTexture);
        return readableTextur2D;
    }

texture2dの内容をrenderTextureに一旦書き込み、編集可能にした後、
readableTextur2Dに対してReadPixelsで読み込み適用しています。

コードを見たとおり
あんまり、難しいことはしていないです。

詰まったのが、texture2d.height-rect.yMax設定箇所なのですが、destX,DestYの0,0は実は左下を表しておりUnityの通常座標系とは異なるようです。
Texture2D-ReadPixels - Unity スクリプトリファレンス

ちなみにMacはグラフィックス APIがMetalで動いているためdestX,DestYの位置がズレるかも知れないです。
同じく実機でも差が出てしまう可能性があるため、この機能を実機で使う場合はご注意ください!
↓参考元

https://anz-note.tumblr.com/post/150526871876/unitytexturereadpixels%E3%81%AE%E5%9F%BA%E6%BA%96%E7%82%B9%E3%81%A3%E3%81%A6%E5%B7%A6%E4%B8%8B00%E3%81%98%E3%82%83%E3%81%AA%E3%81%84%E3%81%AE%E3%81%A3%E3%81%A6%E3%81%84%E3%81%86%E3%81%8A
anz-note.tumblr.com
qiita.com

3. SpriteRendererのuGUIへの変換とImport設定


コードの大半は簡単なので、一部のみ解説します。

    /// <summary>
    /// SpriteRendererをImageに変換
    /// </summary>
    /// <param name="target"></param>
    /// <param name="copy"></param>
    private static void AddImageSprite(SpriteRenderer target, GameObject copy) {
        if (target == null) return;
        var image = copy.AddComponent<UnityEngine.UI.Image>();
            
        var saveBytes = CreateReadabeTexture2D(target.sprite.texture,target.sprite.rect).EncodeToPNG();
            
        Object parentObject = PrefabUtility.GetCorrespondingObjectFromSource(Selection.activeGameObject);
        var path = AssetDatabase.GetAssetPath(parentObject);
        path = Path.GetDirectoryName(path);
        path += "/" + target.sprite.name+".png";
        File.WriteAllBytes(path, saveBytes);

        AssetDatabase.ImportAsset(path);
            
        var importer = AssetImporter.GetAtPath(path) as TextureImporter;
        var settings = new TextureImporterSettings();
        if (!(importer is null)) {
            importer.ReadTextureSettings(settings);
                
            settings.textureType = TextureImporterType.Sprite;
            settings.npotScale = TextureImporterNPOTScale.None;
            settings.spriteMode = (int) SpriteImportMode.Single;
                
            importer.SetTextureSettings(settings);
                
            importer.SaveAndReimport();
        }

        Sprite s = (Sprite)AssetDatabase.LoadAssetAtPath(path, typeof(Sprite));
        image.sprite = s;
    }

まず、引数の説明から、
引数は画像化するSpriteRendererのtargetと
アセットのspriteを最終的にアタッチするcopyゲームオブジェクト
で構成されています。

後、外部変数として選択したオブジェクトを表すSelectionが使われています。
引数からSelectionのオブジェクトを取得するような形にして、依存性の低い丁寧な実装にしても良かったんですが、
ぶっちゃけ、手直しも面倒だったので、テキトーです。

まず、CreateReadabeTexture2Dにてtargetの画像イメージをbyteで取得。
その後、GetCorrespondingObjectFromSourceにて選択したゲームオブジェクトを取得。
GetAssetPathから現在の格納先フォルダを取得して、sprite.nameでpngとして、そのフォルダに吐き出しています。

byteをそのままimage.spriteにアタッチしても良いのですが、
メタファイルはUnity側のデフォルト設定はテキストデータのためメタファイルにbyteを持つのは正直好ましくないです。
なので、pngをアタッチしていきます。

保存したpngをUnityから認識できるようにAssetDatabase.ImportAsset(path);でインポートします。
そのままだと、textureTypeがDefaultになってしまうため、
TextureImporterSettingsを使い、
ReadTextureSettingsで既存のインポート設定を読み込んだ後
textureTypeとnpotScaleとspriteModeだけ変更し
importer.SetTextureSettings(settings);にて設定。

そして、セーブと再インポートをimporter.SaveAndReimport();にて実行します。

後は、保存されたpngをAssetDatabaseで読み込んでimageのspriteに設定しています。

参考元:
qiita.com
hacchi-man.hatenablog.com

まとめ


以上、Affinity DesignerのPSDファイルをUnityのuGUIとして取り込んでみたでした!
Affinity Designerからの読み込みは残念な結果になってしまいましたが、PSDファイルの読み込みは今回のScriptを使えば読み込めると思いますので、
皆さんも試してみてはいかがでしょうか!?

個人的には早くAffinity DesignerがPSDの多重レイヤーを実装して欲しい&実装してくれると信じて待とうかと思います。

今回、久しぶりにアセット拡張周りを触れたので、結構楽しかったし、勉強にもなりました。
特にアセットインポート周りの設定はチマチマしないでスクリプトで設定する方法を学ぶことが出来たのは良かったです。
UI Elementsもマトモに使えるようになってきてると思うので機会があれば、またアセット拡張ネタとか書きたいですね。
個人的には個人制作の作業が効率化出来るようなネタがかけたら嬉しいなぁと思っています。

以上、やまだたいしでした。