EDR_Project / Character (3) 공격 판정

24.10.19 EDR_Boss_Giant의 공격 판정 기능을 위한 내용을 추가하였습니다.

콜리전트레이스 채널에서 이어집니다.

 

 

MyCharacter.h

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

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "MyCharacter.generated.h"

// 공격 애니메이션이 끝났는지 확인
DECLARE_MULTICAST_DELEGATE(FOnAttackEndDelegate);

UCLASS()
class EDR_API AMyCharacter : public ACharacter
{
	GENERATED_BODY()

public:
	// Sets default values for this character's properties

	AMyCharacter();
	virtual void PossessedBy(AController* NewController)override;

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;
	// 받는 데미지 처리 함수
	virtual float TakeDamage(float DamageAmount, struct FDamageEvent const& DamageEvent, class AController* EventInstigator, AActor* DamageCauser) override;

	UPROPERTY(BluePrintReadWrite)
	float hp = 100.0f;

	// 보스 캐릭터의 공격 데미지 설정 변수
	UPROPERTY(BluePrintReadWrite)
	float AttackDamage;


	// 공격 애니메이션 몽타주 담을 변수
	UPROPERTY()
	TObjectPtr<class UAnimMontage> AttackMontage;


	// 사망 애니메이션 몽타주 담을 변수
	UPROPERTY()
	TObjectPtr<class UAnimMontage> DeathMontage;

	// 애니메이션 재생을 위해 공격중인지 확인
	UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Category = Attack, Meta = (AllowPrivateAccess = true))
	bool IsAttacking;


	// 애니메이션 인스턴트 객체
	UPROPERTY()
	class UAnim_EDR_AnimInstance* EDRAnim;

public:
	// Called every frame
	virtual void Tick(float DeltaTime) override;

	// 사망 처리 변수
	UPROPERTY(BluePrintReadWrite)
	bool Death = false;

	// Called to bind functionality to input
	virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;


	// Hp 반환
	UFUNCTION(BlueprintPure, category = "Player")
	float GetHp() { return hp; }


	// Hp 최신화
	UFUNCTION(BlueprintCallable, category = "Player")
	virtual void UpdateHP(float NewHP);


	// 사망 처리 함수
	UFUNCTION(BlueprintCallable, category = "Player")
	virtual void IsDeath();


	// 공격 함수
	virtual void Attack();
	FOnAttackEndDelegate OnAttackEnd;


	// 공격 판정 함수
	void AttackCheck();

	// 공격 종료 확인
	UFUNCTION()
	void OnAttackMontageEnded(UAnimMontage* Montage, bool bInterrupted);



private:

	// 공격 범위 관련 변수들
	UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Category = Attack, Meta = (AllowPrivateAccess = true))
	float AttackRange;


	UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Category = Attack, Meta = (AllowPrivateAccess = true))
	float AttackRadius;


};

 

반응형

MyCharacter.cpp

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


#include "MyCharacter.h"
#include "Engine/World.h"
#include "Anim_EDR_AnimInstance.h"
#include "Components/CapsuleComponent.h"
#include "DrawDebugHelpers.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "Engine/DamageEvents.h"

// Sets default values
AMyCharacter::AMyCharacter()
{
	// Set this character to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;
	IsAttacking = false;
	// 캡슐컴포넌트가 MyCharacter프리셋을 사용하도록 함
	GetCapsuleComponent()->SetCollisionProfileName(TEXT("MyCharacter"));
	// 공격 범위 변수 초기화
	AttackRange = 400.0f;
	AttackRadius = 200.0f;
}

void AMyCharacter::PossessedBy(AController* NewController)
{
	Super::PossessedBy(NewController);
	
	// 캐릭터 회전 부드럽게
	bUseControllerRotationYaw = false;
	GetCharacterMovement()->bUseControllerDesiredRotation = false;
	GetCharacterMovement()->bOrientRotationToMovement = true;
	GetCharacterMovement()->RotationRate = FRotator(0.0f, 480.0f, 0.0f);
	GetCharacterMovement()->MaxWalkSpeed = 300.0f;
}

// Called when the game starts or when spawned
void AMyCharacter::BeginPlay()
{
	Super::BeginPlay();

}

// Called every frame
void AMyCharacter::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

}

// Called to bind functionality to input
void AMyCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);

}

// 공격 애니메이션 종료
void AMyCharacter::OnAttackMontageEnded(UAnimMontage* Montage, bool bInterrupted)
{
	if (!IsAttacking)
	{
		return;
	}
	
	GEngine->AddOnScreenDebugMessage(-1, 2.0f, FColor::Red, TEXT("end play"));
	IsAttacking = false;
	OnAttackEnd.Broadcast();
}

// 사망 애니메이션
void AMyCharacter::IsDeath()
{
	// 이미 죽어있으면 애니메이션 실행 안함
	if (Death)
	{
		GEngine->AddOnScreenDebugMessage(-1, 2.0f, FColor::Red, TEXT("Death true"));
		return;
	}

	// 애니메이션 몽타주 실행
	PlayAnimMontage(DeathMontage);
	Death = true;
}
void AMyCharacter::UpdateHP(float NewHP)
{
	hp += NewHP;

	GEngine->AddOnScreenDebugMessage(-1, 3.f, FColor::Red, FString::Printf(TEXT("UpdateHP() - %s HP : %f"), *GetName(), hp));
}

void AMyCharacter::Attack()
{

	// Death가 true 일경우 공격 애니메이션 정지 
	
	if (Death)
	{
		GEngine->AddOnScreenDebugMessage(-1, 2.0f, FColor::Red, TEXT("is Death true"));
		return;
	}

	// 공격중일 경우 애니메이션 정지
	if (IsAttacking)
	{
		GEngine->AddOnScreenDebugMessage(-1, 2.0f, FColor::Red, TEXT("is attacking true"));
		return;
	}

	EDRAnim = Cast<UAnim_EDR_AnimInstance>(GetMesh()->GetAnimInstance());
	
	if (nullptr == EDRAnim)
	{
		return;
	}

	// 애니메이션 몽타주 실행
	PlayAnimMontage(AttackMontage, 0.5f);
	IsAttacking = true;
	// 공격 판정
	EDRAnim->OnAttackHitCheck.AddUObject(this, &AMyCharacter::AttackCheck);
	// 공격 종료
	EDRAnim->OnMontageEnded.AddDynamic(this, &AMyCharacter::OnAttackMontageEnded);
}

//  공격 판정
void AMyCharacter::AttackCheck()
{
	FHitResult HitResult;
	FCollisionQueryParams Params(NAME_None, false, this);

	// 구체의 중심을 캐릭터 발 앞에 설정
	FVector StartLocation = GetActorLocation() + FVector(0.0f, 0.0f, -GetCapsuleComponent()->GetScaledCapsuleHalfHeight());
	FVector ForwardLocation = StartLocation + GetActorForwardVector() * AttackRange;

	bool bResult = GetWorld()->SweepSingleByChannel(
		HitResult, // 물리적 충돌이 탐지된 경우 관련 정보를 담을 구조체
		StartLocation, // 탐색 시작 위치
		ForwardLocation,
		FQuat::Identity,
		ECollisionChannel::ECC_GameTraceChannel2, // 물리 충돌 감지에 사용할 트레이스 채널
		FCollisionShape::MakeSphere(AttackRadius), // 탐색 사용할  도형
		Params);

	// 디버그 출력 정보를 담은 변수
#if ENABLE_DRAW_DEBUG
	FVector TraceVec = GetActorForwardVector() * AttackRange;
	FVector Center = StartLocation + TraceVec * 0.5f;  // 구체의 중심을 설정
	FQuat CapsuleRot = FRotationMatrix::MakeFromZ(TraceVec).ToQuat();
	FColor DrawColor = bResult ? FColor::Green : FColor::Red;
	float DebugLifeTime = 1.0f;

	// 디버그 출력
	DrawDebugSphere(GetWorld(),
		ForwardLocation,
		AttackRadius,
		12, // 세그먼트 수, 더 많을수록 부드러워짐
		DrawColor,
		false,
		DebugLifeTime);

#endif
	// 액터 감지시
	if (bResult)
	{
		if (HitResult.GetActor() != nullptr)
		{
			// 데미지 정보 전달
			FDamageEvent DamageEvent;
			HitResult.GetActor()->TakeDamage(AttackDamage, DamageEvent, GetController(), this);
		}
	}
}


// 데미지 받는 함수
float AMyCharacter::TakeDamage(float DamageAmount, FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser)
{
	if (Death)
	{
		return Super::TakeDamage(DamageAmount, DamageEvent, EventInstigator, DamageCauser);
	}


	// 입은 데미지 만큼 hp 차감
	UpdateHP(-DamageAmount);


	// hp가 0이 되었을경우 사망 함수 호출
	if (this->hp <= 0)
	{
		this->hp = 0;
		IsDeath();
	}

	return Super::TakeDamage(DamageAmount, DamageEvent, EventInstigator, DamageCauser);
}

 

공격 판정을 위해 공격 범위 값인AttackRange, AttackRadius변수를 추가하고 AttackCheck()를 작성하였습니다.

Attack() 호출하게 되면

애니메이션인스턴스

 

EDR_Project / AnimInstance (2) Notify

24.10.19 AnimInstance 추가 사항입니다.더이상 사용하지 않는다고 하였으나 EDR_Boss_Character의 공격 애니메이션이 재생될때 어느 타이밍에 공격 판정을 내릴지에 대한 애니메이션 노티파이를 추가하였

lgj415415.tistory.com

에 있는 노티파이가 호출되고 AttackCheck()가 호출됩니다.

 

 

GetWorld()->SweepSingleByChannel()을 통해 충돌 여부를 가립니다.

 

HitResult : 물리적 충돌이 탐지된 경우 관련된 정보를 담을 구조체

Start : 탐색 시작 위치

End : 탐색 종료 위치

Rot : 탐색에 사용할 도형의 회전

TraceChannel : 물리 충돌 감지에 사용할 트레이스 채널 정보

CollisionShape : 탐색에 사용할 기본 도형 정보, 구체, 캡슐, 박스를 사용합니다.

Params : 탐색 방법에 대한 설정 값을 모아둔 구조체

ResponserParams : 탐색 반응을 설정하기 위한 구조체

 

 

디버그 드로잉을 위해 #include "DrawDebugHelpers.h" 헤더파일 추가하였습니다.

 

데미지 전달 부분은 공격 범위에 액터 감지했을 경우(bResult에 값이 있을 경우) 실행됩니다.

DamageEvent를 위해 #include "Engine/DamageEvents.h" 헤더를 추가하고 

HitResult.GetActor()로 오버랩된 액터를 가져와 TakeDamage로 데미지를 전달해주었습니다.

 

 

 

실행 화면

 

 

공격 하였을때 범위가 출력되는 것을 볼 수 있습니다.