언어:
페이지 정보
엔진 버전:
언리얼 엔진

3. 블루프린트로 C++ 확장 및 덮어쓰기

언리얼 엔진

이번 튜토리얼 섹션에는 블루프린트 를 사용하여 C++ 클래스의 함수성을 확장시킵니다. 하지만 이는 C++ 코드가 제대로 작성되었나 테스트하기 위한 용도이지, 블루프린트 튜토리얼은 아닙니다. 블루프린트 에 대한 제대로 된 소개는, 블루프린트 퀵스타트 가이드 문서를 추천합니다.

  1. ACountdown 인스턴스 중 "Countdown1" 이라는 녀석의 작동방식을 에디터에서 변경하기 위해, 먼저 그 편집가능 블루프린트 버전을 만들어야 합니다. 그러기 위해서는, 월드 아웃라이너 에서 선택한 다음 디테일 패널블루프린트/스크립트 추가 버튼을 누릅니다.

    AddScript.png

    거기서, 우리 변경된 ACountdown 클래스가 저장될 블루프린트 애셋의 경로와 이름을 제공해 주면 됩니다.

    SelectBlueprintPath.png

    그러면 "Countdown1" 의 블루프린트 버전을 나타내는 애셋이 새로 생깁니다. "Countdown1" 을 새로 만든 블루프린트 인스턴스로 대체되기도 하므로, 블루프린트 에 가한 변경사항이 게임에서 "Countdown1" 에 영향을 끼칩니다.

  2. 언리얼 에디터 는 자동으로 콘텐츠 브라우저 에서 새 애셋 위치로 이동되며, 거기에 우클릭한 다음 "편집..." 을 선택하여 블루프린트 그래프, 컴포넌트 계층구조, 기본값 을 변경합니다.

    BlueprintInContentBrowser.png

    EditBlueprint.png

  3. 함수와 이벤트는 이벤트 그래프 탭에서 찾을 수 있으며, 먼저 선택하겠습니다.

    SelectEventGraph.png

    그런 다음 이벤트 그래프 창 아무데나 우클릭한 다음 우리 CountdownHasFinished 함수를 이벤트 노드로 추가하여 그 작동방식을 정의합니다.

    SelectEvent.png

  4. 이제 새로운 노드 오른편의 하양 ("실행") 핀을 좌클릭으로 끌어 놓는 것으로 함수성을 원하는 만큼 추가시킬 수 있습니다.

    DragExecPin.png

    왼쪽 마우스 버튼을 놓을 때, 어떤 함수나 이벤트를 실행하겠느냐고 묻습니다. 이 튜토리얼에서는 카운트 다운이 완료되면 파티클 시스템 을 스폰하겠습니다. Spawn Emitter At Location 노드가 필요하니, 목록에서 선택해 줍니다. 검색창에 "spawn loc" 정도 텍스트를 부분 입력하면 시간이 절약될 수 있습니다. 그런 다음 노랑 "Location" 핀을 좌클릭으로 끌어 Get Actor Location 함수에 연결합니다.

    GetActorLocation.png

    이제 보고픈 이펙트를 선택해 주면 됩니다. 이미터 템플릿 아래 애셋 선택 을 클릭하여 적합한 이펙트 애셋 목록을 구할 수 있습니다. "P_Explosion" 이 괜찮으니 선택해 주겠습니다.

    SelectParticle.png

  5. 블루프린트 에디터 좌상단의 컴파일 버튼을 클릭하여 변경사항을 저장합니다.

  6. 이제 플레이 를 누르면 카운트다운이 있은 후 0 에 도달하면 폭발이 생기는 것을 볼 수 있습니다.

    Explosion_Zero.png

    그런데 카운트다운이 "0" 이 아닌 "GO!" 라고 하도록 프로그래밍을 해 놨었습니다. C++ 함수성을 블루프린트 비주얼 스크립트로 완전히 대체했기에 더이상 벌어지지 않습니다. 이 경우는 의도했던 바가 아닌데, 이 함수의 C++ 버전 호출을 추가해 주겠습니다. Countdown Has Finished 노드를 우클릭하고 컨텍스트 메뉴에서 "Add call to parent function" 를 선택해 주면 됩니다.

    CallToParent_Menu.png

    이렇게 하면 이벤트 그래프Parent: Countdown Has Finished 라는 이름의 노드가 배치됩니다. "부모" 노드는 보통 이벤트 노드에 직접 연결하므로, 여기서는 그렇게 해 주겠습니다. 하지만 필수는 아닌데, 부모 호출 노드는 다른 것과 마찬가지로 원하는 곳 아무데서나, 심지어 몇 번이고 호출할 수 있기 때문입니다.

    CallToParent_ConnectPins.png

    이렇게 하면 Spawn Emitter At Location 로의 접속이 대체되므로, Parent: Countdown Has Finished 노드의 오른편 (나가는) 실행 핀을 거기에 연결해 주지 않으면 실행되지 않습니다.

    CallToParent_FixPins.png

    이제 게임 실행하고, 카운트다운이 끝나면 (C++ 코드의) "GO!" 와 (블루프린트 그래프 의) 폭발을 둘 다 볼 수 있습니다.

    Explosion_Go.png

완성 코드

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: 
    // Sets default values for this actor's properties
    ACountdown();

protected:
    // Called when the game starts or when spawned
    virtual void BeginPlay() override;

public:
    // Called every frame
    virtual void Tick( float DeltaSeconds ) override;

    //How long, in seconds, the countdown will run
    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 "Countdown.h"

// Sets default values
ACountdown::ACountdown()
{
    // Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
    PrimaryActorTick.bCanEverTick = false;

    CountdownText = CreateDefaultSubobject<UTextRenderComponent>(TEXT("CountdownNumber"));
    CountdownText->SetHorizontalAlignment(EHTA_Center);
    CountdownText->SetWorldSize(150.0f);
    RootComponent = CountdownText;

    CountdownTime = 3;
}

// Called when the game starts or when spawned
void ACountdown::BeginPlay()
{
    Super::BeginPlay();

    UpdateTimerDisplay();
    GetWorldTimerManager().SetTimer(CountdownTimerHandle, this, &ACountdown::AdvanceTimer, 1.0f, true);
}

// Called every frame
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)
    {
        // We're done counting down, so stop running the timer.
        GetWorldTimerManager().ClearTimer(CountdownTimerHandle);
        //Perform any special actions we want to do when the timer ends.
        CountdownHasFinished();
    }
}

void ACountdown::CountdownHasFinished_Implementation()
{
    //Change to a special readout
    CountdownText->SetText(TEXT("GO!"));
}