Enemy(4)

groot616·2024년 5월 5일
0

언리얼5공부복습용

목록 보기
21/22

이어서 리팩토링을 진행한다.

Enemy.h 리팩토링

Enemy.h 파일에 주구장창 선언해둔 변수 및 함수들도 정리를 해야한다.
아래는 Enemy.h 전문이다.
DirectionalHitReact() 는 오류때문에 public에 임시로 두고 사용x(원래 BaseCharacter 클래스에 있어야 함)

  • Enemy.h
// 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 관련 코드가 있을지 모르므로 컴파일을 시도해서 오류가 발생하는지 확인한다.

BaseCharacter.h 리팩토링

  • BaseCharacter.h
    동일하게 섹션을 필요에 따라 옮겨주고 간단하게 정리한다.
// 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;


};

컴파일후 오류가 발생하지 않는지 확인한다.

WraithCharacter.h 리팩토링

// 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 부분은 삭제한다.

Enemy.h 및 Enemy.cpp 리팩토링

헤더파일에 선언된것과 동일하게 순서를 변경한다. 작업을 진행하면서 리팩토링이 필요한 부분은 리팩토링을 진행한다.

  • Enemy.h
...
private:
	...
    /** AI Behavior */
    // Enemy 초기화
    void InitializeEnemy()
    ...
    // Enemy에 무기 스폰 및 장착시키는 함수
    void SpawnDefaultWeapon();
    
    UFUNCTION()
    void PawnSEen(...)
  • Enemy.cpp
// 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;
	}
}

WraithCharacter.h 및 WraithCharacter.cpp 리팩토링

  • WraithCharacter.h
...
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;
}
...

헤더파일에 선언된 순서와 동일하게 모든 함수 위치를 변경시킨다.

Weapon.h 및 Weapon.cpp 리팩토링

  • Weapon.h
...
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;
    ...
  • Weapon.cpp
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 에 동일하게 피격 기능을 구현한다.

  • WraithCharacter.h
...
public:
	...
    /** <IHitInterface> */
	// 피격시 실행될 함수
	virtual void GetHit_Implementation(const FVector& ImpactPoint) override;
	/** </IHitInterface> */


protected:
	...
  • WraithCharacter.cpp
...
void AWraithCharacter::GetHit_Implementation(const FVector& ImpactPoint)
{
	PlayHitSound(ImpactPoint);
    SpawnHitParticles(ImpactPoint);
}

컴파일 후 에디터로 돌아와서 WraithCharacterAM_AttackMontage 를 오픈한다.
살펴보면 무기의 충돌이 가능한 notify와 불가능한 notify를 추가하여 그 사이에만 공격이 들어가도록 되어있다.
동일하게 BP_PaladinAM_Attack 도 똑같이 notify를 추가해준다.
BP_WraithCharacter 파일을 열고 HitSoundHitParticles 를 추가해준다.
이제 Collision 관련 설정을 해주어야 한다.
팔라딘이 장착한 무기의 콜리전은 Pawn을 무시하게 설정되어 있다.
캐릭터의 캡슐 컴포넌트를 보면 오브젝트 타입이 Pawn 으로 되어있으니, 캡슐 컴포넌트는 무시하고 메시에 직접적으로 충돌을 발생시킬 수 있다.
캐릭터의 경우 Generate Overlap Events 가 비활성화되어있어, 오버랩 관련 이벤트가 발생하지 않으므로 해당부분을 활성화 시켜주어야 한다.
그리고 오브젝트 타입도 무기와 동일한 World Dynamic 으로의 변경, Visibility 채널에 대한 Block 설정, WorldDynamic 에 대한 Overlap 설정이 필요하다.

  • WraithCharacter.cpp
...
#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 의 태그명을 변경한다.(캐스팅한것과 다르게 해서 헷갈림 방지)

  • WraithCharacter.cpp
...
void AWraithCharacter::BeginPlay()
{
	Super::BeginPlay();

	Tags.Add(FName("EngageableTarget"));

}
...

태그 관련 코드를 Enemy 클래스에서도 사용하니 해당 부분도 수정해준다.

  • Enemy.cpp
...
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();
	}
}

이제 무기 클래스에 태그를 확인하고, 해당 태그가 적 클래스에서 선언된 태그라면 해당 태그를 소유한 적에게 아무것도 하지 않도록 구현한다.

  • Enemy.cpp
...
void AEnemy::BeginPlay()
{
	Super::BeginPlay();
    
    ...
    Tags.Add(FName("Enemy"));
}
  • Weapon.h
...
private:
	// Tag명 확인후 Weapon의 소유자(Paladin) 태그명와 타켓의 대그명(또다른 Paladin)이 Enemy일경우 함수 리턴
    bool ActorIsSameType(AActor* OtherActor);
    ...
  • Weapon.cpp
// 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 끼리는 데미지가 들어가지 않는 것을 확인할 수 있고, 자기 자신에게 충돌이 발생해 죽는 문제도 해결되었음을 확인할 수 있다.

0개의 댓글