🎮점수 UI 제작

⚙️점수 누적 함수

UMG 모듈 시스템을 이용해 점수 출력 위젯을 C++로 구현하고 위젯 설정창에서 시각적 배치를 하였습니다. 게임 규칙에 관한 기능은 게임 모드 베이스에 구현하는게 좋습니다. 게임 모드 베이스 클래스 헤더 파일에 현재 점수를 저장할 변수와 점수를 증가시킬 함수를 선언합니다.

점수를 증가시키는 함수는 public으로 설정하여 외부 클래스에서 접근할 수 있도록 하고 함수를 통해 점수를 증가만 시킬 수 있게 합니다. 하지만 현재 점수는 외부 클래스에서 마음대로 수정할 수 없도록 private으로 설정합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// ShootingSampleGameModeBase.h

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/GameModeBase.h"
#include "ShootingSampleGameModeBase.generated.h"

/**
 * 
 */
UCLASS()
class SHOOTINGSAMPLE_API AShootingSampleGameModeBase : public AGameModeBase
{
	GENERATED_BODY()

public:
	void AddScore(int32 point);

protected:
	virtual void BeginPlay() override;
	
private:
	int32 currentScore = 0;
};


게임 모드 베이스 클래스의 소스 파일에서 AddScore 함수를 구현합니다. 정수형 매개변수 point를 통해 넘겨받은 점수를 현재 점수에 누적합니다.

1
2
3
4
5
6
7
8
// ShootingSampleGameModeBase.cpp

#include "ShootingSampleGameModeBase.h"

void AShootingSampleGameModeBase::AddScore(int32 point)
{
	currentScore += point;
}


점수가 추가되는 상황은 플레이어의 총알이 적 액터와 충돌하는 상황입니다. 따라서 총알 충돌 기능을 구현했던 Bullet 클래스에 점수 추가 기능을 구현하면 됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// Bullet.cpp

void ABullet::OnBulletOverlapEnemy(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor,
	UPrimitiveComponent* OtherComp, int32 OtherBodyIndex,
	bool bFromSweep, const FHitResult& SweepResult)
{
	AEnemyActor* enemy = Cast<AEnemyActor>(OtherActor);

	if (enemy != nullptr)
	{
		enemy->Destroy();

		// 현재 게임 모드 가져옴
		AGameModeBase* currentMode = GetWorld()->GetAuthGameMode();
		// AShootingSampleGameModeBase 클래스로 변환
		AShootingSampleGameModeBase* currentGameModeBase = Cast<AShootingSampleGameModeBase>(currentMode);

		// 게임 모드 베이스를 가져왔다면
		if (currentGameModeBase != nullptr)
		{
			// 게임 모드 베이스의 점수 1점 추가
			currentGameModeBase->AddScore(1);
		}
	}

	Destroy();
}



⚙️UMG 모듈 추가

언리얼 모션 그래픽 UI 디자이너(UMG)는 게임 내 HUD, 메뉴, 기타 인터페이스 관련 그래픽 요소로 사용자에게 보여주고 싶은 것들을 만들어 사용할 수 있도록 하는 비주얼 UI 제작 툴입니다.

UMG의 핵심에 Widget이라는 것이 있는데, 이는 미리 만들어진 (버튼, 체크박스, 슬라이더, 진행상황 바 등의) 함수 시리즈로, 이들을 조립해서 UI를 만들 수 있습니다. 이러한 위젯은 전용 Widget Blueprint (위젯 블루프린트)에서 편집됩니다.

UMG 기능은 블루프린트 모듈로 따로 분리되어 있어 C++ 코드에서 사용하기 위해 모듈을 등록해야 합니다. 비주얼 스튜디오 솔루션 탐색기에서 프로젝트이름.Build.cs 파일을 열어 UMG 모듈을 추가해 줍니다.

💡모듈
엔진은 대규모 모듈 모음으로 구현되며 게임은 모듈을 보강하기 위해 자체 모듈을 제공합니다. 각 모듈은 일련의 기능을 캡슐화하고 다른 모듈에서 사용할 공용 인터페이스 및 컴파일 환경을 제공할 수 있습니다.

모듈은 확장자가 .Build.cs인 C# 소스 파일을 통해 선언되며 프로젝트의 소스 디렉터리 아래에 저장됩니다. 모듈에 속하는 C++ 소스 코드는 .Build.cs 파일 옆이나 하위 디렉토리에 저장됩니다.

각 .Build.cs 파일은 ModuleRules 기본 클래스에서 파생되는 클래스를 선언하고 생성자에서 빌드해야 하는 방법을 제어하는 ​​속성을 설정합니다. 이 .Build.cs 파일은 UnrealBuildTool에 의해 컴파일되고 전체 컴파일 환경을 결정하도록 구성됩니다.

PublicDependencyModuleNames는 외부 모듈 파일을 프로젝트에 포함시키는 속성입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// ShootingSample.Build.cs

// Copyright Epic Games, Inc. All Rights Reserved.

using UnrealBuildTool;

public class ShootingSample : ModuleRules
{
	public ShootingSample(ReadOnlyTargetRules Target) : base(Target)
	{
		PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
	
		PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "UMG" });

		PrivateDependencyModuleNames.AddRange(new string[] {  });
	}
}



⚙️UserWidget 클래스 생성

점수가 누적되는 것을 게임 화면에서 보기 위해 UI 위젯을 제작합니다. UI를 만들기 위해 부모 클래스는 위젯 베이스 클래스인 UserWidget 클래스가 필요합니다. 클래스 생성 버튼을 눌러 UserWidget 클래스를 검색하여 생성합니다.

UserWidget 클래스를 생성했다면 헤더파일에 텍스트 블록 변수를 선언합니다. 점수 문구를 적을 텍스트 블록과 실제 점수를 표시할 텍스트 블록 두 개를 선언해줍니다. UPROPERTY 옵션 중 meta 옵션은 메타데이터 지정자 옵션입니다. 코드를 통해 언리얼 에디터에 대한 제어를 할 때 사용합니다. 에디터에 변수를 노출실킬 때 코드와 이름을 다르게 하거나 변수 입력 값을 특정 범위로 제한하는 등 다양한 기능들을 사용할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// MainWidget.h

#pragma once

#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "MainWidget.generated.h"

/**
 * 
 */
UCLASS()
class SHOOTINGSAMPLE_API UMainWidget : public UUserWidget
{
	GENERATED_BODY()
	
public:
	UPROPERTY(EditAnywhere, meta = (BindWidget))
	class UTextBlock* scoreText;

	UPROPERTY(EditAnywhere, meta = (BindWidget))
	class UTextBlock* scoreData;
};


BindWidget 지정자는 C++ 코드에서 만든 위젯 관련 변수를 위젯 블루프린트와 연동하는 역할을 합니다. 또한 코드에서 변경된 값이 위젯 블루프린트에도 반영되게 하는 역할도 합니다. BindWidget 지정자가 있는 변수는 해당 클래스를 상속한 위젯 블루프린트에서 반드시 구현해야합니다. 변수에 해당하는 위젯 컴포넌트를 구현하지 않으면 컴파일 에러가 발생합니다.

MainWidget 클래스를 부모 클래스로 하는 블루프린트 위젯을 생성합니다. 위젯 블루프린트를 열어보면 컴파일러 경고를 확인할 수 있습니다. scoreText와 scoreData에 해당하는 텍스트 블록들이 존재하지 않기 때문입니다.

텍스트 블록을 배치하기 전에 Canvas Panel 위젯을 먼저 배치하고 Canvas Panel의 자식으로 텍스트 위젯을 등록합니다. 텍스트 위젯의 이름은 코드에서 만든 변수와 동일하게 변경해줘야 합니다.

TextBlock



⚙️점수를 위젯에 반영

게임 모드 베이스에서 현재 점수를 위젯의 텍스트 블록에 전달해주기 위해 게임 모드 베이스에서 위젯 블루프린트에 접근해야 합니다. 따라서 게임 모드 베이스 헤더파일에 위젯 블루프린트 변수를 선언합니다.

점수 UI는 게임이 시작하자마자 생성되어야 하기 때문에 BeginPlay() 함수에 기능을 구현하면 될 것 같습니다. 하지만 액터 클래스와 달리 게임 모드 베이스 클래스에는 BeginPlay() 함수가 구현되어 있지 않아 직접 만들어줘야 합니다. 게임 모드 베이스 클래스는 액터 클래스를 상속받아 파생된 클래스라서 부모 클래스로부터 BeginPlay() 함수를 받아와서 재정의 해줄 수 있습니다.

블루프린트를 뷰 포트에 생성하려면 메모리에 생성해서 로드하는 과정이 필요합니다. 위젯 블루프린트를 메모리에 생성했다면 해당 위젯에 접근해서 제어하기 위해 실제 생성된 위제을 저장해 놓을 변수가 필요합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// ShootingSampleGameModeBase.h

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/GameModeBase.h"
#include "ShootingSampleGameModeBase.generated.h"

/**
 * 
 */
UCLASS()
class SHOOTINGSAMPLE_API AShootingSampleGameModeBase : public AGameModeBase
{
	GENERATED_BODY()

public:
	void AddScore(int32 point);

	UPROPERTY(EditAnywhere)
	TSubclassOf<class UMainWidget> mainWidget;

protected:
	virtual void BeginPlay() override;
	
private:
	int32 currentScore = 0;
    
	// 뷰 포트에 로드된 위젯 저장 변수
	class UMainWidget* mainUI;
};


선언이 끝났으면 소스파일로 이동해서 BeginPlay() 함수에 UI를 뷰 포트에 출력하는 기능을 구현합니다. CreateWidget() 함수를 이용하여 위젯 변수에 할당된 블루프린트 파일을 메모리에 생성합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// ShootingSampleGameModeBase.cpp

#include "ShootingSampleGameModeBase.h"
#include "Blueprint/UserWidget.h"
#include "MainWidget.h"

void AShootingSampleGameModeBase::BeginPlay()
{
	Super::BeginPlay();

	if (mainWidget != nullptr)
	{
		// 위젯 블루프린트 파일을 메모리에 로드
		mainUI = CreateWidget<UMainWidget>(GetWorld(), mainWidget);

		// 위젯이 메모리에 로드되면 뷰 포트에 출력
		if (mainUI != nullptr)
		{
			mainUI->AddToViewport();
		}
	}
}


코드를 빌드하고 게임 모드 베이스 블루프린트의 설정에서 Main Widget 변수에 위젯 블루프린트를 할당합니다.

SetBPwidget


적을 처치할 때 마다 점수가 누적되는 것을 반영하기 위해 게임 모드 베이스 헤더파일에 점수를 출력하는 함수를 구현합니다. currentScore 변수의 값을 위젯 블루프린트에 있는 scoreData에 입력하는 기능을 구현할 함수입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// ShootingSampleGameModeBase.h

...생략

UCLASS()
class SHOOTINGSAMPLE_API AShootingSampleGameModeBase : public AGameModeBase
{
	GENERATED_BODY()

public:
	void AddScore(int32 point);

	UPROPERTY(EditAnywhere)
	TSubclassOf<class UMainWidget> mainWidget;

protected:
	virtual void BeginPlay() override;
	
private:
	int32 currentScore = 0;

	class UMainWidget* mainUI;

	void PrintScore();
};


소스파일에서 PrintScore() 함수를 구현합니다. mainUI 포인터 변수를 이용하면 UMainWidget 클래스의 멤버변수인 scoreData에 접근할 수 있습니다. 그리고 SetText() 함수를 사용하여 scoreData 변수에 텍스트 값을 넣도록 합니다.

하지만 currentScore는 정수형 변수이기 때문에 SetText() 함수의 매개변수로 전달할 수 없습니다. SetText() 함수는 FText형 변수를 매개변수로 전달해야하기 때문입니다. 따라서 FText 클래스의 멤버함수 AsNumber를 사용하여 형 변환을 한 다음 점수를 전달합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
...생략

void AShootingSampleGameModeBase::PrintScore()
{
	if (mainUI != nullptr)
	{
		mainUI->scoreData->SetText(FText::AsNumber(currentScore));
	}
}

void AShootingSampleGameModeBase::AddScore(int32 point)
{
	currentScore += point;

	// AddScore() 함수가 실행될 때마다 점수 반영
	PrintScore();
}



Leave a comment