무한하게 실행되는 전투가 메인 컨텐츠인 만큼, 이번에는 전투 시스템을 더욱 더 정교하게 만들고자 한다
추가할 요소는 다음과 같다
- 캐릭터는 무기를 들 때 더 긴 공격범위를 가진다
- 무기에는 공격력 증가치가 랜덤으로 부여된다
- 현재 게임 스코어가 높다면 NPC의 레벨 또한 증가한다
무기에 관한 부분을 하나로 묶어 정리하고, 레벨을 따로 나누도록 하겠다
무기에 공격 범위인 AttackRange, 공격력과 보정치인 AttackDamage 와 AttackModifier 변수를 추가한다
ABWeapon.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "ArenaBattle.h"
#include "GameFramework/Actor.h"
#include "ABWeapon.generated.h"
UCLASS()
class ARENABATTLE_API AABWeapon : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
AABWeapon();
float GetAttackRange() const;
float GetAttackDamage() const;
float GetAttackModifier() const;
UPROPERTY(VisibleAnywhere, Category = Weapon)
USkeletalMeshComponent* Weapon;
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Attack)
float AttackRange;
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, BlueprintReadOnly, Category = Attack)
float AttackDamage;
UPROPERTY(Transient, VisibleInstanceOnly, BlueprintReadOnly, Category = Attack)
float AttackModifier;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
};
ABWeapon.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "ABWeapon.h"
// Sets default values
AABWeapon::AABWeapon()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = false;
Weapon = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("WEAPON"));
RootComponent = Weapon;
static ConstructorHelpers::FObjectFinder<USkeletalMesh> SK_WEAPON
(TEXT("/Game/InfinityBladeWeapons/Weapons/Blade/Swords/Blade_HeroSword11/SK_Blade_HeroSword11.SK_Blade_HeroSword11"));
if (SK_WEAPON.Succeeded())
Weapon->SetSkeletalMesh(SK_WEAPON.Object);
Weapon->SetCollisionProfileName(TEXT("NoCollision"));
AttackRange = 150.0f;
AttackDamageMin = -2.5f;
AttackDamageMax = 10.0f;
AttackModifierMin = 0.85f;
AttackModifierMax = 1.25f;
}
float AABWeapon::GetAttackRange() const
{
return AttackRange;
}
float AABWeapon::GetAttackDamage() const
{
return AttackDamage;
}
float AABWeapon::GetAttackModifier() const
{
return AttackModifier;
}
// Called when the game starts or when spawned
void AABWeapon::BeginPlay()
{
Super::BeginPlay();
AttackDamage = FMath::RandRange(AttackDamageMin, AttackDamageMax);
AttackModifier = FMath::RandRange(AttackModifierMin, AttackModifierMax);
ABLOG(Warning, TEXT("Weapon Damage : %f, Modifier : %f"), AttackDamage, AttackModifier);
}
설정된 변수들을, 캐릭터가 습득할때 받을 수 있도록 설정해준다
ABCharacter.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "ArenaBattle.h"
#include "GameFramework/Character.h"
#include "ABCharacter.generated.h"
DECLARE_MULTICAST_DELEGATE(FOnAttackEndDelegate);
UCLASS()
class ARENABATTLE_API AABCharacter : public ACharacter
{
GENERATED_BODY()
public:
// Sets default values for this character's properties
AABCharacter();
void SetCharacterState(ECharacterState NewState);
ECharacterState GetCharacterState() const;
int32 GetExp() const;
float GetFinalAttackRange() const;
float GetFinalAttackDamage() const;
...
}
ABCharacter.cpp
...
AABCharacter::AABCharacter()
{
...
AttackRange = 80.0f;
AttackRadius = 50.0f;
...
}
...
float AABCharacter::GetFinalAttackRange() const
{
return (nullptr != CurrentWeapon) ? CurrentWeapon->GetAttackRange() : AttackRange;
}
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;
}
...
bool AABCharacter::CanSetWeapon()
{
return true;
}
...
void AABCharacter::SetWeapon(AABWeapon* NewWeapon)
{
if (nullptr != CurrentWeapon)
{
CurrentWeapon->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform);
CurrentWeapon->Destroy();
CurrentWeapon = nullptr;
}
...
}
...
void AABCharacter::AttackCheck()
{
FHitResult HitResult;
FCollisionQueryParams Params(NAME_None, false, this);
bool bResult = GetWorld()->SweepSingleByChannel
(
HitResult,
GetActorLocation(),
GetActorLocation() + GetActorForwardVector() * GetFinalAttackRange(),
FQuat::Identity,
ECollisionChannel::ECC_GameTraceChannel2,
FCollisionShape::MakeSphere(AttackRadius),
Params
);
#if ENABLE_DRAW_DEBUG
FVector TraceVec = GetActorForwardVector() * GetFinalAttackRange();
FVector Center = GetActorLocation() + TraceVec * 0.5f;
float HalfHeight = GetFinalAttackRange() * 0.5f + AttackRadius;
FQuat CapsuleRot = FRotationMatrix::MakeFromZ(TraceVec).ToQuat();
FColor DrawColor = bResult ? FColor::Green : FColor::Red;
float DebugLifeTime = 3.0f;
DrawDebugCapsule
(
GetWorld(),
Center,
HalfHeight,
AttackRadius,
CapsuleRot,
DrawColor,
false,
DebugLifeTime
);
#endif
if (bResult)
{
if (HitResult.GetActor()->IsValidLowLevel())
{
UGameplayStatics::ApplyDamage(HitResult.GetActor(), GetFinalAttackDamage(), GetController(), this, UDamageType::StaticClass());
}
}
}
...
다음은 행동 트리에서도 캐릭터의 사거리를 받아 공격이 가능하게끔 설정해주어야 한다
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의 레벨은 현재 스코어를 받아와 로딩 상태일때 스코어를 기반으로 만들어주도록 하겠다
ABGameMode.h
...
public:
AABGameMode();
virtual void PostInitializeComponents() override;
virtual void PostLogin(APlayerController* NewPlayer) override;
void AddScore(class AABPlayerController* ScoredPlayer);
int32 GetScore() const;
...
}
ABGameMode.cpp
int32 AABGameMode::GetScore() const
{
return ABGameState->GetTotalGameScore();
}
ABCharacter.cpp
void AABCharacter::SetCharacterState(ECharacterState NewState)
{
CurrentState = NewState;
switch (CurrentState)
{
case ECharacterState::LOADING:
{
if (bIsPlayer)
{
DisableInput(ABPlayerController);
ABPlayerController->GetHUDWidget()->BindCharacterStat(CharacterStat);
auto ABPlayerState = Cast<AABPlayerState>(GetPlayerState());
CharacterStat->SetNewLevel(ABPlayerState->GetCharacterLevel());
}
else
{
auto ABGameMode = Cast<AABGameMode>(GetWorld()->GetAuthGameMode());
int32 TargetLevel = FMath::CeilToInt(((float)ABGameMode->GetScore() * 0.8f));
int32 FinalLevel = FMath::Clamp<int32>(TargetLevel, 1, 20);
CharacterStat->SetNewLevel(FinalLevel);
}
SetActorHiddenInGame(true);
HPBarWidget->SetHiddenInGame(true);
SetCanBeDamaged(false);
break;
}
...
}
이때 FMath::CeilToInt
는 float 값을 반올림해 Int 값으로 리턴해주는 함수이다
이제부턴 레벨에 따라 강한 NPC가 나오게 된다