LoginSignup
1
1

More than 1 year has passed since last update.

UE4 チャンク機能(pak分割)についてのメモ

Last updated at Posted at 2021-06-24

概要

UnrealEngine4のコンテンツpakファイルを分割するチャンク機能についてのメモです。
モバイルアプリだけではなくDLCとかでも多分使います。

更新履歴

日付 内容
2021/07/13 手動設定について記述修正

参考

以下の記事を参考にいたしました、ありがとうございます。
UE4公式:クック処理とチャンク化
UE4公式:チャンク用のアセットを準備する
UE4公式:チャンク化による高速ダウンロードを可能にするツールと最適化の考察
UE4のモバイル開発におけるコンテンツアップデートの話 - Chunk IDとの激闘編 -
[UE4] uasset以外のアセットにChunkIDを設定する(ue4.22以降)

環境

Windows10
Visual Studio 2017
UnrealEngine 4.26

Chunkについて

DLCなどアセットの大きな単位をチャンク(Chunk)と呼んでいて、ChunkIDを設定したものが同じパックファイル(.pak)としてクック時にまとめられます。
ChunkID = 0 はデフォルトパッケージでDLCなどの追加分はChunkIDを1以降に設定します。

プロジェクト設定

[プロジェクト設定] → [プロジェクト/パッケージ化]
で [pakファイルを使用][チャンクの生成]を有効にする必要があります。

ProjectSetting.jpg

パッケージ作成時にpakファイルができるフォルダ

以下のフォルダに
Saved\StagedBuilds{Platform}{ProjectName}\Content\Paks\

pakchunk0-{Platform}.pak
pakchunk1-{Platform}.pak
pakchunk2-{Platform}.pak
...

という名前で作成されるようです。

pakファイルのファイルリスト確認

以下のフォルダに
Saved\TmpPackaging{Platform}\

pakchunk0.txt
pakchunk1.txt
pakchunk2.txt
...

という名前でchunk毎にファイルリストが作成されるようです。

ChunkIDの設定方法

手動設定

[エディタの環境設定] -> [実験段階] -> [ユーザーインターフェース] -> [チャンクIDの割り当てを許可]
にチェックを入れます。
AllowChunkIDAssignments.png

アセットを右クリックから
[アセットアクション] -> [チャンクに割り当てる] -> [チャンクIDを入力]

ChunkId_Manual.png

複数まとめての設定もできます。

PrimaryAssetLabel を使った設定

コンテンツブラウザから右クリック -> [その他] -> [データアセット] を選択し、[データアセットクラスを選択]にて PrimaryAssetLabel を選択することでアセットラベルを作成でき、個のアセットにてチャンクIDの設定をすることができます。

Sel_PrimaryAssetLabel.jpg

▼ 関連ソース
"Engine\Source\Runtime\Engine\Classes\Engine\DataAsset.h"
"Engine\Source\Runtime\Engine\Private\DataAsset.cpp"
"Engine\Source\Runtime\Engine\Classes\Engine\PrimaryAssetLabel.h"
"Engine\Source\Runtime\Engine\Private\PrimaryAssetLabel.cpp"

PrimaryAssetLabel.jpg

上記のようにChunk ID1にして、Label Asset in My Directory のチェックを入れるとこのアセットが置かれたフォルダ以下全てのアセットに対しチャンクID=1 が設定されます。(重複がない場合)

PrimaryAssetLabel を自前で改造して使う例

UPrimaryAssetLabelUPrimaryDataAsset を継承したクラスなので、更に継承して(あるいはUPrimaryDataAssetを継承して)プロパティを追加したり処理を修正することが可能です。
以下のクラスは bLabelAssetsInMyDirectory をチェックすると自身のアセットが置かれているフォルダ以下全てのチャックIDを設定するところを更に ExcludeMyDirectory を設定することによって対象を除去する機能をつけてみたクラスです。

UPrimaryAssetLabelクラスとの違いは、
1.対象外ディレクトリ指定用のExcludeMyDirectoryを追加
2.プライマリアセットタイプの追加(AssetType)とその関連メソッド(GetIdentifierString(), GetPrimaryAssetId())の追加
です。

MyPrimaryAssetLabel.h
#pragma once

#include "Engine/DataAsset.h"
#include "Engine/AssetManagerTypes.h"
#include "Engine/EngineTypes.h"
#include "MyPrimaryAssetLabel.generated.h"

// 参照されたアセットをこのプライマリアセットの一部としてマークするために作成されるシードファイル
UCLASS()
class TESTAPP_API UMyPrimaryAssetLabel : public UPrimaryDataAsset
{
    GENERATED_BODY()

public:
    // ディレクトリタグ付きアセットに使用されるバンドル名
    static const FName DirectoryBundle;

    // コレクションアセットに使用されるバンドル
    static const FName CollectionBundle;

public:
    // コンストラクタ
    UMyPrimaryAssetLabel();

    // プライマリアセットID名を取得する
    UFUNCTION(BlueprintCallable, Category = "MyPrimaryAssetLabel")
        FString GetIdentifierString() const;

    // プライマリアセットIDを取得する(要オーバーライド)
    virtual FPrimaryAssetId GetPrimaryAssetId() const override;

    // プライマリアセットタイプ
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "MyPrimaryAssetLabel")
        FPrimaryAssetType AssetType;

    // この特定のアセットの管理ルールが設定されている場合、タイプルールが上書きされます
    UPROPERTY(EditAnywhere, Category = "Rules", meta = (ShowOnlyInnerProperties))
    FPrimaryAssetRules Rules;

    // このディレクトリとサブディレクトリ内のすべてにラベルを付けるにはTrue
    UPROPERTY(EditAnywhere, Category = "PrimaryAssetLabel")
    uint32 bLabelAssetsInMyDirectory : 1;

    // このディレクトリからラベルを付けないディレクトリ
    UPROPERTY(EditAnywhere, Category = "PrimaryAssetLabel", meta = (EditCondition=bLabelAssetsInMyDirectory, RelativeToGameContentDir, LongPackageName))
    TArray<FDirectoryPath> ExcludeMyDirectory;

    // ラベルアセット自体をクックして実行時に使用できるようにする必要がある場合は、trueに設定します。
    // これは、ラベルが付けられているアセットには影響しません。クックルールで設定されます
    UPROPERTY(EditAnywhere, Category = "PrimaryAssetLabel")
    uint32 bIsRuntimeLabel : 1;

    // ラベル付けする手動で指定されたアセットのリスト
    UPROPERTY(EditAnywhere, Category = "PrimaryAssetLabel", meta = (AssetBundles = "Explicit"))
    TArray<TSoftObjectPtr<UObject>> ExplicitAssets;

    // ラベルを付けるために手動で指定したブループリントアセットのリスト
    UPROPERTY(EditAnywhere, Category = "PrimaryAssetLabel", meta = (AssetBundles = "Explicit", BlueprintBaseOnly))
    TArray<TSoftClassPtr<UObject>> ExplicitBlueprints;

    // アセット参照をロードするためのコレクション
    UPROPERTY(EditAnywhere, Category = "PrimaryAssetLabel")
    FCollectionReference AssetCollection;

    // これがクックされたビルドで利用できない場合にのみエディターに設定します
    virtual bool IsEditorOnly() const
    {
        return !bIsRuntimeLabel;
    }

#if WITH_EDITORONLY_DATA
    // これにより、クラスでアセットプロパティのAssetBundlesメタデータがスキャンされ、InitializeAssetBundlesFromMetadataを使用してAssetBundleDataが初期化されます。
    virtual void UpdateAssetBundleData();
#endif

};

 
UPrimaryAssetLabelクラスとの違いは、
1. DirectoryAssets を回すfor文の中で対象外のディレクトリをチェックする処理を追加(除外パスのチェック処理が文字列を含むかContainsのチェックしかやっていないので厳密なチェックではないことに注意)
2. コンストラクタでプライマリアセットタイプの設定
3. プライマリアセットID関連メソッドの追加
です。

MyPrimaryAssetLabel.cpp
#include "MyPrimaryAssetLabel.h"
#include "Engine/DataAsset.h"
#include "Engine/EngineTypes.h"
#include "Misc/PackageName.h"
#include "UObject/Package.h"
#include "Engine/AssetManager.h"
#include "AssetRegistryModule.h"

#if WITH_EDITOR
#include "CollectionManagerTypes.h"
#include "ICollectionManager.h"
#include "CollectionManagerModule.h"
#endif

const FName UMyPrimaryAssetLabel::DirectoryBundle = FName("Directory");
const FName UMyPrimaryAssetLabel::CollectionBundle = FName("Collection");

// コンストラクタ
UMyPrimaryAssetLabel::UMyPrimaryAssetLabel()
{
    bLabelAssetsInMyDirectory = false;
    bIsRuntimeLabel = false;

    // By default have low priority and don't recurse
    Rules.bApplyRecursively = false;
    Rules.Priority = 0;

    AssetType = TEXT("MyPrimaryAssetLabel");
}

// プライマリアセットID名を取得する
FString UMyPrimaryAssetLabel::GetIdentifierString() const
{
    return( GetPrimaryAssetId().ToString() );
}

// プライマリアセットIDを取得する
FPrimaryAssetId UMyPrimaryAssetLabel::GetPrimaryAssetId() const
{
    return( FPrimaryAssetId(AssetType, GetFName()) );
}

#if WITH_EDITORONLY_DATA
// アセットバンドルデータの更新
void UMyPrimaryAssetLabel::UpdateAssetBundleData()
{
    Super::UpdateAssetBundleData();

    if (!UAssetManager::IsValid())
    {
        return;
    }

    UAssetManager& Manager = UAssetManager::Get();
    IAssetRegistry& AssetRegistry = Manager.GetAssetRegistry();

    // アセットが置かれているディレクトリ以下を含むか?
    if (bLabelAssetsInMyDirectory)
    {
        FName PackagePath = FName(*FPackageName::GetLongPackagePath(GetOutermost()->GetName()));

        TArray<FAssetData> DirectoryAssets;
        AssetRegistry.GetAssetsByPath(PackagePath, DirectoryAssets, true);

        TArray<FSoftObjectPath> NewPaths;

        for (const FAssetData& AssetData : DirectoryAssets)
        {
            // 除去ディレクトリチェック
            // #除外パス文字列を含むかどうかしか調べていないのでフォルダ構成によっては問題がある
            bool _IsExclude = false;
            FString _PakPath = AssetData.PackagePath.ToString();
            for(auto& _ExcludeDir : ExcludeMyDirectory){
                if( _PakPath.Contains(_ExcludeDir.Path) ){
                    _IsExclude = true;
                    break;
                }
            }
            if(_IsExclude)continue;

            FSoftObjectPath AssetRef = Manager.GetAssetPathForData(AssetData);

            if (!AssetRef.IsNull())
            {
                NewPaths.Add(AssetRef);
            }
        }

        // Fast set, destroys NewPaths
        AssetBundleData.SetBundleAssets(DirectoryBundle, MoveTemp(NewPaths));
    }

    if (AssetCollection.CollectionName != NAME_None)
    {
        TArray<FSoftObjectPath> NewPaths;
        TArray<FName> CollectionAssets;
        ICollectionManager& CollectionManager = FCollectionManagerModule::GetModule().Get();
        CollectionManager.GetAssetsInCollection(AssetCollection.CollectionName, ECollectionShareType::CST_All, CollectionAssets);
        for (int32 Index = 0; Index < CollectionAssets.Num(); ++Index)
        {
            FAssetData FoundAsset = Manager.GetAssetRegistry().GetAssetByObjectPath(CollectionAssets[Index]);

            FSoftObjectPath AssetRef = Manager.GetAssetPathForData(FoundAsset);

            if (!AssetRef.IsNull())
            {
                NewPaths.Add(AssetRef);
            }
        }

        // Fast set, destroys NewPaths
        AssetBundleData.SetBundleAssets(CollectionBundle, MoveTemp(NewPaths));
    }

    // Update rules
    FPrimaryAssetId PrimaryAssetId = GetPrimaryAssetId();
    Manager.SetPrimaryAssetRules(PrimaryAssetId, Rules);
}
#endif

また作成したクラスは、[プロジェクト設定] -> [ゲーム/アセットマネージャ―] -> [スキャンするプライマリアセットタイプ]へPrimaryAssetLabelと同じように追加登録が必要です。
PrimaryAssetType_Setting.jpg

アセットは以下の様になります。ExcludeMyDirectory設定が追加されています。
MyPrimaryAssetLabel.jpg

不要な設定を除去するとすっきりしていいかもしれません。

ChunkIDの親子設定

DefaultEngine.iniに対して親子関係を設定することができます。
チャンク1を親として、チャンク100と101が子になっている設定例

DefaultEngine.ini
[/Script/UnrealEd.ChunkDependencyInfo]
+DependencyArray=(ChunkID=100,ParentChunkID=1)
+DependencyArray=(ChunkID=101,ParentChunkID=1)

ChunkIDの確認方法

AssetAuditを使う

[ウィンドウ] -> [デベロッパーツール] -> [アセットの監査]
で AssetAudit ウィンドウが開き、[チャンクを追加]にてチャンクIDとディスクサイズ等が確認できるようです。
AssetAudit.jpg

右クリックから[参照ビューワ]も使えますが、アセット数が多いとわけわからないのでちょっと使いにくいと思います。

パッケージ作成した場合に Saved\TmpPackaging\{Platform}\ フォルダにチャンクごとのファイルリストが作られるのでこれを見るのが一番確実かもしれません。

まとめ

アセットが大量だとチャンクIDが正しく設定されているのかどうかの確認がかなり大変でフォルダ分けなどが考慮されているといいのですが、そうはいかないケースが多いと思います。(途中で入れる内容が変わったり)
なのでチャンクIDの確認が(AssetAuditよりも)より簡単にできるといいのですが、例えばコンテンツブラウザなどでフィルター表示などができれば便利かもしれません。:smile:

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1