이어서 리팩토링을 진행한다.
Enemy.h
파일에 주구장창 선언해둔 변수 및 함수들도 정리를 해야한다.
아래는 Enemy.h
전문이다.
DirectionalHitReact()
는 오류때문에 public에 임시로 두고 사용x(원래 BaseCharacter
클래스에 있어야 함)
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "Characters/BaseCharacter.h"
#include "Characters/CharacterTypes.h"
#include "Interfaces/HitInterface.h"
#include "Enemy.generated.h"
class UHealthBarComponent;
class AAIController;
class UPawnSensingComponent;
UCLASS()
class STUDY_API AEnemy : public ABaseCharacter
{
GENERATED_BODY()
public:
// Sets default values for this character's properties
AEnemy();
/** <AActor> */
// Called every frame
virtual void Tick(float DeltaTime) override;
// Called to bind functionality to input
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
// 피격시 데미지 받는 함수
virtual float TakeDamage(float DamageAmount, struct FDamageEvent const& DamageEvent, class AController* EventInstigator, AActor* DamageCauser) override;
// Enemy 처치시 무기 파괴(Actor에 있던 함수 override)
virtual void Destroyed() override;
/** </AActor> */
/** <IHitInterface> */
// 피격시 실행될 함수
virtual void GetHit_Implementation(const FVector& ImpactPoint) override;
/** </IHitInterface> */
protected:
/** <AActor> */
// Called when the game starts or when spawned
virtual void BeginPlay() override;
/** </AActor> */
/** <ABaseCharacter> */
// DeathMontage 재생 함수
virtual void Die() override;
// 공격 함수 override
virtual void Attack() override;
// CanAttack 함수 override
virtual bool CanAttack() override;
// 데미지 받는 함수 override
virtual void HandleDamage(float DamageAmount) override;
// DeathMontage 재생 함수
virtual int32 PlayDeathMontage() override;
// AttackEnd 함수 override
virtual void AttackEnd() override;
/** </ABaseCharacter> */
// DeathAnimation 종류
UPROPERTY(BlueprintReadOnly)
TEnumAsByte<EDeathPose> DeathPose;
// Enemy State
UPROPERTY(BlueprintReadOnly)
EEnemyStates EnemyState = EEnemyStates::EES_Patrolling;
private:
/** AI Behavior */
void CheckPatrolTarget();
void CheckCombatTarget();
// PatrolTimer 끝나고 실행될 Callback함수
void PatrolTimerFinished();
// HealthBarWidget 숨기는 함수
void HideHealthBarWidget();
// HealthBarWidget 나타내는 함수
void ShowHealthBarWidget();
// 어그로 제거 함수
void LoseInterest();
// 패트롤 시작 함수
void StartPatrolling();
// 추적 시작 함수
void StartChasing();
// CombatTarget이 CombatRadius 외부에 존재하는지 확인하는 함수, 외부존재시 true 반환
bool IsOutsideCombatRadius();
// CombatTarget이 AttackRadius 외부에 존재하는지 확인하는 함수, 외부존재시 true 반환
bool IsOutsideAttackRadius();
// CombatTarget이 AttackRadius 외부에 존재하는지 확인하는 함수, 외부존재시 true 반환
bool IsInsideAttackRadius();
// 추적 여부 리턴 함수
bool IsChasing();
// 공격가능 여부 리턴 함수
bool IsAttacking();
// 사망 여부 확인 함수
bool IsDead();
// 공격 여부 확인 함수
bool IsEngaged();
// PatrolTimer clear 함수
void ClearPatrolTimer();
// AttackTimer 시작 함수
void StartAttackTimer();
// AttackTimer clear 함수
void ClearAttackTimer();
// Target이 Radius 내에 존재하는지 확인하는 함수
bool InTargetRange(AActor* Target, double Radius);
// Target 위치로 이동시키는 함수
void MoveToTarget(AActor* Target);
// 랜덤하게 PatrolTarget 선택하는 함수
AActor* ChoosePatrolTarget();
// 델리게이트에 바인딩하기 위한 함수, UFUNCTION 매크로 사용해야함
// 블루프린트에서 On See Pawn 노드의 경우 델리게이트 이용
// UPawnSensingComponent -> f12 -> OnSeePawn 검색시 FSeePawnDelegate 타입으로 표시됨
// 콜백함수를 델리게이트에 바인딩해야 하므로, 콜백함수 생성해야함
// Enemy가 캐릭터를 발견했을 경우 실행
UFUNCTION()
void PawnSeen(APawn* SeenPawn); // UPawnSeinsingComopnent에 있는 OnPawnSEen에 대한 콜백함수
// WidgetComponent class
UPROPERTY(VisibleAnywhere)
UHealthBarComponent* HealthBarWidget;
// PawnSensing Component
UPROPERTY(VisibleAnywhere)
UPawnSensingComponent* PawnSensing;
// 장착할 무기, AWeapon 타입 파생클래스만 할당 가능
UPROPERTY(EditAnywhere)
TSubclassOf<class AWeapon>WeaponClass;
// 전투 대상
UPROPERTY()
AActor* CombatTarget;
// 전투 반경(반경 오버시 HealthBarWidget 숨김처리)
UPROPERTY(EditAnywhere)
double CombatRadius = 500.f;
// 공격범위
UPROPERTY(EditAnywhere)
double AttackRadius = 200.f;
// 오차범위 설정
UPROPERTY(EditAnywhere, Category = "Combat", meta = (AllowPrivateAccess = "true"))
double AcceptanceRadius = 10.f;
// Enemy 컨트롤러
UPROPERTY()
AAIController* EnemyController;
// 패트롤 타겟지점
UPROPERTY(EditInstanceOnly, Category = "AI Navigation")
AActor* PatrolTarget;
// 패트롤 타켓지점 배열
UPROPERTY(EditInstanceOnly, Category = "AI Navigation")
TArray<AActor*> PatrolTargets;
// TargetPoint 감지 범위(범위사이즈내에 도달하면 다음 지점으로 이동)
UPROPERTY(EditAnywhere)
double PatrolRadius = 200.f;
// 다음 Patrol 대기시간, 끝나면 PatrolTimerFinished() 콜백
FTimerHandle PatrolTimer;
// patrol 최소 대기시간
UPROPERTY(EditAnywhere, Category = "AI Navigation", meta = (AllowPrivateAccess = "true"))
float PatrolWaitMin = 2.f;
// patrol 최대 대기시간
UPROPERTY(EditAnywhere, Category = "AI Navigation", meta = (AllowPrivateAccess = "true"))
float PatrolWaitMax = 5.f;
// 패트롤시 이동 속도
UPROPERTY(EditAnywhere, Category = "Combat")
float PatrollingSpeed = 125.f;
// 공격 딜레이 타이머
FTimerHandle AttackTimer;
// 공격 딜레이 최소시간
UPROPERTY(EditAnywhere, Category = "Combat")
float AttackMin = 0.5f;
// 공격 딜레이 최대시간
UPROPERTY(EditAnywhere, Category = "Combat")
float AttackMax = 1.f;
// 추적시 이동 속도
UPROPERTY(EditAnywhere, Category = "Combat")
float ChasingSpeed = 300.f;
// 사망시 액터 삭제까지 소요시간
UPROPERTY(EditAnywhere, Category = "Combat", meta = (AllowPrivateAccess = "true"))
float DeathLifeSpan = 6.f;
public:
// 피격시 피격 위치 디버그 및 Animation montage section 지정
void DirectionalHitReact(const FVector& ImpactPoint);
};
이어서 Enemy.cpp
파일을 리팩토링 해야한다.
그전에 헤더파일을 먼저 정리한다.
#include "Enemy/Enemy.h"
#include "AIController.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "Kismet/KismetSystemLibrary.h"
#include "Perception/PawnSEnsingComponent.h"
#include "Components/AttributeComponent.h"
#include "Components/CapsuleComponent.h"
#include "HUD/HealthBarComponent.h"
#include "Items/Weapons/Weapon.h"
#include "Study/DebugMacros.h"
현재 include시킨 헤더파일중 CapsuleComponent
의 경우 모든 적들이 가져야 할 컴포넌트이므로 관련 코드를 Enemy.cpp
에서 BaseCharacter.cpp
로 옮기는 것이 괜찮아보인다. 또한 KitmetSystemLibrary.h
파일은 DebugSpehre
를 나타내기 위해 사용했던 것인데, 이제는 필요없으니 삭제한다. 마지막으로 DebugMacros.h
파일도 더이상 사용하지 않으니 해당 코드를 삭제한다.
관련 코드는
// 캡슐과 카메라의 충돌 무시
GetCapsuleComponent()->SetCollisionResponseToChannel(ECollisionChannel::ECC_Camera, ECollisionResponse::ECR_Ignore);
인데 헤더파일과 함께 BaseCharacter.cpp
로 옮겨주자.
다른 부분에서 혹시 모를 CapsuleComponet 관련 코드가 있을지 모르므로 컴파일을 시도해서 오류가 발생하는지 확인한다.
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "Interfaces/HitInterface.h"
#include "Components/AttributeComponent.h"
#include "BaseCharacter.generated.h"
class AWeapon;
class UAttributeComponent;
UCLASS()
class STUDY_API ABaseCharacter : public ACharacter, public IHitInterface
{
GENERATED_BODY()
public:
// Sets default values for this character's properties
ABaseCharacter();
// Called every frame
virtual void Tick(float DeltaTime) override;
// Called to bind functionality to input
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
// 공격 함수
virtual void Attack();
// DeathMontage 재생 함수
virtual void Die();
// void DirectionalHitReact(const FVector& ImpactPoint);
// 데미지 받는 기능 함수
virtual void HandleDamage(float DamageAmount);
// HitSound 재생 함수
void PlayHitSound(const FVector& ImpactPoint);
// HitParticles 스폰 함수// HitSound 재생 함수
void SpawnHitParticles(const FVector& ImpactPoint);
// 캡슐 컴포넌트 콜리전을 NoCollision으로 변경
void DisableCapsuleCollision();
// 공격 가능 여부 함수
virtual bool CanAttack();
// 생존 여부 확인 함수
virtual bool IsAlive();
// HitReactMontage 재생 함수
virtual void PlayHitReactMontage(const FName& SectionName);
// AttakMontage SelectionNumber 반환 함수
virtual int32 PlayAttackMontage();
// DeathMontage SelectionNumber 반환 함수
virtual int32 PlayDeathMontage();
// 장착중인 무기
UPROPERTY(VisibleAnywhere, Category = "Weapon")
AWeapon* EquippedWeapon;
// Atrributes
UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
UAttributeComponent* Attributes;
// AnimNotify_AttackEnd 실행시 UnOccupied 상태로 변경
UFUNCTION(BlueprintCallable)
virtual void AttackEnd();
// 노티파이에 따른 Collsion 종류 변경
UFUNCTION(BlueprintCallable)
void SetWeaponCollision(ECollisionEnabled::Type CollisionEnabled);
private:
// Montage SectionName 재생 함수
void PlayMontageSection(UAnimMontage* Montage, const FName& SectionName);
// 랜덤 몽타주 섹션 재생 함수
int32 PlayRandomMontageSection(UAnimMontage* Montage, const TArray<FName>& SectionNames);
// 피격 사운드
UPROPERTY(EditAnywhere, Category = "Sounds")
USoundBase* HitSound;
// 피격시 visual effects
UPROPERTY(EditAnywhere, Category = "VisualEffects")
UParticleSystem* HitParticles;
// 공격 애니메이션 몽타주
UPROPERTY(EditDefaultsOnly, Category = "Montages")
UAnimMontage* AttackMontage;
// 피격시 재생시킬 Animation Montage
UPROPERTY(EditDefaultsOnly, Category = "Montages")
UAnimMontage* HitReactMontage;
// 사망 애니메이션 몽타주
UPROPERTY(EditDefaultsOnly, Category = "Montages")
UAnimMontage* DeathMontage;
// AttackMontage Section명 배열
UPROPERTY(EditAnywhere, Category = "Combat")
TArray<FName>AttackMontageSections;
// DeathMontage Section명 배열
UPROPERTY(EditAnywhere, Category = "Combat")
TArray<FName>DeathMontageSections;
};
컴파일후 오류가 발생하지 않는지 확인한다.
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Characters/BaseCharacter.h"
#include "GameFramework/Character.h"
#include "CharacterTypes.h"
#include "WraithCharacter.generated.h"
class USkeletalMeshComponent;
class USpringArmComponent;
class UCameraComponent;
class AItem;
class UAnimMontage;
class UBoxComponent;
UCLASS()
class STUDY_API AWraithCharacter : public ABaseCharacter
{
GENERATED_BODY()
public:
// Sets default values for this character's properties
AWraithCharacter();
// Called every frame
virtual void Tick(float DeltaTime) override;
// Called to bind functionality to input
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
/**
* input을 위한 callback 함수
*/
// 전후 이동 함수
void MoveForward(float Value);
// 좌우 이동 함수
void MoveRight(float Value);
// 좌우 회전 함수
void Turn(float Value);
// 상하 회전 함수
void LookUp(float Value);
// 드롭 상태의 아이템 픽업 함수
void PickUp();
// 공격 함수
virtual void Attack() override;
/**
* 전투 관련
*/
// AnimNotify_AttackEnd 실행시 UnOccupied 상태로 변경
virtual void AttackEnd() override;
// 공격 가능 여부 함수
virtual bool CanAttack() override;
// 무장해제 가능 여부 함수
bool CanDisArm();
// 무장 가능 여부 함수
bool CanArm();
// ArmingMontage play 함수
void PlayArmingMontage(const FName& Section);
// 무장시 RightHandSocket에 무기 부착
UFUNCTION(BlueprintCallable)
void AttachWeaponToHand();
// 무장해제시 SpineWeaponSocket에 무기 부착
UFUNCTION(BlueprintCallable)
void DetachWeaponToSpine();
// 무장해제 및 무장 종료시 ActionState 변경을 위한 함수
UFUNCTION(BlueprintCallable)
void FinishDetachOrAttachWeapon();
private:
/**
* 캐릭터 컴포넌트
*/
// SpringArm Component
UPROPERTY(VisibleAnywhere)
USpringArmComponent* CameraLine;
// Camera Component
UPROPERTY(VisibleAnywhere)
UCameraComponent* Camera;
// 오버랩된 아이템
UPROPERTY(VisibleAnywhere)
AItem* OverlappingItem;
// 무장, 무장해제 애니메이션 몽타주
UPROPERTY(EditDefaultsOnly, Category = "Montages", meta = (AllowPrivateAccess = "true"))
UAnimMontage* ArmingMontage;
UPROPERTY(VisibleDefaultsOnly)
ECharacterStates CharacterState = ECharacterStates::ECS_UnEquipped;
// 캐릭터 공격 관련 상태
UPROPERTY(VisibleDefaultsOnly)
EActionStates ActionState = EActionStates::EAS_UnOccupied;
public:
FORCEINLINE void SetOverlappingItem(AItem* Item) { OverlappingItem = Item; }
FORCEINLINE ECharacterStates GetCharacterState() const { return CharacterState; }
};
추가로 WraithCharacter.coo
에서 더이상 BoxComponent
를 사용하지 않으므로 BoxComponent.h
부분은 삭제한다.
헤더파일에 선언된것과 동일하게 순서를 변경한다. 작업을 진행하면서 리팩토링이 필요한 부분은 리팩토링을 진행한다.
...
private:
...
/** AI Behavior */
// Enemy 초기화
void InitializeEnemy()
...
// Enemy에 무기 스폰 및 장착시키는 함수
void SpawnDefaultWeapon();
UFUNCTION()
void PawnSEen(...)
// BeginPlay 리팩토링 필요
void AEnemy::BeginPlay()
{
Super::BeginPlay();
if(PawnSensing)
{
PawnSensing->OnSeePawn.AddDynamic(this, &AEnemy::PawnSeen);
}
// 캐스팅 부분은 코드 앞쪽에 해두는 것이 오류 방지 가능
EnemyController = Cast<AAIController>(GetController));
}
...
// Enemy 초기화
void AEnemy::InitializeEnemy()
{
MoveToTarget(PatrolTarget);
HideHealthBarWidget(); // if(HealthBarWidget) 를 이전에 생성해둔 함수로 대체
SpawnDefaultWeapon();
}
...
// Enemy에 무기 스폰 및 장착시키는 함수
void AEnemy::SpawnDefaultWeapon()
{
UWorld* World = World->SpawnActor<AWeapon>(WeaponClass);
if(world && WeaponClass)
{
DefaultWeapon->Equip(GetMesh(), FName("RightHandSocket"), this, this);
EquippedWeapon = DefaultWeapon;
}
}
...
protected:
/**
* 전투 관련
*/
...
// 무기 장착 함수
void EquipWeapon(AWeapon* Weapon);
...
// 무장 가능 여부 함수
bool CanArm();
// 무장해제
void Disarm();
// 무장
void Arm();
// ArmingMontage play 함수
void PlayArmingMontage(const FName& Section);
private:
...
* WraithCharacter.cpp
```cpp
...
void AWraithCharacter::AWraithCharacter()
{
// 지금 당장은 사용안하므로 false로 변경
PrimaryActorTick.bCanEverTick = false;
...
}
...
// Item pickup 구현
void AWraithCharacter::PickUp()
{
// OverlappingItem을 Weapon으로 캐스팅
AWeapon* OverlappingWeapon = Cast<AWeapon>(OverlappingItem);
if (OverlappingWeapon)
{
EquipWeapon(OverlappingWeapon);
}
else
{
if (CanDisarm())
{
Disarm();
}
else if (CanArm())
{
Arm();
}
}
}
...
void AWraithCharacter::EquipWeapon(AWeapon* Weapon)
{
// Weapon 클래스에 있던 EquipWeapon과 함수가 겹쳐 혼동이 발생할 수 있으므로
// Weapon 클래스의 EquipWeapon ->EquipWeaponToSocket으로 변경
Weapon->EquipWeaponToSocket(GetMesh(), FName("RightHandSocket"), this, this);
CharacterState = ECharacterStates::ECS_EquippedOneHandedWeapon;
Weapon = nullptr;
EquippedWeapon = Weapon;
}
...
헤더파일에 선언된 순서와 동일하게 모든 함수 위치를 변경시킨다.
...
public:
...
// 무기 장착시 부착된 빛나는 이펙트 비활성화
void DeactivateEmbers();
// OverlapSphere 콜리전 삭제 함수
void DisableSphereCollision();
// 무기 획득시 사운드 재생
void PlayEquipSound();
...
protected:
UFUNCTION()
...
// void OnSphereBeginOverlap(); 사용안함
// void OnSphereEndOverlap(); 사용안함
...
pirvate:
// Execute_GetHit 함수 실행 함수
void ExecuteGetHit(FHitResult& CollisionBoxHit);
// BoxTrace 실행 함수
void BoxTrace(FHitResult& CollisionBoxHit);
// BoxTrace시 사용하는 BoxComponent 사이즈
UPROPERTY(EditAnywhere, Category = "Weapon Properties")
FVector BoxTraceExtent = FVector(5.f, 15.f, 5.f);
// Debug 출력 여부
UPROPERTY(EditAnywhere, Category = "Weapon Properties")
bool bShowBoxDebug = false;
...
void AWeapon::EquipWeaponToSocket()
{
ItemState = EItemStates::EIS_Equipped;
SetOwner(NewOwner);
SetInstigagor(NewInstigagor);
AttachMeshToSocket(InParent, InsocketName);
DisableSphereCollision();
PlayEquipSound();
DeactivateEmbers();
}
// 무기 획득시 사운드 재생
void AWeapon::PlayEquipSound()
{
if (EquipSound)
{
UGameplayStatics::PlaySoundAtLocation(this, EquipSound, GetActorLocation());
}
}
...
// void OnSphereBeginOverlap() { ... } 사용안함
// void OnSphereEndOverlap() { ... } 사용안함
...
void AWeapon::OnBoxBeginOverlap(...)
{
FHitResult BoxHit;
BoxTrace(BoxHit);
if (CollisionBoxHit.GetActor())
{
// CollisionBoxHit 발생시 데미지를 가함
UGameplayStatics::ApplyDamage(BoxHit.GetActor(), Damage, GetInstigator()->GetController(), this, UDamageType::StaticClass());
ExecuteGetHit(BoxHit);
CreateFields(BoxHit.ImpactPoint);
}
}
...
// Execute_GetHit 함수 실행 함수
void ExecuteGetHit(FHitResult& CollisionBoxHit)
{
// Overlap된 Actor를 가져와서 IHitInterface로 캐스팅
// 캐스트 성공시 해당 Actor가 인터페이스를 implement했다는 의미
IHitInterface* HitInterface = Cast<IHitInterface>(CollisionBoxHit.GetActor());
if (HitInterface)
{
// UFUNCTION(BlueprintNativeEvent) 매크로를 사용함->Execute_GetHit 사용해야함->parameter 2개 필요함->블루프린트사용
HitInterface->Execute_GetHit(CollisionBoxHit.GetActor(), CollisionBoxHit.ImpactPoint);
}
}
// BoxTrace 실행 함수
void AWeapon::BoxTrace(FHitResult& CollisionBoxHit)
{
const FVector Start = BoxTraceStart->GetComponentLocation();
const FVector End = BoxTraceEnd->GetComponentLocation();
TArray<AActor*> ActorsToIgnore;
ACtorsToIgnore.Add(this);
for(AActor* Actor : IgnoreActors)
{
ActorsToIgnore.AddUnique(Actor);
}
// FHitResult CollisionBoxHit; 함수의 parameter로 받아오기 때문에 필요없음
UKismetSystemLibrary::BoxTraceSingle(
this,
start,
End,
BoxTraceExtent,
BoxTraceStart->GetComponentRotation(),
ETraceTypeQuery::TraceTypeQuery1,
false,
ActorsToIgnore,
bShowBoxDebug ? EDrawDebugTrace::ForDuration : EDrawDebugTrace::None,
CollisionBoxHit,
true
);
IgnoreActors.AddUnique(CollisionBoxHit.GetActor());
}
이제 어느정도 리팩토링이 끝났으니 적의 공격에 캐릭터가 데미지를 입도록 구현해야 한다.
Weapon.cpp
에 있는 코드를 살펴보면 BoxComponent
가 오버랩될 때, HitResult
가 발생하고, ApplyDamage
함수가 실행되고, ExecuteGetHit
함수에 의해 GetHit
함수가 실행된다.
Enemy
클래스를 참조하면 피격시 실행될 함수에 대해 작성되어 있다. 이를 참조하여 WraithCharacter
에 동일하게 피격 기능을 구현한다.
...
public:
...
/** <IHitInterface> */
// 피격시 실행될 함수
virtual void GetHit_Implementation(const FVector& ImpactPoint) override;
/** </IHitInterface> */
protected:
...
...
void AWraithCharacter::GetHit_Implementation(const FVector& ImpactPoint)
{
PlayHitSound(ImpactPoint);
SpawnHitParticles(ImpactPoint);
}
컴파일 후 에디터로 돌아와서 WraithCharacter
의 AM_AttackMontage
를 오픈한다.
살펴보면 무기의 충돌이 가능한 notify와 불가능한 notify를 추가하여 그 사이에만 공격이 들어가도록 되어있다.
동일하게 BP_Paladin
의 AM_Attack
도 똑같이 notify를 추가해준다.
BP_WraithCharacter
파일을 열고 HitSound
와 HitParticles
를 추가해준다.
이제 Collision 관련 설정을 해주어야 한다.
팔라딘이 장착한 무기의 콜리전은 Pawn을 무시하게 설정되어 있다.
캐릭터의 캡슐 컴포넌트를 보면 오브젝트 타입이 Pawn
으로 되어있으니, 캡슐 컴포넌트는 무시하고 메시에 직접적으로 충돌을 발생시킬 수 있다.
캐릭터의 경우 Generate Overlap Events
가 비활성화되어있어, 오버랩 관련 이벤트가 발생하지 않으므로 해당부분을 활성화 시켜주어야 한다.
그리고 오브젝트 타입도 무기와 동일한 World Dynamic
으로의 변경, Visibility
채널에 대한 Block
설정, WorldDynamic
에 대한 Overlap
설정이 필요하다.
...
#include "Components/StaticMeshComponent.h" // Collision 설정을 위해 GetMesh() 필요
...
void AWraithCharacter::BeginPlay()
{
// 회전 관련 설정
...
// CharacterMovement() 관련 설정
...
// object type 변경
GetMesh()->SetCollisionObjectType(ECollisionChannel::ECC_WorldDynamic);
GetMesh()->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore);
GetMesh()->SetCollisionResponseToChannel(ECollisionChannel::ECC_Visibility, ECollisionResponse::ECR_Block);
GetMEsh()->SetCollisionResponseToChannel(ECollisionChannel::ECC_WorldDynamic, ECollisionResponse::ECR_Overlap);
GetMesh()->SetGeneratedOverlapEvents(true);
...
}
그리고 ABP_Paladin
파일에서 AnimNotify
노드를 추가해 해당 노티파이 도달시 무기의 콜리전을 설정하도록 노드를 연결한다.
적이 장착한 무기에서 Show Box Debug
를 활성화시키고 실행하면 정상적으로 충돌이 발생하는 것을 확인할 수 있다.
여기서도 문제가 발생하는데, 적이 장착한 무기에 자기자신이 피격당하는 문제이다.
태그를 이용해서 해결 가능하다.
우선 기존에 있던 Wraithcharacter
의 태그명을 변경한다.(캐스팅한것과 다르게 해서 헷갈림 방지)
...
void AWraithCharacter::BeginPlay()
{
Super::BeginPlay();
Tags.Add(FName("EngageableTarget"));
}
...
태그 관련 코드를 Enemy
클래스에서도 사용하니 해당 부분도 수정해준다.
...
void AEnemy::PawnSeen(APawn* SeenPawn)
{
const bool bShouldChaseTarget =
EnemyState != EEnemyStates::EES_Dead &&
EnemyState != EEnemyStates::EES_Chasing &&
EnemyState < EEnemyStates::EES_Attacking &&
SeenPawn->ActorHasTag(FName("EngageableTarget")); // 수정
if (bShouldChaseTarget)
{
CombatTarget = SeenPawn;
ClearPatrolTimer();
StartChasing();
}
}
이제 무기 클래스에 태그를 확인하고, 해당 태그가 적 클래스에서 선언된 태그라면 해당 태그를 소유한 적에게 아무것도 하지 않도록 구현한다.
...
void AEnemy::BeginPlay()
{
Super::BeginPlay();
...
Tags.Add(FName("Enemy"));
}
...
private:
// Tag명 확인후 Weapon의 소유자(Paladin) 태그명와 타켓의 대그명(또다른 Paladin)이 Enemy일경우 함수 리턴
bool ActorIsSameType(AActor* OtherActor);
...
// Tag명 확인후 Weapon의 소유자(Paladin) 태그명와 타켓의 대그명(또다른 Paladin)이 Enemy일경우 함수 리턴
bool ActorIsSameType(AActor* OtherActor)
{
return GetOawner()->ActorHasTag(FName("Enemy")) && OtherActor->ActorHasTAg(FName("Enemy"));
}
...
void AWeapon::OnBoxBeginOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
// Tag명 확인후 Weapon의 소유자(Paladin) 태그명와 타켓의 대그명(또다른 Paladin)이 Enemy일경우 함수 리턴
if(ActorIsSameType(OtherActor))
{
return;
}
...
if(BoxHit.GetActor()
{
// 동일하게 CollisionBox에 충돌 발생시 태그 확인후 무기를 소유한 적 자신 혹은 동일한 클래스기반 액터 무시
if(ActorIsSameType(BoxHit.GetActor()))
{
return;
}
}
...
}
실행시, 같은 BP_Paladin
끼리는 데미지가 들어가지 않는 것을 확인할 수 있고, 자기 자신에게 충돌이 발생해 죽는 문제도 해결되었음을 확인할 수 있다.