이 튜토리얼은 에디터에 변수 및 함수를 노출하고, 타이머를 사용하여 코드 실행을 지연하거나 반복하고, 이벤트를 사용하여 액터 간 커뮤니케이션하는 방법을 보여줍니다.
1. 타이머를 사용하는 액터 생성하기
언리얼 엔진 을 처음 사용하신다면 프로그래밍 퀵스타트를 먼저 읽어보세요. 이 튜토리얼에서는 여러분이 프로젝트 생성, 프로젝트에 C++ 코드 추가에 익숙하다고 가정합니다.
-
시작용 콘텐츠를 활용하여 이름이 HowTo_VTE인 새 기본 코드 프로젝트를 생성하고 액터(Actor) 클래스를 추가하는 것부터 시작합니다. 이 튜토리얼에서는 액터 클래스 이름을 Countdown으로 지정합니다.
-
먼저 게임 내에서 볼 수 있는 간단한 카운트다운 타이머를 만들어 보겠습니다. Countdown.h에서 클래스 정의 끝에 다음 줄을 추가합니다.
int32 CountdownTime; UTextRenderComponent* CountdownText; void UpdateTimerDisplay();
-
Countdown.cpp에서 렌더링 가능한 텍스트 컴포넌트 를 생성하고 카운트다운 시간을 3초로 초기화할 수 있습니다. 또한 이 액터 타입의 틱(Ticking) 은 필요하지 않으므로 비활성화해도 됩니다. 이렇게 하려면 'include' 섹션이 다음과 같도록 파일 맨 위에 컴포넌트 헤더를 추가해야 합니다.
#include "GameFramework/Actor.h" #include "Components/TextRenderComponent.h" #include "Countdown.generated.h"
헤더를 포함했다면
ACountdown::ACountdown
을 작성할 수 있습니다. 다음과 같아야 합니다.ACountdown::ACountdown() { // 이 액터가 프레임마다 Tick()을 호출하도록 설정합니다. 이 설정이 필요 없는 경우 비활성화하면 퍼포먼스가 향상됩니다. PrimaryActorTick.bCanEverTick = false; CountdownText = CreateDefaultSubobject<UTextRenderComponent>(TEXT("CountdownNumber")); CountdownText->SetHorizontalAlignment(EHTA_Center); CountdownText->SetWorldSize(150.0f); RootComponent = CountdownText; CountdownTime = 3; }
-
ACountdown::UpdateTimerDisplay
는 텍스트 표시를 업데이트하여 남은 시간을 표시하도록 하며, 시간이 다 된 경우에는 0을 표시하도록 합니다. 이 코드는 게임에ACountdown
을 처음으로 스폰할 때 실행되며,CountdownTime
변수가 0이 될 때까지 초당 1번씩 실행됩니다.void ACountdown::UpdateTimerDisplay() { CountdownText->SetText(FString::FromInt(FMath::Max(CountdownTime, 0))); }
-
함수를 실행하도록 타이머(Timer) 를 할당할 때마다 타이머 핸들(Timer Handle) 이 생깁니다. 카운트다운이 완료될 때 타이머를 종료하도록 해당 핸들을 유지해야 합니다. 시간을 카운트다운하는 함수와 그 함수를 제어하는 타이머 핸들을
Countdown.h
의 클래스 정의에 추가합니다. 또한 카운트다운이 종료될 때 특수한 동작을 하는 함수도 추가합니다.void AdvanceTimer(); void CountdownHasFinished(); FTimerHandle CountdownTimerHandle;
이제
Countdown.cpp
의ACountdown::AdvanceTimer
및ACountdown::CountdownHasFinished
의 바디도 작성할 수 있습니다.void ACountdown::AdvanceTimer() { --CountdownTime; UpdateTimerDisplay(); if (CountdownTime < 1) { //카운트다운이 끝났으므로 타이머 실행을 중지합니다. GetWorldTimerManager().ClearTimer(CountdownTimerHandle); CountdownHasFinished(); } } void ACountdown::CountdownHasFinished() { //특수 리드아웃으로 변경 CountdownText->SetText(TEXT("GO!")); }
-
ACountdown::BeginPlay
에 새 업데이트 함수에 대한 호출을 추가하고 타이머가 초당 1번씩 카운트다운을 진행 및 업데이트하도록 설정하여 텍스트 표시를 초기화합니다.UpdateTimerDisplay(); GetWorldTimerManager().SetTimer(CountdownTimerHandle, this, &ACountdown::AdvanceTimer, 1.0f, true);
ACountdown::ACountdown
이 아닌ACountdown::BeginPlay
의 디스플레이를 업데이트하는 것은 언리얼 에디터의 변수에 설정된 값이 생성자 이후, BeginPlay 이전에 할당되기 때문입니다. 이러한 값은 나중에CountdownTime
을 에디터에 노출했을 때 고려하고자 합니다. -
언리얼 에디터 에서 컴파일(Compile) 을 눌러 지금까지의 진행 상황을 확인합니다.
그런 다음 콘텐츠 브라우저 에서 업데이트된
ACountdown
을 드래그해 레벨 에디터 에 드롭합니다.ACountdown::ACountdown
이 아닌ACountdown::BeginPlay
도중 카운트다운 텍스트를 설정했기 때문에, 디폴트 텍스트는 레벨 에디터 에 표시됩니다.플레이 를 누르면 카운트다운이 예상대로 진행되어 3, 2, 1, GO!가 표시됩니다.
이 시점에서 벌써 타이머를 사용하는 간단한 클래스가 생성되었습니다. 비프로그래머 사용자가 카운트다운 시간을 설정하거나 카운트다운 완료 시의 행동을 변경할 수 있다면 그 활용 방식은 훨씬 더 다양해질 것입니다. 다음으로는 이러한 기능을 에디터에 노출합니다.
작업 중인 코드
Countdown.h
// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
#pragma once
#include "GameFramework/Actor.h"
#include "Countdown.generated.h"
UCLASS()
class HOWTO_VTE_API ACountdown : public AActor
{
GENERATED_BODY()
public:
// 이 액터 프로퍼티의 디폴트값 설정
ACountdown();
protected:
// 게임 시작 또는 스폰 시 호출
virtual void BeginPlay() override;
public:
// 프레임마다 호출
virtual void Tick( float DeltaSeconds ) override;
//카운트다운 실행 시간(초)
int32 CountdownTime;
UTextRenderComponent* CountdownText;
void UpdateTimerDisplay();
void AdvanceTimer();
void CountdownHasFinished();
FTimerHandle CountdownTimerHandle;
};
Countdown.cpp
// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
#include "HowTo_VTE.h"
#include "Components/TextRenderComponent.h"
#include "Countdown.h"
// 디폴트값 설정
ACountdown::ACountdown()
{
// 이 액터가 프레임마다 Tick()을 호출하도록 설정합니다. 이 설정이 필요 없는 경우 비활성화하면 퍼포먼스가 향상됩니다.
PrimaryActorTick.bCanEverTick = false;
CountdownText = CreateDefaultSubobject<UTextRenderComponent>(TEXT("CountdownNumber"));
CountdownText->SetHorizontalAlignment(EHTA_Center);
CountdownText->SetWorldSize(150.0f);
RootComponent = CountdownText;
CountdownTime = 3;
}
// 게임 시작 또는 스폰 시 호출
void ACountdown::BeginPlay()
{
Super::BeginPlay();
UpdateTimerDisplay();
GetWorldTimerManager().SetTimer(CountdownTimerHandle, this, &ACountdown::AdvanceTimer, 1.0f, true);
}
// 프레임마다 호출
void ACountdown::Tick( float DeltaTime )
{
Super::Tick( DeltaTime );
}
void ACountdown::UpdateTimerDisplay()
{
CountdownText->SetText(FString::FromInt(FMath::Max(CountdownTime, 0)));
}
void ACountdown::AdvanceTimer()
{
--CountdownTime;
UpdateTimerDisplay();
if (CountdownTime < 1)
{
// 카운트다운이 끝났으므로 타이머 실행을 중지합니다.
GetWorldTimerManager().ClearTimer(CountdownTimerHandle);
//타이머가 종료되면 하고자 하는 특수한 액션을 수행합니다.
CountdownHasFinished();
}
}
void ACountdown::CountdownHasFinished()
{
//특수 리드아웃으로 변경
CountdownText->SetText(TEXT("GO!"));
}
2. 에디터에 변수 및 함수 노출하기
-
앞서 만든 카운트다운 타이머는 현재 3초의 값을 사용하도록 하드코딩되어 있습니다. 에디터 내 원하는 모든 값이 카운트다운 시간이 되도록 설정할 수 있다면 더 유용하게 활용할 수 있으며, 설정하기도 쉽습니다.. Visual Studio 에서
Countdown.h
를 열면 다음과 같은 줄을 찾을 수 있습니다.int32 CountdownTime;
이 변수를 언리얼 엔진에 노출하려면
UPROPERTY
로 만들어야 합니다. 이를 통해 게임을 실행하거나 저장된 레벨을 로드할 때 엔진이 변수의 값을 보존하도록 할 수 있습니다. 빈 괄호가 있는UPROPERTY
태그는 해당 태그가 영향을 미치는 변수 바로 위에 추가됩니다.UPROPERTY() int32 CountdownTime;
UPROPERTY
는 언리얼 엔진이 변수를 사용하는 방식을 변경하는 실행인자를 지원합니다. 변수를 편집 가능하게 만들 예정이므로EditAnywhere
실행인자를 추가합니다.UPROPERTY(EditAnywhere) int32 CountdownTime;
C++에서 변수에 코멘트도 추가할 수 있으며, 이러한 코멘트는 다음과 같이 언리얼 에디터에서 변수의 설명이 됩니다.
//카운트다운 실행 시간(초) UPROPERTY(EditAnywhere) int32 CountdownTime;
UPROPERTY
로 할 수 있는 작업은 훨씬 더 많습니다. 또한BlueprintReadWrite
및Category
와 같은 다른 지정자를 살펴보는 것 역시 다음 단계로 알맞을 수 있지만, 지금 시점에서는 필요한 요소가 모두 갖춰진 상태입니다.언리얼 에디터로 돌아와 컴파일 을 누르면 변수가 이전에 배치한
ACountdown
의 디테일 패널 에 표시되며, 이 값을 변경하고 플레이 를 눌러 다양한 타이머 값을 테스트해 볼 수 있습니다. -
비프로그래머 개발자가 타이머 값을 변경하는 것뿐만 아니라 타이머가 종료되면 발생하는 일도 변경할 수 있도록 하겠습니다. Visual Studio에서 Countdown.h를 열고 다음 줄을 찾습니다.
void CountdownHasFinished();
이 함수를 다음과 같이
UFUNCTION
으로 만들어 언리얼 엔진에 노출할 수 있습니다.UFUNCTION() void CountdownHasFinished();
UPROPERTY
매크로와 마찬가지로, 비프로그래머 개발자가 더 많은 기능을 활성화하고 액세스할 수 있도록 하려면 수행할 수 있는 작업에 대한 정보를 제공해야 합니다. 고려할 수 있는 옵션은 3개입니다.-
BlueprintCallable
함수는 C++로 작성되며 블루프린트 그래프 에서 호출되지만, C++ 코드를 편집하지 않고는 변경하거나 오버라이드할 수 없습니다. 이런 방식으로 마킹된 함수는 보통 비프로그래머가 사용하도록 프로그래밍되었지만 변경하면 안 되거나 변경이 불가능한 기능입니다. 쉬운 예시로는 모든 종류의 수학 함수를 들 수 있습니다. -
BlueprintImplementableEvent
함수는 C++ 헤더(.h) 파일에서 구성되지만, 함수 바디 전체는 C++가 아닌 블루프린트 그래프에서 작성됩니다. 이러한 함수는 주로 예상되는 기본 액션이나 표준 행동이 없는 특수한 상황에 대한 커스텀 반응을 비프로그래머도 만들 수 있도록 하기 위해 생성됩니다. 우주선 게임에서 파워업이 플레이어의 함선을 건드릴 경우 발생하는 이벤트를 예로 들 수 있습니다. -
BlueprintNativeEvent
함수는BlueprintCallable
및BlueprintImplementableEvent
함수의 조합과 같습니다. 디폴트 비헤이비어는 C++로 프로그래밍되어 있지만, 블루프린트 그래프에서 오버라이드하여 보완하거나 대체할 수 있습니다. 이러한 프로그래밍을 할 경우 C++ 코드는 항상 이름 끝에 _Implementation이 추가된 가상 함수로 들어갑니다. 이 옵션이 가장 유연하므로, 이 튜토리얼에 사용해 보겠습니다.
비프로그래머가 C++ 함수를 호출하고 블루프린트 로 오버라이드할 수 있도록 하려면
Countdown.h
를 다음과 같이 변경해야 합니다.UFUNCTION(BlueprintNativeEvent) void CountdownHasFinished(); virtual void CountdownHasFinished_Implementation();
그런 다음 Countdown.cpp에서 다음 줄을 변경해야 합니다.
void ACountdown::CountdownHasFinished()
변경 후:
void ACountdown::CountdownHasFinished_Implementation()
-
이제 비프로그래머가 액세스 및 변경 가능한 변수와 함수를 만들었으며, 디폴트값 및 함수 기능을 C++로 제공했습니다. 비프로그래머가 이를 사용하는 방식을 확인하기 위해 ACountdown
클래스의 블루프린트 익스텐션을 만들고 직접 수정해 보겠습니다.
완성된 코드
Countdown.h
// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
#pragma once
#include "GameFramework/Actor.h"
#include "Countdown.generated.h"
UCLASS()
class HOWTO_VTE_API ACountdown : public AActor
{
GENERATED_BODY()
public:
// 이 액터 프로퍼티의 디폴트값 설정
ACountdown();
protected:
// 게임 시작 또는 스폰 시 호출
virtual void BeginPlay() override;
public:
// 프레임마다 호출
virtual void Tick( float DeltaSeconds ) override;
//카운트다운 실행 시간(초)
UPROPERTY(EditAnywhere)
int32 CountdownTime;
UTextRenderComponent* CountdownText;
void UpdateTimerDisplay();
void AdvanceTimer();
UFUNCTION(BlueprintNativeEvent)
void CountdownHasFinished();
virtual void CountdownHasFinished_Implementation();
FTimerHandle CountdownTimerHandle;
};
Countdown.cpp
// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
#include "HowTo_VTE.h"
#include "Components/TextRenderComponent.h"
#include "Countdown.h"
// 디폴트값 설정
ACountdown::ACountdown()
{
// 이 액터가 프레임마다 Tick()을 호출하도록 설정합니다. 이 설정이 필요 없는 경우 비활성화하면 퍼포먼스가 향상됩니다.
PrimaryActorTick.bCanEverTick = false;
CountdownText = CreateDefaultSubobject<UTextRenderComponent>(TEXT("CountdownNumber"));
CountdownText->SetHorizontalAlignment(EHTA_Center);
CountdownText->SetWorldSize(150.0f);
RootComponent = CountdownText;
CountdownTime = 3;
}
// 게임 시작 또는 스폰 시 호출
void ACountdown::BeginPlay()
{
Super::BeginPlay();
UpdateTimerDisplay();
GetWorldTimerManager().SetTimer(CountdownTimerHandle, this, &ACountdown::AdvanceTimer, 1.0f, true);
}
// 프레임마다 호출
void ACountdown::Tick( float DeltaTime )
{
Super::Tick( DeltaTime );
}
void ACountdown::UpdateTimerDisplay()
{
CountdownText->SetText(FString::FromInt(FMath::Max(CountdownTime, 0)));
}
void ACountdown::AdvanceTimer()
{
--CountdownTime;
UpdateTimerDisplay();
if (CountdownTime < 1)
{
// 카운트다운이 끝났으므로 타이머 실행을 중지합니다.
GetWorldTimerManager().ClearTimer(CountdownTimerHandle);
//타이머가 종료되면 하고자 하는 특수한 액션을 수행합니다.
CountdownHasFinished();
}
}
void ACountdown::CountdownHasFinished_Implementation()
{
//특수 리드아웃으로 변경
CountdownText->SetText(TEXT("GO!"));
}
3. C++를 블루프린트로 확장 및 오버라이드
이 튜토리얼 섹션은 블루프린트 를 사용하여 C++ 클래스의 함수 기능을 확장합니다. 하지만 이 섹션의 목적은 C++ 코드가 올바르게 작성되었는지 확인하기 위한 테스트이며, 블루프린트 튜토리얼이 아닙니다. 블루프린트에 대한 적절한 소개로는 %programming-and-scripting/blueprints-visual-scripting/GettingStarted/QuickStart:title%를 권장합니다.
-
에디터에서 ACountdown 인스턴스(Countdown1)의 행동을 변경하려면 먼저 해당 인스턴스의 편집 가능한 블루프린트 버전을 만들어야 합니다. 이렇게 하려면 월드 아웃라이너(World Outliner) 에서 선택하고 디테일 패널 에서 블루프린트/스크립트 추가(Blueprint/Add Script) 버튼을 클릭하면 됩니다.
여기에서 수정된 ACountdown 클래스를 포함할 블루프린트 에셋의 경로 및 이름을 제공할 수 있습니다.
이렇게 하면 Countdown1의 블루프린트 버전을 나타내는 새 에셋이 생성됩니다. 또한 Countdown1을 이 새 블루프린트의 인스턴스로 대체하여 블루프린트에 적용되는 변경 사항이 게임의 Countdown1에 영향을 미치게 됩니다.
-
언리얼 에디터 에서 자동으로 콘텐츠 브라우저 의 새 에셋으로 이동하면 우클릭 하고 '편집...(Edit...)'을 선택하여 블루프린트 그래프, 컴포넌트 계층구조, 디폴트값 을 수정하면 됩니다.
-
이벤트 그래프 탭에서 함수와 이벤트를 찾을 수 있으므로 먼저 선택해 보겠습니다.
그런 다음 이벤트 그래프 창에서 아무 곳이나 우클릭 하여 CountdownHasFinished 함수를 행동을 정의하는 이벤트 노드로 추가합니다.
-
이제 새 노드 우측의 흰색(실행) 핀을 좌클릭하고 드래그하여 원하는 추가 함수 기능을 추가할 수 있습니다.
왼쪽 마우스 버튼을 뗄 경우 어떤 함수 또는 이벤트를 실행할지 질문이 표시됩니다. 이 튜토리얼에서는 카운트다운이 완료되면 파티클 시스템(Particle System) 을 스폰해 보겠습니다. Spawn Emitter At Location 노드가 필요하므로 목록에서 해당 노드를 선택합니다. 이를 통해 검색창에 생성 위치(spawn loc) 등의 파티클 구문을 입력할 시간을 절약할 수 있습니다. 그런 다음 노란색 'Location' 핀을 좌클릭하고 드래그하여 Get Actor Location 함수에 어태치하면 됩니다.
이제 보고 싶은 이펙트를 선택하기만 하면 됩니다. 이미터 템플릿에서 에셋 선택(Select Asset)을 클릭하여 적합한 이펙트 에셋 목록을 볼 수 있습니다. P_Explosion이 잘 어울릴 것 같으니 선택해 보겠습니다.
-
블루프린트 에디터 좌측 상단의 컴파일 버튼을 눌러 변경 사항을 저장합니다.
-
이제 플레이 를 누르면 카운트다운이 이루어지는 모습을 볼 수 있으며, 폭발은 카운트다운 숫자가 0에 도달하면 일어납니다.
하지만 앞서 카운트다운 종료 시 0이 아닌 GO!를 표시하도록 프로그래밍했는데, C++ 함수 기능을 블루프린트 비주얼 스크립팅으로 완전히 대체했기 때문에 GO!는 더 이상 표시되지 않습니다. 이는 이 예시에서 의도한 바가 아니므로 이 함수의 C++ 버전에 대한 호출을 추가해야 합니다. Countdown Has Finished 노드를 우클릭하고 컨텍스트 메뉴에서 부모 함수에 대한 호출 추가(Add call to parent function) 를 선택하면 됩니다.
이 작업이 완료되면 이름이 Parent: Countdown Has Finished 인 노드가 이벤트 그래프 에 배치됩니다. 부모 노드는 보통 이벤트 노드에 연결하며, 이 예시에서도 이벤트 노드에 연결합니다. 그러나 부모 호출 노드는 다른 노드와 마찬가지로 원하는 곳 어디에서나 여러 번 호출할 수 있기 때문에 이러한 연결이 필수는 아닙니다.
이는 Spawn Emitter At Location 으로의 연결을 대체하므로 **Parent: programming-and-scripting/programming-language-implementation/cpp-in-unreal-engine/unreal-engine-cpp-tutorials를 연결해야 한다는 점에 유의하세요.
이제 카운트다운이 완료되면 게임을 실행할 때 C++ 코드로부터 단어 GO!가 표시되고 블루프린트 그래프로부터 폭발이 표시됩니다.
4. 직접 해보기!
여기에서 배운 것을 활용하여 다음과 같은 작업을 해보세요.
- 이벤트 가 실행되면 타깃 트랜스폼에 대해 이동하거나 회전하는 액터 를 생성합니다. 게임 내 움직이는 플랫폼이나 문으로 사용할 수 있습니다. 이벤트가 두 번째 이벤트를 트리거하는 타이머 를 시작하고, 두 번째 이벤트가 액터를 원래 위치로 이동하도록 만듭니다. 적합한 경우라면 하드코딩된 값 대신 노출된 변수(
UPROPERTY
를 통해 노출)를 사용하세요. - 타이머 핸들과 몇 개의 커스텀 이벤트를 사용하여 타서 없어지는 횃불(불꽃 파티클 시스템 컴포넌트 를 비활성화하는 방법 사용 가능)을 만들어 봅니다. 예를 들어, AddFuel 이벤트로 횃불이 타는 시간을 연장할 수도 있습니다. DouseWithWater 이벤트로 횃불을 바로 끄고 향후 AddFuel이 작동하지 못하도록 방지할 수도 있습니다. 두 기능 모두 Tick 을 사용하지 않아도 핸들에서 실행 타이머를 수정하여 간단하게 작성할 수 있습니다.
이 튜토리얼에서 배운 내용을 더 알아보세요.
- 타이머에 대한 자세한 정보는 게임플레이 타이머 페이지를 참조하세요.
UPROPERTY
태그를 사용하는 클래스 또는 구조체의 변수에 대한 전체 레퍼런스는 %programming-and-scripting/programming-language-implementation/unreal-engine-reflection-system/Properties:title% 페이지를 참조하세요.UFUNCTIONS
및 이벤트 생성에 대한 자세한 정보는 %programming-and-scripting/programming-language-implementation/unreal-engine-reflection-system/Functions:title% 페이지를 참조하세요.- 추가 튜토리얼은 %programming-and-scripting/programming-language-implementation/cpp-in-unreal-engine/tutorials:title% 페이지를 참조하세요.