とあるゲームプログラマの挑戦と敗北の歴史

UE4とプログラミングとmayaとpythonとhtmlとマラソンを中心に情報を発信する元「技術ブログを目指すブログ」から再始動した毎日の日々を発信するブログです。

UE4:独自のアセットの作り方

UE4で独自のアセットの作り方を制作し、
外部ファイルからインポートする方法についてのおぼえ書きです。


さっそくでなんですが・・
お世話になっているヒストリアさんのブログを参考にする方が早いです。
※こっから学びました、無事動作しました。

https://historia.co.jp/archives/1401/
https://historia.co.jp/archives/1423/
https://historia.co.jp/archives/1425/
historia.co.jp



いつもどおりプロジェクトを作ります。
ここではプロジェクト名をResTestとしています。
f:id:toncrimentan_w:20210307162554p:plain



プロジェクトに
MyAsset.h MyAsset.cpp MyAssetFactory.h MyAssetFactory.cpp
をそれぞれを追加します。
f:id:toncrimentan_w:20210307162720p:plain
※追加したら忘れずにGenerateVisualStudioProjectfilesを実行し
 VisualStudioのプロジェクトファイルを更新しておく必要があります。
f:id:toncrimentan_w:20210307162737p:plain



ResTest.Build.csの改造
事前にPublicDependencyModuleNamesにUnrealEdを追加しておく必要があります。
ResTest.Build.csを開き以下のように改造します

PublicDependencyModuleNames.AddRange(new string { "Core", "CoreUObject", "Engine", "InputCore" });
  ↓
PublicDependencyModuleNames.AddRange(new string
{ "Core", "CoreUObject", "Engine", "InputCore", "UnrealEd" });

using UnrealBuildTool;

public class ResTest : ModuleRules
{
	public ResTest(ReadOnlyTargetRules Target) : base(Target)
	{
		PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
		PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "UnrealEd" });
		PrivateDependencyModuleNames.AddRange(new string[] {  });
	}
}


つづいてMyAssetの中身を実装します。
MyAsset.h)

#pragma once
 
#include "CoreMinimal.h"

#include "MyAsset.generated.h"

UCLASS()
class RESTEST_API UMyAsset 
	: public UObject
{
	GENERATED_UCLASS_BODY()
	
public:
	UPROPERTY(EditAnywhere)
	int32 ValueA;
 
	UPROPERTY(EditAnywhere)
	int32 ValueB;
 
	UPROPERTY(EditAnywhere)
	int32 ValueC;
};

MyAsset.cpp)

#include "MyAsset.h"
  
UMyAsset::UMyAsset(const FObjectInitializer& ObjectInitializer) 
	: Super(ObjectInitializer)
{
}

つづいてMyAssetFactoryの中身を実装します。
MyAssetFactory.h)

#pragma once

#include "CoreMinimal.h"
#include "Factories/Factory.h"

#include "MyAssetFactory.generated.h"

UCLASS()
class RESTEST_API UMyAssetFactory 
	: public UFactory
{
	GENERATED_UCLASS_BODY()

	virtual bool DoesSupportClass(UClass* Class) override;
	virtual UClass* ResolveSupportedClass() override;
	virtual UObject* FactoryCreateNew(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override;
	virtual UObject* FactoryCreateText(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, const TCHAR* Type, const TCHAR*& Buffer, const TCHAR* BuferEnd, FFeedbackContext* Warn) override;
};

MyAssetFactory.cpp)

#include "MyAssetFactory.h"

#include "Misc/FileHelper.h"
#include "MyAsset.h"

UMyAssetFactory::UMyAssetFactory(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
{
	SupportedClass = UMyAsset::StaticClass();
	bCreateNew = false;
	bEditorImport = true;
	bText = true;
	Formats.Add(TEXT("myasset;My Asset"));
}

bool UMyAssetFactory::DoesSupportClass(UClass* Class)
{
	return (Class == UMyAsset::StaticClass());
}

UClass* UMyAssetFactory::ResolveSupportedClass()
{
	return UMyAsset::StaticClass();
}

UObject* UMyAssetFactory::FactoryCreateNew(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn)
{
	UMyAsset* NewMyAsset = CastChecked<UMyAsset>(StaticConstructObject_Internal(InClass, InParent, InName, Flags));
	return NewMyAsset;
}

UObject* UMyAssetFactory::FactoryCreateText(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, const TCHAR* Type, const TCHAR*& Buffer, const TCHAR* BuferEnd, FFeedbackContext* Warn)
{
	TArray<FString> Values;
	FString(Buffer).ParseIntoArray(Values, TEXT(","), true);

	UMyAsset* NewMyAsset = CastChecked<UMyAsset>(StaticConstructObject_Internal(InClass, InParent, InName, Flags));
	if (NewMyAsset && (3 <= Values.Num()))
	{
		NewMyAsset->ValueA = FCString::Atoi(*Values[0]);
		NewMyAsset->ValueB = FCString::Atoi(*Values[1]);
		NewMyAsset->ValueC = FCString::Atoi(*Values[2]);
	}

	return NewMyAsset;
}

これで無事ビルドがとおるはずです。



外部データのインポート
ためしにインポート元のファイル(test.myasset⦆を作成しインポートしてみたいと思います。

test.myassetの中身は1,2,3としています。
f:id:toncrimentan_w:20210307164433p:plain



test.myassetをドロップするとアセットが出来上がることが確認できます。
f:id:toncrimentan_w:20210307235553p:plain



中身を開くとこのようにtest.myassetの中身が読み込まれていることを
確認できます。
f:id:toncrimentan_w:20210307235630p:plain





しかしながらもう一度インポートすると失敗してしまいます。
こんどは再インポートに対応する方法を記述します。
f:id:toncrimentan_w:20210307235909p:plain



MyAsset.hの修正
・AssetImportDataメンバ変数を指定作成します。

#pragma once
 
#include "CoreMinimal.h"

#include "MyAsset.generated.h"

UCLASS()
class RESTEST_API UMyAsset 
	: public UObject
{
	GENERATED_UCLASS_BODY()
	
public:
	UPROPERTY(EditAnywhere)
	int32 ValueA;
 
	UPROPERTY(EditAnywhere)
	int32 ValueB;
 
	UPROPERTY(EditAnywhere)
	int32 ValueC;

	// ☆追加
#if WITH_EDITORONLY_DATA
	UPROPERTY(EditAnywhere, Instanced, Category = Reimport)
	class UAssetImportData* AssetImportData;
#endif
};

MyAssetFactory.hの修正
・EditorReimportHandler.hをインクルード
・FReimportHandlerを継承するように修正
・オーバーラード関数を定義します。

#pragma once

#include "CoreMinimal.h"
#include "Factories/Factory.h"
#include "EditorReimportHandler.h"

#include "MyAssetFactory.generated.h"

UCLASS()
class RESTEST_API UMyAssetFactory 
	: public UFactory
	, public FReimportHandler
{
	GENERATED_UCLASS_BODY()

	virtual bool DoesSupportClass(UClass* Class) override;
	virtual UClass* ResolveSupportedClass() override;
	virtual UObject* FactoryCreateNew(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override;
	virtual UObject* FactoryCreateText(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, const TCHAR* Type, const TCHAR*& Buffer, const TCHAR* BuferEnd, FFeedbackContext* Warn) override;

	// ☆追加
	virtual bool CanReimport(UObject* Obj, TArray<FString>& OutFilenames) override;
	virtual void SetReimportPaths(UObject* Obj, const TArray<FString>& NewReimportPaths) override;
	virtual EReimportResult::Type Reimport(UObject* Obj) override;
};

MyAssetFactory.cppの修正
・AssetImportData.hをインクルード
・MyAsset.AssetImportDataの設定部分を追加
・オーバーライド関数の実装を定義します。

#include "MyAssetFactory.h"

#include "Misc/FileHelper.h"
#include "EditorFrameWork/AssetImportData.h"
#include "MyAsset.h"

UMyAssetFactory::UMyAssetFactory(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
{
	SupportedClass = UMyAsset::StaticClass();
	bCreateNew = false;
	bEditorImport = true;
	bText = true;
	Formats.Add(TEXT("myasset;My Asset"));
}

bool UMyAssetFactory::DoesSupportClass(UClass* Class)
{
	return (Class == UMyAsset::StaticClass());
}

UClass* UMyAssetFactory::ResolveSupportedClass()
{
	return UMyAsset::StaticClass();
}

UObject* UMyAssetFactory::FactoryCreateNew(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn)
{
	UMyAsset* NewMyAsset = CastChecked<UMyAsset>(StaticConstructObject_Internal(InClass, InParent, InName, Flags));
	return NewMyAsset;
}

UObject* UMyAssetFactory::FactoryCreateText(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, const TCHAR* Type, const TCHAR*& Buffer, const TCHAR* BuferEnd, FFeedbackContext* Warn)
{
	TArray<FString> Values;
	FString(Buffer).ParseIntoArray(Values, TEXT(","), true);

	UMyAsset* NewMyAsset = CastChecked<UMyAsset>(StaticConstructObject_Internal(InClass, InParent, InName, Flags));
	if (NewMyAsset && (3 <= Values.Num()))
	{
		NewMyAsset->ValueA = FCString::Atoi(*Values[0]);
		NewMyAsset->ValueB = FCString::Atoi(*Values[1]);
		NewMyAsset->ValueC = FCString::Atoi(*Values[2]);

		// ☆追加
		if (!NewMyAsset->AssetImportData)
		{
			NewMyAsset->AssetImportData = NewObject<UAssetImportData>(NewMyAsset, UAssetImportData::StaticClass());
		}
		NewMyAsset->AssetImportData->Update(CurrentFilename);
	}

	return NewMyAsset;
}

// ☆追加
bool UMyAssetFactory::CanReimport(UObject* Obj, TArray<FString>& OutFilenames)
{
    UMyAsset* MyAsset = Cast<UMyAsset>(Obj);
    if (MyAsset && MyAsset->AssetImportData)
    {
        MyAsset->AssetImportData->ExtractFilenames(OutFilenames);
        return true;
    }
    return false;
}

// ☆追加
void UMyAssetFactory::SetReimportPaths(UObject* Obj, const TArray<FString>& NewReimportPaths)
{
    UMyAsset* MyAsset = Cast<UMyAsset>(Obj);
    if (MyAsset && ensure(NewReimportPaths.Num() == 1))
    {
        MyAsset->AssetImportData->UpdateFilenameOnly(NewReimportPaths[0]);
    }
}

// ☆追加
EReimportResult::Type UMyAssetFactory::Reimport(UObject* Obj)
{
    UMyAsset* MyAsset = Cast<UMyAsset>(Obj);
    if (!MyAsset)
    {
        return EReimportResult::Failed;
    }

    const FString Filename = MyAsset->AssetImportData->GetFirstFilename();
    if (!Filename.Len() || IFileManager::Get().FileSize(*Filename) == INDEX_NONE)
    {
        return EReimportResult::Failed;
    }

    EReimportResult::Type Result = EReimportResult::Failed;
    if (UFactory::StaticImportObject(MyAsset->GetClass(), MyAsset->GetOuter(), *MyAsset->GetName(), RF_Public|RF_Standalone, *Filename, NULL, this))
    {
        if (MyAsset->GetOuter())
        {
            MyAsset->GetOuter()->MarkPackageDirty();
        }else
        {
            MyAsset->MarkPackageDirty();
        }
        return EReimportResult::Succeeded;
    }

    return EReimportResult::Failed;
}

これで無事ビルドがとおるはずです。



ためしにもう一度ファイルをドロップしてインポートしても
問題なく再インポートに成功します。
f:id:toncrimentan_w:20210308001409p:plain