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로 데미지를 전달해주었습니다.
실행 화면
공격 하였을때 범위가 출력되는 것을 볼 수 있습니다.
'Unreal Engine 5 > EDR_Project' 카테고리의 다른 글
EDR_Project / GameMode (0) | 2024.10.21 |
---|---|
EDR_Project / Character(4) 공격판정 수정 (0) | 2024.10.21 |
EDR_Project / TraceChannel (0) | 2024.10.20 |
EDR_Project / Character (2) Collision 프리셋 (0) | 2024.10.20 |
EDR_Project / AnimInstance (2) Notify (0) | 2024.10.20 |