Unreal

4. 인터페이스

튀김족발 2023. 8. 31. 20:08

하... 야근 짱짱많다 ㅠㅠ

 

인터페이스

  • 인터페이스란?
    • 객체가 반드시 구현해야 할 생동을 지정하는데 활동되는 타입
    • 다형성(Polymorphism)의 구현, 의존성이 분리(Decouple)된 설계에 유용하게 활용
  • 언리얼 엔진에서 게임 콘텐츠를 구성하는 오브젝트의 설계 예시
    • 월드에 배치되는 모든 오브젝트, 안 움직이는 오브젝트를 포함(Actor)
    • 움직이는 오브젝트 (Pawn)
    • 길찾기 시스템을 받느시 사용하면서 움직이는 오브젝트
      (INavAgentInterface 인터페이스를 구현한 Pawn)

 

이번 예제

  • 수업에 참여하는 사람과 참여하지 않는 사람의 구분
    • 수업에 반드시 참여해야 하는 학교 구성원 : 학생, 선생
    • 수업에 참여하지 않는 학교 구성원 : 교직원
    • 수업 행동에 관련된 인터페이스 : ILessonInterface

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

  • 인터페이스를 생성하면 두 개의 클래스가 생성됨
    • U로 시작하는 타입 클래스
    • I로 시작하는 인터페이스 클래스
  • 객체를 설계할 때 I 인터페이스 클래를 사용
    • U타입 클래스 정보는 런타임에서 인터페이스 구현 여부를 파악하는 용도로 사용됨.
    • 실제로 U타입 클래스에서 작업할 일은 없음.
    • 인터페이스에 관련된 구성 및 구현은 I인터페이스 클래스에서 진행
  • C++ 인터페이스의 특징
    • 추상 타입으로만 선언할 수 있는 Java, C#과 달리 언리얼은 인터페이스에도 구현이 가능함.

 

예제

Person

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

#pragma once

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

/**
 * 
 */
UCLASS()
class UNREALINTERFACE_API UPerson : public UObject
{
	GENERATED_BODY()
	
public:
	UPerson();

	FORCEINLINE FString& GetName() { return Name; }
	FORCEINLINE void SetName(const FString& InName) { Name = InName; }

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


#include "Person.h"

UPerson::UPerson()
{
	Name = TEXT("홍길동");
}

선생, 학생, 스태프의 상위 클래스인 Person을 선언해준다.

Teacher

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

#pragma once

#include "CoreMinimal.h"
#include "Person.h"
#include "Teacher.generated.h"

/**
 * 
 */
UCLASS()
class UNREALINTERFACE_API UTeacher : public UPerson
{
	GENERATED_BODY()
	
public:
	UTeacher();
};
// Fill out your copyright notice in the Description page of Project Settings.


#include "Teacher.h"

UTeacher::UTeacher()
{
	Name = TEXT("스앵님");
}

선생을 만들어준다.

 

Student

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

#pragma once

#include "CoreMinimal.h"
#include "Person.h"
#include "Student.generated.h"

/**
 * 
 */
UCLASS()
class UNREALINTERFACE_API UStudent : public UPerson
{
	GENERATED_BODY()
	
public:
	UStudent();
};
// Fill out your copyright notice in the Description page of Project Settings.


#include "Student.h"

UStudent::UStudent()
{
	Name = TEXT("유학생");
}

학생을 만들어준다.

Staff

#pragma once

#include "CoreMinimal.h"
#include "Person.h"
#include "Staff.generated.h"

/**
 * 
 */
UCLASS()
class UNREALINTERFACE_API UStaff : public UPerson
{
	GENERATED_BODY()
	
public:
	UStaff();
};
// Fill out your copyright notice in the Description page of Project Settings.


#include "Staff.h"

UStaff::UStaff()
{
	Name = TEXT("스태프");
}

이 전에 사용했던 학생과 선생처럼 수업에 참여하지 않는 스태프를 추가한다.

 

MyGameInstance

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

#pragma once

#include "CoreMinimal.h"
#include "Engine/GameInstance.h"
#include "MyGameInstance.generated.h"

/**
 * 
 */
UCLASS()
class UNREALINTERFACE_API UMyGameInstance : public UGameInstance
{
	GENERATED_BODY()
public:
	UMyGameInstance();

	virtual void Init() override;

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

#include "MyGameInstance.h"
#include "Student.h"
#include "Teacher.h"
#include "Staff.h"

UMyGameInstance::UMyGameInstance()
{
	SchoolName = TEXT("기본학교");
}

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

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

	TArray<UPerson*> Persons = { NewObject<UStudent>(), NewObject<UTeacher>(), NewObject<UStaff>()};
	for(const TArray<UPerson*>::ElementType Person : Persons)
	{
		UE_LOG(LogTemp, Log, TEXT("구성원 이름 : %s"), *Person->GetName());
	}
	UE_LOG(LogTemp, Log, TEXT("================================="));
}

TArray 배열을 사용해서 선생, 학생, 스태프를 만들어주고, 각 이름을 출력한다.

 

이 상태로 실행을 하면

LogTemp: =================================
LogTemp: 구성원 이름 : 학생
LogTemp: 구성원 이름 : 스앵님
LogTemp: 구성원 이름 : 스태프
LogTemp: =================================

이렇게 출력이 된다.

이제 인터페이스를 만들어 넣어보자

 

Interface

cpp파일과 h파일 둘다 나오지만, 여기서는 헤더 파일만 수정한다.

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

#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 UNREALINTERFACE_API ILessonInterface
{
	GENERATED_BODY()

public:
	virtual void DoLesson()
	{
		UE_LOG(LogTemp, Log, TEXT("수업에 입장합니다."));
	}
};

 

 

인터페이스를 추적하기 위해 UINTERFACE 매크로가 나오고 그 밑에 타입 정보를 생성하기 위한 클래스가 생성된다.
사용하려는 함수들과 기능을 구현하기 위해선 그 밑에 있는 ILessonInterface를 수정해준다.

 

DoLesson()을 가상함수로 만들어준다. 추상 함수로 만들어도 문제 없다. 그리고 DoLesson()을 호출하면 나오는 기본 값을  넣어준다.

 

이 인터페이스를 학생, 선생에만 넣어주자.

Teacher

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

#pragma once

#include "CoreMinimal.h"
#include "Person.h"
#include "LessonInterface.h"
#include "Teacher.generated.h"

/**
 * 
 */
UCLASS()
class UNREALINTERFACE_API UTeacher : public UPerson, public ILessonInterface
{
	GENERATED_BODY()
	
public:
	UTeacher();

	virtual void DoLesson() override;
};
// Fill out your copyright notice in the Description page of Project Settings.


#include "Teacher.h"

UTeacher::UTeacher()
{
	Name = TEXT("스앵님");
}

void UTeacher::DoLesson()
{
	ILessonInterface::DoLesson();
	UE_LOG(LogTemp, Log, TEXT("%s님은 가르칩니다."), *Name);
}

Student

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

#pragma once

#include "CoreMinimal.h"
#include "Person.h"
#include "LessonInterface.h"
#include "Student.generated.h"

/**
 * 
 */
UCLASS()
class UNREALINTERFACE_API UStudent : public UPerson, public ILessonInterface
{
	GENERATED_BODY()
	
public:
	UStudent();

	virtual void DoLesson() override;
};
// Fill out your copyright notice in the Description page of Project Settings.


#include "Student.h"

UStudent::UStudent()
{
	Name = TEXT("학생");
}

void UStudent::DoLesson()
{
	ILessonInterface::DoLesson();
	UE_LOG(LogTemp, Log, TEXT("%s님은 가르칩니다."), *Name);
}

 

이 상태로 GameInstance에서 ILeesonInterface를 호출하자.

MyGameInstance

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

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

	TArray<UPerson*> Persons = { NewObject<UStudent>(), NewObject<UTeacher>(), NewObject<UStaff>()};
	for(const TArray<UPerson*>::ElementType Person : Persons)
	{
		UE_LOG(LogTemp, Log, TEXT("구성원 이름 : %s"), *Person->GetName());
	}
	UE_LOG(LogTemp, Log, TEXT("================================="));

	for (const TArray<UPerson*>::ElementType Person : Persons)
	{
		ILessonInterface* LessonInterface = Cast<ILessonInterface>(Person);
		if (LessonInterface != nullptr)
		{
			UE_LOG(LogTemp, Log, TEXT("%s님은 수업에 참여할 수 있습니다."), *Person->GetName());
			LessonInterface->DoLesson();
		}
		else
		{
			UE_LOG(LogTemp, Log, TEXT("%s님은 수업에 참여할 수 없습니다."), *Person->GetName());
		}
	}
	UE_LOG(LogTemp, Log, TEXT("================================="));
}

인터페이스 클래스가 없다면 nullptr을 반환한다.

이 상태로 실행을 하면

LogTemp: =================================
LogTemp: 구성원 이름 : 학생
LogTemp: 구성원 이름 : 스앵님
LogTemp: 구성원 이름 : 스태프
LogTemp: =================================
LogTemp: 학생님은 수업에 참여할 수 있습니다.
LogTemp: 수업에 입장합니다.
LogTemp: 학생님은 가르칩니다.
LogTemp: 스앵님님은 수업에 참여할 수 있습니다.
LogTemp: 수업에 입장합니다.
LogTemp: 스앵님님은 가르칩니다.
LogTemp: 스태프님은 수업에 참여할 수 없습니다.
LogTemp: =================================

이렇게 출력이 된다.