UE - Lyra와 Data Asset
UnrealEngine
오늘부터 팀원들과 Lyra
분석을 시작했다.
분석 내용
나는 시스템 플로우를 분석하기 위해 첫 시작 맵을 기준으로 분석을 시작했다.
프로젝트를 실행하고 첫 화면은 이렇게 생겼다.
옆 아웃라이너를 확인해 보니 플레이 중일 때 생성되는 객체가 있다는 걸 확인했다.
확인해 보니 텔레포트가 생성되는 부분을 찾을 수 있었다.
저 클래스 인스턴스를 삭제하면 포탈 생성이 안될테니 테스트까지 해봤다.
원본
지운 뒤
확실하기에 블루프린트를 살펴봤다.
텔레포트는 Primary Asset Id
를 가지고 생성된다.
Primary Asset Id
는 Data 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
인데, UPROPERTY
의 AssetRegistrySearchable
이 붙으면 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
에 대해 공부해서 사용해볼 예정이다.