점수와 게임종료에 대한 UI를 제작해보겠습니다.
이러한 게임 전반에 관한 관리를 게임모드에서 하는게 좋습니다.
Widget는 점수를 표시하는 MainWidget와 게임 종료시 선택할수 있게 하는 MenuWidget 2개를 작성합니다. 부모는 UserWidget으로 합니다.

VisualStudio로 위젯을 사용하기 위해 ShootingCPP.Build.cs에 UMG모듈을 추가해줍니다. 그래야 UMG의 기능에 접근할수 있습니다.


MainWidget.h를 열고 scoreData 변수를 만들어 줍니다.
#pragma once
#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "MainWidget.generated.h"
/**
*
*/
UCLASS()
class SHOOTINGCPP_API UMainWidget : public UUserWidget
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, meta = (BindWidget))
class UTextBlock* scoreText;
UPROPERTY(EditAnywhere, meta = (BindWidget))
class UTextBlock* scoreData;
};
MainWidget클래스를 부모로 BP_MainWidget를 만들고 열어보면 BindWidgets에 scoreText와 scoreData가 보입니다.

똑같은 이름으로 TextBlock 2개를 구현해서 화면 좌상에 배치해 줍니다.


UserWidget을 부모로 C++ 클래스를 만들고 이름을 MenuWidget으로 합니다.
UButton클래스의 변수를 2개 만들고 클릭되면 동작할 함수 Restart(), Quit()도 만듭니다.
#pragma once
#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "MenuWidget.generated.h"
/**
*
*/
UCLASS()
class SHOOTINGCPP_API UMenuWidget : public UUserWidget
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, meta = (BindWidget))
class UButton* button_Restart;
UPROPERTY(EditAnywhere, meta = (BindWidget))
class UButton* button_Quit;
protected:
virtual void NativeConstruct() override;
UFUNCTION()
void Restart();
UFUNCTION()
void Quit();
};
CPP에서 구현해줍니다.
UI는 BeginPlay()대신 NativeConstruct()가 시작처리를 합니다. 여기서 button들을 충돌처리를 합니다.(마우스클릭처리) 이건 총알의 충돌처리와 똑같습니다.
Restart()에서는 OpenLevel()을 이용해 레벨을 열어주고, Quit()시 QuitGame()함수를 불러 게임을 종료해줍니다. Kismet/ 라이브로리 2종류가 필요합니다.
#include "MenuWidget.h"
#include "Components/Button.h"
#include "Kismet/GameplayStatics.h"
#include "Kismet/KismetSystemLibrary.h"
void UMenuWidget::NativeConstruct()
{
Super::NativeConstruct();
button_Restart->OnClicked.AddDynamic(this, &UMenuWidget::Restart);
button_Quit->OnClicked.AddDynamic(this, &UMenuWidget::Quit);
}
void UMenuWidget::Restart()
{
UGameplayStatics::OpenLevel(GetWorld(), "ShootingMap");
}
void UMenuWidget::Quit()
{
UWorld* currentWorld = GetWorld();
UKismetSystemLibrary::QuitGame(currentWorld, currentWorld->GetFirstPlayerController(),
EQuitPreference::Quit, false);
}
마찬가지로 MenuWidget클래스를 부모로 BP_MenuWidget블루프린트 클래스를 만들어주고 열어보면 바인딘된 변수를 확인할수 있구요 똑같은 이름으로 Button을 만들어서 배치해줍니다.



맨처음 만들었던 SootingGameModeBase.h를 열고
currentScore : 점수를 저장하는 변수
AddScore() : 점수를 증가시켜주는 기능
를 우선 만듭니다.
ShowMenu() : Menu UI를 동적으로 켜주는 기능
mainWidget, menuWidget은 위젯클래스를 저장하는데 나중에 블루프린트를 만들어 꼭 연결해줘야 합니다.
mainUI, menuUI는 윗젯객체를 저장하는 변수입니다.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/GameModeBase.h"
#include "ShootingGameModeBase.generated.h"
/**
*
*/
UCLASS()
class SHOOTINGCPP_API AShootingGameModeBase : public AGameModeBase
{
GENERATED_BODY()
public:
void AddScore(int32 point);
UPROPERTY(EditAnywhere)
TSubclassOf<class UMainWidget> mainWidget;
UPROPERTY(EditAnywhere)
TSubclassOf<class UMenuWidget> menuWidget;
void ShowMenu();
protected:
virtual void BeginPlay() override;
private:
int32 currentScore = 0;
class UMainWidget* mainUI;
class UMenuWidget* menuUI;
void PrintScore();
};
SootingGameModeBase.cpp
AddScore는 currentScore를 증가히켜주고 PrintScore()를 불러줍니다. PrintScore()는 mainUI->scoreData->SetText()에 점수를 써줍니다. 타입이 Text라 FText::AsNumver(score)로 바꿔줘야 합니다.
ShowMenu()에서 위젯을 켜고 게임을 멈추고, 커서를 보여줘 버튼을 클릭할수 있게 합니다.
#include "ShootingGameModeBase.h"
#include "Blueprint/UserWidget.h"
#include "MainWidget.h"
#include "MenuWidget.h"
#include "Components/TextBlock.h"
#include "Kismet/GameplayStatics.h"
void AShootingGameModeBase::AddScore(int32 point)
{
currentScore += point;
PrintScore();
}
void AShootingGameModeBase::ShowMenu()
{
if (menuWidget)
{
menuUI = CreateWidget<UMenuWidget>(GetWorld(), menuWidget);
if (menuUI)
{
menuUI->AddToViewport();
}
// set game pause mode
UGameplayStatics::SetGamePaused(GetWorld(), true);
GetWorld()->GetFirstPlayerController()->SetShowMouseCursor(true);
}
}
void AShootingGameModeBase::BeginPlay()
{
Super::BeginPlay();
if (mainWidget)
{
mainUI = CreateWidget<UMainWidget>(GetWorld(), mainWidget);
if (mainUI != nullptr)
{
mainUI->AddToViewport();
}
}
}
void AShootingGameModeBase::PrintScore()
{
if (mainUI)
{
mainUI->scoreData->SetText(FText::AsNumber(currentScore));
}
}
BP_ShootingGameModeBase를 열어 BP_MainWidget, BP_MenuWidget을 꼭 연결해줍니다.

점수는 총알이 충돌하면서 발생되므로 Bullet.cpp을 열어 GetWorld()->를 이용해 게임모드를 얻어 ShootingGameModeBase로 캐스트해서 AddScore()를 실행시켜줍니다.
void ABullet::OnBulletOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor,
UPrimitiveComponent* OtherComp, int32 OtherBodyIndex,
bool bFromSweep, const FHitResult& SweepResult)
{
AEnemyActor* enemy = Cast<AEnemyActor>(OtherActor);
if (enemy)
{
OtherActor->Destroy();
UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), explosionFX, GetActorLocation(), GetActorRotation());
}
AGameModeBase* currentMode = GetWorld()->GetAuthGameMode();
AShootingGameModeBase* currentGameMode = Cast<AShootingGameModeBase>(currentMode);
if (currentGameMode)
{
currentGameMode->AddScore(1);
}
Destroy();
}
게임 종료는 Enemy가 PlayerPawn과 충돌할때 발생하므로 EnemyActor.cpp를 열어 충돌처리함수부분에서 게임모드를 캐스팅해서 ShowMenu()에 접근합니다.
void AEnemyActor::OnEnemyOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor,
UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
APlayerPawn* player = Cast<APlayerPawn>(OtherActor);
if (player)
{
player->Destroy();
}
AShootingGameModeBase* currentGameMode = Cast<AShootingGameModeBase>( GetWorld()->GetAuthGameMode());
if (currentGameMode)
{
currentGameMode->ShowMenu();
}
Destroy();
}
'언리얼게임프로젝트 > 슈팅게임 C++' 카테고리의 다른 글
킬존 Kill Zone 제작하기 C++ (0) | 2025.03.25 |
---|---|
충돌이벤트와 델리게이트 Delegate (0) | 2025.03.12 |
Ureal C++ Collision처리 (0) | 2025.03.12 |
EnemyActor Factory 생성하기 Spawn (0) | 2025.03.12 |
Unreal Shooting Game C++ EnemyActor (0) | 2025.03.11 |