uasset の拡張方法
はじめに uasset の拡張方法を説明する。
アセットデータ(uasset) を拡張するにはオブジェクトバージョンを追加する必要がある。
オブジェクトバージョンの定義は Engine/Source/Runtime/Core/Public/UObject/ObjectVersion.h
で行う。
プロジェクト(ライセンシー)側で追加するには enum EUnrealEngineObjectLicenseeUE4Version
に追加する。
enum EUnrealEngineObjectUE4Version
はエンジン側が定義しているオブジェクトバージョンであり、こちらに追加するとエンジンアップデート時に困ったことになる。
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 までの UE4Version は 517 だが、拡張バージョンを追加したことで 518 となっていた。
4.24 では、エンジン側でバージョンが追加され UE4Version は 518 となっている。
この競合状態が原因で、ロード処理が失敗し、エディタが起動する前にクラッシュするようになってしまった。
とりあえず、追加した拡張バージョンを 518、エンジン側で追加されたバージョンを 519 にしてしまえばいいやと思っていた。
ロードに失敗していたプロジェクトコンテンツはロードできるようになり、別のクラッシュが発生していたが、時間も遅かったため、適当に Qiita にまとめて帰宅した。
次の日、作業を再開してエンジンコンテンツがロードできないことに気づいた。
エンジンコンテンツに新しく追加・更新されたファイルも UE4Version = 518 を持っていたのだ。
拡張したエンジンで作成した 518 なファイルは、プロジェクト側の拡張データを持つが、エンジン側で新規に実装されたデータは持っていない。
最新のエンジンコンテンツ 518 なファイルは、エンジン側で新規に実装されたデータを持つが、プロジェクト側の拡張データは持っていない。
この時点では、誤った改造を行ったことに気づいておらず、「詰んだ」とツイートしたり、UDN に質問を投げたりしていた。ごめんなさい。
調査を進めるため、コードを読んでいたら VersionLicensee
という文字が目に入った。
Licensee ... ライセンシーバージョン ?
半年以上前の過ちに気づいた。
私と同じ過ちを犯す同士が必ずいるだろうと思い、ここに記録を残しておく。
プロジェクト前提
エンジンコンテンツはアップデート時に衝突を起こすため、修正が必要であればプロジェクト側にコピーして行っており、エンジンコンテンツに拡張データを持つアセットは存在しない。
競合回避処理の実装
バージョン定義を修正
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
にバージョンを追加。
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_ROUGH
と VER_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)
{
... // 拡張データのロード処理
無事に起動することができた。。
副作用
コンテンツロード時に文字列比較を行うため、ロードが遅くなっていると思われる。
プロジェクトコンテンツを再保存すれば対処コードを削除することが出来るため、タイミングを見て全プロジェクトコンテンツを更新する。