OpenSiv3DでPSDファイルを読み込む

SivPSD

この記事はSiv3D Advent Calendar 2023の21日目の記事として執筆いたしました。

OpenSiv3Dのゲーム開発でPSDファイルを利用したいと思い、PSD読み込みライブラリをpsd_sdkを用いて作成しました。その導入方法と使い方についてまとめます。

github.com

要件

導入方法

📝 導入するOpenSiv3Dのプロジェクトを新規作成または開いてください。ここでは、HelloPSDというプロジェクトで進めます。

📗ターミナルで以下のコマンドを実行し、SivPSDとpsd_sdkをサブモジュールとして追加します。

git submodule add https://github.com/sashi0034/SivPSD
git submodule update --init --recursive
  • gitを用いない場合は、代わりにSivPSDをダウンロードしてプロジェクト直下に配置してください。

📝 SivPSDは現在v0.6.12のSiv3Dが設定されています。ご自身のプロジェクトのバージョンと違う場合は手動またはスクリプトを実行してSivPSDで使うSiv3Dのバージョンを変更してください。例えば、v0.6.10に変更する場合は以下のように実行すれば簡単に変更できます (しかし、バージョンによっては engine 以下や Resource.rc 等を手動で修正する必要があります)

cd .\SivPSD\SivPSD\
python .\s3d_switch.py 0_6_10

📗 SivPSDをソリューションに追加します。ソリューションエクスプローラーのルートから右クリックをして、追加 / 既存のプロジェクトを選択します。プロジェクトルートからSivPSD/SivPSD/SivPSD.vcproj を探して追加します。

📝 同様の手順で SivPSD/psd_sdk/build/VS2022/Psd.vcxproj も追加します。

📗 プロジェクトからSivPSDを参照します。自身のプロジェクトを右クリックして、追加 / 参照 をクリックします。SivPSDにチェックマークを入れてOKを押してください。

🎉以上で導入は終わりです。

(かなり手間のかかる作業になるので、時間のあるときに psd_sdk と一緒にヘッダオンリにまとめて簡略化したいところです...)

使い方

基本的なコード

📝 以下のようPSDを読み込んで使用できます。

PSDImporter psdImporter{U"path/to/file.psd"}; // 読み込み
if (const auto e = psdImporter.getCriticalError()) throw e; // エラー
PSDObject psdObject = psdImporter.getObject(); // 読み込んだデータの取得
while (System::Update())
{
    psdObject.draw(); // PSDを描画
}

📗 psdObject.layers[index] でレイヤー単体にもアクセスが可能です。以下にサンプルコードを掲載するのでご参考ください。イラスト miko15.psdSivPSD/Test/App/psd に配置しました。

gist.github.com

実行結果

非同期処理にして実行

📝 このPSDImporterですが、以下のようにコンストラクタで細かな設定が可能です。

  • storeTarget でレイヤーを Image として格納、DynamicTexture として格納するかの設定が可能です。

  • storeTarget で読み込み時に用いるスレッド数を決められます。適切なスレッド数を決めることで高速な読み込みが実現できます。

  • asyncStartでバックグラウンドとして非同期実行ができます。

PSDImporter psdImporter{
    {
        .filepath = U"psd/miko15.psd",
        .storeTarget = StoreTarget::MipmapTexture,
        .maxThreads = 4,
        .asyncStart= true
    }
};

📗 非同期処理にしたサンプルコードは以下をご確認ください。

gist.github.com

PSD形式の注意について

今回実装したライブラリではレイヤーのマスク処理やクリッピング機能が実装されていません。

また、カラーモードやカラーチャンネルについてもRGB、8ビット/チャンネルと制限しました。

CLIP STUDIO PAINTでイラストを作成するときは基本的にこのカラーモードやカラーチャンネルになっているようです。

Live2D Cubismのマニュアルを確認しますとこのような細かい制約が確認できます。

今回の開発においてもこの規則に則っていくことにしました。

🤖レイヤーの統合作業は大変だと思います。こちらは蛇足になりますが、今回次のようなクリスタで使えるレイヤー自動統合スクリプトを作ってみました。ぜひご活用ください。

C#でdefer

C#でdefer

IDisposableとusingで再現。

    public class ScopedDefer : IDisposable
    {
        private readonly Action action;

        public ScopedDefer(Action action)
        {
            this.action = action;
        }

        public void Dispose()
        {
            action();
        }
    }

使い方

stack++;
using var _ = new ScopedDefer(() => stack--);

DOTweenとUniTaskのキャンセル処理について

DOTweenとDOTweenの間にキャンセル処理を挟むとき....

await transform.DOMoveY(5.0f, 0.3f).SetEase(Ease.OutBack)
    .ToUniTask(cancellationToken: cancel);
cancel.ThrowIfCancellationRequested();
await transform.DOMoveY(-5.0f, 0.3f).SetEase(Ease.OutBack)
    .ToUniTask(cancellationToken: cancel);

これだとコードが冗長になってしまう感じがする

キャンセル用の拡張メソッドをつくる

    public static class UniTaskExtensions
    {
        public static async UniTask ThrowIfCancelled(this UniTask task, CancellationToken cancel)
        {
            await task;
            cancel.ThrowIfCancellationRequested();
        }

        public static async UniTask ThrowIfCancelled(this Tween tween, CancellationToken cancel)
        {
            await tween.ToUniTask(cancellationToken: cancel).ThrowIfCancelled(cancel);

        }
    }

こうすれば簡潔に書けてよさそう

await transform.DOMoveY(5.0f, 0.3f).SetEase(Ease.OutBack).ThrowIfCancelled(cancel);
await transform.DOMoveY(-5.0f, 0.3f).SetEase(Ease.OutBack).ThrowIfCancelled(cancel);

VisualC++で異なるディレクトリに同名ファイルが入っていてもビルドが通るようにする

以下の記事を参照した。

altebute.hatenablog.com

構成プロパティ / C++ / 出力ファイル

ASM リストの場所オブジェクト ファイル名を変える。

上記事では $(IntDir)%(RelativeDir) であったが、%(RelativeDir)絶対パスになってしまったのでこれだけならビルドが通った

追加: <ObjectFileName>$(IntDir)%(Directory)</ObjectFileName> でいけた