언리얼5

[UE5 Multiplayer Shooting-15] 팀 데스매치, 점령전 구현(영상)

TIN9 2023. 9. 27.
반응형

그동안 너무 구현만 하느라 언리얼 관련 블로그를 너무 못 올린 거 같네요.

다시 구현한 내용 올려보도록 하겠습니다.

구현내용

  • TeamsGameMode 구현, CaptureThePointGameMode 구현
  • TeamScores Widget 추가
  • TeamPlayerStart 구현
  • BlasterGameState에 TeamScore관련 추가
  • WeaponSelectUI 구현(부활할때마다 무기 선택 가능)
  • StartUpMap관련 UI 구현(맵 선택)

버그 수정

  • 수류탄 처음에 2발로 나와있음 표시 오류
  • 게임 종료 후 Blue Team 텍스트 잘못된거 수정
  • 장전도중 피격되면 장전 멈추고 공격도 안됨
  • DeathMatch맵 클라이언트에서 팀 스코어 표기되는거 수정
  • 스나이퍼 줌에 플레이어 가림
  • 두 무기중에 하나만 선택하면 에러뜸
  • 장전 후 잘 안쏴지는 버그
  • 데스매치에서 서버가 죽고 부활했을때 총알 카운트 이상하게 나오는것
  • 클라이언트에서 킬로그가 안 뜨는 부분
  • 쉴드 채워지는거 51에서 50으로 변경 부분

구현 코드 일부

Gamestate.h/.cpp

GameState에 블루 팀과 레드팀의 점수를 관리하도록 구현

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/GameState.h"
#include "BlasterGameState.generated.h"

/**
 * 
 */
UCLASS()
class BLASTER_API ABlasterGameState : public AGameState
{
	GENERATED_BODY()

public:
	.
    	.
    	.
    
	// 팀
	TArray<class ABlasterPlayerState*> RedTeam;

	TArray<class ABlasterPlayerState*> BlueTeam;
	
	UPROPERTY(ReplicatedUsing = OnRep_RedTeamScore)
	float RedTeamScore = 0.f;

	UPROPERTY(ReplicatedUsing = OnRep_BlueTeamScore)
	float BlueTeamScore = 0.f;
	
	UFUNCTION()
	void OnRep_RedTeamScore();

	UFUNCTION()
	void OnRep_BlueTeamScore();

	void AddRedTeamScores();
	void AddBlueTeamScores();

	void AddRedTeamScores(float Scores);
	void AddBlueTeamScores(float Scores);


private:
	.
    	.
    	.
};
// Fill out your copyright notice in the Description page of Project Settings.


#include "BlasterGameState.h"
#include "../PlayerState/BlasterPlayerState.h"
#include "../PlayerController/BlasterPlayerController.h"
#include "Net/UnrealNetwork.h"

void ABlasterGameState::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
	Super::GetLifetimeReplicatedProps(OutLifetimeProps);
	
    .
    .
    .
    
	DOREPLIFETIME(ABlasterGameState, RedTeamScore);
	DOREPLIFETIME(ABlasterGameState, BlueTeamScore);
}

void ABlasterGameState::OnRep_RedTeamScore()
{
	ABlasterPlayerController* BlasterPlayerController = Cast<ABlasterPlayerController>(GetWorld()->GetFirstPlayerController());

	if (BlasterPlayerController)
	{
		BlasterPlayerController->SetHUDRedTeamScore(RedTeamScore);
	}
}

void ABlasterGameState::OnRep_BlueTeamScore()
{
	ABlasterPlayerController* BlasterPlayerController = Cast<ABlasterPlayerController>(GetWorld()->GetFirstPlayerController());

	if (BlasterPlayerController)
	{
		BlasterPlayerController->SetHUDBlueTeamScore(BlueTeamScore);
	}
}

void ABlasterGameState::AddRedTeamScores()
{
	++RedTeamScore;

	ABlasterPlayerController* BlasterPlayerController = Cast<ABlasterPlayerController>(GetWorld()->GetFirstPlayerController());

	if (BlasterPlayerController)
	{
		BlasterPlayerController->SetHUDRedTeamScore(RedTeamScore);
	}
}

void ABlasterGameState::AddBlueTeamScores()
{
	++BlueTeamScore;

	ABlasterPlayerController* BlasterPlayerController = Cast<ABlasterPlayerController>(GetWorld()->GetFirstPlayerController());

	if (BlasterPlayerController)
	{
		BlasterPlayerController->SetHUDBlueTeamScore(BlueTeamScore);
	}
}

void ABlasterGameState::AddRedTeamScores(float Scores)
{
	RedTeamScore += Scores;;

	ABlasterPlayerController* BlasterPlayerController = Cast<ABlasterPlayerController>(GetWorld()->GetFirstPlayerController());

	if (BlasterPlayerController)
	{
		BlasterPlayerController->SetHUDRedTeamScore(RedTeamScore);
	}
}

void ABlasterGameState::AddBlueTeamScores(float Scores)
{
	BlueTeamScore += Scores;

	ABlasterPlayerController* BlasterPlayerController = Cast<ABlasterPlayerController>(GetWorld()->GetFirstPlayerController());

	if (BlasterPlayerController)
	{
		BlasterPlayerController->SetHUDBlueTeamScore(BlueTeamScore);
	}
}

 

TeamsGameMode.h/.cpp

팀 게임모드에서에서는 게임 시작시 플레이어의 팀 셋팅 내용을 PlayerState클래스로 넘겨 해당 플레이어가 어떤 팀인지 알도록 설정 하였고 플레이어간 데미지를 받아 상대 플레이어가 죽게되면 GameMode는 GameState한테 점수 관리를 하도록 요청하고 갱신된 점수를 다시 GameState는 PlayerController의 HUD에게 점수 Widget를 갱신하도록 요청하는 구조로 설계

중요 코드

void ATeamsGameMode::PlayerEliminated(ABlasterCharacter* ElimmedCharacter, ABlasterPlayerController* VictimController, 
	ABlasterPlayerController* AttackerController)
{
	Super::PlayerEliminated(ElimmedCharacter, VictimController, AttackerController);

	ABlasterGameState* BlasterGameState = Cast<ABlasterGameState>(UGameplayStatics::GetGameState(this));

	ABlasterPlayerState* AttackerPlayerState = AttackerController ? Cast<ABlasterPlayerState>(AttackerController->PlayerState) : nullptr;

	if (BlasterGameState && AttackerPlayerState)
	{
		if (AttackerPlayerState->GetTeam() == ETeam::ET_BlueTeam)
		{
			BlasterGameState->AddBlueTeamScores();
		}
		if (AttackerPlayerState->GetTeam() == ETeam::ET_RedTeam)
		{
			BlasterGameState->AddRedTeamScores();
		}
	}
}
void ATeamsGameMode::HandleMatchHasStarted()
{
	Super::HandleMatchHasStarted();

	ABlasterGameState* BlasterGameState = Cast<ABlasterGameState>(UGameplayStatics::GetGameState(this));

	if (BlasterGameState)
	{
		for (auto PlayerState : BlasterGameState->PlayerArray)
		{
			ABlasterPlayerState* BlasterPlayerState = Cast<ABlasterPlayerState>(PlayerState.Get());

			if (BlasterPlayerState && BlasterPlayerState->GetTeam() == ETeam::ET_NoTeam)
			{
				if (BlasterGameState->BlueTeam.Num() >= BlasterGameState->RedTeam.Num())
				{
					BlasterGameState->RedTeam.AddUnique(BlasterPlayerState);
					BlasterPlayerState->SetTeam(ETeam::ET_RedTeam);
				}
				else
				{
					BlasterGameState->BlueTeam.AddUnique(BlasterPlayerState);
					BlasterPlayerState->SetTeam(ETeam::ET_BlueTeam);
				}
			}
		}
	}
}

 

부활 관련

부활은 TeamsSpawnStart 액터를 하나 만들고 해당 액터에 ETeam(enum class)를 갖도록 설정

플레이어에서 아래와 같이 구현 (내용 주석으로 설명)

void ABlasterCharacter::SetSpawnPoint()
{
	if (HasAuthority() && BlasterPlayerState->GetTeam() != ETeam::ET_NoTeam)
	{
		TArray<AActor*> PlayerStarts;
		// 배치해둔 모든 플레이어스타트액터를 얻어온다.
		UGameplayStatics::GetAllActorsOfClass(this, ATeamPlayerStart::StaticClass(), PlayerStarts);
		TArray<ATeamPlayerStart*> TeamPlayerStarts;
		for (auto Start : PlayerStarts)
		{
			ATeamPlayerStart* TeamStart = Cast<ATeamPlayerStart>(Start);
			// 플레이어 스타트의 팀이랑 플레이어의 팀이랑 같다면 TeamPlayerStarts에 팀 스타트 액터를 저장한다.
			if (TeamStart && TeamStart->GetTeam() == BlasterPlayerState->GetTeam())
			{
				TeamPlayerStarts.Add(TeamStart);
			}
		}

		// 결국 배열에 저장된 부분은 같은 팀 스폰포인트가 저장된거기 때문에
		// 그 중 랜덤의 액터 위치에 해당 플레이어를 스폰하는것이다.
		if (TeamPlayerStarts.Num() > 0)
		{
			ATeamPlayerStart* ChosenPlayerStart = TeamPlayerStarts[FMath::RandRange(0, TeamPlayerStarts.Num() - 1)];
			SetActorLocationAndRotation(ChosenPlayerStart->GetActorLocation(), ChosenPlayerStart->GetActorRotation());
		}
	}
}

무기 선택

인게임에서 무기를 선택하면 해당 무기에 따라 맴버변수로 갖고있는 Enum이 달라지게 되고, 무기를 선택한 상황에서 선택을 클릭하게되면 Widget에서 Server RPC를 통해 캐릭터의 ServerHandleWeaponSelection()으로 넘어가게되어 선택한 무기enum값에 따라 해당 무기를 생성하고 장착하도록 설계

 


UCLASS()
class BLASTER_API UWeaponSelectOverlay : public UUserWidget
{
	GENERATED_BODY()

private:
	UPROPERTY(BlueprintReadWrite, meta = (AllowPrivateAccess = "true"))
		EMainWeapon_Type MainWeapons;

	UPROPERTY(BlueprintReadWrite, meta = (AllowPrivateAccess = "true"))
		ESubWeapon_Type SubWeapons;
}

bool UWeaponSelectOverlay::Initialize()
{
	if (!Super::Initialize())
		return false;

	SelectButton->OnClicked.AddDynamic(this, &UWeaponSelectOverlay::OnWeaponSelectButtonClicked);

	return true;
}

void UWeaponSelectOverlay::OnWeaponSelectButtonClicked()
{
	HandleWeaponSelection();
}

void UWeaponSelectOverlay::HandleWeaponSelection()
{
	UWorld* World = GetWorld();
	if (World == nullptr)
		return;
	APlayerController* PlayerController = World->GetFirstPlayerController();
	if (PlayerController == nullptr)
		return;
	ABlasterCharacter* BlasterCharacter = Cast<ABlasterCharacter>(PlayerController->GetCharacter());
	if (BlasterCharacter == nullptr)
		return;

	BlasterCharacter->ServerHandleWeaponSelection(MainWeapons, SubWeapons);

	RemoveFromParent();
	PlayerController->SetInputMode(FInputModeGameOnly());
	PlayerController->SetShowMouseCursor(false);
}
UCLASS()
class BLASTER_API ABlasterCharacter : public ACharacter, public IInteractWithCrosshairsInterface
{
	GENERATED_BODY()

	UFUNCTION(Server, Reliable)
		void ServerHandleWeaponSelection(EMainWeapon_Type MainWeapon_Type, ESubWeapon_Type SubWeapon_Type);
        
	UPROPERTY(EditAnywhere, meta = (AllowPrivateAccess = "true"))
		TMap<EMainWeapon_Type, TSubclassOf<class AWeapon>> MapMainWeapons;

	UPROPERTY(EditAnywhere, meta = (AllowPrivateAccess = "true"))
		TMap<ESubWeapon_Type, TSubclassOf<class AWeapon>> MapSubWeapons;
}
        

void ABlasterCharacter::ServerHandleWeaponSelection_Implementation(EMainWeapon_Type MainWeapon_Type, ESubWeapon_Type SubWeapon_Type)
{
	UWorld* World = GetWorld();

	AWeapon* MainWeapon = World->SpawnActor<AWeapon>(MapMainWeapons[EMainWeapon_Type::EMW_AssaultRifle]);
	AWeapon* SubWeapon = World->SpawnActor<AWeapon>(MapSubWeapons[ESubWeapon_Type::ESW_Pistol]);

	// 선택한 무기에 따라 스폰되는 무기 변경
	switch (MainWeapon_Type)
	{
	case EMainWeapon_Type::EMW_AssaultRifle:
		MainWeapon = World->SpawnActor<AWeapon>(MapMainWeapons[EMainWeapon_Type::EMW_AssaultRifle]);
		break;
	case EMainWeapon_Type::EMW_SniperRifle:
		MainWeapon = World->SpawnActor<AWeapon>(MapMainWeapons[EMainWeapon_Type::EMW_SniperRifle]);
		break;
	case EMainWeapon_Type::EMW_RocketLauncher:
		MainWeapon = World->SpawnActor<AWeapon>(MapMainWeapons[EMainWeapon_Type::EMW_RocketLauncher]);
		break;
	case EMainWeapon_Type::EMW_Shotgun:
		MainWeapon = World->SpawnActor<AWeapon>(MapMainWeapons[EMainWeapon_Type::EMW_Shotgun]);
		break;
	}

	switch (SubWeapon_Type)
	{
	case ESubWeapon_Type::ESW_Pistol:
		SubWeapon = World->SpawnActor<AWeapon>(MapSubWeapons[ESubWeapon_Type::ESW_Pistol]);
		break;
	case ESubWeapon_Type::ESW_SMG:
		SubWeapon = World->SpawnActor<AWeapon>(MapSubWeapons[ESubWeapon_Type::ESW_SMG]);
		break;
	case ESubWeapon_Type::ESW_GrenadeLauncher:
		SubWeapon = World->SpawnActor<AWeapon>(MapSubWeapons[ESubWeapon_Type::ESW_GrenadeLauncher]);
		break;
	}
	MainWeapon->bDestroyWeapon = true;
	SubWeapon->bDestroyWeapon = true;
	
	if (Combat)
	{
		Combat->EquipWeapon(MainWeapon);
		Combat->EquipWeapon(SubWeapon);
	}
}

 

팀데스매치 영상

https://youtu.be/QfryoG-MTQ0

 

점령전 영상

https://youtu.be/vlGOLTF4koc

 

반응형

댓글