#include "Today_Code_Adventure.h"
#include "Level_Up.h"
#include "TIL_What_is_Cast<>().h"
class Quest
{
public:
TIL today_I_Learned = What_is_Cast<>();
Implementation Implementations =
{
커서의 위치를 찾는다 // PlayerControllerRef->GetHitResultUnderCursor(),
Turret을 커서의 위치에 따라 회전한다. // AddActorLocalRotation(),
발사체를 만든다. // GetWorld()->SpawnActor<>(),
발사체가 앞으로 날아간다. // UProjectileMovementComponent,
발사체와 닿은 물체는 피해를 입는다. // UGameplayStatics::ApplyDamage(),
물체와 닿은 발사체는 파괴된다. // Destroy(),
};
Today_Progress TodaysProgress =
{
Section 5: Toon Tank 128. Casting ~ 139. Applying Damage
}
}
커서의 위치를 알기 위해 GetHitResultUnderCursor() 함수를 사용했다.
이 함수는 APlayerController에 있는 기능이다.
APawn에는 GetController()라는 함수가 있는데 AController* 를 반환한다.
AplayerController는 AController를 상속받기에 APlayerController로 형변환을 해주어야 한다.
자식은 부모에 대한 정보가 있지만 부모는 자식에 대한 정보가 없기 때문이다.
언리얼에서 Cast는 형변환이 불가능할 시 nullptr를 반환한다.
그~래서 Cast가 어떻게 작동하는지 한번 들여다보자!
Cast()는 Cast.h에 정의되어 있다.
크게 3 부분으로 나눠서 보자
From(Interface) -> To(Interface) / To(UObject)
From이 IInterface일 경우
CastFlag
UClass의 CastFlag를 통한 Cast
From(UObject) -> To(Interface) / To(UObject)
From이 IInterface가 아니고 TCastFlag도 없을 경우
From(Interface) -> To(Interface) / To(UObject)
From이 IInterface일 경우
일단 두 Object가 Cast가 가능한지 확인한다.
static_assert는 컴파일러가
조건 /*From(현재 타입)과 To(바꿀 타입)의 크기가 0보다 큰가? */을
확인하여 불완전한 타입이라면 오류를 발생시킨다.
Src가 NULL이 아닐 경우 (NULL이라면 it문에 들어가지 못하고 바로 return nullptr가 된다)
constexpr (constant expression) 또한 컴파일 시간에 평가된다
TIsIInterface는 다음과 같이 class가 IInterface인지 판별한다.
1. UObject는 IInterface가 아님
2. UClassType typedef 멤버가 없는 타입은 IInterface가 아님
3. UClass::Type::StaticClassFlags에 CLASS_Interface가 없는 경우 IInterface가 아님
Interface.h를 가보면 모든 인터페이스의 근-본이라 한다.
IInterface의 UIClassType이 있고 UInterface는 UObject를 상속받는다.
사실 언리얼의 모든 개체가 UObject를 상속받는다.
그리고 보면 UInterface의 EClassFlags가 CLASS_Interface로 되어있다.
위 세 가지 조건은 결국 모든 인터페이스의 Base가 되는 클래스에서 기본값으로 가지고 있는 것들이다.
그래서 없으면 인터페이스가 아닌 것!
Cast 하려는 'From'이 IInterface라면 source(From* Src)의 UObject를 가져온다. <- 없다면 return nullptr이다.
그리고 변환하려는 타입 'To'가 Interface인지 확인한다.
만약 'To'도 Interface라면 Inteface -> Interface 변환이다.
여기서 GetInterfaceAddrss가 무엇인지 보자
특정 인터페이스 클래스의 포인터로 안전하게 변환하여 반환한다고 한다.
반환되는 포인터는 지정된 인터페이스 타입의 변수이거나,
개체의 클래스가 표시된 인터페이스를 구현하지 않았을 경우 Null이 된다.
그리고 인터페이스의 클래스가 Native(C++로 구현된)가 아닐 경우, (Script Interface == Blueprint로 구현된)
현재 개체의 주소가 반환이 된다.
만약 'To'가 Interface가 아니라면
To가 UObject인지 본다. std::is_same_v <To, UObject>에서 두 타입이 같은지 확인한다.
같은 타입이라면 Src의 UObject를 반환한다.
다른 타입이라면 Obj->IsA <To>()를 호출한다. (<- false면 return nullptr이다)
To의 UClass를 반환한다.
CastFlag
IInterface인지 확인하는 if문을 지나 그다음 else if 문을 보자면
UE_USE_CAST_FLAGS && TCastFlags <To>::Value!= CASTCLASS_None이 있다! 무엇인지 또 파보자!
UE_USE_CAST_FLAGS는 cast.h의 상단에 정의되어 있다. 무엇인지 들어가 보자면!
ObjectMacros.h에 정의되어 있다.
USTRUCT_ISCHILDOF_OUTERWALK 1 == super struct chain을 따라가며 구조체의 관계를 확인한다.
IsA(상속 관계 확인)과 같다.
USTRUCT_ISCHILDOF_STRUCTARRAY 2 == 각 구조체의 부모들을 배열에 저장해 두고 관계를 확인한다.
위의 방식보다 빠르고 Thread-safe 방식이지만 Blueprint reinstancing이나 Hot reload시 문제가 발생할 수 있다.
if UE_EDITOR
에디터에서는 USTRUCT_FAST_ISCHILDOF_IMPL을 USTRUCT_ISCHILDOF_OUTERWALK로 사용한다.
그래서 UE_USE_CAST_FLAGS == 1이 된다.
그다음에는 TCastFlags <To>::Value가 무엇인지 보자!
구조체 TCastFlags는 EClassCastFlags라는 상수를 가지고 있다!
EClassCastFlags는 ObjectMacros.h에 정의되어 있다.
Flag는 이 개체가 어떤 클래스인지 알려주며 빠른 캐스팅에 사용된다.
TCastFlags::Value!= CASTCLASS_None
UE_USE_CAST_FLAGS == 1이고 CastFlag가 None이 아니라면
To와 From의 상속 관계를 확인한다.
To가 Base (=Parent)이고 From이 Derived(=Child)인지 확인하고 맞다면 (To*) 부모 클래스로 캐스팅한다.
만약 둘이 상속 관계가 아니라면
"Attempting to use Cast <> on types that are not related"라고
서로 관계되어있지 않은 타입끼리 형변환을 하려고 한다고 주의를 준다.
그리고 Scr의 Uclass의 CastFlags를 확인하고 해당 타입으로 형변환이 된다.
(*C++의 상속관계가 아닌 클래스끼리의 형변환인 reinterpret_cast와 비슷하다)
From(UObject) -> To(Interface) / To(UObject)
From이 IInterface가 아니고 TCastFlag도 없을 경우
드디어 마지막 else 문이다.
잊어버릴 거 같아서 다시 정리하자면
처음에 'From'이 Interface인지 확인하였고 맞다면 여기
From이 interface가 아닌데 UE_USE_CAST_FLAGS가 1이고 TCastFlags가 CASTCLASS_None이 아닌 경우
마지막으로 위의 어떤 것도 아닌 경우이다.
이제 From이 UObject와 상속 관계인지 확인한다.
그리고 'To'가 Interface인지 확인한다.
만약 Interface로 형변환을 하는 거라면 Scr(==From)을 UObject로 형변환을 해주고 다시 To로 형변환을 해준다.
만약 'To'가 Interface가 아니라면!
To와 From의 상속 관계를 확인한다. To가 From의 Base Classs (== Parent)인지?
맞다면 From을 반환한다.
왜 바로 반환할까 생각했는데
여기까지 왔으면 From은 Interface가 아니고 CastFlag도 없지만 UObject를 상속받았으며 To의 하위 클래스이다.
즉 From은 To를 상속받았기 때문에 추가적인 타입 변환이 필요하지 않아서 그냥 반환하지 않았을까?...
일종의 Upcasting이기 때문!
그런데 말입니다.
To가 From의 Base Class가 아닐 경우
From이 To의 Base Class인지 확인하고 아니라면 Cast Warnings 메시지가 나온다!
둘이 상속 관계가 아닌데 형변환을 하려고 하는 것이다.
진짜 마지막으로 From을 UObject로 형변환 후 IsA <To> To와 상속 관계인지 확인한다.
ObjClass(= From)이 -> IsChildOf(testCls (= To)) 으로 From이 To의 하위 클래스인지 확인한다.
맞다면 (To*)으로 형변환한다.
??? : else if constexpr (std::is_base_of_v <To, From>) 이거랑
if (((const UObject*) Src)->IsA <To>()) 이거 둘 다 From이 To의 하위 클래스인지 보는 거 아님??
라고 생각이 들었다.
그래서 std::is_base_of_v <To, From>와 ((const UObject*) Src)->IsA <To>()의 차이가 무엇인지 알아보자.
std::is_base_of_v <To, From> VS ((const UObject*) Src)->IsA <To>()
std::is_base_of_v <to, from=""></to,>는 C++의 템플릿 메타 프로그래밍 기능을 사용하여
컴파일 시간에 두 개의 타입 관계를 판별하여 오류 발생 시 컴파일 시간에 발생하여 런타임 오류를 예방한다.
((const UObject*) Src)->IsA <To>()는 Unreal Engine의 Reflection System을 사용하여
런타임에 개체의 타입 관계를 동적으로 확인한다.
IsA 메서드는 Src 개체가 To의 인스턴스인지 동적으로 확인한다.
여기까지 왔는데 Casting이 되지 않는다면 nullptr를 반환한다.
어.. 실제 강의를 듣고 따라한 것보다 더 많은 시간을 들여 Cast <>()를 파헤쳐보았다.
중간에 글이 날아가서 좀.. 그랬지만 그래도 다시 정리하니 머릿속에 더 잘 정리가 된 거 같다.
쭉 파들어가면서 느낀건 언리얼에서 안정성과 효율성을 매우 중요시한다고 생각했다.
이번 강의까지 구현한 모습이다.
카메라가 조준된 방향에 맞춰 움직이면 더 좋을 거 같다.
참고 링크 :
Unreal Property System (Reflection)
https://docs.unrealengine.com/4.27/en-US/API/Runtime/CoreUObject/UObject/EClassFlags/
https://forums.unrealengine.com/t/what-exactly-is-castflags/474124
'Unreal Engine > Udemy:UE5 C++ 학습하고 게임 만들기' 카테고리의 다른 글
Unreal Day - 35 - End of Section 5. ToonTank 🚙 (0) | 2024.07.12 |
---|---|
Unreal Day - 33 - Sweep! 감지한다 충돌 (2) | 2024.07.03 |
Unreal Day - 32 - Default & Instance of Blueprint (0) | 2024.06.30 |
Unreal Day - 31 - Start Toon Tank! (0) | 2024.06.27 |
Unreal Day - 30 - End of Section 4. Crypt Raider (0) | 2024.06.26 |