일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 | 31 |
- 224일선
- URP
- 공부
- 리팩터링
- 스즈메의 문단속
- 112일선
- 1일차
- 주식
- 구글 컨퍼런스
- shader
- JavaScript
- 2023 게이밍 인 구글 클라우드
- 스포일러 주의
- 이득우의 언리얼 프로그래밍 1
- unity
- 언리얼5
- 언리얼 5
- 2023 Gaming
- 작계훈련
- 주식단테
- 리팩터링 3장
- 2023 게이밍
- 448일선
- 2023 구글 클라우드
- GenAI
- 산토리 하이볼
- 전주비빔 라이스 버거
- 상계9동
- 이득우의 언리얼 프로그래밍1
- 리팩터링 4장
- Today
- Total
개발 이야기 안하는 개발자
언리얼5_1_2 : 언리얼 C++ 모던객체지향 설계 본문
UInterface
언리얼 Interface는 기존 C++와 다르게 설계되어 있다.
기본 클래스에 가까운 형태를 띄고 있다.
Uinterface를 제작하면 2개의 클래스가 생성이 되는데, 이때 UINTERFACE라는 매크로를 받는 클래스는 타입정보를 관리하기 위해 언리얼 에디터가 제작한 클래스이다. 해당 클래스는 수정할 게 없다.
그 하단에 있는 클래스(접두사 I가 붙어있는 클래스_인터페이스)를 수정하면 된다.
언리얼이 사용하는 인터페이스는 정의가 가능하다. (기본 틀이 클래스이기 때문)
virtual void DoLesson()
{
UE_LOG(LogTemp, Log, TEXT("수업에 입장을 합니다"));
}
이를 상속받는 자식 클래스에서는 override한다.
UCLASS()
class UNREALINTERFACE_API UStudent : public UPerson, public ILessonInterface
{
GENERATED_BODY()
public :
UStudent();
virtual void DoLesson() override;
private :
};
이때, DoLesson의 인터페이스 정의 부분을 호출하려고 할 때 Super는 사용할 수 없다. 왜냐하면 Super가 가르키는 대상은 UPerson이기 때문이다. 따라서 ILessonInterface가 정의하는 DoLesson()을 호출하고 싶다면 아래와 같이 호출해야 한다.
ILessonInterface::DoLesson();
UInterface의 형변환
TArray<UPerson*> Person = { NewObject<UStudent>(), NewObject<UTeacher>(), NewObject<UStaff>() };
for (const auto ThePerson : Person)
{
ILessonInterface* LessonInterface = Cast<ILessonInterface>(ThePerson);
if (LessonInterface)
{
UE_LOG(LogTemp, Log, TEXT("%s님은 수업에 참여할 수 있습니다."), *ThePerson->GetName());
LessonInterface->DoLesson();
}
else
{
UE_LOG(LogTemp, Log, TEXT("%s님은 수업에 참여할 수 없습니다."), *ThePerson->GetName());
}
}
컴포지션(Composition)
객체지향설계에서 상속을 가진 Is_A 관계만으론 설계와 유지보수가 어려움.
다른 성질을 가진 클래스를 가진 Has_A 관계를 컴포지션이라 함.
언리얼에선 컴포지션을 구현하기 위해선 2가지 방법이 있다.
1. CDO에 미리 생성하기 (필수적 포함)
2. CDO에 빈 포인터만 넣고 런타임에 생성하기 (선택적 포함)
서브오브젝트
언리얼 오브젝트가 가지고 있는 다른 언리얼 오브젝트.
아우터
서브오브젝트(본인)을 가지고 있는 다른 언리얼 오브젝트.
Enum
앞에 접두사 E 붙여줄 것.
UENUM으로 매크로를 붙여줘야 하며, UMETA라는 메타 정보를 집어놓고 데이터를 활용 할 수 있다.
CDO에 미리 생성하는 예제를 보기 위해 학생, 선생, 스탭이라는 Person의 부모 클래스에 서브오브젝트인 Card 클래스를 만들어 보자.
/**
*
*/
UCLASS()
class UNREALCOMPOSITION_API UCard : public UObject
{
GENERATED_BODY()
public :
UCard();
ECardType GetCardType() const { return CardType; }
void SetCardType(ECardType InCardType) { CardType = InCardType; }
private :
UPROPERTY()
ECardType CardType;
UPROPERTY()
uint32 Id;
};
UCLASS()
class UNREALCOMPOSITION_API UPerson : public UObject
{
GENERATED_BODY()
public :
UPerson();
protected :
UPROPERTY()
FString Name;
UPROPERTY()
TObjectPtr<class UCard> Card;
private :
};
여기서 중요한 점은 Card를 서브오브젝트를 가진 Person에서 class UCard* Card 가 아닌 TObjectPtr<class UCard> Card 라고 선언한 점이다.
언리얼 5로 넘어오면서 TObjectPtr이란 탬플릿으로 감싸서 사용하는 것을 선택사항이지만 권장한다.
이는 빌드할때는 큰 차이가 없지만 에디터에서 사용할땐 더 나은 환경에서 테스트를 진행하게 도와준다고 명시되어 있다.
따라서 언리얼5에선 더 나은 경험을 위해선 TObjectPtr<class T> val 로 선언하는게 좋다.
UPerson::UPerson()
{
Name = TEXT("홍길동");
Card = CreateDefaultSubobject<UCard>(TEXT("Name_Card"));
}
CDO에서 미리 선언하는 방식인데 CreateDefaultSubobject라는 메소드를 활용해서 생성하게 된다.
그리고 Person을 상속받는 Student의 생성자 이다.
UStudent::UStudent()
{
Name = TEXT("최학생");
Card->SetCardType(ECardType::Studen);
}
같은 방식으로 Teacher와 Staff를 정의하고 테스트를 진행한다.
TArray<UPerson*> Person = { NewObject<UStudent>(), NewObject<UTeacher>(), NewObject<UStaff>() };
for (const auto ThePerson : Person)
{
const UCard* OwnCard = ThePerson->GetCard();
check(OwnCard);
ECardType CardType = OwnCard->GetCardType();
UE_LOG(LogTemp, Log, TEXT("%s님이 소유한 카드 종류 %d"), *ThePerson->GetName(), CardType);
const UEnum* CardEnumType = FindObject<UEnum>(nullptr, TEXT("/Script/UnrealComposition.ECardType"));
if (CardEnumType)
{
FString CardMetaData = CardEnumType->GetDisplayNameTextByValue((int64)CardType).ToString();
UE_LOG(LogTemp, Log, TEXT("%s님이 소유한 카드 종류 %s"), *ThePerson->GetName(), *CardMetaData);
}
}
출력 :
LogTemp: 최학생님이 소유한 카드 종류 1
LogTemp: 최학생님이 소유한 카드 종류 For Student
LogTemp: 이선생님이 소유한 카드 종류 2
LogTemp: 이선생님이 소유한 카드 종류 For Teacher
LogTemp: 이직원님이 소유한 카드 종류 3
LogTemp: 이직원님이 소유한 카드 종류 For Staff
Enum을 사용하는데 사용된 FindObject는 찾고자 하는 EnumClass의 위치를 가져와서 읽어 들인다.
해당 Person이 가지고 있는 Enum타입이 어떤 메타데이터를 가지고 있는지 검색해서 가져오는 방식이다.
델리게이트
함수를 오브젝트처럼 관리해서 느스한 결합 구조를 간편하고 안정적으로 구현하는데 사용된다.
델리게이트 문서 : https://docs.unrealengine.com/5.1/ko/delegates-and-lamba-functions-in-unreal-engine/
강한 결합 : 클래스들이 서로 의존성을 가지는 경우
느슨한 결합 : 실물에 의존하지 말고 추상적 설계에 의존하는 경우 (DIP 원칙)
델리게이트 매크로 선언
DECLARE_{델리게이트 유형}DELEGATE{함수정보}
일대일 형태면서 C++만 지원한다 - DECLARE_DELEGATE
일대다 형태면서 C++만 지원한다 - DECLARE_MULTICAST
일대일 형태면서 블루프린트를 지원한다 - DECLARE_DYNAMIC
일대다 형태면서 블루프린트를 지원한다 - DECLARE_DYNAMIC_MULTICAST
인자가 없고 반환값이 없다 - DECLARE_DELEGATE
인자가 하나고 반환값이 없다 - DECLARE_DELEGATE_OneParam
인자가 세개고 반환값이 있다 - DECLARE_DELEGATE_RetVal_ThreeParams (MULTICAST는 반환값을 지원안함)
최대 9개까지 인자를 받는다.
예시 - 다수 인원을 대상으로 발송, 오직 C++에서만, 인자 2개
DECLARE_MULTICAST_DELEGATE_TwoParams
델리게이트를 사용하는 클래스 제작
DECLARE_MULTICAST_DELEGATE_TwoParams(FCourseInfoOnChangedSignature, const FString&, const FString&);
/**
*
*/
UCLASS()
class UNREALDELEGATE_API UCourseInfo : public UObject
{
GENERATED_BODY()
public :
UCourseInfo();
FCourseInfoOnChangedSignature OnChanged;
void ChnageCourseInfo(const FString& InSchoolName, const FString& InNewContents);
private:
FString Contents;
};
void UCourseInfo::ChnageCourseInfo(const FString& InSchoolName, const FString& InNewContents)
{
Contents = InNewContents;
UE_LOG(LogTemp, Log, TEXT("[CourseInfo] 학사 정보가 변경되어 알림을 발송합니다"));
OnChanged.Broadcast(InSchoolName, Contents);
}
CourseInfo 클래스는 DECLARE_MULTICAST_DELEGATE_TwoParams라는 메크로로 델리게이트를 사용할 예정이다.
해당 델리게이트의 이름은 FCourseInfoOnChangedSignature 이다.
보통 언리얼에선 델리게이트 끝에(접미사에) Signature를 붙인다.
해당 ChangeCourseInfo 메소드는 변경사항이 생겼을 경우 델리게이트를 호출하겠다라는 의미를 포함하고 있다.
델리게이트로 제작된 멤버변수 OnChaged 를 Broadcast라는 메소드를 통해 변경사항이 생긴것을 알린다.
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->ChnageCourseInfo(SchoolName, TEXT("변경된 학사 정보"));
출력 :
LogTemp: [CourseInfo] 학사 정보가 변경되어 알림을 발송합니다
LogTemp: [Student]학생3님이 기본 학교로부터 받은 메세지 : 변경된 학사 정보
LogTemp: [Student]학생2님이 기본 학교로부터 받은 메세지 : 변경된 학사 정보
LogTemp: [Student]학생1님이 기본 학교로부터 받은 메세지 : 변경된 학사 정보
void UStudent::GetNotification(const FString& School, const FString& NewCourseInfo)
{
UE_LOG(LogTemp, Log, TEXT("[Student]%s님이 %s로부터 받은 메세지 : %s"), *Name, *School, *NewCourseInfo);
}
OnChanged에 AddUObject를 통해 델리게이트에 등록한다.
마지막에 등록된 순서대로 (LIFO) 호출된다.
'Unreal > 이득우의 언리얼 프로그래밍' 카테고리의 다른 글
언리얼 5_4_1 게임플레이 어빌리티 시스템 캐릭터 제작 기초 (0) | 2024.03.24 |
---|---|
언리얼5_2_1 : 게임 컨텐츠의 기본 구조 (3) | 2024.03.06 |
언리얼5_1_4 : 언리얼 프로젝트의 에셋 (1) | 2024.03.05 |
언리얼5_1_3 : 언리얼 엔진의 자료구조와 메모리 관리 (1) | 2024.02.16 |
언리얼5_1_1 : 언리얼 오브젝트의 이해 (1) | 2024.02.16 |