Unreal Engine

Unreal Engine - 데디케이티드 서버 9 (게임 종료)

gbleem 2025. 5. 4. 04:22

1. 캐릭터 Death 처리


 

흐름

  • StatComponent의 OnOutOfCurrentHP -> PlayerCharacter와 Delegate 바인딩
  • PlayerCharacter -> PlayerController
  • PlayerController -> GameMode

 

코드

//게임 모드
void ADXGameModeBase::OnCharacterDead(ADXPlayerController* InController)
{
	if (!IsValid(InController) || AlivePlayerControllers.Find(InController) == INDEX_NONE)
	{
		return;
	}
	AlivePlayerControllers.Remove(InController);
	DeadPlayerControllers.Add(InController);
} 

//플레이어 컨트롤러
void ADXPlayerController::OnCharaterDead()
{
	ADXGameModeBase* GameMode = Cast<ADXGameModeBase>(UGameplayStatics::GetGameMode(this));
	//server
	if (HasAuthority() && IsValid(GameMode))
	{
		GameMode->OnCharacterDead(this);
	}
}

//플레이어 캐릭터
void ADXPlayerCharacter::BeginPlay()
{
	...
	StatusComponent->OnOutOfCurrentHP.AddUObject(this, &ThisClass::OnDeath);
}

void ADXPlayerCharacter::OnDeath()
{
	ADXPlayerController* PlayerController = GetController<ADXPlayerController>();
	//server
	if (IsValid(PlayerController) && HasAuthority())
	{
		PlayerController->OnCharaterDead();
	}
}

 

 

2. 게임 종료 UI 출력


PlayerController에서 Client RPC 함수를 통해 구현

  • 각 캐릭터마다 각각의 UI를 출력하기 때문에 Client RPC로 구현하기

 

코드

void ADXPlayerController::ClientRPCShowGameResultWidget_Implementation(int32 InRanking)
{
	if (IsLocalController())
	{
		if (IsValid(GameResultUIClass))
		{
			UUW_GameResult* GameResultUI = CreateWidget<UUW_GameResult>(this, GameResultUIClass);
			if (IsValid(GameResultUI))
			{
				GameResultUI->AddToViewport(3);

				FString GameResultString = FString::Printf(TEXT("%s"), InRanking == 1 ? TEXT("Winner Winner Chicken Dinner!") : TEXT("Better Luck Next Time!"));
				GameResultUI->ResultText->SetText(FText::FromString(GameResultString));

				FString RankingString = FString::Printf(TEXT("#%02d"), InRanking);
				GameResultUI->RankingText->SetText(FText::FromString(RankingString));

				FInputModeUIOnly Mode;
				Mode.SetWidgetToFocus(GameResultUI->GetCachedWidget());
				SetInputMode(Mode);

				bShowMouseCursor = true;
			}
		
		}
	}
}

 

GameMode에 UI를 출력해주는 로직 추가

void ADXGameModeBase::OnMainTimerElapsed()
{
	ADXGameStateBase* DXGameState = GetGameState<ADXGameStateBase>();

	if (!IsValid(DXGameState))
	{
		return;
	}

	switch (DXGameState->MatchState)
	{
	...
	case EMatchState::Playing:
	{
		...
		if (DXGameState->AlivePlayerControllerCount <= 1)
		{
			DXGameState->MatchState = EMatchState::Enging;
                        //UI출력
	                AlivePlayerControllers[0]->ClientRPCShowGameResultWidget(1);
		}
		break;
	}
	...
	}
}

void ADXGameModeBase::OnCharacterDead(ADXPlayerController* InController)
{
	if (!IsValid(InController) || AlivePlayerControllers.Find(InController) == INDEX_NONE)
	{
		return;
	}
        //UI 출력
	InController->ClientRPCShowGameResultWidget(AlivePlayerControllers.Num());
	AlivePlayerControllers.Remove(InController);
	DeadPlayerControllers.Add(InController);
}

 

 

3. Title 레벨로 나가기


흐름

  • GameResult UI에 버튼 추가
  • OpenLevel 함수를 통해 Title 레벨로 이동
더보기
#pragma once

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

class UTextBlock;
class UButton;

UCLASS()
class SCC_DEDICATEDX_API UUW_GameResult : public UUserWidget
{
	GENERATED_BODY()

protected:
	virtual void NativeConstruct() override;

private:
	UFUNCTION()
	void OnReturnToTitleButtonClicked();
		
public:
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, meta = (BindWidget))
	TObjectPtr<UTextBlock> ResultText;

	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, meta = (BindWidget))
	TObjectPtr<UTextBlock> RankingText;

	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, meta = (BindWidget))
	TObjectPtr<UButton> ReturnToTitleButton;
};

 

#include "UI/UW_GameResult.h"

#include "Components/Button.h"
#include "Kismet/GameplayStatics.h"

void UUW_GameResult::NativeConstruct()	
{
	Super::NativeConstruct();

	if (!ReturnToTitleButton.Get()->OnClicked.IsAlreadyBound(this, &ThisClass::OnReturnToTitleButtonClicked))
	{
		ReturnToTitleButton.Get()->OnClicked.AddDynamic(this, &ThisClass::OnReturnToTitleButtonClicked);
	}
}

void UUW_GameResult::OnReturnToTitleButtonClicked()
{
	UGameplayStatics::OpenLevel(GetWorld(), FName(TEXT("Title")), true);
}

 

 

4. 플레이어 강제 추방


PlayerController에 Client RPC를 통해서 Title로 돌아가는 함수 구현

void ADXPlayerController::ClientRPCReturnToTitle_Implementation()
{
	//only client's level change
	if (IsLocalController())
	{
		UGameplayStatics::OpenLevel(GetWorld(), FName(TEXT("Title")), true);
	}
}

 

이후 GameMode에서 일정 시간 이후 해당 함수가 호출되도록 구현

  • 참고) 타이머의 Invalidate() 함수를 통해 타이머를 무효화
case EMatchState::Enging:
{
	FString NotificationString = FString::Printf(TEXT("Waiting %d for returning to title."), RemainWaitingTimeForEnding);
	NotifyToAllPlayer(NotificationString);

	--RemainWaitingTimeForEnding;

	if (RemainWaitingTimeForEnding <= 0)
	{
		for (auto AliveController : AlivePlayerControllers)
		{
			AliveController->ClientRPCReturnToTitle();
		}

		for (auto DeadController : DeadPlayerControllers)
		{
			DeadController->ClientRPCReturnToTitle();
		}
		MainTimerHandle.Invalidate();
		return;
	}
	break;
}

 

버튼과 타이머로 로비로 추방하는 모습

 

 

5. 서버 초기화