Post

UE - Lyra와 Data Asset

UnrealEngine

UE - Lyra와 Data Asset

오늘부터 팀원들과 Lyra 분석을 시작했다.

분석 내용

나는 시스템 플로우를 분석하기 위해 첫 시작 맵을 기준으로 분석을 시작했다.

프로젝트를 실행하고 첫 화면은 이렇게 생겼다.

이건 실행한 모습

옆 아웃라이너를 확인해 보니 플레이 중일 때 생성되는 객체가 있다는 걸 확인했다.

실행 전

실행 후

확인해 보니 텔레포트가 생성되는 부분을 찾을 수 있었다.

저 클래스 인스턴스를 삭제하면 포탈 생성이 안될테니 테스트까지 해봤다.

원본

지운 뒤

확실하기에 블루프린트를 살펴봤다.

텔레포트는 Primary Asset Id를 가지고 생성된다.

Primary Asset IdData Asset을 상속받은 클래스인데, 여기서 Data Asset은 게임의 다양한 데이터와 설정을 저장하고 관리하기 위해 사용되는 특수한 데이터 객체다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
UCLASS(abstract, MinimalAPI, Meta = (LoadBehavior = "LazyOnDemand"))
class UDataAsset : public UObject
{
	GENERATED_UCLASS_BODY()
public:
	// UObject interface
#if WITH_EDITORONLY_DATA
	ENGINE_API virtual void Serialize(FStructuredArchiveRecord Record) override;
#endif

private:
	UPROPERTY(AssetRegistrySearchable)
	TSubclassOf<UDataAsset> NativeClass;
};

이게 UDataAsset 코드 모습이다.

UCLASS 에 대해 설명하자면

  • abstract: 추상 클래스다.
  • MinimalAPI: 멤버를 모듈 외부에서 사용할 수 있도록 최소한의 API 노출을 제공한다.
  • Meta = (LoadBehavior = "LazyOnDemand"): 클래스에 메타데이터를 추가하기 위한 옵션인데, 이는 클래스의 로드 방식을 필요할 때만 로드하도록 지정한다는 뜻 같다.

사실 모듈과 로드는 아직 와닿지 않아서 잘 모르겠다.

여기서 기억에 남는 건 변수 NativeClass인데, UPROPERTYAssetRegistrySearchable이 붙으면 AssetRegistry에서 해당 속성을 검색할 수 있게 설정하는 메타데이터라고 한다.

이러면 에셋 브라우저나 에셋 검색 시스템에서 탐색이 가능해진다.

1
2
3
4
5
UDataAsset::UDataAsset(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
{
	NativeClass = GetClass();
}

이렇게 생성자에서 자신의 클래스 타입을 저장하고, 저걸 AssetRegistry가 찾을 수 있게 되는 것이다.

사실 플로우에 대해서 좀 더 공부하고 싶었지만, Data Asset을 보니 사용할 부분이 많을 것 같아서 사용법에 대해 공부했다.

Data Asset


이 문서를 보고 만들어봤다.

여기 가장 첫 문단의 설명을 보면 프라이머리 데이터 에셋이 Primary Asset ID와 에셋 번들이 지원되어 Asset Manager에서 수동으로 로드 및 언로드할 수 있다고 나온다.

프라이머리 데이터 에셋은 Asset Manager로 관리가 가능하고 고유 ID를 가지며, 이 ID로 탐색이 가능하다고 한다. 이 외에도 같은 클래스 타입의 프라이머리 데이터 에셋들은 하나의 구조체 안에서 관리된다고 한다. 이 구조체 자체를 반환하는 함수도 있는 것 같아서 좀 더 유틸성이 좋아 보인다.

아무튼, 데이터 에셋을 생성하는 법은 간단하다.

우선 C++로 DataAsset을 상속받는 클래스를 만들어줬다.

그다음, 저장할 데이터 정보를 담은 구조체와, 구조체들을 담을 컨테이너를 변수로 만들었다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#pragma once

#include "CoreMinimal.h"
#include "Engine/DataAsset.h"
#include "WeaponDataAsset.generated.h"

USTRUCT(BlueprintType)
struct FWeaponAssetInfo
{
	GENERATED_BODY()

	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	FString WeaponName;

	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	UStaticMesh* Mesh;

	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	float Damage;
};


UCLASS(BlueprintType)
class LYRAPRACTICE_API UWeaponDataAsset : public UDataAsset
{
	GENERATED_BODY()
public:
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon Data")
	TArray<FWeaponAssetInfo> Assets;
};

이제 이 클래스를 가지고 콘텐츠 브라우저에서 데이터 에셋을 만들어준다.

테스트를 위해 값을 넣어줬다.

블루프린트 버전

블루프린트 클래스에서 데이터 에셋 클래스 변수와 에셋 데이터를 담은 구조체 변수를 만들어줬다.

DataAsset이 데이터 에셋 변수고, AssetInfo가 정보를 담은 구조체다.

DataAsset에 초기값을 설정해줬다. 기본값을 선택할 수 있는데, 여기서 방금 만든 데이터 에셋을 넣어줬다.

이제 데이터 에셋의 첫 번째 인덱스에 있는 값을 사용하면 된다.

일단은 메쉬만

C++ 버전

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "WeaponDataAsset.h"
#include "C_Weapon.generated.h"


UCLASS()
class LYRAPRACTICE_API AC_Weapon : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	AC_Weapon();

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;
	FWeaponAssetInfo info;

	UPROPERTY(VisibleAnywhere)
	UStaticMeshComponent* MeshComponent;
};

블루프린트와 비슷하게 에셋 정보를 담을 데이터와 실제 컴포넌트로 사용할 메쉬를 생성했다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
AC_Weapon::AC_Weapon()
{
	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;
	
	MeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("WeaponMesh"));
	RootComponent = MeshComponent;
	
	{
		// 자산의 경로를 제공하여 자산을 로드하는 방법
		static ConstructorHelpers::FObjectFinder<UWeaponDataAsset> DataAsset(TEXT("/Game/BPC_WeaponAsset.BPC_WeaponAsset"));
		
		//AssetManager->GetAssetDataForPath();
		if (DataAsset.Succeeded())
		{
			UWeaponDataAsset* WeaponDataAsset = Cast<UWeaponDataAsset>(DataAsset.Object);
			if (WeaponDataAsset)
			{
				info = WeaponDataAsset->Assets[0]; // 첫 번째 자산 정보 가져오기
				MeshComponent->SetStaticMesh(info.Mesh); // Mesh를 설정
			}
		}
	}
}

static ConstructorHelpers::FObjectFinder<UWeaponDataAsset> DataAsset(TEXT("/Game/BPC_WeaponAsset.BPC_WeaponAsset"));

이게 중요한데, 이렇게 매개변수로 콘텐츠 브라우저에 있는 파일의 경로를 설정해주면 그 데이터를 가져올 수 있다.

참고로 이 함수는 생성자에서만 사용 가능하다. 다른 곳에서 하면 튕긴다.

이걸로 아까처럼 첫 번째 인덱스에 넣어준 값들을 복사해주면 된다.

결과

블루프린트는 Begin Play에서 메쉬를 설정해주니 플레이 버튼을 눌러야 메쉬가 세팅되는 걸 확인할 수 있다. 생성자에서 세팅과 Begin Play에서 세팅 차이도 큰 차이가 있을 것이라고 생각한다. 이 특징도 기억해야겠다.

이렇게 Data Asset을 이용해 객체를 생성하는 방법에 대해 공부해봤다. 내일은 Primary Data Asset에 대해 공부해서 사용해볼 예정이다.

This post is licensed under CC BY 4.0 by the author.