언리얼 C++ 설계 Ⅰ - 인터페이스
인터페이스란?
- 객체가 반드시 구현해야 할 행동을 지정함
- 다형성(Polymorphism) 구현
- 의존성 분리(Decouple) 예시
- 월드에 배치되는 모든 오브젝트(Actor)
- 움직이는 오브젝트(Pawn)
- 길찾기 시스템을 반드시 사용하면서 움직이는 오브젝트(INavAgentInterface를 구현한 Pawn)
#pragma once#include "CoreMinimal.h"#include "UObject/Interface.h"#include "LessonInterface.generated.h"// This class does not need to be modified.UINTERFACE(MinimalAPI)class ULessonInterface : public UInterface{ GENERATED_BODY()};/** * */class UEPART1_API ILessonInterface{ GENERATED_BODY() // Add interface functions to this class. This is the class that will be inherited to implement this interface.public: virtual void DoLesson() = 0; // 추상함수 구현 가능= virtual void DoLesson(); // 직접 구현 가능};인터페이스를 생성하면 두 개의 클래스가 생성됨 객체를 설계할 땐 I--Interface 클래스를 사용
- U타입 클래스 정보는 런타임에서 인터페이스 구현 여부를 파악하는 용도로 사용됨
- 실제로 U타입 클래스에서 작업할 일은 없음
- 인터페이스에 관련된 구성 및 구현은 I 인터페이스 클래스에서 진행 C++ 인터페이스의 특징
- 추상 타입으로만 선언할 수 있는 Java, C#과 달리 언리얼은 인터페이스 안에서도 구현 가능
- 기본적으로 C++ 자체가 다중상속을 지원하나 가급적이면 언리얼 C++의 인터페이스를 사용한 최소한의 다중상속을 구현하는게 유지보수에 유리하다
언리얼 C++ 설계 Ⅱ - 컴포지션
컴포지션이란?
- 객체 지향 설계에서 Is-A 관계만 활용해서는 확장성있는 설계에 한계가 있음
- 컴포지션은 객체 지향 설계에서 Has-A 관계를 구현하는 방법 모던 객체 설계 기법(SOLID)
- Single Responsibility Principle
- Open-Closed Principle
- Liscov Substitution Principle
- Interface Segregation Principle
- Dependency Inversion Principle
- 설계 핵심은 상속을 단순화하고 단순한 기능을 가진 다수의 객체를 조합해 복잡한 객체를 구성하는데 있음 구현방법 언리얼 오브젝트에 다른 언리얼 오브젝트를 조합할 때 다음의 선택지가 존재한다
-
- CDO에 미리 언리얼 오브젝트를 생성해 조합한다(필수적 포함)
- '틱 그룹'에 자동적으로 포함되며 소유주의 틱이 돌 때 같이 실행됨
-
- CDO에 빈 포인터만 넣고 런타임에서 언리얼 오브젝트를 생성해 조합한다(선택적 포함)
- '틱 그룹'에 자동적으로 포함되지 않음 컴포지션 정보
- 내가 소유한 언리얼 오브젝트를 Subobject라고 한다
- 나를 소유한 언리얼 오브젝트를 Outer라고 한다
별첨: 언리얼 C++ 포인터
멤버변수에 포인터 변수를 두려면 원시 포인트 대신 TObjectPtr<타입> 변수를 작성할 것
- 스마트포인터는 아님
- 64비트 포인터를 지원하기 위해 랩핑된 객체
- identically to a raw pointer to a UObject. When resolved, its participation in garbage collection is identical to a raw pointer to a UObject.
- 메모리 관리(가비지콜렉팅)는 TObjectPtr과 상관없이 UCLASS() 매크로를 통해 원시 포인터까지 자동으로 관리해줌. 예시
- 게임 프레임을 ffmpeg로 영상화할 때, ffmpeg 객체는 UObject가 아니기 때문에 언리얼 외로 유저가 책임지고 관리해야하는 메모리 값임. 이럴 땐 수동으로 생성/소멸을 관리해줘야 함
별첨2: 언리얼 경로
/Engine → 설치된(사용 중인) 엔진 경로 /Game → 프로젝트 경로/Content /Script → 프로젝트 경로/Source
별첨3: TObjectPtr
참고 : https://velog.io/@geokim4491/UE-5-TObjectPtr-vs-TSoftObjectPtr-vs-TWeakObjectPtr TObjectPtr은
- raw pointer와 같다
- 액터 로드 시에 같이 메모리에 올라간다 TSoftObjectPtr은
- 내부적으로 에셋 경로(FSoftObjectPath)를 들고 있고
- 액터가 로드되어도 직접 로드하기 전까지는 메모리에 올라가지 않는다
- 따라서 메모리 절약 및 초기 로딩 최적화에 유리하다(세이브 파일, 블루프린트, 에디터 에셋 필드, 마우스/게임패드 IMC)
.LoadSynchronous()→.Get()