개요
인터페이스(Interface)는 다양한 액터 클래스에서 구현할 수 있는 일반적인 행동이나 기능 세트를 정의합니다. 이 커뮤니케이션 방법은 여러 액터 클래스에서 동일한 타입의 기능을 구현할 때 이상적입니다. 예를 들면 프로젝트에서 여러 액터 클래스에 대한 공통 활성화 행동을 구현할 수 있습니다. 이러한 액터 클래스에는 문, 창문, 자동차 등이 포함됩니다. 각 액터 클래스는 서로 다르며 Open Interface 함수가 호출되었을 때 모두 다른 행동을 수행합니다.
이 예시에서는 인터페이스를 형변환하는 것이 선호됩니다. 모든 액터 클래스에서 개별적으로 형변환할 필요 없이 동일한 함수 호출을 사용할 수 있기 때문입니다. 인터페이스를 형변환에 사용하면 퍼포먼스상의 이점이 있습니다. 다른 액터로 형변환하는 액터를 로드하면 액터를 메모리로도 로드하기 때문입니다. 하지만 주의해서 다루지 않으면 로딩에 연쇄적인 영향을 미칠 수 있습니다. 단일 액터의 로딩으로 인해 다수의 다른 액터가 메모리로 로드될 수 있기 때문입니다. 이 방법을 사용하려면 각 액터가 일반 함수에 액세스할 수 있도록 인터페이스를 구현해야 합니다. 또한 액터에 대한 레퍼런스가 필요합니다. 그래야 해당 레퍼런스를 사용하여 인터페이스 함수를 호출할 수 있습니다. 이 커뮤니케이션 방법은 작업 중인 액터와 타깃 액터 사이의 일대일 관계를 사용합니다.
이 퀵스타트 가이드에서는 서로 다른 두 액터와 커뮤니케이션하는 간단한 인터랙션 시스템을 만들며 인터페이스의 사용법을 알아봅니다.
필수 설정
-
게임(Games) > 삼인칭(Third Person) > 블루프린트(Blueprint) 순서로 BPCommunication 라는 이름의 새 프로젝트를 생성합니다. 시작용 콘텐츠는 활성화합니다.
(w:600)
인터페이스 생성
-
콘텐츠 브라우저(Content Browser) 를 우클릭하고 블루프린트(Blueprint) > 블루프린트 인터페이스(Blueprint Interface) 를 선택합니다. 인터페이스를 BPI_Interact 로 명명합니다.
접두사 BPI_ 는 블루프린트 인터페이스에 대한 일반 명명 규칙입니다.
-
콘텐츠 브라우저(Content Browser) 에서 BPI_Interact 를 더블클릭하여 엽니다. 함수(Functions) 목록에서 첫 함수를 Interact 로 명명합니다.
-
인터페이스를 컴파일(Compile) 하고 저장(Save) 합니다.
인터랙티브 램프 생성
-
콘텐츠 브라우저(Content Browser) 에서 StarterContent > Blueprints 로 이동합니다. Blueprint_CeilingLight 를 우클릭하고 복제(Duplicate) 를 선택합니다. 이 블루프린트를 BP_Lamp 로 명명하고 게임 폴더로 이동합니다.
-
콘텐츠 브라우저(Content Browser) 에서 BP_Lamp 를 더블클릭하여 엽니다. 이벤트 그래프(Event Graph) 에서 우클릭한 뒤 커스텀 이벤트 추가(Add Custom Event) 를 검색하고 선택합니다. 이벤트를 ToggleLight 로 명명합니다.
-
ToggleLight 노드에서 드래그하여 Flip Flop 을 검색하고 선택합니다.
-
Point Light 1 컴포넌트를 이벤트 그래프(Event Graph) 로 드래그하여 레퍼런스 노드를 생성합니다. Point Light 1 노드에서 드래그하여 Set Visibility 를 검색하고 선택합니다. Flip Flop 노드의 A 핀을 아래와 같이 Set Visibility 노드에 연결합니다.
-
Point Light 1 및 Set Visibility 노드를 복사하여 Flip Flop 노드의 B 핀에 연결합니다. New Visibility 를 true로 설정합니다.
-
메뉴 바에서 클래스 세팅(Class Settings) 을 클릭하고 디테일(Details) 패널로 이동합니다.
-
인터페이스(Interfaces) 섹션으로 내려가서 추가(Add) 드롭다운을 클릭한 다음 BPI_Interact 를 검색하여 선택합니다. 블루프린트를 컴파일(Compile) 하고 저장(Save) 합니다.
-
내 블루프린트(My Blueprint) 탭의 인터페이스(Interfaces) 섹션으로 이동합니다. Interact 인터페이스 함수를 우클릭하고 이벤트 실행(Implement Event) 을 선택합니다. 이벤트 그래프(Event Graph) 에 Interact 이벤트 노드가 나타나는 것을 볼 수 있습니다.
-
Interact 이벤트 노드에서 드래그하여 ToggleLight 를 검색하고 선택합니다.
-
블루프린트를 컴파일(Compile) 하고 저장(Save) 합니다.
인터랙티브 문 생성
-
콘텐츠 브라우저(Content Browser) 에서 우클릭한 다음 기본 에셋 생성(Create Basic Asset) 섹션에서 블루프린트 클래스(Blueprint Class) 를 클릭합니다.
-
부모 클래스로 Actor 를 선택하고 블루프린트를 BP_Door 로 명명합니다.
-
콘텐츠 브라우저(Content Browser) 에서 BP_Door 를 더블클릭하여 엽니다. 그런 다음 블루프린트 에디터에서 컴포넌트(Components) 패널로 이동하여 컴포넌트 추가(Add Component) 드롭다운을 클릭합니다. Static Mesh 컴포넌트를 검색하고 선택한 뒤 컴포넌트 이름을 Frame 으로 정합니다.
-
Static Mesh 를 하나 더 추가하고 Door 로 명명합니다.
-
Frame 컴포넌트를 선택하고 디테일(Details) 패널에서 Static Mesh 드롭다운을 클릭한 뒤 SM_DoorFrame 을 검색하고 선택합니다.
-
Door 컴포넌트에도 위 단계를 반복하여 SM_Door 스태틱 메시를 추가합니다.
-
Door 컴포넌트를 선택하고 아래와 같이 Y 위치를 45.0 으로 설정합니다. 그러면 문이 프레임에 맞춰 정렬될 것입니다.
-
이벤트 그래프(Event Graph) 를 우클릭한 뒤 커스텀 이벤트 추가(Add Custom Event) 를 검색하고 선택합니다. 이벤트를 OpenDoor 로 명명합니다. 이 프로세스를 반복하여 또 다른 이벤트 CloseDoor 를 생성합니다.
-
OpenDoor 이벤트 노드에서 드래그하여 타임라인 추가(Add Timeline) 를 검색하고 선택합니다. 타임라인을 TM_Door 로 명명합니다.
-
CloseDoor 이벤트를 TM_Door 의 Reverse 실행 핀에 연결합니다.
-
TM_Door 를 더블클릭하여 엽니다. Float 트랙 추가(Add Float Track) 를 클릭하여 Float 트랙을 추가하고 Alpha 로 명명합니다. 길이를 1.00 으로 설정합니다.
-
그래프에서 우클릭한 뒤 CurveFloat_1 에 키 추가(Add key to CurveFloat_1) 를 선택하여 새 포인트를 추가합니다. 시간(Time) 및 값(Value) 을 0.0 으로 설정합니다.
-
위 단계를 반복하여 포인트를 하나 더 추가하고 시간(Time) 과 값(Value) 을 1.0 으로 설정합니다.
-
이벤트 그래프(Event Graph) 로 돌아가서 Door 스태틱 메시 컴포넌트를 이벤트 그래프 로 드래그하여 노드를 생성합니다. Door 노드에서 드래그하여 SetRelativeRotation 을 검색하고 선택합니다.
-
TM_Door 의 Update 핀을 SetRelativeRotation 노드에 연결합니다. SetRelativeRotation 노드의 New Rotation 핀을 우클릭하고 구조체 핀 분할(Split Struct Pin) 을 선택합니다.
-
이벤트 그래프(Event Graph) 를 우클릭한 뒤 Lerp Float 를 검색하고 선택합니다. Lerp 노드의 Return Value 를 SetRelativeRotation 노드의 Yaw 핀에 연결합니다. TM_Door 의 Alpha 핀을 Lerp 노드의 Alpha 핀에 연결합니다. 마지막으로 B 값을 아래와 같이 90.0 으로 설정합니다.
-
TM_Door 의 Finished 핀에서 드래그하여 Retriggerable Delay 를 검색하고 선택합니다. 노드의 값을 2.0 으로 설정합니다.
-
Retriggerable Delay 노드에서 드래그하여 CloseDoor 를 검색하고 선택합니다.
-
메뉴 바에서 클래스 세팅(Class Settings) 을 클릭합니다.
-
인터페이스(Interfaces) 섹션의 추가(Add) 드롭다운을 클릭한 다음 BPI_Interact 를 검색하여 선택합니다.
-
Interact 이벤트 노드에서 드래그하여 OpenDoor 를 검색하고 선택합니다.
-
블루프린트를 컴파일(Compile) 하고 저장(Save) 합니다.
플레이어 블루프린트 수정하기
-
블루프린트 에디터에서 ThirdPersonCharacter 블루프린트를 엽니다.
-
블루프린트 에디터에서 컴포넌트(Components) 패널로 이동하여 컴포넌트 추가(Add Component) 버튼을 클릭합니다. Sphere Collision 을 검색하고 선택합니다. 이렇게 하면 블루프린트에 스피어 콜리전 컴포넌트가 추가됩니다.
-
Sphere Collision 컴포넌트를 선택한 상태로 디테일(Details) 패널로 이동해 Sphere Radius 를 200으로 설정합니다.
-
Sphere Collision 컴포넌트를 우클릭하고 OnComponentBeginOverlap 이벤트를 선택하여 이벤트 그래프(Event Graph) 에 추가합니다.
-
On Component Begin Overlap 노드에서 드래그하여 Interact (Message) 를 검색하고 선택합니다. BPI Interact 카테고리에 있는 함수를 선택해야 합니다.
-
On Component Begin Overlap 이벤트의 Other Actor 노드를 Interact 함수의 Target 핀에 연결합니다.
-
블루프린트를 컴파일(Compile) 하고 저장(Save) 합니다.
6 - 인터랙션 시스템 테스트
-
BP_Door 및 BP_Lamp 액터를 레벨로 드래그합니다.
(w:600) -
플레이 를 누르고 각 액터에 접근하여 플레이어와 상호작용하는 것을 확인합니다.
(w:600)(convert:false)
다음 단계
이제 인터페이스의 사용법을 배웠으니 액터 커뮤니케이션 문서 페이지에 있는 다른 커뮤니케이션 타입에 대해서도 알아보세요.
개요
인터페이스(Interface)는 다양한 액터에서 구현할 수 있는 일반적인 행동 또는 메서드 세트를 정의합니다. 이 커뮤니케이션 타입은 여러 액터 클래스 블루프린트에서 동일한 타입의 함수 기능을 구현할 때 특히 유용합니다.
예를 들어 문, 창문, 자동차 등 프로젝트 내의 여러 블루프린트 클래스에 대해 일반적인 '열기' 행동을 구현할 때 인터페이스를 사용할 수 있습니다. 이 예시에서 각 액터는 서로 다른 클래스이며 '열기' 인터페이스가 호출되면 서로 다른 반응을 보일 것입니다.
또한 형변환에 인터페이스를 사용하면 퍼포먼스상의 이점이 있습니다. 다른 액터 클래스 블루프린트로 형변환하는 액터 클래스 블루프린트를 로드하면 해당 블루프린트를 메모리로도 로드하기 때문입니다. 하지만 주의해서 사용하지 않으면 로딩에 연쇄적인 영향을 미칠 수 있습니다. 단일 블루프린트의 로딩으로 인해 다수의 다른 블루프린트가 메모리로 로드될 수 있기 때문입니다.
이 방법을 사용하려면 각 액터 클래스 블루프린트가 일반 함수에 액세스할 수 있도록 인터페이스를 구현해야 합니다.
목표
이 퀵스타트 가이드에서는 두 액터 클래스 블루프린트와 커뮤니케이션하는 단순 인터랙션 시스템을 만들며 인터페이스의 사용법을 알아봅니다.
세부 목표
-
Interact 함수로 인터페이스를 생성합니다.
-
인터랙티브 램프와 문 액터를 생성하고 인터페이스를 구현합니다.
-
BPCommunication Character 클래스를 수정하여 주변 오브젝트에서 Interact 인터페이스 함수를 호출합니다.
1 - 필수 구성
-
메뉴의 새 프로젝트 카테고리(New Project Categories) 섹션에서 게임(Games) 을 선택하고 다음(Next) 을 클릭합니다.
-
삼인칭(Third Person) 템플릿을 선택하고 다음(Next) 을 클릭합니다.
-
C++ 프로젝트 및 시작용 콘텐츠 포함(With Starter Content) 옵션을 선택하고 프로젝트 생성(Create Project) 을 클릭합니다.
섹션 결과
이제 새로운 삼인칭 프로젝트가 생성되었으며, 인터페이스를 알아볼 준비가 되었습니다.
2 - 인터페이스 만들기
-
C++ 클래스 마법사(C++ Class Wizard) 에서 InteractInterface 라는 이름의 새 언리얼 인터페이스 클래스를 생성합니다.
-
InteractInterface.h의 클래스 디폴트에 다음 메서드를 구현합니다.
public:
UFUNCTION() virtual void OnInteract() = 0; -
코드를 컴파일합니다.
완성된 코드
InteractionInterface.h
#pragma once
#include "CoreMinimal.h"
#include "UObject/Interface.h"
#include "InteractInterface.generated.h"
// This class does not need to be modified.
UINTERFACE(MinimalAPI)
class UInteractInterface : public UInterface
{
GENERATED_BODY()
};
/**
*
*/
class BPCOMMUNICATION_API IInteractInterface
{
GENERATED_BODY()
// Add interface functions to this class. This is the class that will be inherited to implement this interface.
public:
UFUNCTION()
virtual void OnInteract() = 0;
};
섹션 결과
이 섹션에서는 언리얼 인터페이스를 생성하고 OnInteract 함수를 추가했습니다. 이제 이 인터페이스를 구현하는 모든 액터 클래스 블루프린트는 이 함수를 사용할 수 있습니다.
2 - 인터랙티브 천장 조명 액터 만들기
-
C++ 클래스 마법사(C++ Class Wizard) 에서 CeilingLight 라는 이름의 새 액터 클래스를 생성합니다.
CeilingLight.h 의 클래스 디폴트에서 아래 클래스 라이브러리를 선언합니다.
#include "InteractInterface.h"그런 다음 아래 코드를 구현합니다.
UCLASS() class BPCOMMUNICATION_API ACeilingLight : public AActor, public IInteractInterface { GENERATED_BODY() public: virtual void OnInteract(); protected: UPROPERTY(EditAnywhere, BlueprintReadWrite) class UPointLightComponent* PointLightComp; UPROPERTY(EditAnywhere, BlueprintReadWrite) UStaticMeshComponent* StaticMeshComp; UPROPERTY(EditAnywhere, BlueprintReadWrite) float Brightness; UPROPERTY(EditAnywhere, BlueprintReadWrite) float SourceRadius; UPROPERTY(EditAnywhere, BlueprintReadWrite) FLinearColor Color; UPROPERTY(EditAnywhere, BlueprintReadWrite) bool bIsLightOn; UFUNCTION() void ToggleLight(); } -
다음으로 CeilingLight.cpp 로 이동하여 아래의 Include 라이브러리를 선언합니다.
#include "Components/PointLightComponent.h"그런 다음 아래 코드를 구현합니다.
ACeilingLight::ACeilingLight() { // 이 액터가 프레임마다 Tick()을 호출하도록 설정합니다. 이 설정이 필요 없는 경우 비활성화하면 퍼포먼스가 향상됩니다. PrimaryActorTick.bCanEverTick = true; RootComponent = CreateDefaultSubobject<URootComponent>(TEXT("RootComponent")); PointLightComp = CreateDefaultSubobject<UPointLightComponent>(TEXT("PointLightComp")); StaticMeshComp = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("StaticMeshComp")); PointLightComp->AttachToComponent(RootComponent,FAttachmentTransformRules::KeepRelativeTransform); StaticMeshComp->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepRelativeTransform); PointLightComp->SetWorldLocation(FVector(0, 0, -130)); Brightness = 1700.f; Color = FLinearColor(1.f, 0.77f, 0.46f); SourceRadius = 3.5f; PointLightComp->SetIntensity(Brightness); PointLightComp->SetLightColor(Color); PointLightComp->SetSourceRadius(SourceRadius); } void ACeilingLight::OnInteract() { ToggleLight(); } void ACeilingLight::ToggleLight() { if (bbIsLightOn) { PointLightComp->SetVisibility(false); bbIsLightOn = false; } else { PointLightComp->SetVisibility(true); bbIsLightOn = true; } } -
코드를 컴파일합니다.
-
C++ 클래스 폴더(C++ Classes folder) 에서 CeilingLight 액터를 우클릭한 다음 C++ 클래스 액션(C++ Class Actions) 드롭다운 메뉴에서 CeilingLight 기반 블루프린트 클래스 생성(Create Blueprint class based on CeilingLight) 을 선택합니다. 블루프린트를 BP_CeilingLight 로 명명합니다.
-
BP_CeilingLight 의 클래스 디폴트에서 컴포넌트(Components) 패널로 이동한 다음 StaticMeshComp 를 선택합니다.
-
디테일(Details) 패널에서 Static Mesh 카테고리로 이동한 다음 Static Mesh 변수 옆에 있는 드롭다운 화살표를 선택하고 SM_Lamp_Ceiling 을 검색하여 선택합니다.
완성된 코드
CeilingLight.h
// 프로젝트 세팅의 설명 페이지에 저작권 통지를 채워 넣으세요.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "InteractInterface.h"
#include "CeilingLight.generated.h"
UCLASS()
class BPCOMMUNICATION_API ACeilingLight : public AActor, public IInteractInterface
{
GENERATED_BODY()
public:
// 이 액터 프로퍼티의 디폴트값 설정
ACeilingLight();
virtual void OnInteract();
protected:
// 게임 시작 또는 스폰 시 호출
virtual void BeginPlay() override;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
class UPointLightComponent* PointLightComp;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
UStaticMeshComponent* StaticMeshComp;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
float Brightness;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
float SourceRadius;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FLinearColor Color;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
bool bbIsLightOn;
public:
// 프레임마다 호출
virtual void Tick(float DeltaTime) override;
UFUNCTION()
void ToggleLight();
};
CeilingLight.cpp
//Copyright Epic Games, Inc. All Rights Reserved.
#include "CeilingLight.h"
#include "Components/PointLightComponent.h"
// 디폴트값 설정
ACeilingLight::ACeilingLight()
{
// 이 액터가 프레임마다 Tick()을 호출하도록 설정합니다. 이 설정이 필요 없는 경우 비활성화하면 퍼포먼스가 향상됩니다.
PrimaryActorTick.bCanEverTick = true;
RootComponent = CreateDefaultSubobject<URootComponent>(TEXT("RootComponent"));
PointLightComp = CreateDefaultSubobject<UPointLightComponent>(TEXT("PointLightComp"));
StaticMeshComp = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("StaticMeshComp"));
PointLightComp->AttachToComponent(RootComponent,FAttachmentTransformRules::KeepRelativeTransform);
StaticMeshComp->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepRelativeTransform);
PointLightComp->SetWorldLocation(FVector(0, 0, -130));
Brightness = 1700.f;
Color = FLinearColor(1.f, 0.77f, 0.46f);
SourceRadius = 3.5f;
PointLightComp->SetIntensity(Brightness);
PointLightComp->SetLightColor(Color);
PointLightComp->SetSourceRadius(SourceRadius);
}
void ACeilingLight::OnInteract()
{
ToggleLight();
}
// 게임 시작 또는 스폰 시 호출
void ACeilingLight::BeginPlay()
{
Super::BeginPlay();
}
void ACeilingLight::ToggleLight()
{
if (bIsLightOn)
{
PointLightComp->SetVisibility(false);
bIsLightOn = false;
}
else
{
PointLightComp->SetVisibility(true);
bIsLightOn = true;
}
}
// 프레임마다 호출
void ACeilingLight::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
섹션 결과
이 섹션에서는 천장 조명 액터 클래스를 생성하고 라이트의 비저빌리티를 토글하는 커스텀 함수를 추가했습니다. 또한 ToggleLight 이벤트의 실행을 트리거할 Interact 인터페이스를 구현했습니다.
4 - 인터랙티브 문 생성
-
C++ 클래스 마법사(Class Wizard) 에서 DoorActor 라는 이름의 신규 액터(Actor) 클래스를 만듭니다.
-
DoorActor.h 파일로 이동한 뒤 다음을 선언합니다.
#include "Components/TimelineComponent.h" #include "InteractInterface.h" -
DoorActor 클래스 네임스페이스에서 Interact 인터페이스를 상속해야 합니다.
UCLASS() class BPCOMMUNICATION_API ADoorActor : public AActor, public IInteractInterface -
다음 클래스 정의를 선언합니다.
// 커브 에셋을 보관하는 변수 UPROPERTY(EditAnywhere) UCurveFloat* DoorTimelineFloatCurve; UFUNCTION() virtual void OnInteract(); protected: // 게임 시작 또는 스폰 시 호출 virtual void BeginPlay() override; //문 에셋을 나타내는 MeshComponents UPROPERTY(VisibleAnywhere, BlueprintReadWrite) UStaticMeshComponent* DoorFrame; UPROPERTY(VisibleAnywhere, BlueprintReadWrite) UStaticMeshComponent* Door; //문 메시를 애니메이팅하는 TimelineComponent UPROPERTY(VisibleAnywhere, BlueprintReadWrite) UTimelineComponent* DoorTimelineComp; //업데이트 트랙 이벤트를 처리할 Float 트랙 시그니처 FOnTimelineFloat UpdateFunctionFloat; //타임라인 그래프에 따라 문의 상대적 위치를 업데이트하는 함수 UFUNCTION() void UpdateTimelineComp(float Output); -
DoorActor.cpp 에서 다음 클래스 정의를 구현합니다.
ADoorActor::ADoorActor() { //디폴트 컴포넌트 생성 DoorFrame = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("DoorFrameMesh")); Door = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("DoorMesh")); DoorTimelineComp = CreateDefaultSubobject<UTimelineComponent>(TEXT("DoorTimelineComp")); //어태치먼트 구성 DoorFrame->SetupAttachment(RootComponent); Door->AttachToComponent(DoorFrame, FAttachmentTransformRules::KeepRelativeTransform); Door->SetRelativeLocation(FVector(0, 35, 0)); } void ADoorActor::OnInteract() { DoorTimelineComp->Play(); } // 게임 시작 또는 스폰 시 호출 void ADoorActor::BeginPlay() { Super::BeginPlay(); //Float 트랙을 UpdateTimelineComp 함수의 출력에 바인딩 UpdateFunctionFloat.BindDynamic(this, &ADoorActor::UpdateTimelineComp); //Float 커브가 있는 경우 그래프를 업데이트 함수에 바인딩 if (DoorTimelineFloatCurve) { DoorTimelineComp->AddInterpFloat(DoorTimelineFloatCurve, UpdateFunctionFloat); } } void ADoorActor::UpdateTimelineComp(float Output) { // 타임라인 커브(Timeline Curve)의 출력을 바탕으로 문의 새 상대적 위치 설정 및 구성 FRotator DoorNewRotation = FRotator(0.0f, Output, 0.f); Door->SetRelativeRotation(DoorNewRotation); } -
코드를 컴파일합니다.
-
콘텐츠 브라우저(Content Browser) 에서 추가/임포트(Add/Import) > 기타(Miscellaneous) > 커브(Curve) 를 선택합니다.
-
CurveFloat 를 선택하고 CurveFloat 에셋을 DoorCurveFloat 로 명명합니다.
-
DoorCurveFloat 에셋을 더블클릭합니다. Float 커브에 두 개의 키를 추가하고 시간 값을 각각 (0, 0)과 (4, 90)으로 설정합니다.
-
Shift+클릭하여 두 키를 함께 선택한 다음 자동 큐빅(Auto Cubic) 보간으로 설정하고 커브를 저장합니다.
-
DoorCurveFloat를 저장합니다.
-
콘텐츠 브라우저에서 C++ 클래스 폴더(C++ Classes folder) 로 이동하여 DoorActor 클래스를 우클릭합니다. DoorActor 기반 블루프린트 클래스 생성(Create Blueprint Class based on DoorActor) 을 선택하고 블루프린트 액터를 BP_DoorActor 로 명명합니다.
-
BP_DoorActor 의 클래스 디폴트(Class Defaults) 에서 컴포넌트(Components) 탭을 찾고, DoorFrame 스태틱 메시(Static Mesh) 컴포넌트를 선택한 뒤 디테일(Details) 패널에서 스태틱 메시를 SM_DoorFrame 으로 변경합니다.
-
컴포넌트 탭에서 DoorMesh 컴포넌트를 선택합니다. 디테일 패널로 이동한 뒤 스태틱 메시를 SM_Door 로 변경합니다.
-
디테일 패널의 Door Timeline Float Curve 드롭다운 메뉴에서 DoorCurveFloat를 선택합니다.
-
블루프린트를 컴파일하고 저장합니다.
완성된 코드
DoorActor.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Components/TimelineComponent.h"
#include "InteractInterface.h"
#include "DoorActor.generated.h"
UCLASS()
class BPCOMMUNICATION_API ADoorActor : public AActor, public IInteractInterface
{
GENERATED_BODY()
public:
// 이 액터 프로퍼티의 디폴트값 설정
ADoorActor();
// 커브 에셋을 보관하는 변수
UPROPERTY(EditAnywhere)
UCurveFloat* DoorTimelineFloatCurve;
UFUNCTION()
virtual void OnInteract();
protected:
// 게임 시작 또는 스폰 시 호출
virtual void BeginPlay() override;
//문 에셋을 나타내는 MeshComponents
UPROPERTY(VisibleAnywhere, BlueprintReadWrite)
UStaticMeshComponent* DoorFrame;
UPROPERTY(VisibleAnywhere, BlueprintReadWrite)
UStaticMeshComponent* Door;
//문 메시를 애니메이팅하는 TimelineComponent
UPROPERTY(VisibleAnywhere, BlueprintReadWrite)
UTimelineComponent* DoorTimelineComp;
//업데이트 트랙 이벤트를 처리할 Float 트랙 시그니처
FOnTimelineFloat UpdateFunctionFloat;
//타임라인 그래프에 따라 문의 상대적 위치를 업데이트하는 함수
UFUNCTION()
void UpdateTimelineComp(float Output);
public:
// 프레임마다 호출
virtual void Tick(float DeltaTime) override;
};
DoorActor.cpp
#include "DoorActor.h"
// 디폴트값 설정
ADoorActor::ADoorActor()
{
// 이 액터가 프레임마다 Tick()을 호출하도록 설정합니다. 이 설정이 필요 없는 경우 비활성화하면 퍼포먼스가 향상됩니다.
PrimaryActorTick.bCanEverTick = true;
//디폴트 컴포넌트 생성
DoorFrame = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("DoorFrameMesh"));
Door = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("DoorMesh"));
DoorTimelineComp = CreateDefaultSubobject<UTimelineComponent>(TEXT("DoorTimelineComp"));
//어태치먼트 구성
DoorFrame->SetupAttachment(RootComponent);
Door->AttachToComponent(DoorFrame, FAttachmentTransformRules::KeepRelativeTransform);
Door->SetRelativeLocation(FVector(0, 35, 0));
}
void ADoorActor::OnInteract()
{
DoorTimelineComp->Play();
}
// 게임 시작 또는 스폰 시 호출
void ADoorActor::BeginPlay()
{
Super::BeginPlay();
//Float 트랙을 UpdateTimelineComp 함수의 출력에 바인딩
UpdateFunctionFloat.BindDynamic(this, &ADoorActor::UpdateTimelineComp);
//Float 커브가 있는 경우 그래프를 업데이트 함수에 바인딩
if (DoorTimelineFloatCurve)
{
DoorTimelineComp->AddInterpFloat(DoorTimelineFloatCurve, UpdateFunctionFloat);
}
}
void ADoorActor::UpdateTimelineComp(float Output)
{
// 타임라인 커브(Timeline Curve)의 출력을 바탕으로 문의 새 상대적 위치 설정 및 구성
FRotator DoorNewRotation = FRotator(0.0f, Output, 0.f);
Door->SetRelativeRotation(DoorNewRotation);
}
// 프레임마다 호출
void ADoorActor::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
섹션 결과
이 섹션에서는 Interact 인터페이스의 OnInteract 메서드가 호출될 때 열리는 인터랙티브 문 액터를 생성했습니다.
5 - BPCommunicationCharacter 클래스 수정
-
BPCommunicationCharacter.h 파일을 열고 클래스 정의에서 다음을 선언합니다.
protected: virtual void NotifyActorBeginOverlap(AActor* OtherActor); class USphereComponent* SphereComp; -
BPCommunicationCharacter.cpp 파일로 이동하여 아래의 클래스 라이브러리를 선언합니다.
#include "Components/SphereComponent.h" #include "InteractInterface.h"그런 다음 아래 클래스 메서드를 구현합니다.
ABPCommunicationCharacter::ABPCommunicationCharacter() { SphereComp = CreateDefaultSubobject<USphereComponent>(TEXT("SphereComp")); SphereComp->AttachToComponent(GetMesh(), FAttachmentTransformRules::KeepRelativeTransform); SphereComp->SetSphereRadius(200); } void ABPCommunicationCharacter::NotifyActorBeginOverlap(AActor* OtherActor) { if (IInteractInterface* ActorCheck = Cast<IInteractInterface>(OtherActor)) { ActorCheck->OnInteract(); } } -
코드를 컴파일합니다.
완성된 코드
BPCommunicationCharacter.h
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "BPCommunicationCharacter.generated.h"
UCLASS(config=Game)
class ABPCommunicationCharacter : public ACharacter
{
GENERATED_BODY()
/** 캐릭터 뒤에 카메라를 배치하는 카메라 붐 */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))
class USpringArmComponent* CameraBoom;
/** 카메라 따라가기 */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))
class UCameraComponent* FollowCamera;
public:
ABPCommunicationCharacter();
/** 베이스 회전 속도, 단위는 도(º)/초. 다른 스케일 값 조절로 인해 최종 회전 속도가 영향을 받을 수 있습니다. */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category=Camera)
float BaseTurnRate;
/** 베이스 올려다보기/내려다보기 속도, 단위는 도(º)/초. 다른 스케일 값 조절로 인해 최종 속도가 영향을 받을 수 있습니다. */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category=Camera)
float BaseLookUpRate;
protected:
virtual void NotifyActorBeginOverlap(AActor* OtherActor);
class USphereComponent* SphereComp;
/** VR의 HMD 오리엔테이션을 리셋합니다. */
void OnResetVR();
/** 앞뒤 입력으로 호출 */
void MoveForward(float Value);
/** 좌우 입력으로 호출 */
void MoveRight(float Value);
/**
* 입력을 통해 호출되어 지정된 속도로 회전
* @param Rate 정규화된 비율이며, 1.0인 경우 지정된 회전 속도의 100%를 의미합니다.
*/
void TurnAtRate(float Rate);
/**
* 입력을 통해 호출되어 지정된 속도로 올려다보기/내려다보기
* @param Rate 정규화된 비율이며, 1.0인 경우 지정된 회전 속도의 100%를 의미합니다.
*/
void LookUpAtRate(float Rate);
/** 터치 입력 시작 시 핸들러 */
void TouchStarted(ETouchIndex::Type FingerIndex, FVector Location);
/** 터치 입력 중지 시 핸들러 */
void TouchStopped(ETouchIndex::Type FingerIndex, FVector Location);
protected:
// APawn 인터페이스
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
// APawn 인터페이스 종료
public:
/** CameraBoom 서브오브젝트 반환 **/
FORCEINLINE class USpringArmComponent* GetCameraBoom() const { return CameraBoom; }
/** FollowCamera 서브오브젝트 반환 **/
FORCEINLINE class UCameraComponent* GetFollowCamera() const { return FollowCamera; }
};
BPCommunicationCharacter.cpp
// Copyright Epic Games, Inc. All Rights Reserved.
#include "BPCommunicationCharacter.h"
#include "HeadMountedDisplayFunctionLibrary.h"
#include "Camera/CameraComponent.h"
#include "Components/CapsuleComponent.h"
#include "Components/InputComponent.h"
#include "Components/SphereComponent.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "GameFramework/Controller.h"
#include "InteractInterface.h"
#include "GameFramework/SpringArmComponent.h"
//////////////////////////////////////////////////////////////////////////
// ABPCommunicationCharacter
ABPCommunicationCharacter::ABPCommunicationCharacter()
{
// 콜리전 캡슐 크기 설정
GetCapsuleComponent()->InitCapsuleSize(42.f, 96.0f);
// 입력에 대한 회전 속도 설정
BaseTurnRate = 45.f;
BaseLookUpRate = 45.f;
// 컨트롤러 회전 시 회전하지 않습니다. 카메라에만 영향을 미치도록 합니다.
bUseControllerRotationPitch = false;
bUseControllerRotationYaw = false;
bUseControllerRotationRoll = false;
// 캐릭터 무브먼트 환경설정
GetCharacterMovement()->bOrientRotationToMovement = true; // 캐릭터가 입력 방향으로 이동
GetCharacterMovement()->RotationRate = FRotator(0.0f, 540.0f, 0.0f); // 위의 캐릭터가 이동하는 회전 속도
GetCharacterMovement()->JumpZVelocity = 600.f;
GetCharacterMovement()->AirControl = 0.2f;
// 카메라 붐 생성(콜리전 있을 시 플레이어 쪽으로 들어옴)
CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));
CameraBoom->SetupAttachment(RootComponent);
CameraBoom->TargetArmLength = 300.0f; // 캐릭터 뒤의 카메라가 이 거리에서 따라옴
CameraBoom->bUsePawnControlRotation = true; // 컨트롤러 기반으로 암 회전
// 카메라 따라가기 생성
FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera"));
FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName); // 카메라를 붐 끝에 어태치하여 붐이 컨트롤러 오리엔테이션에 맞추어 조절되도록 함
FollowCamera->bUsePawnControlRotation = false; // 카메라가 암 기준으로 회전하지 않음
SphereComp = CreateDefaultSubobject<USphereComponent>(TEXT("SphereComp"));
SphereComp->AttachToComponent(GetMesh(), FAttachmentTransformRules::KeepRelativeTransform);
SphereComp->SetSphereRadius(200);
// 참고: 캐릭터로부터 상속받는 메시 컴포넌트에 대한 스켈레탈 메시와 애님 블루프린트 레퍼런스는
// C++ 직접 콘텐츠 레퍼런스를 방지하기 위해 이름이 MyCharacter인 파생 블루프린트 에셋에서 설정됨
}
//////////////////////////////////////////////////////////////////////////
// 입력
void ABPCommunicationCharacter::SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent)
{
// 게임플레이 키 바인딩 설정
check(PlayerInputComponent);
PlayerInputComponent->BindAction("Jump", IE_Pressed, this, &ACharacter::Jump);
PlayerInputComponent->BindAction("Jump", IE_Released, this, &ACharacter::StopJumping);
PlayerInputComponent->BindAxis("MoveForward", this, &ABPCommunicationCharacter::MoveForward);
PlayerInputComponent->BindAxis("MoveRight", this, &ABPCommunicationCharacter::MoveRight);
// 2가지 버전의 회전 바인딩이 있어 서로 다른 종류의 디바이스를 다양한 방식으로 처리할 수 있습니다.
// 'turn'은 마우스와 같은 절대 델타를 제공하는 디바이스를 처리합니다.
// 'turnrate'는 아날로그 조이스틱과 같이 변화의 속도를 취급할 디바이스에 사용합니다.
PlayerInputComponent->BindAxis("Turn", this, &APawn::AddControllerYawInput);
PlayerInputComponent->BindAxis("TurnRate", this, &ABPCommunicationCharacter::TurnAtRate);
PlayerInputComponent->BindAxis("LookUp", this, &APawn::AddControllerPitchInput);
PlayerInputComponent->BindAxis("LookUpRate", this, &ABPCommunicationCharacter::LookUpAtRate);
// 터치 디바이스 처리
PlayerInputComponent->BindTouch(IE_Pressed, this, &ABPCommunicationCharacter::TouchStarted);
PlayerInputComponent->BindTouch(IE_Released, this, &ABPCommunicationCharacter::TouchStopped);
// VR 헤드셋 기능
PlayerInputComponent->BindAction("ResetVR", IE_Pressed, this, &ABPCommunicationCharacter::OnResetVR);
}
void ABPCommunicationCharacter::NotifyActorBeginOverlap(AActor* OtherActor)
{
if (IInteractInterface* ActorCheck = Cast<IInteractInterface>(OtherActor))
{
ActorCheck->OnInteract();
}
}
void ABPCommunicationCharacter::OnResetVR()
{
// 언리얼 에디터의 '기능 추가'를 사용해 프로젝트에 BPCommunication을 추가한 경우, BPCommunication.Build.cs에서 HeadMountedDisplay의 종속성이 자동 전파되지 않습니다.
// 또한 링커 오류가 발생합니다.
// 다음 중 하나를 실행해야 합니다.
// [프로젝트 이름].Build.cs의 PublicDependencyModuleNames에 'HeadMountedDisplay'를 추가하면 빌드에 성공합니다. VR 지원 시 적합합니다.
// 또는
// 아래의 ResetOrientationAndPosition 호출을 코멘트 처리하거나 삭제합니다. VR 미지원 시 적합합니다.
UHeadMountedDisplayFunctionLibrary::ResetOrientationAndPosition();
}
void ABPCommunicationCharacter::TouchStarted(ETouchIndex::Type FingerIndex, FVector Location)
{
Jump();
}
void ABPCommunicationCharacter::TouchStopped(ETouchIndex::Type FingerIndex, FVector Location)
{
StopJumping();
}
void ABPCommunicationCharacter::TurnAtRate(float Rate)
{
// 속도 정보로부터 이 프레임에 대한 델타 계산
AddControllerYawInput(Rate * BaseTurnRate * GetWorld()->GetDeltaSeconds());
}
void ABPCommunicationCharacter::LookUpAtRate(float Rate)
{
// 속도 정보로부터 이 프레임에 대한 델타 계산
AddControllerPitchInput(Rate * BaseLookUpRate * GetWorld()->GetDeltaSeconds());
}
void ABPCommunicationCharacter::MoveForward(float Value)
{
if ((Controller != nullptr) && (Value != 0.0f))
{
// 앞쪽 찾기
const FRotator Rotation = Controller->GetControlRotation();
const FRotator YawRotation(0, Rotation.Yaw, 0);
// 앞쪽 벡터 구하기
const FVector Direction = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
AddMovementInput(Direction, Value);
}
}
void ABPCommunicationCharacter::MoveRight(float Value)
{
if ( (Controller != nullptr) && (Value != 0.0f) )
{
// 오른쪽 찾기
const FRotator Rotation = Controller->GetControlRotation();
const FRotator YawRotation(0, Rotation.Yaw, 0);
// 오른쪽 벡터 구하기
const FVector Direction = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);
// 해당 방향으로 이동 추가
AddMovementInput(Direction, Value);
}
}
섹션 결과
이 섹션에서는 오버랩되는 액터를 탐지하기 위해 스피어(Sphere) 컴포넌트를 ThirdPersonCharacter 클래스에 추가했습니다. 액터가 스피어와 오버랩되면 캐릭터는 Interact 인터페이스로 형변환하고 오버랩된 액터에 대한 OnInteract 함수를 트리거합니다.
6 - 인터랙션 시스템 테스트
-
BP_DoorActor 인스턴스와 BP_CeilingLamp 블루프린트를 레벨 뷰포트로 드래그합니다.
-
플레이를 클릭하고 각 블루프린트에 접근하여 플레이어와 상호작용하는 것을 확인합니다.
섹션 결과
이 섹션에서는 인터랙티브 문과 천장 조명 액터 블루프린트로 테스트하여 인터랙션 시스템이 의도한 대로 작동하는 것을 확인했습니다.
이 퀵스타트 가이드에서는 각 액터 클래스 블루프린트가 동일한 인터페이스를 어떻게 구현하는지 알아보고, 각 함수 기능을 따로 지정하는 방법을 알아보았습니다. 또한 다양한 블루프린트 클래스에서 형변환할 필요 없이 유사한 함수 기능을 구현하는 데 인터페이스가 이상적인 이유도 알아보았습니다.
다음 단계
지금까지 블루프린트 인터페이스 사용법을 알아보았으니, 액터 커뮤니케이션 문서 페이지에 나와 있는 다른 커뮤니케이션 타입도 확인해 보세요.