게임 개발/언리얼 5

언리얼5 - 캐릭터 공격 판정

싹난 감자 2024. 11. 28. 04:43

애니메이션 이벤트 발생을 위한 노티파이 설정

공격 판정을 위한 트레이스 채널 설정과 판정, 시각적 디버깅

대미지 프레임워크를 활용한 대미지 전달과 Dead 상태의 구현


충돌 채널의 설정 

캐릭터 액션의 충돌 판정

월드가 제공하는 충돌 판정 서비스를 사용

월드는 크게 세 가지의 충돌 판정 서비스를 제공함.

  • LineTrace : 지정한 방향으로 선을 투사해 선이 어떤 물체와 충돌되는지 검사 (RayCast)
  • Sweep : LineTrace와 유사, 선이 아니고 어떠한 도형을 지정한 방향으로 투사
  • Overlap : 투사가 아닌 지정한 영역에 큰 도형을 설정해서 해당 볼륨 영역과 물체가 충돌되는지를 검사

월드 내 배치된 충돌체와 충돌하는지 파악하고, 충돌한 액터 정보를 얻을 수 있음.

프로젝트에서 사용할 트레이스 채널과 충돌 프로필 생성

충돌 판정 기능을 구현하기 위해서는 언리얼이 제공하는 기본 물리 설정에 별도로 채널이나 프로필을 생성하는 것이 좋다. (Layer)

액션  판정을 위한 트레이스 채널 생성 : ABAction. 기본 반응은 무시.

캐릭터 캡슐용 프로필 : ABAction 트레이스 채널에 반응. 오브젝트 타입은 Pawn. 새롭게 이름을 지을 수 있다.

스켈레탈 메시용 프로필 : 래그돌 구현을 위해 주로 활용됨. 이번 프로젝트에서는 사용하지 않음..

기믹 트리거용 프로필 : 우리가 오브젝트로 지정한 폰 캡슐에만 반응하도록 설정. 오브젝트 타입은 WorldStatic

 

ProjectSetting의 Collision에서 원하는 트레이스 채널과 프로필을 추가할 수 있다.

New Trace Channel

Default Response(기본 반응) 설정

Block : 길을 막음

Overlap : 길은 막지 않지만 이벤트는 발생

Ignore : 무시

 

Block으로 설정하면 해당 트레이스로 충돌 감지 액션을 구현할 때 모든 물체가 반응하게 됨

지정한 캡슐 컴포넌트에만 반응해야하므로 Ignore로 설정

Accept하고 Preset을 열어 새로 추가

(프로필 이름은 ABCharacter가 아니라 ABCapsule임. 이후의 ABCharacter 프로필은 모두 ABCapsule.)

CollisionEnabled : Query Only

Object Type : Pawn

Trace Type

ABAction에 반응할 수 있도록 Block으로 설정

Camera : 카메라가 물체에 걸려 시야에 방해되면 앞으로 당겨주는 카메라 콜리전 테스트에 사용하는 채널

 

Object Type

Destructible : Destructible 기능을 사용해 오브젝트를 파괴했을 때 파괴된 조각들과 부딪히면 불편하기 때문에 그런 경우 Ignore로 설정하면 좋음. 상황에 따라 설정.

저장하면 Preset에 추가됨

Trigger도 추가.

ABCharacter와 ABTrigger는 각각 유니티에서 캐릭터에 캡슐 콜라이더를 추가하는것과 이벤트용 트리거를 추가하는 것과 비슷하다고 생각하면 될 듯. 유니티와는 적용하는 방법이 다르다.

 

이렇게 트레이스 채널 세팅을 추가하면 Config - DefaultEngine.ini 텍스트파일에 저장됨.

Collisoin Profile 정보가 많이 추가된 것을 볼 수 있음.

엔진에서 기본으로 제공하는 정보를 수정했기 때문에 다시 새로운 정보로 덮어썼음.

 

ABAction을 검색해보면, DefaultChannelResponses라는 정보의 ABAction이라는 이름이 엔진에서 지정하고 있는 내부 열거형의 ECC_GameTraceChannel1이라고 하는 열거형 값을 사용한다고 지정되어 있음.

ABAction이라는 이름을 지정했지만 코드 내에서는 ECC_GameTraceChannel1이라고 하는 열거형 값을 바로 사용해주는 것이 편리함.

해당 문장 바로 위에는 설정한 프로필 2개가 추가되어 있음. 이러한 정보들을 사용해 공격 판정 기능 구현.

 

몽타주로 만들어놓은 애니메이션으로부터 이벤트를 발생시켜서 공격 판정을 구현해야함.

몽타주의 섹션을 Ctrl+마우스 휠로 확대해서 자세히 살펴보면 공격 판정에 필요한 프레임이 어디쯤인지 알 수 있다.

해당 프레임에 이벤트를 발생시켜야하는데, 애니메이션 노티파이 기능을 사용하면 된다.

 

Notifies 채널에 우클릭하면 다양한 종류의 Notify를 볼 수 있는데, 이 프로젝트에서는 전용 노티파이를 생성해서 추가.

All Classes에서 AnimNotify를 검색하면 몽타주에서 봤던 목록들이 AnimNotify_ 라는 이름의 클래스로 정의되어 있음.

AnimNofity를 상속받고 규칙에 따라 AnimNotify_AttackHitCheck라는 이름으로 클래스 생성

컴파일이 완료되고 다시 몽타주을 열어 Notifies의 1번 채널에 우클릭을 하면 방금 생성한 Anim Notify Attack Hit Check가 뜨는 것을 볼 수 있다. 추가해주고 프레임을 맞춰준다.

각 섹션에 모두 추가

 

코드로 돌아와서, AttackHitCheck가 상속받은 AnimNotify에 들어가보면 2개의 Notify 함수가 있는데, 첫 번째 Notify는 5.0부터 사용하지 않음. 두 번째 함수를 오버라이드해서 사용.

virtual void Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, const FAnimNotifyEventReference& EventReference);

AnimNotify.h

 

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "Animation/AnimNotifies/AnimNotify.h"
#include "AnimNotify_AttackHitCheck.generated.h"

/**
 * 
 */
UCLASS()
class ARENABATTLE_API UAnimNotify_AttackHitCheck : public UAnimNotify
{
	GENERATED_BODY()

protected:
	virtual void Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, const FAnimNotifyEventReference& EventReference) override;
	
};

AnimNotify_AttackHitCheck.h

// Fill out your copyright notice in the Description page of Project Settings.


#include "Animation/AnimNotify_AttackHitCheck.h"
#include "Character/ABCharacterBase.h"

// 인자: 애니메이션을 관리하는 스켈레탈 메쉬 컴포넌트, 애니메이션 정보, 추가적인 레퍼런스 정보
void UAnimNotify_AttackHitCheck::Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, const FAnimNotifyEventReference& EventReference)
{
	Super::Notify(MeshComp, Animation, EventReference);
    // 인자로 들어온 정보들로부터 액터에 명령을 내려함.
    // 만약 메쉬 컴포넌트가 있다면 메쉬 컴포넌트를 소유하고 있는 오너 가져오기
    if (MeshComp)
    {
        MeshComp->GetOwner();
    }
}

AnimNotify_AttackHitCheck.cpp

 

MeshComp에서 가져온 해당 Owner가 우리가 선언한 캐릭터인지 체크한 뒤 캐릭터인 경우 공격을 판정하라고 명령을 내릴 수 있음. 이렇게 캐릭터를 바로 사용하는 경우 캐릭터에 대한 헤더를 추가해줘야함.
그런데 Animation 폴더가 아닌 Character 폴더의 헤더. 다른 폴더의 헤더를 추가하는 것은 의존성이 생긴다는 것을 의미함.
노티파이 기능은 공용으로 여러 종류의 캐릭터가 있을 때 사용되는 것이 좋은데 매번 헤더를 추가하는 것은 끔찍함
좀 더 범용적으로 사용할 수 있게 만들기 위해서 인터페이스를 구현해주는 것이 좋음.

 

Interface를 상속받은 ABAnimatoinAttackInterface를 생성.

어쩔 수 없는 의존성이 발생되는 경우 가급적 인터페이스를 통해서 구현하도록 설계하면 보다 범용적으로 기능을 사용할 수 있음.

 

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "UObject/Interface.h"
#include "ABAnimationAttackInterface.generated.h"

// This class does not need to be modified.
UINTERFACE(MinimalAPI)
class UABAnimationAttackInterface : public UInterface
{
	GENERATED_BODY()
};

/**
 * 
 */
class ARENABATTLE_API IABAnimationAttackInterface
{
	GENERATED_BODY()

	// Add interface functions to this class. This is the class that will be inherited to implement this interface.
public:
	virtual void AttackHitCheck() = 0;
};

ABAnimationAttackInterface.h

인터페이스에 함수를 추가하고 이 인터페이스를 구현하도록 구조 변경.

 

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "Interface/ABAnimationAttackInterface.h" // 인터페이스 헤더 추가
#include "ABCharacterBase.generated.h"

// 컨트롤 데이터 ENUM
UENUM()
enum class ECharacterControlType : uint8
{
	Shoulder,
	Quater
};

UCLASS()
class ARENABATTLE_API AABCharacterBase : public ACharacter, public IABAnimationAttackInterface
{
	GENERATED_BODY()

public:
	AABCharacterBase();

	...

// Attack Hit Section
protected:
	virtual void AttackHitCheck() override;
};

ABCharacterBase.h

// Fill out your copyright notice in the Description page of Project Settings.


#include "Character/ABCharacterBase.h"
#include "Components/CapsuleComponent.h"
// Component's'임
#include "ABCharacterControlData.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "Animation/AnimMontage.h"
#include "ABComboActionData.h"

	...

void AABCharacterBase::AttackHitCheck()
{
    // 트레이스 채널을 활용해서 물체가 서로 충돌되는지 검사하는 로직

}

ABCharacterBase.cpp

캐릭터 베이스에 인터페이스를 추가

 

// Fill out your copyright notice in the Description page of Project Settings.


#include "Animation/AnimNotify_AttackHitCheck.h"
//#include "Character/ABCharacterBase.h" 직접 캐릭터 헤더 추가 말고 인터페이스 추가
#include "Interface/ABAnimationAttackInterface.h"

// 인자: 애니메이션을 관리하는 스켈레탈 메쉬 컴포넌트, 애니메이션 정보, 추가적인 레퍼런스 정보
void UAnimNotify_AttackHitCheck::Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, const FAnimNotifyEventReference& EventReference)
{
        
    // 인자로 들어온 정보들로부터 액터에 명령을 내려함.
    // 만약 메쉬 컴포넌트가 있다면 메쉬 컴포넌트를 소유하고 있는 오너 가져오기
    if (MeshComp)
    {
        // 인터페이스를 추가하고 나면
        // Mesh의 Owner가 인터페이스를 구현했는지를 체크해주면 됨
        IABAnimationAttackInterface* AttackPawn = Cast<IABAnimationAttackInterface>(MeshComp->GetOwner());
        if (AttackPawn)
        {
            AttackPawn->AttackHitCheck();
        }
    }
}

AnimNotify_AttackHitCheck.cpp

 

이후 AttackHitCheck() 구현


 

공격 판정의 구현

월드 트레이싱 함수 선택

언리얼에서 제공하는 트레이싱 함수들이 여러가지 있는데, 크게 세 가지 카테고리로 나눠 원하는 함수를 결정.

카테고리 1: 처리 방법

  • LineTrace
  • Sweep
  • Overlap

카테고리 2: 대상

  • Test: 무언가 감지되었는지를 테스트
  • Single 또는 AnyTest: 감지된 단일 물체 정보를 반환 (Single : LineTrace와 Sweep, Any Test :  Overlap)
  • Multi: 감지된 모든 물체 정보를 배열로 반환

카테고리 3: 처리 설정

  • ByChannel: 채널 정보를 사용해 감지
  • ByObjectType: 물체에 지정된 물리 타입 정보를 사용해 감지
  • ByProfile: 프로필 정보를 사용해 감지

{처리 방법}{대상}{처리 설정}

 

캐릭터 공격 판정의 구현

캐릭터의 위치에서 시선 방향으로 물체가 있는지 감지,

작은 구체를 제작하고 시선 방향으로 특정 거리까지만 투사,

트레이스 채널을 사용해 하나의 물체만 감지하도록 구현.

{처리 방법}{대상}{처리 설정} -> {Sweep}{Single}{ByChannel}


AttackHitCheck에서 트레이스 채널을 이용해야하는데, 간단한 헤더 파일을 만들어서 매크로를 설정해주면 편리함.

Source 폴더에 Physics라는 폴더를 만들고 ABCollision.h라는 이름으로 텍스트 파일 생성.

해당 파일을 프로젝트에 포함시키기 위해 VS프로젝트 재생성

 

#pragma once

#include "CoreMinimal.h"

// 생성한 프로필의 이름
#define CPROFILE_ABACPSULE TEXT("ABCapsule")
#define CPROFILE_ABTRIGGER TEXT("ABTrigger")

// 추가한 트레이스 액션 이름
// ini파일에서 지정된 채널 열거형 값으로 바로 변환됨
#define CCHANNEL_ABACTION ECC_GameTraceChannel1

ABCollision.h

 

// Fill out your copyright notice in the Description page of Project Settings.


#include "Character/ABCharacterBase.h"
#include "Components/CapsuleComponent.h"
// Component's'임
#include "ABCharacterControlData.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "Animation/AnimMontage.h"
#include "ABComboActionData.h"
#include "Physics/ABCollision.h" // 트레이스 채널 매크로 헤더

	...

void AABCharacterBase::AttackHitCheck()
{
    // 트레이스 채널을 활용해서 물체가 서로 충돌되는지 검사하는 로직
    FHitResult OutHitResult;
    // Params의 인자
    // 1. InTraceTag : 이 콜리전을 분석할 때 식별자 정보로 사용됨
    // 2. bInTraceComplex : 복잡한 형태의 충돌체, 캡슐이나 구 같은 흔히 Convex라고 부르는 단순한 볼륨,
    // 볼록한 볼륨을 대상으로 충돌을 감지하면 빠르게 지정할 수 있고 물리적인 시뮬레이션도 구현할 수 있음
    // 복잡한 메쉬도 지정할 수 있지만 그 경우 올라서는 행위만 할 수 있음
    // 이러한 복잡한 형태의 충돌체도 감지할지에 대한 옵션.
    // 3. 무시할 액터
    FCollisionQueryParams Params(SCENE_QUERY_STAT(Attck), false, this);
    // SCENE_QUERY_STAT : 언리얼 엔진이 제공하는 분석 툴.
    // Attack이라는 태그로 우리가 수행한 작업에 대해 조사할 수 있게 태그를 추가해주는 것

    const float AttackRange = 40.0f;
    // 투사할 구체의 반지름
    const float AttackRadius = 50.0f;
    const float AttackDamage = 30.0f;
    // 구체를 투사할 시작지점
    const FVector Start = GetActorLocation() + GetActorForwardVector() * GetCapsuleComponent()->GetScaledCapsuleRadius();
    // 투사의 끝지점
    const FVector End = Start + GetActorForwardVector() * AttackRange;

    // SweepSingleByChannel은 월드가 제공하는 서비스이기 때문에 GetWorld 함수를 호출해서 포인터를 얻어와야 함.
    // MakeSphere 함수로 구체의 영역을 지정할 수 있음
    // (결과값을 받아올 수 있는 구조체, 시작지점, 끝지점, 방향, 사용할 트레이스 채널, 구체의 영역, 파라미터)
    bool HitDetected = GetWorld()->SweepSingleByChannel(OutHitResult, Start, End, FQuat::Identity, CCHANNEL_ABACTION, FCollisionShape::MakeSphere(AttackRadius), Params);
    // HitDetected가 true가 되었다는 것은 무언가 감지되었다는 뜻
   
}

ABCharacterBase.cpp

 

여기까지 구현은 완료되었지만 무언가 감지된 것을 알 방법이 로그 찍기밖에 없으나 언리얼의 디버그 드로잉 기능을 사용하면 트레이싱을 지정한 영역이 물체에 감지되었는지를 화면에 표시할 수 있음.

이를 위해서는 캡슐에 대한 정보를 알아야함.


물리 충돌 테스트

디버그 드로잉 함수를 사용해 물리 충돌을 시각적으로 테스트

충돌 테스트를 위해 트레이싱한 영역. 시작 지점, 어택 길이, 끝지점, 투사한 구체의 반지름. 캡슐 모양을 가짐

캐릭터 자체에도 캡슐이 있고 캡슐의 반지름이 지정되어 있음

그려진 캡슐 모양의 위치별 용어

  • Origin : 기준점
  • HalfHeight :구체의 시작점부터 끝점까지의 거리.
    시작점 구체와 끝점 구체의 양 끝, 튀어나온 절반의 동그란 부분을 제외한 (전체의 절반) 거리.
  • Radius : 구체의 반지름

세 가지를 사용해 캡슐 영역을 만들 수 있음

캡슐을 90도로 회전시켜 누워진 상태로 그리면 캡슐(어택)의 범위

 

#if ENABLE_DRAW_DEBUG

    // 캡슐의 원점
    // 시작 지점에서 끝 지점을 뺀 값을 절반 나눈 값
    FVector CapsuleOrigin = Start + (End - Start) * 0.5f;
    float CapsuleHalfHeight = AttackRange * 0.5f;
    // 무언가 충돌했으면 녹색 아니면 빨간색
    FColor DrawColor = HitDetected ? FColor::Green : FColor::Red;

    // DrawDebugCapsule(월드 제공 서비스, Origin, HalfHeight, 캡슐의 반지름, 방향, 색깔, 계속해서 유지할 것인지 여부, 유지하지 않다면 몇초 지속인지)
    // FRotationMatrix::MakeFromZ(GetActorForwardVector()).ToQuat() 시선 방향으로 회전
    DrawDebugCapsule(GetWorld(), CapsuleOrigin, CapsuleHalfHeight, AttackRadius, FRotationMatrix::MakeFromZ(GetActorForwardVector()).ToQuat(), DrawColor, false, 5.0f);
#endif

ABCharacterBase.cpp

캡슐을 그려준다.

실행해보면 플레이어가 공격한 방향에 캡슐이 그려진다. 현재 캐릭터에게만 반응하도록 되어 있기 때문에 빨간색.

 

ABCharacterBase를 상속받은 ABCharacterNonPlayer(NPC) 클래스 생성

 

새 레벨을 만들고 C++ 클래스에서 NonPlayer를 끌어서 배치

 

콤보 액션 관련된 설정과 콤보 액션 데이터를 블루프린트에서 설정하도록 해놨기 때문에 기본값이 없음.

스켈레탈 메쉬를 바꿔주고 저장.

 

AABCharacterBase::AABCharacterBase()
{
    // Pawn 기본 설정
    // 폰의 회전을 지정하기 위한 값
    bUseControllerRotationPitch = false;
    bUseControllerRotationYaw = false;
    bUseControllerRotationRoll = false;

    // 루트 컴포넌트, 캡슐(Capsule) 설정
    // GetCapsuleComponent를 사용해 가져올 수 있음, 헤더 추가
    GetCapsuleComponent()->InitCapsuleSize(42.f, 96.0f);
    // 새로 생성하고 매크로를 추가한 CPROFILE_ABACPSULE값 넣어줌
    GetCapsuleComponent()->SetCollisionProfileName(CPROFILE_ABACPSULE);

    // 움직임(Movement) 설정
    // 점프의 크기, 이동 속도 등
    GetCharacterMovement()->bOrientRotationToMovement = true;
    GetCharacterMovement()->RotationRate = FRotator(0.0f, 500.0f, 0.0f);
    GetCharacterMovement()->JumpZVelocity = 700.f;
    GetCharacterMovement()->AirControl = 0.35f;
    GetCharacterMovement()->MaxWalkSpeed = 500.f;
    GetCharacterMovement()->MinAnalogWalkSpeed = 20.f;
    GetCharacterMovement()->BrakingDecelerationWalking = 2000.f;

    // 메쉬(Mesh) 설정
    // 애니메이션 모드와 캐릭터들의 모델링 파일을 진행방향에 맞춰 상대 위치와 회전을 지정해주어야 함.
    GetMesh()->SetRelativeLocationAndRotation(FVector(0.0f, 0.0f, -100.0f), FRotator(0.0f, -90.0f, 0.0f));
    GetMesh()->SetAnimationMode(EAnimationMode::AnimationBlueprint);
    // 캡슐 콜리전을 추가했기 때문에 메쉬는 NoCollision 선언
    GetMesh()->SetCollisionProfileName(TEXT("NoCollision"));

    
    ...


    // 콤보 액션 관련 기본값
    static ConstructorHelpers::FObjectFinder<UAnimMontage> ComboActionMontageRef(TEXT("/Script/Engine.AnimMontage'/Game/ArenaBattle/Animation/AM_ComboAttack.AM_ComboAttack'"));
    if (ComboActionMontageRef.Object)
    {
        ComboActionMontage = ComboActionMontageRef.Object;
    }

    static ConstructorHelpers::FObjectFinder<UABComboActionData> ComboActionDataRef(TEXT("/Script/ArenaBattle.ABComboActionData'/Game/ArenaBattle/CharacterAction/ABA_ComboAttack.ABA_ComboAttack'"));
    if (ComboActionDataRef.Object)
    {
        ComboActionData = ComboActionDataRef.Object;
    }
}

ABCharacterBase.cpp

베이스 클래스에 기본값 추가.

 

빌드 후 캐릭터를 보면 기본값이 잘 들어갔음. 캡슐의 콜리전 세팅도 잘 변경됨. 

 

플레이어의 공격이 캡슐 콜리전과 충돌을 감지함.

 

죽는 애니메이션 몽타주를 추가하기 위해 AM_Dead라는 애니메이션 몽타주를 추가.

 

죽으면 애니메이션이 바로 출력되도록 Enable Auto Blend Out 옵션을 해제

애니메이션 몽타주는 그룹으로 관리할 수 있음.

이를 슬롯이라 하고, 슬롯을 통해 몽타주에 관련된 여러가지 모션들을 그룹핑해서 각각 따로 처리할 수 있음.

 

Slot Manager를 열어보면 DefaultSlot으로 배정되어 있음. 콤보 액션 몽타주도 마찬가지.

DeadSlot 추가해 사망 모션은 다른 그룹으로 관리.

 

슬롯을 바꾸고 Dead 섹션 추가

현재 애니메이션 블루프린터에서는 DefaultSlot으로 되어 있음

 

Slot 노드 추가, Dead Slot으로 변경

애니메이션을 재생할 때 죽는 모션이 항상 최우선 순위로 재생되도록 Output과  DeadSlot 연결. 

 


데미지와 사망 구현

언리얼 엔진 엑터 설정에 공격했을 때 데미지를 입히거나 공격당했을 때 데미지를 받는 함수가 있음. 별도로 해당 기능을 구현할 필요는 없고 TakeDamage함수를 사용. TakeDamage함수는 액터에서부터 이미 구현되어 있음. 오버라이드해서 내부를 구현해주면 됨.

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "Interface/ABAnimationAttackInterface.h"
#include "ABCharacterBase.generated.h"

// 컨트롤 데이터 ENUM
UENUM()
enum class ECharacterControlType : uint8
{
	Shoulder,
	Quater
};

UCLASS()
class ARENABATTLE_API AABCharacterBase : public ACharacter, public IABAnimationAttackInterface
{
	GENERATED_BODY()

public:
	AABCharacterBase();

	...

// Attack Hit Section
protected:
	virtual void AttackHitCheck() override;
	// 데미지를 입는 함수. AActor에서 상속받음
	virtual float TakeDamage(float DamageAmount, struct FDamageEvent const& DamageEvent, class AController* EventInstigator, AActor* DamageCauser) override;

// Dead Anim Section (몽타주)
protected:
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Stat, Meta = (AllowPrivateAccess = "true"))
	TObjectPtr<class UAnimMontage> DeadMontage;

	// 죽는 상태 구현 함수
	virtual void SetDead();
	// 사망 모션 출력 함수
	void PlayerDeadAnimation();
};

ABCharacterBase.h

// Fill out your copyright notice in the Description page of Project Settings.


#include "Character/ABCharacterBase.h"
#include "Components/CapsuleComponent.h"
// Component's'임
#include "ABCharacterControlData.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "Animation/AnimMontage.h"
#include "ABComboActionData.h"
#include "Physics/ABCollision.h"
#include "Engine/DamageEvents.h"

// Sets default values
AABCharacterBase::AABCharacterBase()
{
    ...

    // 사망 모션 기본값
    static ConstructorHelpers::FObjectFinder<UAnimMontage> DeadMontageRef(TEXT("/Script/Engine.AnimMontage'/Game/ArenaBattle/Animation/AM_Dead.AM_Dead'"));
    if (DeadMontageRef.Object)
    {
        DeadMontage = DeadMontageRef.Object;
    }
}

void AABCharacterBase::AttackHitCheck()
{
    // 트레이스 채널을 활용해서 물체가 서로 충돌되는지 검사하는 로직
    FHitResult OutHitResult;
    // Params의 인자
    // 1. InTraceTag : 이 콜리전을 분석할 때 식별자 정보로 사용됨
    // 2. bInTraceComplex : 복잡한 형태의 충돌체, 캡슐이나 구 같은 흔히 Convex라고 부르는 단순한 볼륨,
    // 볼록한 볼륨을 대상으로 충돌을 감지하면 빠르게 지정할 수 있고 물리적인 시뮬레이션도 구현할 수 있음
    // 복잡한 메쉬도 지정할 수 있지만 그 경우 올라서는 행위만 할 수 있음
    // 이러한 복잡한 형태의 충돌체도 감지할지에 대한 옵션.
    // 3. 무시할 액터
    FCollisionQueryParams Params(SCENE_QUERY_STAT(Attck), false, this);
    // SCENE_QUERY_STAT : 언리얼 엔진이 제공하는 분석 툴.
    // Attack이라는 태그로 우리가 수행한 작업에 대해 조사할 수 있게 태그를 추가해주는 것

    const float AttackRange = 40.0f;
    // 투사할 구체의 반지름
    const float AttackRadius = 50.0f;
    const float AttackDamage = 30.0f;
    // 구체를 투사할 시작지점
    // 현재 액터의 위치와 액터의 시선 방향에 캡슐 컴포넌트의 반지름 값을 추가
    // 액터의 위치가 아닌 정면에 있는 캡슐의 위치에서부터 시작
    const FVector Start = GetActorLocation() + GetActorForwardVector() * GetCapsuleComponent()->GetScaledCapsuleRadius();
    // 투사의 끝지점
    // 시작지점에서 액터의 앞 방향으로 AttackRange만큼 앞
    const FVector End = Start + GetActorForwardVector() * AttackRange;

    // SweepSingleByChannel은 월드가 제공하는 서비스이기 때문에 GetWorld 함수를 호출해서 포인터를 얻어와야 함.
    // MakeSphere 함수로 구체의 영역을 지정할 수 있음
    // (결과값을 받아올 수 있는 구조체, 시작지점, 끝지점, 방향, 사용할 트레이스 채널, 구체의 영역, 파라미터)
    bool HitDetected = GetWorld()->SweepSingleByChannel(OutHitResult, Start, End, FQuat::Identity, CCHANNEL_ABACTION, FCollisionShape::MakeSphere(AttackRadius), Params);
    // HitDetected가 true가 되었다는 것은 무언가 감지되었다는 뜻
    // 여기까지는 구현이 완료되었지만 무언가 감지된 것을 알 방법이 로그 찍기밖에 없음
    // 언리얼의 디버그 드로잉 기능을 사용하면 트레이싱을 지정한 영역이 물체에 감지되었는지를 표시할 수 있음.
    if (HitDetected)
    {
        // 공격 판정이 이루어지면 TakeDamage 함수를 호출해 상대방에게 대미지 입히기
        // 대미지를 전달할 때 대미지 종류를 지정할 수 있음. Engine/DamageEvents.h 헤더파일 추가
        FDamageEvent DamageEvent;
        OutHitResult.GetActor()->TakeDamage(AttackDamage, DamageEvent, GetController(), this);
    }

#if ENABLE_DRAW_DEBUG

    // 캡슐의 원점
    // 시작 지점에서 끝 지점을 뺀 값을 절반 나눈 값
    FVector CapsuleOrigin = Start + (End - Start) * 0.5f;
    float CapsuleHalfHeight = AttackRange * 0.5f;
    // 무언가 충돌했으면 녹색 아니면 빨간색
    FColor DrawColor = HitDetected ? FColor::Green : FColor::Red;

    // DrawDebugCapsule(월드 제공 서비스, Origin, HalfHeight, 캡슐의 반지름, 방향, 색깔, 계속해서 유지할 것인지 여부, 유지하지 않다면 몇초 지속인지)
    // FRotationMatrix::MakeFromZ(GetActorForwardVector()).ToQuat() 시선 방향으로 회전
    DrawDebugCapsule(GetWorld(), CapsuleOrigin, CapsuleHalfHeight, AttackRadius, FRotationMatrix::MakeFromZ(GetActorForwardVector()).ToQuat(), DrawColor, false, 5.0f);
#endif
}

float AABCharacterBase::TakeDamage(float DamageAmount, FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser)
{
    // EventInstigator: 나에게 피해를 입힌, 가해자
    // DamageCauser: 가해자가 피해를 입힌 무기나 빙의하고 있는 폰 등 액터 정보
    Super::TakeDamage(DamageAmount, DamageEvent, EventInstigator, DamageCauser);
    // Super로 기본적인 기능을 액터에서부터 처리
    // 이후 추가적으로 계산할 것(방어력 등)을 계산하고 최종값으로 리턴하면 됨.

    // 사망 처리
    SetDead();

    return DamageAmount;
    // 리턴값: 최종적으로 액터가 받은 대미지 값
}

void AABCharacterBase::SetDead()
{
    // 이동기능 제한
    GetCharacterMovement()->SetMovementMode(EMovementMode::MOVE_None);
    // 사망 애니메이션 출력
    PlayerDeadAnimation();
    // 액터의 모든 콜리전 기능 끄기
    SetActorEnableCollision(false);
}

void AABCharacterBase::PlayerDeadAnimation()
{
    UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
    // 재생중이던 모든 애니메이션 중지
    AnimInstance->StopAllMontages(0.0f);
    AnimInstance->Montage_Play(DeadMontage, 1.0f);
}

ABCharacterBase.cpp

공격을 받으면 사망하고 콜리전이 사라져 길을 막지 않음.

 

사망한 NPC가 일정 시간 후 사라지는 기능 구현

	// 죽은 뒤 일정 시간이 지나고 어떤 이벤트가 발생하도록
	// 시간 딜레이 변수
	float DeadEventDelayTime = 5.0f;

ABCharacterBase.h

딜레이 변수 선언

 

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "Character/ABCharacterBase.h"
#include "ABCharacterNonPlayer.generated.h"

/**
 * 
 */
UCLASS()
class ARENABATTLE_API AABCharacterNonPlayer : public AABCharacterBase
{
	GENERATED_BODY()

public:
	AABCharacterNonPlayer();

protected:
	void SetDead() override;

	
};

ABCharacterNonPlayer.h

// Fill out your copyright notice in the Description page of Project Settings.


#include "Character/ABCharacterNonPlayer.h"

AABCharacterNonPlayer::AABCharacterNonPlayer()
{
}

void AABCharacterNonPlayer::SetDead()
{
    Super::SetDead();

    // 딜레이 시간 이후 없어지는 기능
    FTimerHandle DeadTimerHandle;
    // 월드로부터 GetTimerManager를 호출해 서비스를 받음
    // SetTimer함수로 호출, 핸들을 지정해주고
    // 딜레이 시간이 지난 이후에 어떤 함수를 실행할 지 함수를 호출할 수 있음
    // 멤버 함수를 선언해서 매핑할 수도 있지만
    // 액터를 없애는 함수를 구현할텐데 이것을 위해 멤버함수를 또 만드는 것은 번거로움
    // 간편하게 만들 수 있는 람다 함수를 즉석에서 만들어 타이머 델리게이트 구조체에 부착시켜 바로 넘길 것.
    // 함수 안에서 바로 구조체 생성, CreateLamda함수로 람다함수를 만들어 바로 호출
    GetWorld()->GetTimerManager().SetTimer(DeadTimerHandle, FTimerDelegate::CreateLambda(
        [&]() // 본문 캡처
        {
            Destroy();
        }
        // 위의 본문을 가진 람다 함수와 연결된 타이머 델리게이트를 즉석에서 만들어서 연결
    ), DeadEventDelayTime, false);
}

ABCharacterNonPlayer.cpp

 

빌드 후 실행하면 NPC가 사망하고 5초 뒤 사라짐