게임 개발 (언리얼 엔진)

UE4 생존게임 제작 - 18 (Game Ending 시스템 제작)

언린이 2020. 11. 26. 00:18

1. Game Ending Trigger 제작

 

	UPROPERTY(Category = Collision, VisibleAnywhere, BlueprintReadOnly, meta = (AllowPrivateAccess = "true"))
	UBoxComponent* TriggerBox;

 

public:
	// 충돌이 시작할시에 호출되는 델리게이트에 등록하는 함수
	UFUNCTION()
	void TriggerEndingSceneBeginOverlap(UPrimitiveComponent* OverlappedComponent,
		AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex,
		bool bFromSweep, const FHitResult& SweepResult);

	// 충돌이 종료될시에 호출되는 델리게이트에 등록하는 함수
	UFUNCTION()
	void TriggerEndingSceneEndOverlap(UPrimitiveComponent* OverlappedComponent,
		AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex);

 

TriggerMoisture 제작과 동일하게 박스 컴포넌트를 선언하고 충돌시에 호출되는 델리게이트에 등록하는 함수들을 선언해주었습니다.

 

 

ATriggerEndingScene::ATriggerEndingScene()
{
	PrimaryActorTick.bCanEverTick = true;

	TriggerBox = CreateDefaultSubobject<UBoxComponent>(TEXT("TriggerBox"));
	SetRootComponent(TriggerBox);

	// 프로파일 지정
	TriggerBox->SetCollisionProfileName(TEXT("TriggerEnding"));
	TriggerBox->SetBoxExtent(FVector(20.f, 20.f, 20.f));
	TriggerBox->SetCollisionEnabled(ECollisionEnabled::QueryOnly);

	bTriggerEnable = true;
}

 

생성자에서 박스 컴포넌트를 생성해주고 만들어둔 프로파일을 지정해주었습니다.

해당 프로파일은 플레이어와만 겹치도록 설정하였습니다.

 

 

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

	// 델리게이트에 생성한 함수 등록
	TriggerBox->OnComponentBeginOverlap.AddDynamic(this, &ATriggerEndingScene::TriggerEndingSceneBeginOverlap);
	TriggerBox->OnComponentEndOverlap.AddDynamic(this, &ATriggerEndingScene::TriggerEndingSceneEndOverlap);
}

 

그리고 BeginPlay 함수에서 박스 컴포넌트의 델리게이트에 생성한 함수를 등록하였습니다.

 

 

// Overlap이 시작할 때 호출되는 함수
void ATriggerEndingScene::TriggerEndingSceneBeginOverlap(UPrimitiveComponent* OverlappedComponent,
	AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex,
	bool bFromSweep, const FHitResult& SweepResult)
{
	if (bTriggerEnable)
	{
		// 한번만 실행되도록 설정
		bTriggerEnable = false;

		APlayerSurvivor* Survivor = Cast<APlayerSurvivor>(GetWorld()->GetFirstPlayerController()->GetPawn());

		if (IsValid(Survivor))
		{
			// 플레이어가 Game Ending할 상태인지를 확인하는 함수
			if (Survivor->GetEndingEnable())
			{
				USurvivorAnim* SurvivorAnim = Cast<USurvivorAnim>(Survivor->GetMesh()->GetAnimInstance());

				if (IsValid(SurvivorAnim))
					SurvivorAnim->SetAnimType(ESurvivorAnim::Idle);

				// Ending 레벨로 넘겨준다
				UGameplayStatics::OpenLevel(GetWorld(), TEXT("Ending"));
			}
		}
	}
}

// Overlap이 끝날 때 호출되는 함수
void ATriggerEndingScene::TriggerEndingSceneEndOverlap(UPrimitiveComponent* OverlappedComponent,
	AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
	bTriggerEnable = true;
}

 

플레이어와 트리거 박스가 Overlap 되었을 때, 플레이어가 Game Ending이 가능한 상태인지를 확인하고 가능하다면 레벨을 Ending 레벨로 넘겨주었습니다.

 

 

 

 

마지막으로 만들어준 Trigger 클래스를 블루프린트로 생성한 후, 월드에 배치해줬습니다.

이제 플레이어가 Game Ending에 대한 조건을 만족한 상태에서 트리거 박스와 겹쳐진다면 Ending 레벨로 넘어갈 것입니다.

 

 

2. Ending 레벨 및 Ending UI 제작

 

 

우선 Ending 레벨을 생성해주고

 

 

 

Ending UI 또한 제작해주었습니다.

 

 

	UPROPERTY(VisibleAnywhere, BlueprintReadWrite, meta = (AllowPrivateAccess = "true"))
	class UButton* EndButton;

 

public:
	// 버튼을 눌렀을 때, 호출될 델리게이트에 등록할 함수
	UFUNCTION(BlueprintCallable)
	void EndButtonCallback();

 

그리고 나서 Ending UI의 부모 클래스를 생성해준 후, 버튼과 버튼을 눌렀을 때, 호출될 델리게이트에 등록할 함수를 선언해주었습니다.

 

 

// 위젯 클래스의 생성자
void UUIGameEnding::NativeConstruct()
{
	Super::NativeConstruct();

	// 위젯 블루프린트의 버튼을 이름을 통해 가져온다
	EndButton = Cast<UButton>(GetWidgetFromName(TEXT("EndButton")));

	// 버튼 클릭시 호출될 델리게이트에 함수를 등록한다
	EndButton->OnClicked.AddDynamic(this, &UUIGameEnding::EndButtonCallback);
}

 

생성자에서 위젯 블루프린트의 버튼을 가져온 후, 델리게이트에 함수를 등록해주었습니다.

 

 

// End 버튼 클릭시 실행될 함수
void UUIGameEnding::EndButtonCallback()
{
	// Start 레벨로 넘겨준다
	UGameplayStatics::OpenLevel(GetWorld(), TEXT("Start"));
}

 

해당 함수가 호출되면 Start 레벨로 넘겨주었습니다.

 

 

 

이제 만들어준 위젯을 Ending 레벨에서 띄워주기 위해서 일단 Ending Game Mode를 생성해주었습니다.

 

 

private:
	TSubclassOf<UUserWidget> GameEndingWidgetClass;
	class UUIGameEnding* GameEndingWidget;

protected:
	class USoundWave* EndingSound;

 

AEndingGameMode::AEndingGameMode()
{
	static ConstructorHelpers::FClassFinder<UUIGameEnding> GameEndingWidgetAsset(TEXT("WidgetBlueprint'/Game/UI/UI_GameEnding.UI_GameEnding_C'"));

	if (GameEndingWidgetAsset.Succeeded())
		GameEndingWidgetClass = GameEndingWidgetAsset.Class;

	static ConstructorHelpers::FClassFinder<AStartPlayerController>	ControllerClass(TEXT("Blueprint'/Game/Player/BPStartPlayerController.BPStartPlayerController_C'"));

	if (ControllerClass.Succeeded())
		PlayerControllerClass = ControllerClass.Class;

	static ConstructorHelpers::FObjectFinder<USoundWave> EndingSoundAsset(TEXT("SoundWave'/Game/Variety_Audio_Asset/EndingSound.EndingSound'"));

	if (EndingSoundAsset.Succeeded())
		EndingSound = EndingSoundAsset.Object;
}

 

위젯과 사운드를 선언해주고 생성자에서 ConstructorHelpers를 사용하여 모두 정의해주었습니다.

컨트롤러는 Start 레벨과 동일하게 InputMode를 UI로만 설정해주면 되기 때문에 StartPlayerController를 그대로 사용하였습니다.

 

 

// 프로그램이 시작하면 호출되는 함수
void AEndingGameMode::BeginPlay()
{
	Super::BeginPlay();

	if (IsValid(GameEndingWidgetClass))
	{
		// 위젯을 생성한다
		GameEndingWidget = Cast<UUIGameEnding>(CreateWidget(GetWorld(), GameEndingWidgetClass));

		if (IsValid(GameEndingWidget))
		{
			UGameplayStatics::PlaySound2D(this, EndingSound);

			// 위젯을 뷰포트에 추가한다
			GameEndingWidget->AddToViewport();
		}
	}
}

 

그리고 나서 BeginPlay 함수에서 위젯을 생성해준 후, 뷰포트에 추가해주었습니다.

 

 

 

마지막으로 Ending 레벨의 월드 세팅에서 게임 모드를 EndingGameMode로 설정해주었습니다.

 

 

 

이제 Ending 레벨로 넘어가면 Ending UI가 화면에 뜨는 모습을 확인할 수 있습니다.