Help us understand the problem. What is going on with this article?

[UE4] 誤った方法で uasset を拡張して開発を続け、エンジンアップデートで詰んだと思った話

uasset の拡張方法

はじめに uasset の拡張方法を説明する。

アセットデータ(uasset) を拡張するにはオブジェクトバージョンを追加する必要がある。
オブジェクトバージョンの定義は Engine/Source/Runtime/Core/Public/UObject/ObjectVersion.h で行う。

プロジェクト(ライセンシー)側で追加するには enum EUnrealEngineObjectLicenseeUE4Version に追加する。
enum EUnrealEngineObjectUE4Version はエンジン側が定義しているオブジェクトバージョンであり、こちらに追加するとエンジンアップデート時に困ったことになる。

EUnrealEngineObjectLicenseeUE4Version
    VER_LIC_NONE = 0,
+   VER_LIC_CUSTOM_PARAMETER,
    // - this needs to be the last line (see note below)
    VER_LIC_AUTOMATIC_VERSION_PLUS_ONE,
    VER_LIC_AUTOMATIC_VERSION = VER_LIC_AUTOMATIC_VERSION_PLUS_ONE - 1

オブジェクトファイルのバージョンを取得するには FArchive::LicenseeUE4Ver() or FPackageFileSummary::GetFileVersionLicenseeUE4() or UPackage::LinkerLicenseeVersion を利用する。

データの読み書きは次のようにする。

if (Ar.LicenseeUE4Ver() >= VER_LIC_CUSTOM_PARAMETER) {
    Ar << CustomParameter;
}

このコードでシリアライズ/デシリアライズが行われる。 Ar.IsSaving() でどちらの処理かわかる。
シリアライズ/デシリアライズはシークしながら行われるため、該当機能の Serialize(...) の最後に追加し、順番を変更してはならない。

アセットとパッケージのバージョンを管理する | Unreal Engine ドキュメント

UE4Ver 関数呼び出しを LicenseeUE4Ver に変更すると、正式な Epic のバージョン番号ではなく、ライセンシーのバージョン番号を使用するようにコードが変更されます。 この方法は、独自のバージョンの Unreal Engine 4 を管理している非 Epic ユーザーにお勧めです。

本題

本記事では失敗談として EUnrealEngineObjectUE4Version にバージョンを追加してしまった経緯、問題、回避策を記す。

uassets の拡張方法を知りたいだけの方は、ここで回れ右。

誤改造から問題発覚まで

マテリアルの設定を追加するため、エンジンのコードをお手本に改造を行った。
エンジンの実装は EUnrealEngineObjectUE4Version を参照しているため、何の疑いもなく EUnrealEngineObjectUE4Version に拡張バージョンの定義を追加をした。それから半年以上、何度かエンジンのアップデートを行ったが、何の問題も起きなかった。
この時のエンジンのバージョンは確か4.21。

今回必要に迫られ、4.24 へアップデートし問題が発覚した。
4.24 では EUnrealEngineObjectUE4Version にバージョンが追加されていたのだ。
4.23 までの UE4Version517 だが、拡張バージョンを追加したことで 518 となっていた。
4.24 では、エンジン側でバージョンが追加され UE4Version518 となっている。

この競合状態が原因で、ロード処理が失敗し、エディタが起動する前にクラッシュするようになってしまった。

とりあえず、追加した拡張バージョンを 518、エンジン側で追加されたバージョンを 519 にしてしまえばいいやと思っていた。
ロードに失敗していたプロジェクトコンテンツはロードできるようになり、別のクラッシュが発生していたが、時間も遅かったため、適当に Qiita にまとめて帰宅した。

次の日、作業を再開してエンジンコンテンツがロードできないことに気づいた。
エンジンコンテンツに新しく追加・更新されたファイルも UE4Version = 518 を持っていたのだ。

拡張したエンジンで作成した 518 なファイルは、プロジェクト側の拡張データを持つが、エンジン側で新規に実装されたデータは持っていない。
最新のエンジンコンテンツ 518 なファイルは、エンジン側で新規に実装されたデータを持つが、プロジェクト側の拡張データは持っていない。

この時点では、誤った改造を行ったことに気づいておらず、「詰んだ」とツイートしたり、UDN に質問を投げたりしていた。ごめんなさい。

調査を進めるため、コードを読んでいたら VersionLicensee という文字が目に入った。

Licensee ... ライセンシーバージョン

半年以上前の過ちに気づいた。

私と同じ過ちを犯す同士が必ずいるだろうと思い、ここに記録を残しておく。

プロジェクト前提

エンジンコンテンツはアップデート時に衝突を起こすため、修正が必要であればプロジェクト側にコピーして行っており、エンジンコンテンツに拡張データを持つアセットは存在しない。

競合回避処理の実装

バージョン定義を修正

EUnrealEngineObjectUE4Version には過去に追加したバージョンとエンジン側で追加されたバージョンが同じ値になるように修正。

EUnrealEngineObjectUE4Version
    // Changed the source orientation of point lights to match spot lights (z axis)
    VER_UE4_POINTLIGHT_SOURCE_ORIENTATION,
    // LocalizationId has been added to the package summary (editor-only)
    VER_UE4_ADDED_PACKAGE_SUMMARY_LOCALIZATION_ID,
    // Fixed case insensitive hashes of wide strings containing character values from 128-255
    VER_UE4_FIX_WIDE_STRING_CRC,
+   // Yomuneo Custom: Mobile Fully Rough
+   VER_UE4_YOMUNECO_MOBILE_FULLY_ROUGH,
+   // Added package owner to allow private references
+   VER_UE4_ADDED_PACKAGE_OWNER = VER_UE4_YOMUNECO_MOBILE_FULLY_ROUGH,

    // -----<new versions can be added before this line>-------------------------------------------------
    // - this needs to be the last line (see note below)

    VER_UE4_AUTOMATIC_VERSION_PLUS_ONE,
    VER_UE4_AUTOMATIC_VERSION = VER_UE4_AUTOMATIC_VERSION_PLUS_ONE - 1

EUnrealEngineObjectUE4Version にバージョンを追加。

EUnrealEngineObjectLicenseeUE4Version
    VER_LIC_NONE = 0,
+   VER_LIC_MOBILE_FULLY_ROUGH,
    // - this needs to be the last line (see note below)
    VER_LIC_AUTOMATIC_VERSION_PLUS_ONE,
    VER_LIC_AUTOMATIC_VERSION = VER_LIC_AUTOMATIC_VERSION_PLUS_ONE - 1

拡張データを持つプロジェクトコンテンツを判定する

VER_UE4_YOMUNECO_MOBILE_FULLY_ROUGH より古いバージョンは拡張データを持っておらず、バージョンアップ後のエンジンで更新したコンテンツファイルのライセンシーバージョンは VER_LIC_MOBILE_FULLY_ROUGH となる。
また、最新のエンジンコンテンツは VER_UE4_ADDED_PACKAGE_OWNER == VER_UE4_YOMUNECO_MOBILE_FULLY_ROUGHVER_LIC_NONE をバージョン番号として持っている。
このため、誤ったバージョン番号で拡張データを持つファイルは (UE4Ver == VER_UE4_YOMUNECO_MOBILE_FULLY_ROUGH) && (LicenseeUE4Ver == VER_LIC_NONE) 且つファイルパスが FPaths::ProjectContentDir() で始まるファイルである。

bool bIsLegacyProjectContent = [] (FArchive& Ar) {
    if (!(Ar.UE4Ver() == VER_UE4_YOMUNECO_MOBILE_FULLY_ROUGH && Ar.LicenseeUE4Ver() == VER_LIC_NONE)) {
        return false;
    }
    static auto ProjectContentDir = FPaths::ProjectContentDir();
    static bool bHasProjectContent = FPaths::EngineContentDir() != ProjectContentDir;
    return bHasProjectContent && Ar.GetArchiveName().StartsWith(ProjectContentDir);
} ARCHIVE);

シリアライズ処理を修正

エンジン側で追加された VER_UE4_ADDED_PACKAGE_OWNER のシリアライズ処理を bIsLegacyProjectContent なファイルで行わないように修正。

if (BaseArchive.IsSaving() || (Sum.FileVersionUE4 >= VER_UE4_ADDED_PACKAGE_OWNER && !bIsLegacyProjectContent))
{
    ... // VER_UE4_ADDED_PACKAGE_OWNER の処理

拡張データのシリアライズ処理では、新規に定義した VER_LIC_MOBILE_FULLY_ROUGH を持つファイルと bIsLegacyProjectContent なファイルで行うように修正。

if (Ar.LicenseeUE4Ver() >= VER_LIC_MOBILE_FULLY_ROUGH || bIsLegacyProjectContent)
{
    ... // 拡張データのロード処理

無事に起動することができた。。

副作用

コンテンツロード時に文字列比較を行うため、ロードが遅くなっていると思われる。
プロジェクトコンテンツを再保存すれば対処コードを削除することが出来るため、タイミングを見て全プロジェクトコンテンツを更新する。

Takezoh
株式会社よむネコでレンダリングを学ぶプログラマ
gumi
Python、Erlang、Elixir などちょっと変わった技術でゲームをつくったりする会社。プログラマだけじゃなく、企画、デザイン、イラストなど開発全般揃ってます。
http://gu3.co.jp
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away