UCLASS()
class AMyCharacter : public ACharacter
{
UPROPERTY(EditDefaultsOnly)
USkeletalMesh* CharacterMesh;
UPROPERTY(EditDefaultsOnly)
UTexture2D* CharacterIcon;
}
TSoftObjectPtr / TSoftClassPtr로 저장됨LoadSynchronous() 또는 StreamableManager를 통해 비동기 로드 가능UCLASS()
class AMyCharacter : public ACharacter
{
UPROPERTY(EditDefaultsOnly)
TSoftObjectPtr<USkeletalMesh> CharacterMesh;
UPROPERTY(EditDefaultsOnly)
TSoftObjectPtr<UTexture2D> CharacterIcon;
}
void ALyraCharacter::EquipCharacterMesh()
{
if (!CharacterMesh.IsNull())
{
USkeletalMesh* LoadedMesh = CharacterMesh.LoadSynchronous();
if (LoadedMesh)
{
GetMesh()->SetSkeletalMesh(LoadedMesh);
}
}
}
void ALyraCharacter::EquipCharacterMeshAsync()
{
if (!CharacterMesh.IsNull())
{
FStreamableManager& Streamable = UAssetManager::GetStreamableManager();
UE_LOG(LogTemp, Log, TEXT("메시 로드 시작..."));
Streamable.RequestAsyncLoad(
CharacterMesh.ToSoftObjectPath(),
FStreamableDelegate::CreateWeakLambda(this, [this]()
{
// 로드가 끝나면 실행
if (USkeletalMesh* LoadedMesh = CharacterMesh.Get())
{
GetMesh()->SetSkeletalMesh(LoadedMesh);
UE_LOG(LogTemp, Log, TEXT("메시 로드 완료!"));
}
else
{
UE_LOG(LogTemp, Error, TEXT("메시 로드 실패!"));
}
})
);
}
}
AssetManager에서 중앙 관리하고, 보통 UPrimaryDataAsset을 상속한 클래스 인스턴스로 사용LoadPrimaryAsset으로 로드class UWeaponDataAsset : public UPrimaryDataAsset
{
GENERATED_BODY()
public:
virtual FPrimaryAssetId GetPrimaryAssetId() const override
{
return FPrimaryAssetId(TEXT("Weapon"), GetFName());
}
UPROPERTY(EditDefaultsOnly)
TSoftObjectPtr<USkeletalMesh> WeaponMesh;
UPROPERTY(EditDefaultsOnly)
TSoftObjectPtr<UTexture2D> WeaponIcon;
};
namespace EAssetBundle
{
const FName Mesh("Mesh");
const FName Sound("Sound");
const FName Text2D("Text2D");
const FName HUDIcon("HUDIcon");
}
// 게임 플레이 상황
UAssetManager::Get().LoadPrimaryAsset(
WeaponId,
{EAssetBundle::Mesh, EAssetBundle::Sound}
);
// 프리뷰 모드
UAssetManager::Get().LoadPrimaryAsset(
WeaponId,
{EAssetBundle::Text2D, EAssetBundle::HUDIcon}
);
: 숫자가 코드에 있어서 수정하려면 직접 코드를 보거나, 에디터에서 수정을 해야 함
class AWeapon : public AActor
{
float Damage = 100.0f;
float FireRate = 0.5f;
float Range = 1000.0f;
};
USTRUCT(BlueprintType)
struct FWeaponStats : public FTableRowBase // 중요! FTableRowBase 상속
{
GENERATED_BODY()
UPROPERTY(EditAnywhere)
float Damage = 10.0f;
UPROPERTY(EditAnywhere)
float FireRate = 1.0f;
UPROPERTY(EditAnywhere)
float Range = 500.0f;
};
Name,Damage,FireRate,Range
Pistol,25,0.3,800
Rifle,40,0.1,1500
Shotgun,100,1.0,300
DataTable로 변환해서 저장FWeaponStats 선택해서 importclass AWeapon : public AActor
{
UPROPERTY(EditDefaultsOnly)
UDataTable* WeaponStatsTable;
UPROPERTY(EditAnywhere)
FName WeaponType = "Pistol";
float Damage;
float FireRate;
float Range;
void BeginPlay()
{
if (WeaponStatsTable)
{
FWeaponStats* Stats = WeaponStatsTable->FindRow<FWeaponStats>(
WeaponType,
TEXT("WeaponStats")
);
if (Stats)
{
Damage = Stats->Damage;
FireRate = Stats->FireRate;
Range = Stats->Range;
}
}
}
};
void AWeapon::Fire()
{
if (WeaponType == "Pistol")
{
SpawnProjectile();
}
else if (WeaponType == "Rifle")
{
for (int i = 0; i < 3; i++)
{
SpawnProjectile();
Wait(0.1f);
}
}
else if (WeaponType == "Shotgun")
{
for (int i = 0; i < 8; i++)
{
SpawnProjectileWithSpread();
}
}
}
UCLASS(BlueprintType)
class UWeaponDataAsset : public UPrimaryDataAsset
{
GENERATED_BODY()
public:
UPROPERTY(EditDefaultsOnly, Category = "Info")
FText WeaponName;
UPROPERTY(EditDefaultsOnly, Category = "Info")
FText Description;
// 주 발사
UPROPERTY(EditDefaultsOnly, Category = "Combat")
TSoftClassPtr<UGameplayAbility> PrimaryFireAbility;
// 보조 발사
UPROPERTY(EditDefaultsOnly, Category = "Combat")
TSoftClassPtr<UGameplayAbility> SecondaryFireAbility;
// 비주얼
UPROPERTY(EditDefaultsOnly, Category = "Visual")
TSoftObjectPtr<UStaticMesh> WeaponMesh;
// 애니메이션
UPROPERTY(EditDefaultsOnly, Category = "Animation")
TSoftObjectPtr<UAnimMontage> FireMontage;
// 이펙트
UPROPERTY(EditDefaultsOnly, Category = "Effects")
TSoftObjectPtr<UNiagaraSystem> MuzzleFlash;
// Primary Asset ID
virtual FPrimaryAssetId GetPrimaryAssetId() const override
{
return FPrimaryAssetId(TEXT("Weapon"), GetFName());
}
};
UCLASS()
class UGA_Fire_Single : public UGameplayAbility
{
void ActivateAbility(...)
{
SpawnProjectile();
}
};
UCLASS()
class UGA_Fire_Burst : public UGameplayAbility
{
void ActivateAbility(...)
{
for (int i = 0; i < 3; i++)
{
SpawnProjectile();
Wait(0.1f);
}
}
};
void ACharacter::EquipWeapon(UWeaponDataAsset* WeaponData)
{
if (!WeaponData) return;
// 1. Ability 로드 및 부여
if (!WeaponData->PrimaryFireAbility.IsNull())
{
// 소프트 레퍼런스 로드
TSubclassOf<UGameplayAbility> AbilityClass =
WeaponData->PrimaryFireAbility.LoadSynchronous();
if (AbilityClass)
{
// 기존 발사 능력 제거
if (CurrentFireAbilityHandle.IsValid())
{
AbilitySystemComponent->ClearAbility(CurrentFireAbilityHandle);
}
// 새 발사 능력 부여
FGameplayAbilitySpec AbilitySpec(AbilityClass, 1, 0, this);
CurrentFireAbilityHandle = AbilitySystemComponent->GiveAbility(AbilitySpec);
UE_LOG(LogTemp, Warning, TEXT("Fire ability granted!"));
}
}
// 2. 비주얼 로드
if (!WeaponData->WeaponMesh.IsNull())
{
UStaticMesh* Mesh = WeaponData->WeaponMesh.LoadSynchronous();
if (Mesh && WeaponMeshComponent)
{
WeaponMeshComponent->SetStaticMesh(Mesh);
}
}
// 3. 애니메이션 저장
if (!WeaponData->FireMontage.IsNull())
{
CurrentFireMontage = WeaponData->FireMontage.LoadSynchronous();
}
}
Experience (게임 모드) : 게임 모드를 새로 정의
├── PawnData (캐릭터 정의)
│ ├── Abilities
│ ├── InputConfig
│ └── Components
└── GameFeatures
UCLASS()
class ULyraExperienceDefinition : public UPrimaryDataAsset
{
GENERATED_BODY()
public:
// 이 게임 모드의 이름
UPROPERTY(EditDefaultsOnly, Category = "Info")
FText ExperienceName;
// 사용 가능한 캐릭터들
UPROPERTY(EditDefaultsOnly, Category = "Gameplay")
TMap<FGameplayTag, UPawnDataAsset*> AvailablePawns;
// 게임 규칙
UPROPERTY(EditDefaultsOnly, Category = "Rules")
TSubclassOf<AGameMode> GameModeClass;
// 추가 기능들 (리스폰, 점수, 스톰 등)
UPROPERTY(EditDefaultsOnly, Category = "Features")
TArray<FString> GameFeaturesToLoad;
// UI
UPROPERTY(EditDefaultsOnly, Category = "UI")
TSoftClassPtr<UHUD> HUDClass;
// 기본 맵
UPROPERTY(EditDefaultsOnly, Category = "Level")
TSoftObjectPtr<UWorld> DefaultMap;
virtual FPrimaryAssetId GetPrimaryAssetId() const override
{
return FPrimaryAssetId(TEXT("Experience"), GetFName());
}
};
void LoadExperience(FString ExperienceName)
{
// Primary Asset ID로 찾기
FPrimaryAssetId ExpId(TEXT("Experience"), ExperienceName);
// Asset Manager에게 로드 요청
UAssetManager& Manager = UAssetManager::Get();
// Experience 로드
Manager.LoadPrimaryAsset(ExpId, {},
FStreamableDelegate::CreateLambda([this, ExpId]()
{
OnExperienceLoaded(ExpId);
})
);
}
void OnExperienceLoaded(FPrimaryAssetId ExpId)
{
// 로드된 Experience 가져오기
ULyraExperienceDefinition* Experience =
UAssetManager::Get().GetPrimaryAssetObject<ULyraExperienceDefinition>(ExpId);
if (!Experience)
{
UE_LOG(LogTemp, Error, TEXT("Failed to load experience!"));
return;
}
// 1. 게임 규칙 적용
UE_LOG(LogTemp, Warning, TEXT("Setting game mode..."));
World->SetGameMode(Experience->GameModeClass);
// 2. 사용 가능한 캐릭터 설정
AvailableCharacters = Experience->AvailablePawns;
UE_LOG(LogTemp, Warning, TEXT("Available characters: %d"),
AvailableCharacters.Num());
// 3. 게임 기능 활성화
for (const FString& Feature : Experience->GameFeaturesToLoad)
{
/** GameFeature 활성화 코드 **/
}
// 4. HUD 생성
if (!Experience->HUDClass.IsNull())
{
TSubclassOf<UHUD> HUDClass = Experience->HUDClass.LoadSynchronous();
/** HUD 생성 코드 **/
}
CurrentExperience = Experience;
}
UCLASS()
class UPawnDataAsset : public UPrimaryDataAsset
{
GENERATED_BODY()
public:
// 캐릭터 이름
UPROPERTY(EditDefaultsOnly, Category = "Info")
FText CharacterName;
// 붙일 컴포넌트들
UPROPERTY(EditDefaultsOnly, Category = "Components")
TArray<TSoftClassPtr<UActorComponent>> Components;
// 캐릭터 능력들
UPROPERTY(EditDefaultsOnly, Category = "Abilities")
TArray<TSoftClassPtr<UGameplayAbility>> Abilities;
// 입력 설정
UPROPERTY(EditDefaultsOnly, Category = "Input")
UInputConfig* InputConfig;
virtual FPrimaryAssetId GetPrimaryAssetId() const override
{
return FPrimaryAssetId(TEXT("PawnData"), GetFName());
}
};
void ApplyPawnData(ALyraCharacter* Character, UPawnDataAsset* PawnData)
{
// 1. Component 붙이기
for (TSoftClassPtr<UActorComponent> CompClass : PawnData->Components)
{
if (!CompClass.IsNull())
{
TSubclassOf<UActorComponent> ComponentClass = CompClass.LoadSynchronous();
if (ComponentClass)
{
UActorComponent* NewComp = NewObject<UActorComponent>(
Character, ComponentClass
);
// 월드에 등록 (중요!)
NewComp->RegisterComponentWithWorld(GetWorld());
}
}
}
// 2. 능력 부여
for (TSoftClassPtr<UGameplayAbility> AbilityClass : PawnData->Abilities)
{
if (!AbilityClass.IsNull())
{
TSubclassOf<UGameplayAbility> Ability = AbilityClass.LoadSynchronous();
if (Ability)
{
Character->AbilitySystemComponent->GiveAbility(
FGameplayAbilitySpec(Ability, 1, INDEX_NONE, Character)
);
}
}
}
// 3. 입력 설정
if (PawnData->InputConfig)
{
Character->SetInputConfig(PawnData->InputConfig);
}
}