데이터 애셋 생성

<.h>

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

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

//아이템 종류 열거형.
//블루프린트와 호환되도록 BlueprintType 지정.
UENUM(BlueprintType)
enum class EItemType : uint8
{
	Weapon = 0,
	Potion,
	Scroll
};

/**
 * 
 */
UCLASS()
class ARENABATTLEDEMO_API UABItemData : public UPrimaryDataAsset
{
	GENERATED_BODY()

public:
	//아이템 타입을 지정하는 열거형 변수.
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Type)
	EItemType Type;
	
};

ItemData를 상속 받는 WeaponItemData 애셋 생성

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "Item/ABItemData.h"
#include "ABWeaponItemData.generated.h"

/**
 * 
 */
UCLASS()
class ARENABATTLEDEMO_API UABWeaponItemData : public UABItemData
{
	GENERATED_BODY()

public:
	//제공할 무기에 대한 스켈레탈 메시.
	UPROPERTY(EditAnywhere, Category = Weapon)
	TSoftObjectPtr<class USkeletalMesh> WeaponMesh;
	
};

 

아이템 박스 객체 클래스 생성

<.h>

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "ABItemBox.generated.h"

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

protected:
	//박스 컴포넌트의 오버랩 델리게이트에 등록할 함수.
	//OnComponentBeginOverlap 델리게이트는 다이나믹으로 지정되어 있기대문에
	//UFUNCTION() 매크로를 지정해야 한다.
	UFUNCTION()
	void OnOverlapBegin(UPrimitiveComponent* OverlappedComponent,
		AActor* OtherActor,
		UPrimitiveComponent* OtherComp,
		int32 OtherBodyIndex,
		bool bFromSweep,
		const FHitResult& SweepResult);

	//파티클 재생 종료 시 발행되는 델리게이트에 등록 할 함수.
	UFUNCTION()
	void OnEffectFinished(class UParticleSystemComponent* PSystem );

protected:
	//액터의 충돌을 담당할 박스 컴포넌트.
	UPROPERTY(VisibleAnywhere, Category = Box)
	TObjectPtr<class UBoxComponent> TriggerBox;

	//아이템 박스를 보여줄 메시 컴포넌트.
	UPROPERTY(VisibleAnywhere, Category = Box)
	TObjectPtr<class UStaticMeshComponent> Mesh;

	//박스와 상호작용 했을때 보여줄 파티클 효과 컴포넌트.(나이아가라 X)
	UPROPERTY(VisibleAnywhere, Category = Box)
	TObjectPtr<class UParticleSystemComponent> Effect;

	//아이템 정보.
	UPROPERTY(EditAnywhere, Category = Item)
	TObjectPtr<class UABItemData> Item;
	
};

<.cpp>

// Fill out your copyright notice in the Description page of Project Settings.


#include "Item/ABItemBox.h"
#include "Components/BoxComponent.h"
#include "Components/StaticMeshComponent.h"
#include "Particles/ParticleSystemComponent.h"
#include "Physics/ABCollision.h"
#include "Interface/ABCharacterItemInterface.h"
#include "ABWeaponItemData.h"

// Sets default values
AABItemBox::AABItemBox()
{
	TriggerBox = CreateDefaultSubobject<UBoxComponent>(TEXT("TriggerBox"));
	Mesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Mesh"));
	Effect = CreateDefaultSubobject<UParticleSystemComponent>(TEXT("Effect"));

	//계층설정.
	RootComponent = TriggerBox;
	Mesh->SetupAttachment(TriggerBox);
	Effect->SetupAttachment(TriggerBox);

	//콜리전 프로파일 설정.
	TriggerBox->SetCollisionProfileName(CPROFILE_ABTRIGGER);
	TriggerBox->SetBoxExtent(FVector(40.0f, 42.0f, 30.0f));
	//트리거가 발생하는 다이나믹 델리게이트에 함수 등록.
	TriggerBox->OnComponentBeginOverlap.AddDynamic(this, &AABItemBox::OnOverlapBegin);
	
	Mesh->SetCollisionProfileName(TEXT("NoCollision"));

	//애셋 로드.
	static ConstructorHelpers::FObjectFinder<UStaticMesh> BoxMeshRef(TEXT("/Game/ArenaBattle/Environment/Props/SM_Env_Breakables_Box1.SM_Env_Breakables_Box1"));
	if (BoxMeshRef.Object)
	{
		Mesh->SetStaticMesh(BoxMeshRef.Object);
	}
	//메시 컴포넌트의 위치 조정.
	Mesh->AddRelativeLocation(FVector(0.0f, -3.5f, -30.0f));

	//파티클 애셋 로드.
	static ConstructorHelpers::FObjectFinder<UParticleSystem> EffectRef(TEXT("/Game/ArenaBattle/Effect/P_TreasureChest_Open_Mesh.P_TreasureChest_Open_Mesh"));
	if (EffectRef.Object)
	{
		//파티클 애셋 설정.
		Effect->SetTemplate(EffectRef.Object);

		//바로 재생되지 않도록 설정.
		Effect->bAutoActivate = false;
	}

	//static ConstructorHelpers::FObjectFinder<UABItemData> ItemDataRef(TEXT("/Game/ArenaBattle/Item/Weapon/ABIW_Weapon.ABIW_Weapon"));
	static ConstructorHelpers::FObjectFinder<UABItemData> ItemDataRef(TEXT("/Game/ArenaBattle/Item/Weapon/ABIW_Weapon.ABIW_Weapon"));
	if (ItemDataRef.Object)
	{
		Item = ItemDataRef.Object;
	}
	
}

void AABItemBox::OnOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor,
	UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
	//꽝 상자도 있다고 가정.
	//Item이 Nullptr이면 꽝.
	if (!Item)
	{
		Destroy();
		return;
	}

	//아이템이 있으면 캐릭터에 아이템 획득 메시지 전달.
	IABCharacterItemInterface* OverlappedPawn = Cast<IABCharacterItemInterface>(OtherActor);
	if (OverlappedPawn)
	{
		OverlappedPawn->TakeItem(Item);
	}
	
	//파티클 재생.
	Effect->Activate();

	//매시는 안보이도록 처리(비활성화).
	Mesh->SetHiddenInGame(true);

	//액터의 콜리전 끄기.
	SetActorEnableCollision(false);

	//파티클 재생 종료시 발행되는 델리게이트에 함수 등록.
	Effect->OnSystemFinished.AddDynamic(this, &AABItemBox::OnEffectFinished);
}

void AABItemBox::OnEffectFinished(class UParticleSystemComponent* PSystem)
{
	//파티클 재생이 완료되면 액터 삭제.
	Destroy();
}

캐릭터Base클래스에 Item에 관한 코드 추가

	//ItemSection.
protected:

	//래퍼 구조체를 관리할수 있는 배열.
	UPROPERTY()
	TArray<FTakeItemDelegateWrapper> TakeItemActions;

	//아이템 획득시 호출될 함수.
	virtual void TakeItem(class UABItemData* InItemData) override;

	//아이템 종류마다 처리될 함수 선언.
	virtual void DrinkPortion(class UABItemData* InItemData);
	virtual void EquipWeapon(class UABItemData* InItemData);
	virtual void ReadScroll(class UABItemData* InItemData);

	//무기 아이템을 획득했을 때 사용할 스켈레탈 메시 컴포넌트.
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Equipent, meta = (AllowPrivateAccess = "true"))
	TObjectPtr<class USkeletalMeshComponent> Weapon;

생성자에서 델리게이트 함수등록 및 스켈레탈 메시 생성.

	//Item Section.
	TakeItemActions.Add(FTakeItemDelegateWrapper(FOnTakeItemDelegate::CreateUObject(this, &AABCharacterBase::EquipWeapon)));
	TakeItemActions.Add(FTakeItemDelegateWrapper(FOnTakeItemDelegate::CreateUObject(this, &AABCharacterBase::DrinkPortion)));
	TakeItemActions.Add(FTakeItemDelegateWrapper(FOnTakeItemDelegate::CreateUObject(this, &AABCharacterBase::ReadScroll)));

	//무기를 보여줄 스켈레탈 메쉬 생성.
	Weapon = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("Weapon"));
	//메시 컴포넌트 하위로 계층을 설정하고, 이때 hand_rSocket 소켓에 부착.
	Weapon->SetupAttachment(GetMesh(), TEXT("hand_rSocket"));

아이템의 트리거 충돌시 TakeItem()함수 호출.

void AABCharacterBase::TakeItem(class UABItemData* InItemData)
{
	//아이템 정보가 넘어오면 처리.
	if (InItemData)
	{
		TakeItemActions[(uint8)InItemData->Type].ItemDelegate.ExecuteIfBound(InItemData);//C# : ?.Invoke()랑 같은 함수이다.
	}
}

void AABCharacterBase::DrinkPortion(class UABItemData* InItemData)
{
	UE_LOG(LogTemp, Log, TEXT("DrinkPortion"));
}

void AABCharacterBase::EquipWeapon(class UABItemData* InItemData)
{
	//UE_LOG(LogTemp, Log, TEXT("EquipWeapon"));
	//함수에 전달된 아이템 데이터 애셋을 무기 데이터로 변환.
	UABWeaponItemData* WeaponItemData = Cast<UABWeaponItemData>(InItemData);
	if (WeaponItemData)
	{
		//무기 메시가 아직 로딩 안된 경우 로드 처리.
		if (WeaponItemData->WeaponMesh.IsPending())
		{
			WeaponItemData->WeaponMesh.LoadSynchronous();	
		}
		//무기 컴포넌트에 로드가 완료된 스켈레탈 메시 설정.
		Weapon->SetSkeletalMesh(WeaponItemData->WeaponMesh.Get());
	}
}

void AABCharacterBase::ReadScroll(class UABItemData* InItemData)
{
	UE_LOG(LogTemp, Log, TEXT("ReadScroll"));
}

소프트 레퍼런싱 vs 하드 레퍼런싱

• 액터 로딩시 TObjectPtr로 선언한 언리얼 오브젝트도 따라서 메모리에 로딩됨.
• 이를 하드 레퍼런싱이라고 함.
• 게임 진행에 필수적인 언리얼 오브젝트는 이렇게 선언해도 되지만 아이템의 경우?
• 데이터 라이브러리에 1000종의 아이템 목록이 있을 때 이를 모두 다 로딩할 것인가?
• 필요한 데이터만 로딩하도록 TSoftObjectPtr로 선언하고 대신 애셋 주소 문자열을 지정함
• 필요시에 애셋을 로딩하도록 구현을 변경할 수 있으나 애셋 로딩 시간이 소요됨.
• 현재 게임에서 로딩되어 있는 스켈레탈 메시의 목록을 살펴보기

'언리얼 엔진 공부 > 언리얼C++' 카테고리의 다른 글

캐릭터 공격 판정  (0) 2025.04.18
캐릭터 애니메이션 시스템  (0) 2025.04.14
데이터 애셋  (0) 2025.04.14
언리얼 엔진 게임 제작 기초  (0) 2025.04.10
어설션(Assertion)  (0) 2025.04.08

월드가 제공하는 충돌 판정 서비스를 사용
• 월드는 크게 세 가지의 충돌 판정 서비스를 제공함.
• 월드 내 배치된 충돌체와 충돌하는지 파악하고, 충돌한 액터 정보를 얻을 수 있음.

• 액션 판정을 위한 트레이스 채널의 생성 : ABAction. 기본 반응은 무시
• 캐릭터 캡슐용 프로필 : ABAction 트레이스 채널에 반응. 오브젝트 타입은 Pawn
• 스켈레탈 메시용 프로필 : 랙돌 구현을 위해 주로 활용됨.
• 기믹 트리거용 프로필 : 폰 캡슐에만 반응하도록 설정. 오브젝트 타입은 WorldStatic

월드 트레이싱 함수의 선택

• 세 가지 카테고리로 원하는 함수 이름을 얻을 수 있음.
 • 카테고리 1 : 처리 방법
 • 카테고리 2 : 대상
  • Test : 무언가 감지되었는지를 테스트
  • Single 또는 AnyTest : 감지된 단일 물체 정보를 반환
  • Multi : 감지된 모든 물체 정보를 배열로 반환
 • 카테고리 3 : 처리 설정
  • ByChannel : 채널 정보를 사용해 감지 • ByObjectType : 물체에 지정된 물리 타입 정보를 사용해 감지 • ByProfile : 프로필 정보를 사용해 감지

{처리방법}{대상}{처리설정}

캐릭터 공격 판정의 구현

• 캐릭터의 위치에서 시선 방향으로 물체가 있는지 감지
• 작은 구체를 제작하고 시선 방향으로 특정 거리까지만 투사.
• 하나의 물체만 감지
• 트레이스 채널을 사용해 감지

물리 충돌 테스트

• 디버그 드로잉 함수를 사용해 물리 충돌을 시각적으로 테스트
• 90도로 회전시킨 캡슐을 그리기
  • Origin
  • HalfHeight
  • Radius

<코드 예시>

몽타주에서 충돌체크할 노티파이를 클래스로 생성.

<.h>

UCLASS()
class ARENABATTLEDEMO_API UAnimNotify_AttackHitCheck : public UAnimNotify
{
	GENERATED_BODY()

protected:
	//애니메이션 노티파이가 발생할때 호출되는 이벤트 함수.
	virtual void Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, const FAnimNotifyEventReference& EventReference) override;
	
	
};

<CPP>

// Fill out your copyright notice in the Description page of Project Settings.


#include "Animation/AnimNotify_AttackHitCheck.h"
#include "Interface/ABAnimationAttackInterface.h"

void UAnimNotify_AttackHitCheck::Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation,
	const FAnimNotifyEventReference& EventReference)
{
	Super::Notify(MeshComp, Animation, EventReference);

	if (MeshComp)
	{
		//애니메이션을 소유하는 액터가 인터페이스를 구현했는지 확인.
		IABAnimationAttackInterface* AttackInterface = Cast<IABAnimationAttackInterface>(MeshComp->GetOwner());

		//인터페이스를 구현한 경우, 함수 호출.
		if (AttackInterface)
		{
			AttackInterface->AttackHitCheck();
		}
	}
}

생성한 노티파이를 몽타주에 설정.

인터페이스 생성.

공격충돌 체크를 순수 가상함수로 만든다.<CPP>

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "UObject/Interface.h"
#include "ABAnimationAttackInterface.generated.h"

// This class does not need to be modified.
UINTERFACE(MinimalAPI)
class UABAnimationAttackInterface : public UInterface
{
	GENERATED_BODY()
};

/**
 * 
 */
class ARENABATTLEDEMO_API IABAnimationAttackInterface
{
	GENERATED_BODY()

	// Add interface functions to this class. This is the class that will be inherited to implement this interface.
public:
	virtual void AttackHitCheck() = 0;
};

캐릭터클래스에서 인터페이스를 상속.

class ARENABATTLEDEMO_API AABCharacterBase : public ACharacter, public IABAnimationAttackInterface

해당 함수를 구현.<CPP>

void AABCharacterBase::AttackHitCheck()
{
	//공격 판정 진행.
	//UE_LOG(LogTemp, Log, TEXT("AttackHitCheck!!"));

	//충돌 시작 지점 계산.
	//캐릭터 몸통에서 약간 앞으로(캡슐의 반지름 만큼) 설정.
	FVector Start = GetActorLocation() + GetActorForwardVector() * GetCapsuleComponent()->GetScaledCapsuleRadius();

	//공격 거리.
	const float AttackRange = 50.0f;
	FVector End = Start + GetActorForwardVector() * AttackRange;

	//SCENE_QUERY_STAT: 언리얼에서 지원하는 분석 툴에 태그를 추가.
	//두번째 인자: 복잡한 형태의 충돌체를 감지할지 여부.
	//세번째 인자: 무시할 액터 목록.
	FCollisionQueryParams Params(SCENE_QUERY_STAT(Attack), false,this);

	//트레이스에 사용할 구체의 반지름.
	const float AttakcRadius = 50.0f;

	//트레이스를 활용해 충돌 검사.
	FHitResult OutHitResult;
	bool HitDetected = GetWorld()->SweepSingleByChannel(
		OutHitResult,
		Start,
		End,
		FQuat::Identity
		, CCHANNEL_ABACTION,
		FCollisionShape::MakeSphere(AttakcRadius),
		Params);

	// 충돌 감지된 경우의 처리.
	if (HitDetected)
	{
		//Damage 양.
		const float AttackDamage = 30.0f;

		//데미지 이벤트.
		FDamageEvent DamageEvent;
		
		//데미지 전달.
		OutHitResult.GetActor()->TakeDamage(AttackDamage, DamageEvent, GetController(), this);
		
	}

	//충돌 디버그(시각적으로 확인할 수 있도록).
#if ENABLE_DRAW_DEBUG
	//캡슐의 중심 위치.
	FVector CapsuleOrigin = Start + (End - Start) * 0.5f;

	//캡슐의 높이 절반 값.
	float CapsuleHalfHeight = AttackRange * 0.5f;

	//표시할 생상(안맞으면 빨강, 맞으면 초록)
	FColor DrawColor = HitDetected ? FColor::Red : FColor::Green;

	//캡슐 그리기.
	DrawDebugCapsule(GetWorld(),
		CapsuleOrigin,
		CapsuleHalfHeight,
		AttakcRadius,
		FRotationMatrix::MakeFromZ(GetActorForwardVector()).ToQuat(),
		DrawColor,
		false,
		5.0f
		);
	
#endif
	
}

 

'언리얼 엔진 공부 > 언리얼C++' 카테고리의 다른 글

아이템 시스템(트리거)  (0) 2025.04.21
캐릭터 애니메이션 시스템  (0) 2025.04.14
데이터 애셋  (0) 2025.04.14
언리얼 엔진 게임 제작 기초  (0) 2025.04.10
어설션(Assertion)  (0) 2025.04.08

UAnimInstance

UAnimInstance는 언리얼 엔진에서 캐릭터의 애니메이션 상태를 관리하는 핵심 클래스이다.

 

  • 현재 캐릭터 상태(속도, 방향, 점프 여부 등)에 따라 애니메이션을 갱신
  • AnimGraph, State Machine 등을 통해 애니메이션 전환 관리
  • C++에서 애니메이션 파라미터를 제어하거나 가져올 수 있음

캐릭터 애니메이션 시스템의 생성

스켈레탈 메시 컴포넌트의 애니메이션 블루프린트 클래스를 지정한다.
• 캐릭터가 초기화될 때 AnimInstance 클래스의 인스턴스를 생성한다.
• 캐릭터는 GetAnimInstance 함수를 사용해 애니메이션 인스턴스를 얻을 수 있음
• 애니메이션 인스턴스는 GetOwningActor 함수를 사용해 자신을 소유한 액터 정보를 얻을 수 있음

 

캐릭터 애니메이션 시스템의 설계

애니메이션 블루프린트는 이벤트 그래프와 애님 그래프의 두 영역으로 구성되어 있음.
• 이벤트 그래프에서는 이벤트로부터 상태를 파악할 수 있는 주요 변수를 저장하는데 사용
• 애님 그래프에서는 저장된 변수로부터 지정된 상태의 애니메이션을 재생
• 애님 그래프의 복잡한 상태는 State Alias로 분리해 효과적으로 설계할 수 있음

<예시>

UAnimInstance를 상속받는 UABAnimInstance 클래스 만들기

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "Animation/AnimInstance.h"
#include "ABAnimInstance.generated.h"

/**
 * 
 */
UCLASS()
class ARENABATTLEDEMO_API UABAnimInstance : public UAnimInstance
{
	GENERATED_BODY()

public:
	UABAnimInstance();

	//애니메이션 초기화될 때 호출.
	virtual void NativeInitializeAnimation() override;
	//애니메이션 업데이트 함수.
	virtual void NativeUpdateAnimation(float DeltaSeconds) override;

	//프로퍼티.
public:
	//애님 인스턴스를 소유하는 캐릭터 참조 변수.
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Character)
	TObjectPtr<class ACharacter> Owner;

	//캐릭터 무브컨트 컴포넌트 참조 변수.
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Character)
	TObjectPtr<class UCharacterMovementComponent> Movement;

	//캐릭터의 현재 이동속도를 저장할 변수.
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Character)
	FVector Velocity;

	//캐릭터의 땅에서의 이동속도를 저장할 변수.
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Character)
	float GroundSpeed;

	//캐릭터가 Idle인지 여부를 확인할 변수.
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Character)
	uint8 bIsIdle : 1;

	//캐릭터가 이동하는지(멈춰있는지) 여부를 판단하는데 사용할 문턱 값.
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Character)
	float MovingThreshold;

	//캐릭터가 공중에서 떨어지는지 여부를 확인하는데 사용할 문턱값.
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Character)
	uint8 bIsFalling :1;

	//캐릭터가 점프하는지 여부를 확인하는데 사용할 문턱값.
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Character)
	uint8 bIsJumping :1;

	//캐릭터가 점프하고있는지 여부를 판단하는데 사용할 문턱 값.
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Character)
	float JumpingThreshold;
};

<CPP>

// Fill out your copyright notice in the Description page of Project Settings.


#include "Animation/ABAnimInstance.h"
#include "GameFramework/Character.h"
#include "GameFramework/CharacterMovementComponent.h"

UABAnimInstance::UABAnimInstance()
{
	//문턱값 설정.
	MovingThreshold = 3.0f;
	JumpingThreshold = 100.0f;
}

void UABAnimInstance::NativeInitializeAnimation()
{
	Super::NativeInitializeAnimation();
	//애님 인스턴스를 소유한 폰 또는 액터를 캐릭터로 변환.
	Owner = Cast<ACharacter>(GetOwningActor());

	//캐릭터로 형변환 성공.
	if (Owner)
	{
		//무브먼트 컴포넌트 검색.
		Movement = Owner->GetCharacterMovement();
	}
}

void UABAnimInstance::NativeUpdateAnimation(float DeltaSeconds)
{
	Super::NativeUpdateAnimation(DeltaSeconds);

	//필요한 값 업데이트.
	if (Movement)
	{
		Velocity = Movement->Velocity;
		GroundSpeed = Velocity.Size2D();
		bIsIdle = GroundSpeed < MovingThreshold;
		bIsFalling = Movement->IsFalling();
		bIsJumping = bIsFalling & (Velocity.Z > JumpingThreshold);
	}
}

캐릭터 블루프린트의 Mesh컴포넌트->Animation->AnimClass에 생성한 UABAnimInstance를 설정

 

블루프린트 새로 공부한 기능 Add State Alias 노드

Add State Alias애니메이션 상태 머신(State Machine) 내에서 기존 상태에 '별칭(Alias)'을 붙이는 기능이다.
애님 블루프린트 로직이 많아질수록 보기가 힘들어지는데 별칭을 만들어서 구분지어 사용하는 기능이다.
별칭은 상태 변경을 하지 않고 구조는 유지하면서 의미를 확장할 때 유용합니다.
!!참고!! 너무 많은 별칭은 오히려 헷갈릴 수 있으니, 상황에 맞게 최소화해서 사용해야 한다.

*ToLand를 Add State Alias노드로 만들어 Jump, Falling 노드들일때만 탈수 있게 했다.

애니메이션 새로 공부한 기능 Additive Anim Type

Additive Animation은 기존의 기준 포즈(Base Pose)에 대해 상대적으로 덧붙이는 방식의 애니메이션이다.

모드설명

No Additive 일반 애니메이션 (덧셈 없음)
Local Space Additive 본 로컬 좌표계 기준으로 차이 계산
Mesh Space Additive 전체 메시 기준의 글로벌 좌표계에서 차이 계산

Local Space는 각 본이 자기 기준으로 움직이고,
Mesh Space는 메시 전체 좌표계를 기준으로 계산합니다.

<예시>

 

'언리얼 엔진 공부 > 언리얼C++' 카테고리의 다른 글

아이템 시스템(트리거)  (0) 2025.04.21
캐릭터 공격 판정  (0) 2025.04.18
데이터 애셋  (0) 2025.04.14
언리얼 엔진 게임 제작 기초  (0) 2025.04.10
어설션(Assertion)  (0) 2025.04.08

UDataAsset을 상속받은 언리얼 오브젝트 클래스이다.
에디터에서 애셋 형태로 편리하게 데이터를 관리할 수 있는 장점이 있다!!.
캐릭터 컨트롤에 관련된 주요 옵션을 모아 애셋으로 관리 한다

 

데이터 애셋 테스트하면서 정리.

두 가지의 컨트롤 모드를 제공
현재 구현된 컨트롤 모드 : 3인칭 솔더뷰
추가로 구현할 컨트롤 모드 : 3인칭 쿼터뷰
입력키 V를 통해 컨트롤 설정을 변경
ENUM을 통해 두 개의 컨트롤 데이터를 관리

데이터 애셋의 구성과 적용

각 섹션별로 데이터를 저장

  • Pawn 카테고리
  • 캐릭터무브먼트 카테고리
  • 입력 카테고리
  • 스프링암 카테고리

Pawn과 캐릭터무브먼트 데이터는 CharacterBase에서 설정
입력과 스프링암 데이터는 CharacterPlayer에서 설정

<코드 예시>UABCharacterControlData클래스 만들기

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

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

/**
 * 
 */
UCLASS()
class ARENABATTLEDEMO_API UABCharacterControlData : public UPrimaryDataAsset
{
	GENERATED_BODY()

public:
	//기본값 설정을 위해서.
	UABCharacterControlData();

	UPROPERTY(EditAnywhere, Category = Pawn)
	uint8 bUseControllerRotationYaw : 1;
	
	UPROPERTY(EditAnywhere, Category = CharacterMovement)
	uint8 bOrientRotationToMovement : 1;

	UPROPERTY(EditAnywhere, Category = CharacterMovement)
	uint8 bUseControllerDesiredRotation: 1;

	UPROPERTY(EditAnywhere, Category = CharacterMovement)
	FRotator RotationRate;

	UPROPERTY(EditAnywhere, BlueprintReadOnly,  Category = Input)
	TObjectPtr<class UInputMappingContext> InputMappingContext;

	UPROPERTY(EditAnywhere,  Category = SpringArm)
	float TargetArmLength;

	UPROPERTY(EditAnywhere,  Category = SpringArm)
	FRotator RelativeRotation;
	
	UPROPERTY(EditAnywhere, Category = SpringArm)
	uint8 bUsePawnControlRotation : 1;

	UPROPERTY(EditAnywhere, Category = SpringArm)
	uint8 bInheritPitch : 1;

	UPROPERTY(EditAnywhere, Category = SpringArm)
	uint8 bInheritYaw : 1;

	UPROPERTY(EditAnywhere, Category = SpringArm)
	uint8 bInheritRoll : 1;

	UPROPERTY(EditAnywhere, Category = SpringArm)
	uint8 bDoCollisionTest : 1;
	
};

<cpp>

// Fill out your copyright notice in the Description page of Project Settings.


#include "Character/ABCharacterControlData.h"

UABCharacterControlData::UABCharacterControlData()
{
	TargetArmLength = 500.0f;
	
}

데이터 에셋 생성

데이터 에셋 데이터 셋팅

Shoulder와 Quater때의 Move함수 코드 분리

void AABCharacterPlayer::QuarterMove(const FInputActionValue& Value)
{
	// 입력 값 읽기.
	FVector2D Movement = Value.Get<FVector2D>();
	
	float MovementVectorSize;
	float MovementVectorSizeSquared = Movement.SizeSquared();

	// 두 방향으로 입력이 들어오면, 이동 방향은 정규화해 크기를 1로 만들고,
	// 입력 스케일을 1로 강제 설정.
	if (MovementVectorSizeSquared > 1.0f)
	{
		Movement.Normalize();
		MovementVectorSize = 1.0f;
	}
	// 입력이 1이하이면, 해당 입력을 스케일로 사용하기 위해 값 계산.
	else
	{
		MovementVectorSize = FMath::Sqrt(MovementVectorSizeSquared);
	}

	FVector MoveDirection = FVector(Movement.X, Movement.Y, 0.0f);

	//캐릭터가 이동하는 방향에 맞게 컨트롤러 회전 설정.
	Controller->SetControlRotation(FRotationMatrix::MakeFromX(MoveDirection).Rotator());
    
	// 입력에 따른 방향으로 이동하도록 입력 전달.
	AddMovementInput(MoveDirection, MovementVectorSize);
}

void AABCharacterPlayer::ShoulderMove(const FInputActionValue& Value)
{
	//입력값 읽기.
	FVector2D Movement = Value.Get<FVector2D>();

	//컨트롤러의 회전 값.
	FRotator Rotation = GetControlRotation();
	FRotator YawRotation(0.0f, Rotation.Yaw, 0.0f);
	
	//방향 구하기.
	FVector ForwardVector = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
	FVector RightVector = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);
	

	//무브먼트 컴ㅍ포넌트에 값 전달.
	AddMovementInput(ForwardVector, Movement.X);
	AddMovementInput(RightVector, Movement.Y);
}

키입력시 호출 함수 바인딩

void AABCharacterPlayer::SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);
	auto EnhancedInputComponent = CastChecked<UEnhancedInputComponent>(PlayerInputComponent);

	//Binding
	EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Triggered, this, &ACharacter::Jump);
	EnhancedInputComponent->BindAction(ChangeControlAction, ETriggerEvent::Triggered, this, &AABCharacterPlayer::ChangeCharacterControl);
	EnhancedInputComponent->BindAction(ShoulderMoveAction, ETriggerEvent::Triggered, this, &AABCharacterPlayer::ShoulderMove);
	EnhancedInputComponent->BindAction(ShoulderLookAction, ETriggerEvent::Triggered, this, &AABCharacterPlayer::ShoulderLook);
	EnhancedInputComponent->BindAction(QuarterMoveAction, ETriggerEvent::Triggered, this, &AABCharacterPlayer::QuarterMove);
}

컨트롤 변경 함수

void AABCharacterPlayer::ChangeCharacterControl()
{
	//사용할 캐릭터 컨트롤을 변경하는 함수.
	if (CurrentCharacterControlType == ECharacterControlType::Quarter)
	{
		SetCharacterControl(ECharacterControlType::Shoulder);
	}
	else if (CurrentCharacterControlType == ECharacterControlType::Shoulder)
	{
		SetCharacterControl(ECharacterControlType::Quarter);
	}
}

해당 컨트롤러 셋팅

void AABCharacterPlayer::SetCharacterControl(ECharacterControlType NewCharacterControlType)
{
	// 변경할 컨트롤 타입에 대응하는 데이터 애셋 로드(TMap으로부터).
	UABCharacterControlData* NewCharacterControl =  CharacterControlManager[NewCharacterControlType];
	check(NewCharacterControl);

	// 데이터 애셋을 사용해 관련 값 설정.
	SetCharacterControlData(NewCharacterControl);

	// Add InputMapping Context to Enhanced Input System.
	APlayerController* PlayerController = CastChecked<APlayerController>(GetController());
	if (auto SubSystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PlayerController->GetLocalPlayer()))
	{
		SubSystem->ClearAllMappings();
		SubSystem->AddMappingContext(
			NewCharacterControl->InputMappingContext,
			0
		);
	}

	// 현재 사용 중인 캐릭터 컨트롤 타입 업데이트.
	CurrentCharacterControlType = NewCharacterControlType;
}

결과

'언리얼 엔진 공부 > 언리얼C++' 카테고리의 다른 글

캐릭터 공격 판정  (0) 2025.04.18
캐릭터 애니메이션 시스템  (0) 2025.04.14
언리얼 엔진 게임 제작 기초  (0) 2025.04.10
어설션(Assertion)  (0) 2025.04.08
직렬화  (2) 2025.04.08

게임 콘텐츠의 구조

게임 제작을 위해 언리얼 엔진은 자체적으로 설계한 프레임웍을 제공한다.
이를 게임플레이 프레임웍(Gameplay Framework)이라고 부른다.
줄여서 게임 프레임웍 언리얼 게임 프레임웍의 각 구성 요소를 파악하고, 이를 확장하면서 게임을 제작하는 것을 권장

월드(World)

게임 콘텐츠를 담기 위해 제공되는 가상의 공간 월드는 시간, 트랜스폼 , 틱을 서비스로 제공한다.
월드 세팅이라는 콘텐츠 제작을 위한 기본 환경 설정을 제공한다.
월드의 기본 단위는 액터(Actor)로 정의되며, 액터 클래스는 언제나 접두사 A를 사용한다.

게임 모드(Game Mode)

게임 규칙을 지정하고 게임을 판정하는 최고 관리자 액터. 형태가 없다.
언리얼 엔진에서 하나의 게임에는 반드시 하나의 게임 모드만 존재한다.
게임 모드에서 입장할 사용자의 규격을 지정할 수 있.
멀티플레이어 게임에서 판정을 처리하는 절대적 권위의 심판

기믹(Gimmick)

게임 진행을 위한 이벤트를 발생시키는 사물 액터이다.
주로 이벤트 발생을 위한 충돌 영역을 설정하는데, 이를 트리거(Trigger)라고 한다.
트리거를 통해 캐릭터와 상호 작용하고, 월드에 액터를 스폰해 콘텐츠를 전개한다.

플레이어(Player)

게임에 입장한 사용자 액터. 형태가 없다.
게임 모드의 로그인을 통해 사용자가 게임 월드에 입장하면 플레이어가 생성된다.
싱글 플레이 게임에는 0번 플레이어가 설정된다.
사용자와의 최종 커뮤니케이션을 담당한다. ( 예) 입력 장치의 해석, 화면 장치로의 출력 )

폰(Pawn)

무형의 액터인 플레이어가 빙의해 조종하는 액터이다.
길찾기를 사용할 수 있으며, 기믹 및 다른 폰과 상호작용한다.
폰 중에서 인간형 폰을 별도로 캐릭터라고 지칭한다.

'언리얼 엔진 공부 > 언리얼C++' 카테고리의 다른 글

캐릭터 애니메이션 시스템  (0) 2025.04.14
데이터 애셋  (0) 2025.04.14
어설션(Assertion)  (0) 2025.04.08
직렬화  (2) 2025.04.08
언리얼C++델리게이트(Delegate)  (0) 2025.04.03

어설션(Assertion)이란?

코드에서 반드시 검증을 하고 넘어가야 할 상황이 있을때 사용 하는 비교 함수이다.
에디터 실행시에는 체크가 되며 빌드 실행시 해당 부분들은 빠지기 때문에 안심하고 에디터에서 사용해도 된다.

언리얼 엔진의 대표 어설션 매크로

check(조건) 조건이 false면 크래시 즉시 중단 (Assert failure) 필수 조건
checkf(조건, TEXT("메시지")) 메시지 포함된 check 즉시 중단 + 로그 디버깅 편의
ensure(조건) 경고 로그 출력만 크래시 안 남 부가적인 안전성 검사
ensureMsgf(조건, TEXT("메시지")) 메시지 포함된 ensure 로그 출력 부가적 설명 포함

어설션디버깅을 돕는 개발자의 안전망
check() = 강한 보장, ensure() = 부드러운 경고

'언리얼 엔진 공부 > 언리얼C++' 카테고리의 다른 글

데이터 애셋  (0) 2025.04.14
언리얼 엔진 게임 제작 기초  (0) 2025.04.10
직렬화  (2) 2025.04.08
언리얼C++델리게이트(Delegate)  (0) 2025.04.03
컴포지션  (0) 2025.04.03

직렬화(Serialization)란?

  • 오브젝트나 연결된 오브젝트의 묶음(오브젝트 그래프)을 바이트 스트림으로 변환하는 과정
    • 복잡한 데이터를 일렬로 세우기 때문에 직렬화
  • 거꾸로 복구시키는 과정도 포함해서 의미
    • 시리얼라이제이션(Serialization) : 오브젝트 그래프에서 바이트 스트림으로
    • 디시리얼라이제이션(Deserialization) : 바이트 스트림에서 오브젝트 그래프로
  • 직렬화가 가지는 장점
    • 현재 프로그램의 상태를 저장하고 필요한 때 복원할 수 있다. ( 게임의 저장 )
    • 현재 객체의 정보를 클립보드에 복사해서 다른 프로그램에 전송할 수 있다.
    • 네트워크를 통해 현재 프로그램의 상태를 다른 컴퓨터에 복원할 수 있다. ( 멀티플레이어 게임 )
    • 데이터 압축, 암호화를 통해 데이터를 효율적이고 안전하게 보관할 수도 있음.

직렬화 구현 시 고려할 점

  • 이러한 직렬화를 직접 구현할 경우 다양한 상황을 고려해야 함.
    • 데이터 레이아웃 : 오브젝트가 소유한 다양한 데이터를 변환할 것인가?
    • 이식성 : 서로 다른 시스템에 전송해도 이식될 수 있는가?
    • 버전 관리 : 새로운 기능이 추가될 때 이를 어떻게 확장하고 처리할 것인가?
    • 성능 : 네트웍 비용을 줄이기 위해 어떤 데이터 형식을 사용할 것인가?
    • 보안 : 데이터를 어떻게 안전하게 보호할 것인가?
    • 에러 처리 : 전송 과정에서 문제가 발생할 경우 이를 어떻게 인식하고 처리할 것인가?
  • 이런 상황을 모두 감안해 직렬화 모델을 만드는 것은 쉬운 일이 아님!

언리얼 엔진의 직렬화 시스템

  • 언리얼 엔진은 이러한 상황을 모두 고려한 직렬화 시스템을 자체적으로 제공하고 있음
  • 직렬화 시스템을 위해 제공하는 클래스 FArchive와 연산자
    • 아카이브 클래스 ( FArchive )
    • Shift(<<) operator
  • 다양한 아카이브 클래스의 제공
    • 메모리 아카이브 ( FMemoryReader, FMemoryWriter )
    • 파일 아카이브 ( FArchiveFileReaderGeneric , FArchiveFileWriterGeneric )
    • 기타 언리얼 오브젝트와 관련된 아카이브 클래스( FArchiveUObject )
  • Json 직렬화 기능 : 별도의 라이브러리를 통해 제공하고 있음

직렬화 FArchive 코드 예시.

// Fill out your copyright notice in the Description page of Project Settings.


#include "MyGameInstance.h"
#include "Student.h"

UMyGameInstance::UMyGameInstance()
{
}

void UMyGameInstance::Init()
{
	Super::Init();

	//데이터 생성.
	FStudentData RawDataSource(23, TEXT("wnwkdqls"));

	//경로
	const FString SavedPath = FPaths::Combine(FPlatformMisc::ProjectDir(), TEXT("Saved"));

	//테스트 출력.
	UE_LOG(LogTemp, Log, TEXT("저장할 파일 경로: %s"), *SavedPath);


	{
		//파일 이름.
		const FString RawDataFileName(TEXT("RawData.bin"));

		//전체 경로 설정 후 경로로 변경.
		FString RawDataAbsolutePath = FPaths::Combine(SavedPath, RawDataFileName);

		UE_LOG(LogTemp, Log, TEXT("저장할 파일 전체 경로: %s"), *RawDataAbsolutePath);

		//절대 경로로 변경.
		FPaths::MakeStandardFilename(RawDataAbsolutePath);
		UE_LOG(LogTemp, Log, TEXT("변경할 파일 전체 경로: %s"), *RawDataAbsolutePath);

		//구조체 데이터 직렬화.
		//CreateFileWriter 함수는 new로 생성 후 반환하기 때문에 delete로 정리 해야 한다.
		FArchive* RawFileWriteAr = IFileManager::Get().CreateFileWriter(*RawDataAbsolutePath);//파일 여는 행위(File.Open이랑 같다)

		if (RawFileWriteAr)
		{
			//데이터 넣기.
			//*RawFileWriteAr << RawDataSource.Order;
			//*RawFileWriteAr << RawDataSource.Name;
			*RawFileWriteAr << RawDataSource;

			//아카이브 닫기.
			RawFileWriteAr->Close();

			//메모리 해제.
			delete RawFileWriteAr;
			RawFileWriteAr = nullptr;
		}

		//역 직렬화(읽기).
		FStudentData RawDataDeserialized;
		FArchive* RawFileReaderAr = IFileManager::Get().CreateFileReader(*RawDataAbsolutePath);

		if (RawFileReaderAr)
		{
			//데이터 읽기.
			//*RawFileReaderAr << RawDataDeserialized.Order;
			//*RawFileReaderAr << RawDataDeserialized.Name;
			*RawFileReaderAr << RawDataDeserialized;

			RawFileReaderAr->Close();
			delete RawFileReaderAr;
			RawFileReaderAr = nullptr;

			UE_LOG(LogTemp, Log, TEXT("[RawData]이름: %s, 순번: %d"), *RawDataDeserialized.Name, RawDataDeserialized.Order);
		}

	}

	//UObject 직렬화.
	StudentSource = NewObject<UStudent>();
	StudentSource->SetOrder(40);
	StudentSource->SetName(TEXT("wnwkdqls"));

	{
		//파일 이름.
		const FString ObjectDataFileName(TEXT("ObjectData.bin"));

		//파일 경로.
		FString ObjectDataAbsolutePath = FPaths::Combine(*SavedPath, *ObjectDataFileName);

		FPaths::MakeStandardFilename(ObjectDataAbsolutePath);

		//직렬화.
		TArray<uint8> BufferArray;
		FMemoryWriter MemoryWriterAr(BufferArray);
		StudentSource->Serialize(MemoryWriterAr);

		if (TUniquePtr<FArchive> FileWriterAr = TUniquePtr<FArchive>(
			IFileManager::Get().CreateDebugFileWriter(*ObjectDataAbsolutePath)))
		{
			*FileWriterAr << BufferArray;
			//StudentSource->Serialize(*FileWriterAr);
			FileWriterAr->Close();
		}

		//역 직렬화.
		if (TUniquePtr<FArchive> FileReaderAr = TUniquePtr<FArchive>(
			IFileManager::Get().CreateFileReader(*ObjectDataAbsolutePath)))
		{
			TArray<uint8> BufferArrayFromFile;
			*FileReaderAr << BufferArrayFromFile;
			FileReaderAr->Close();

			FMemoryReader MemoryReaderAr(BufferArrayFromFile);

			UStudent* StudentDes = NewObject<UStudent>();
			StudentDes->Serialize(MemoryReaderAr);

			UE_LOG(LogTemp, Log, TEXT("이름: %s, 순번: %d"), *StudentDes->GetName(), StudentDes->GetOrder());
		}
	}
}

Json 직렬화

  • Json(JavaScript Object Notation)의 약자
  • 웹 환경에서 서버와 클라이언트 사이에 데이터를 주고받을 때 사용하는 텍스트 기반 데이터 포맷
  • Json 장점
    • 텍스트임에도 데이터 크기가 가벼움.
    • 읽기 편해서 데이터를 보고 이해할 수 있음.
    • 사실 상 웹 통신의 표준으로 널리 사용됨.
  • Json의 단점
    • 지원하는 타입이 몇 가지 안됨. ( 문자, 숫자, 불리언, 널, 배열, 오브젝트만 사용 가능 )
    • 텍스트 형식으로만 사용할 수 있음.
  • 언리얼 엔진의 Json, JsonUtilities 라이브러리 활용

Json 데이터 예시

  • Json 데이터 유형
    • 오브젝트 : {}
      • 오브젝트 내 데이터는 키, 밸류 조합으로 구성됨. 예) { "key" : 10 }
    • 배열 : []
      • 배열 내 데이터는 밸류로만 구성됨. 예) [ "value1", "value2", "value3" ]
    • 이외 데이터
      • 문자열 ( "string" ) , 숫자 ( 10 또는 3.14) , 불리언 ( true 또는 false ) , 널 ( null )로 구성

이미지 예시

언리얼 스마트 포인터 라이브러리 개요

  • 일반 C++ 오브젝트의 포인터 문제를 해결해주는 언리얼 엔진의 라이브러리
  • TUniquePtr(유니크포인터) : 지정한 곳에서만 메모리를 관리하는 포인터.
    • 특정 오브젝트에게 명확하게 포인터 해지 권한을 주고 싶은 경우.
    • delete 구문 없이 함수 실행 후 자동으로 소멸시키고 싶을 때
  • TSharedPtr(공유포인터) : 더 이상 사용되지 않으면 자동으로 메모리를 해지하는 포인터
    • 여러 로직에서 할당된 오브젝트가 공유해서 사용되는 경우
    • 다른 함수로부터 할당된 오브젝트를 Out으로 받는 경우.
    • Null 일 수 있음.
  • TSharedRef(공유레퍼런스) : 공유포인터와 동일하지만, 유효한 객체를 항상 보장받는 레퍼런스
    • 여러 로직에서 할당된 오브젝트가 공유해서 사용되는 경우
    • Not Null을 보장받으며 오브젝트를 편리하게 사용하고 싶은 경우

Json 직렬화 코드 예시

해당 UnrealSerialization.Build.cs 빌트 코드에서 Json 모듈을 추가해 준다.(추가 안하면 빌드에러가 뜸)

//Json 직렬화.
const FString JsonDataFileName(TEXT("StudentJsonData.json"));

//경로 설정.
FString JsonDataAbsolutepath = FPaths::Combine(*SavedPath, *JsonDataFileName);

FPaths::MakeStandardFilename(JsonDataAbsolutepath);

//Json 객체 생성.
TSharedRef<FJsonObject> JsonObjectSource = MakeShared<FJsonObject>();

//UObjcect -> Json 객체 변환.
FJsonObjectConverter::UStructToJsonObject(
	StudentSource->GetClass(),
	StudentSource, 
	JsonObjectSource);


//Json 객체 -> Json문자열.
FString JsonString;
TSharedRef<TJsonWriter<TCHAR>> JsonWriterAr = TJsonWriterFactory<TCHAR>::Create(&JsonString);

if (FJsonSerializer::Serialize(JsonObjectSource, JsonWriterAr))
{
	//파일에 저장.
	FFileHelper::SaveStringToFile(JsonString, *JsonDataAbsolutepath);

}

//Json Read.
FString JsonFromFile;
FFileHelper::LoadFileToString(JsonFromFile, *JsonDataAbsolutepath);

//Json String-> Json Object.
TSharedRef<TJsonReader<TCHAR>> JsonReaderAr = TJsonReaderFactory<TCHAR>::Create(JsonFromFile);

TSharedPtr<FJsonObject> jsonObjectResult;
if (FJsonSerializer::Deserialize(JsonReaderAr, jsonObjectResult))
{
	//Json Object -> UObject.
	UStudent* JsonStudent = NewObject<UStudent>();
	if (FJsonObjectConverter::JsonObjectToUStruct(
		jsonObjectResult.ToSharedRef(),
		JsonStudent->GetClass(),
		JsonStudent
	))
	{
		// Test Print.
		UE_LOG(LogTemp, Log, TEXT("[JsonData] 이름: %s, 순번: %d"),
			*JsonStudent->GetName(), JsonStudent->GetOrder())
	}
}

'언리얼 엔진 공부 > 언리얼C++' 카테고리의 다른 글

언리얼 엔진 게임 제작 기초  (0) 2025.04.10
어설션(Assertion)  (0) 2025.04.08
언리얼C++델리게이트(Delegate)  (0) 2025.04.03
컴포지션  (0) 2025.04.03
인터페이스  (1) 2025.04.02

언리얼 델리게이트(Delegate)

  • 언리얼 엔진은 발행 구독 패턴 구현을 위해 델리게이트 기능을 제공함.
  • 델리게이트의 사전적 의미는 대리자.
    • 학사정보의 구독과 알림을 대리해주는 객체
  • 시나리오 구현을 위한 설계
    • 학사 정보는 구독과 알림을 대행할 델리게이트를 선언.
    • 학생은 학사 정보의 델리게이트를 통해 알림을 구독.
    • 학사 정보는 내용 변경시 델리게이트를 사용해 등록한 학생들에게 알림.

언리얼 델리게이트의 선언

 언리얼 델리게이트 선언 시 고려사항

  • 델리게이트를 설계하기 위한 고려 사항
    • 어떤 데이터를 전달하고 받을 것인가? 인자의 수와 각각의 타입을 설계
      • 몇 개의 인자를 전달할 것인가?
      • 어떤 방식으로 전달할 것인가?
      • 일대일로 전달
      • 일대다로 전달
    • 프로그래밍 환경 설정
      • C++ 프로그래밍에서만 사용할 것인가?
      • UFUNCTION으로 지정된 블루프린트 함수와 사용할 것인가?
    • 어떤 함수와 연결할 것인가?
      • 클래스 외부에 설계된 C++ 함수와 연결
      • 전역에 설계된 정적 함수와 연결
      • 언리얼 오브젝트의 멤버 함수와 연결 ( 대부분의 경우에 이 방식을 사용)

델리게이트의 유형

코드 예시

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "CourseInfo.generated.h"

// Declare Delegate
DECLARE_MULTICAST_DELEGATE_TwoParams(FCourseInfoOnChanged, const FString&, const FString&);//리턴값이 없는 형. 
//DECLARE_MULTICAST_DELEGATE_RetVal_TwoParams()//리턴값이 있는 형.

/**
 * 
 */
UCLASS()
class UNREALDELEGATE_API UCourseInfo : public UObject
{
	GENERATED_BODY()

public:
	UCourseInfo();

	FCourseInfoOnChanged OnChanged;

	void ChangeCourseInfo(const FString& InSchoolName, const FString& InNewContents);

private:
	UPROPERTY()
	FString Contents;
	
};
// Fill out your copyright notice in the Description page of Project Settings.


#include "MyGameInstance.h"//본인 클래스의 헤더는 항상 제일 먼저 와야 한다.
#include "Teacher.h"
#include "Student.h"
#include "Staff.h"
#include "Card.h"
#include "CourseInfo.h"

UMyGameInstance::UMyGameInstance()
{
	//기본 값 설정.
	//CDO(Class Default Object).언리얼에 있는 독특한 기능
	//템플릿 클래스 객체로 생성이 된다.
	SchoolName = TEXT("스쿨");
}

void UMyGameInstance::Init()
{
	Super::Init();

	UE_LOG(LogTemp, Log, TEXT("======================================="));

	//학사 정보 객체 생성.
	CourseInfo = NewObject<UCourseInfo>(this);

	//학생 객체 생성.
	UStudent* Student1 = NewObject<UStudent>();
	Student1->SetName(TEXT("학생 1"));

	UStudent* Student2 = NewObject<UStudent>();
	Student2->SetName(TEXT("학생 2"));

	UStudent* Student3 = NewObject<UStudent>();
	Student3->SetName(TEXT("학생 3"));

	//구독할 함수 등록.
	CourseInfo->OnChanged.AddUObject(Student1, &UStudent::GetNotification);
	CourseInfo->OnChanged.AddUObject(Student2, &UStudent::GetNotification);
	CourseInfo->OnChanged.AddUObject(Student3, &UStudent::GetNotification);

	//발행.
	CourseInfo->ChangeCourseInfo(SchoolName, TEXT("새로운 학사 정보"));
	
	
	
	UE_LOG(LogTemp, Log, TEXT("======================================="));


}

'언리얼 엔진 공부 > 언리얼C++' 카테고리의 다른 글

어설션(Assertion)  (0) 2025.04.08
직렬화  (2) 2025.04.08
컴포지션  (0) 2025.04.03
인터페이스  (1) 2025.04.02
언리얼 오브젝트 리플렉션 시스템  (0) 2025.04.01

컴포지션

  • 객체 지향 설계에서 상속이 가진 Is-A 관계만 의존해서는 설계와 유지 보수가 어려움이 있다.
  • 컴포지션은 객체 지향 설계에서 Has-A 관계를 구현하는 설계 방법
  • 컴포지션 활용
    • 복합적인 기능을 가진 거대한 클래스를 효과적으로 설계하는 데 유용하게 사용할 수 있습니다.

언리얼 엔진에서의 컴포지션 구현 방법

  • 하나의 언리얼 오브젝트에는 항상 클래스 기본 오브젝트 CDO 가 있다.
  • 언리얼 오브젝트에 다른 언리얼 오브젝트를 조합할 때 두 가지의 선택지가 존재한다.
    • 방법 1 : CDO 에 미리 언리얼 오브젝트를 생성해 조합한다(필수로 포함해야 하는 경우).
    • 방법 2 : CDO 에 빈 포인터만 넣고 런타임에서 언리얼 오브젝트를 생성해 조합한다(선택적으로 포함해야 하는 경우).
  • 언리얼 오브젝트를 생성할 때 컴포지션 정보를 구축할 수 있다.
    • 내가 소유한 언리얼 오브젝트를 Subobject라고 한다.
    • 나를 소유한 언리얼 오브젝트를 Outer 라고 한다.

CreateDefaultSubobject<T>(FName SubobjectName)를 사용하여 CDO가 생성 될때 생성.(생성자에서만 호출해야 함 )
런타임에 필요시에 NewObject()를 사용 하여 생성.

인터페이스란?
객체가 반드시 구현해야 할 행동을 지정하는데 활용하는 타입입니다. 다형성의 구현 의존성이 분리된 설계 유용하게 활용한다.

언리얼 엔진 C++ 인터페이스 특징

1️⃣ UInterface 클래스를 사용하여 선언해야 함
2️⃣ UObject 기반이므로, 언리얼의 리플렉션 시스템(Reflection)을 지원
3️⃣ 다른 UObject 기반 클래스에만 적용 가능 (AActor 등)
4️⃣ 인터페이스의 함수를 반드시 가상 함수(virtual)로 선언해야 함
5️⃣ C++과 블루프린트 모두에서 사용할 수 있음

// IMyInterface.h
#pragma once

#include "UObject/Interface.h"
#include "IMyInterface.generated.h"

// UINTERFACE() 매크로를 사용하여 인터페이스 선언
UINTERFACE(MinimalAPI)
class UMyInterface : public UInterface
{
    GENERATED_BODY()
};

// 인터페이스를 구현할 클래스가 상속받을 실제 인터페이스
class MYPROJECT_API IMyInterface
{
    GENERATED_BODY()

public:
    // 인터페이스의 순수 가상 함수 선언
    virtual void DoSomething() = 0;
};

UINTERFACE(MinimalAPI) → 언리얼의 리플렉션 시스템을 사용하도록 설정
IMyInterface 클래스에서 DoSomething()을 순수 가상 함수로 선언
UMyInterface는 UObject의 기능을 포함하는 언리얼 고유의 인터페이스 시스템

+ Recent posts