언리얼5

[UE5 Multiplayer Shooting-13] Health 및 PlayerState 추가Score, Defeasts 관련

TIN9 2023. 8. 14.
반응형

구현 내용

  • CharacterOverlay Class 구현 (Health ProgressBar, Score, Defeats 관련)
  • Projectile 피격 구현(Damage)
  • BlasterGameMode Class 생성 및 관리 구현
  • Elim Animation 및 Dissolve Material 구현
  • Elim Bot 구현
  • Weapon Change 구현
  • Random Spawn 구현


중요한 부분

1. Projectile 피격 관련

  1. 대미지 적용 (ApplyDamage 호출): UGameplayStatics::ApplyDamage 함수를 호출하여 대미지를 적용하면, 해당 대상의 AActor 클래스로 대미지 정보가 전달되고 이 정보에는 대미지의 양, 대미지 유형, 어떤 컨트롤러가 대미지를 일으켰는지, 대미지의 원인이 된 액터 등이 포함된다.
  2. 대미지 이벤트 처리: ApplyDamage가 호출되면, 대상 액터의 TakeDamage 메서드가 내부적으로 호출
    TakeDamage 메서드는 대상 액터에 대미지를 적용하고, 필요에 따라 특정 대미지 처리 로직을 실행
  3. 대미지 델리게이트 호출: TakeDamage 메서드 내에서는 OnTakeAnyDamage 델리게이트가 호출
    이 델리게이트는 대미지를 받는 대상이 어떻게 반응해야 할지를 외부에서 정의할 수 있게 해 준다.
  4. 콜백 함수 실행 (필자의 코드에서 ReceiveDamage 호출): OnTakeAnyDamage 델리게이트에 바인딩된 ReceiveDamage 함수가 호출됩니다. 이 함수 내에서는 대미지를 받은 후 원하는 로직을 실행할 수 있습니다. 예를 들어, 데미지를 기반으로 캐릭터의 체력을 줄이거나 특정 애니메이션을 재생하는 등의 처리를 수행할 수 있습니다.

Projectile 피격 관련

AProjectileBullet::OnHit함수의 UGameplayStatics::ApplyDamage()관련

해당 코드의 부분은 충돌되었을 때의 처리 과정이다. 위의 1 ~ 3번에 해당

void AProjectileBullet::OnHit(UPrimitiveComponent* HitComp, AActor* OtherActor,
	UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit)
{
	ACharacter* OwnerCharacter = Cast<ACharacter>(GetOwner());
	if (OwnerCharacter)
	{
		AController* OwnerController = OwnerCharacter->Controller;
		if (OwnerController)
		{
			// Hit되게되면 플레이어의 ReceiveDamage 콜백 함수가 실행이 됨
			UGameplayStatics::ApplyDamage(OtherActor, Damage, OwnerController, this,
				UDamageType::StaticClass());
		}
	}

	// 발사체 파괴는 마지막이 되어야함 : 부모로 들어가서 발사체 파괴함
	Super::OnHit(HitComp, OtherActor, OtherComp, NormalImpulse, Hit);
}

ABlasterCharacter::BeginPlay()의 ApplyDamage관련 콜백 함수 등록

ReceiveDamage함수에서는 받은 대미지를 처리하여 HP갱신, HitAnim, Elim관련 내용 실행.

또한 여기서 가장 중요한 부분은 바인딩 부분을 서버에서만 되도록 처리해 준 이유는 이런 식으로 구현해야 서버는 대미지 등을 구현했을 때 실제로 누구를 때렸는지에 대한 여부를 결정할 수 있고 부정행위를 방지하기 위해서임.

.h
UFUNCTION()
void ReceiveDamage(AActor* DamagedActor, float Damage, const UDamageType* DamageType, class AController* InstigatorController, AActor* DamageCauser);

.cpp
void ABlasterCharacter::BeginPlay()
{
	Super::BeginPlay();

	UpdateHUDHealth();

	// 서버에서만 데미지 줘야함
	if (HasAuthority())
	{
		OnTakeAnyDamage.AddDynamic(this, &ABlasterCharacter::ReceiveDamage);
	}
}

2. Eliminated 관련(죽었을 때)

  1. ReceiveDamage함수에서 Health를 관리하고 Health가 0이 되었을 때 GameMode를 통해서 Elim 관련 함수 실행하도록 구조 설계
  2. GameMode는 기본적으로 언리얼엔진에서 게임의 규칙과 관련된 내용을 담당하기 때문에 플레이어의 Eliminated는 GameMode에서 처리하는 것

Eliminated 관련(죽었을 때)

Health가 0이 되었을 경우 BlasterGameMode를 얻어온 뒤 GameMode의 PlayerEliminated함수 실행

ReceiveDamage함수가 무조건 적으로 서버에서 실행되고 있기 때문에 GameMode를 얻어올 수 있는 것이다.

ABlasterCharacter::ReceiveDamage()

void ABlasterCharacter::ReceiveDamage(AActor* DamagedActor, float Damage, const UDamageType* DamageType, 
	AController* InstigatorController, AActor* DamageCauser)
{
	// Health는 복제중 그래서 값이 바뀌는 순간 OnRep_Health()함수가 실행됨
	Health = FMath::Clamp(FMath::CeilToInt(Health - Damage), 0, FMath::CeilToInt(MaxHealth));
	// HUD 업데이트하고 몽타주 실행
	UpdateHUDHealth();
	PlayHitReactMontage();

	// 죽었을때
	if (Health == 0.f)
	{
		// 현재 해당 ReceiveDamage함수는 서버에서 실행되고 있기 때문에 게임모드를 얻어올 수 있다.
		ABlasterGameMode* BlasterGameMode = GetWorld()->GetAuthGameMode<ABlasterGameMode>();
		if (BlasterGameMode)
		{
			BlasterPlayerController = BlasterPlayerController == nullptr ? Cast<ABlasterPlayerController>(Controller) : BlasterPlayerController;
			// 공격한 사람의 플레이어 컨트롤러
			ABlasterPlayerController* AttackerController = Cast<ABlasterPlayerController>(InstigatorController);
			BlasterGameMode->PlayerEliminated(this, BlasterPlayerController, AttackerController);
		}
	}
}

ABlasterGameMode::PlayerEliminated()

게임모드에서 플레이어의 Elim과 HUD관련된 내용을 전반적으로 담당 및 처리

void ABlasterGameMode::PlayerEliminated(ABlasterCharacter* ElimmedCharacter, ABlasterPlayerController* VictimController, 
	ABlasterPlayerController* AttackerController)
{
	// 공격자 플레이어 상태
	ABlasterPlayerState* AttackerPlayerState = AttackerController ? Cast<ABlasterPlayerState>(AttackerController->PlayerState) : nullptr;
	// 피해자 플레이어 상태
	ABlasterPlayerState* VictimPlayerState = VictimController ? Cast<ABlasterPlayerState>(VictimController->PlayerState) : nullptr;
	if (AttackerPlayerState == nullptr)  UE_LOG(LogTemp, Warning, TEXT("AttackerPlayerState 유효하지 않음"));
	if (AttackerPlayerState == VictimPlayerState)  UE_LOG(LogTemp, Warning, TEXT("AttackerPlayerState == VictimPlayerState"));

	if (AttackerPlayerState && AttackerPlayerState != VictimPlayerState)
	{
		AttackerPlayerState->AddToScore(1.f);
	}

	if (VictimPlayerState)
	{
		VictimPlayerState->AddToDefeats(1);
	}

	if (ElimmedCharacter)
	{
		ElimmedCharacter->Elim();
	}
}

 

이후는 영상 첨부

영상

https://youtu.be/RImt1YzCkyY

 

반응형

댓글