* 전투와 관련된 부가 요소를 추가해본다.

1. 플레이너는 게임 진행 중에 HP를 회복할 수 없고 오직 레벨업을 할 떄만 회복된다.
2. 캐릭터는 무기를 들 떄 더 긴 공격 범위를 가진다.
3. 무기에는 공격력 증가치가 랜덤으로 부여되며, 운이 없으면 오히려 무기에 의해 공격력이 저하될 수 있다.
4. 현재 게임 스코어가 높을수록 생성되는 NPC의 레벨도 증가한다.

* 1번은 이미 구현했으므로 이번에는 2번 기능을 구현해본다.

* 무기 액터인 ABWeapon에 AttackRange라는 속성을 추가한다.
* 캐릭터에 무기가 없으면 캐릭터의 AttackRange 속성을 사용하고, 캐릭터가 무기를 들면 무기의 AttackRange 속성을 사용하도록 로직을 수정한다.

* 캐릭터의 AttackRange 속성의 기본값을 80으로 낮춘다. 그리고 무기의 AttackRange 속성의 기본값을 150으로 지정하되, 해당 속성 키워드에 EditAnywgere, BlueprintReadWrite를 지정해 앞으로 ABweapon 클래스를 상속받은 무기 블루프린트에서도 공격범위 값을 다르게 설정할 수 있도록 기능을 부여한다.

* 또한 이번에는 캐릭터가 무기를 들고 있더라고 무기를 변경할 수 있도록 CanSetWeapon의 값을 무조건 true로 설정하고, 기존에 무기가 있는 경우 이를 없애고 새로운 무기를 습득하도록 로직을 변경한다.

//ABWeapon.h
class ARENABATTLE_API AABWeapon : public AActor
{
public:	
	float GetAttackRange() const;

protected:
	UPROPERTY(EditAnywhere,BlueprintReadWrite,Category = Attack)
	float AttackRange;
}

//ABWeapon.cpp
AABWeapon::AABWeapon()
{
.....
	AttackRange = 150.0f;
}


float AABWeapon::GetAttackRange() const
{
	return AttackRange;
}

//ABCharacter.h
class ARENABATTLE_API AABCharacter : public ACharacter
{
public:
	float GetFinalAttackRange() const;
}

AABCharacter::AABCharacter()
{
	AttackRange = 80.0f;
}

float AABCharacter::GetFinalAttackRange() const
{
	return (nullptr != CurrentWeapon) ? CurrentWeapon->GetAttackRange() : AttackRange;
}

void AABCharacter::SetWeapon(AABWeapon * NewWeapon)
{
	ABCHECK(nullptr != NewWeapon);
	if (nullptr != CurrentWeapon)
	{
		CurrentWeapon->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform);
		CurrentWeapon->Destroy();
		CurrentWeapon = nullptr;
	}

	FName WeaponSocket(TEXT("hand_rSocket"));
	if (nullptr != NewWeapon)
	{
		NewWeapon->AttachToComponent(GetMesh(), FAttachmentTransformRules::SnapToTargetNotIncludingScale, WeaponSocket);
		NewWeapon->SetOwner(this);
		CurrentWeapon = NewWeapon;
	}
}

void AABCharacter::AttackCheck()
{
	float FinalAttackRange = GetFinalAttackRange(); // AttackRange -> FinalAttackRange
	
	FHitResult HitResult;
	FCollisionQueryParams Params(NAME_None, false, this);
	bool bResult = GetWorld()->SweepSingleByChannel(
		HitResult,
		GetActorLocation(),
		GetActorLocation() + GetActorForwardVector() * FinalAttackRange,
		FQuat::Identity,
		ECollisionChannel::ECC_GameTraceChannel2,
		FCollisionShape::MakeSphere(AttackRadius),
		Params);

#if ENABLE_DRAW_DEBUG

	FVector TraceVec = GetActorForwardVector() * FinalAttackRange;
	FVector Center = GetActorLocation() + TraceVec * 0.5f;
	float HalfHeight = FinalAttackRange * 0.5f + AttackRadius;
	FQuat CapsuleRot = FRotationMatrix::MakeFromZ(TraceVec).ToQuat();
	FColor DrawColor = bResult ? FColor::Green : FColor::Red;
	float DebugLifeTime = 5.0f;
    .....
}

//BTDecorator_IsInAttackRange.cpp
bool UBTDecorator_IsInAttackRange::CalculateRawConditionValue(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) const
{
	bool bResult = Super::CalculateRawConditionValue(OwnerComp, NodeMemory);

	auto ControllingPawn = Cast<AABCharacter>(OwnerComp.GetAIOwner()->GetPawn()); //변경

	if (nullptr == ControllingPawn)
		return false;

	auto Target = Cast<AABCharacter>(OwnerComp.GetBlackboardComponent()->GetValueAsObject(AABAIController::TargetKey));

	if (nullptr == Target)
		return false;

	bResult = (Target->GetDistanceTo(ControllingPawn) <= ControllingPawn->GetFinalAttackRange());//변경
	return bResult;
}

 

* 이제 캐릭터는 무기를 들 떄 무기가 없는 NPC보다 더 긴 공격 범위를 가진다.


* 이번에는 3번의 기획 요소를 구현해본다. 공격할 떄 무기가 있는 경우 기존 공격력을 증폭시키는 기능을 구현한다.
* 무기에 랜덤한 순수 공격력과 효과치 속성을 설정하고 최종 대미지를 산출할 떄 이 데이터를 활용하도록 로직을 구현해 본다.

//AABWeapon.h
class ARENABATTLE_API AABWeapon : public AActor
{
public:	
	float GetAttackRange() const;
	float GetAttackDamage() const;
	float GetAttackModifier() const;

protected:
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Attack)
		float AttackDamageMin;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Attack)
		float AttackDamageMax;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Attack)
		float AttackModifierMin;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Attack)
		float AttackModifierMax;

	UPROPERTY(Transient, VisibleInstanceOnly, BlueprintReadWrite, Category = Attack)
		float AttackDamage;

	UPROPERTY(Transient, VisibleInstanceOnly, BlueprintReadWrite, Category = Attack)
		float AttackModifier;
}

//AABWeapon.cpp
AABWeapon::AABWeapon()
{
	AttackRange = 150.0f;

	AttackDamageMin = -2.5f;
	AttackDamageMax = 10.0f;
	AttackModifierMin = 0.85f;
	AttackModifierMax = 1.25f;
}

// Called when the game starts or when spawned
void AABWeapon::BeginPlay()
{
	Super::BeginPlay();
	AttackDamage = FMath::RandRange(AttackDamageMin, AttackDamageMax);
	AttackModifier = FMath::RandRange(AttackModifierMin, AttackModifierMax);
}

float AABWeapon::GetAttackDamage() const
{
	return AttackDamage;
}

float AABWeapon::GetAttackModifier() const
{
	return AttackModifier;
}

//AABCharacter.h
public:
float GetFinalAttackDamage() const;

//AABCharacter.cpp
float AABCharacter::GetFinalAttackDamage() const
{
	float AttackDamage = (nullptr != CurrentWeapon) ? (CharacterStat->GetAttack() + CurrentWeapon->GetAttackDamage()) : (CharacterStat->GetAttack());
	float AttackModifier = (nullptr != CurrentWeapon) ? (CurrentWeapon->GetAttackModifier()) : (1.0f);

	return AttackDamage * AttackModifier;
}

void AABCharacter::AttackCheck()
{
.....
	if (bResult)
	{
		if (HitResult.Actor.IsValid())
		{
			FDamageEvent DamageEvent;
			HitResult.Actor->TakeDamage(GetFinalAttackDamage(), DamageEvent, GetController(), this);
		}
	}
}

 

* 로직을 반영하면, 무기를 습득할 때 해당 무기에 의해 플레이어의 공격 대미지가 증가하거나 감소되는 것을 확인할 수 있다.


* 이번에는 마지막 기획요소인 NPC의 레벨을 조정하는 기능을 구현해본다.

* NPC 캐릭터의 LOADING 스테이트에서 현제 게임 스코어를 게임모드에게 질의하고, 이를 기반으로 캐릭터의 레벨값을 설정한다.

//AABGameMode.h
public:
	int32 GetScore() const;

//AABGameMode.cpp
int32 AABGameMode::GetScore() const
{
	return ABGameState->GetTotalGameScore();
}

//ABCharacter.cpp
#include "ABGameMode.h"

void AABCharacter::SetCharacterState(ECharacterState NewState)
{
	ABCHECK(CurrentState != NewState);
	CurrentState = NewState;

	switch (NewState)
	{
	case ECharacterState::LOADING :
	{
		if (bIsPlayer)
		{
			DisableInput(ABPlayerController);

			ABPlayerController->GetHUDWidget()->BindCharacterStat(CharacterStat);

			auto ABPlayerState = Cast<AABPlayerState>(GetPlayerState());
			ABCHECK(nullptr != ABPlayerState);
			CharacterStat->SetNewLevel(ABPlayerState->GetCharacterLevel());
		}
		else //추가
		{
			auto ABGameMode = Cast<AABGameMode>(GetWorld()->GetAuthGameMode());
			ABCHECK(nullptr != ABGameMode);
			int32 TargetLevel = FMath::CeilToInt(((float)ABGameMode->GetScore() * 0.8f));
			int32 FinalLevel  = FMath::Clamp<int32>(TargetLevel, 1, 20);
			ABLOG(Warning, TEXT("New NPC Level : %d"), FinalLevel);
			CharacterStat->SetNewLevel(FinalLevel);
		}

		SetActorHiddenInGame(true);
		HPBarWidget->SetHiddenInGame(true);
		bCanBeDamaged = false;
		break;
	}
    .....

※ 게임 실행 중에 게임 모드의 포인터를 가져올 때는 월드의 GetAuthGameMode라는 함수를 사용한다.
※ 멀티 플레이 게임에서 게임 모드는 게임을 관리하는 방장 역황을 하며 게임에서 중요한 데이터를 인증하는 권한을 가진다.

NPC가 생성된결과 화면

 

+ Recent posts