● 서론
이번에는 캐릭터가 공격 시 정면에 대해서 충돌 체크를 진행하고
만약 앞에 적이 있다면 충돌 성공을 반환하는 것을 구현해 보도록 하겠습니다.
구현 방식은 Anim Notify를 이용할 것 입니다.
1. 캐릭터 공격 모션의 특정 구간에 노티파이를 설정해서 노티파이 클래스를 호출하고
2. 호출된 클래스는 노티파이를 호출한 액터의 충돌 검사 함수를 호출하여
3. 최종적으로 캐릭터의 정면에 대하여 충돌검사를 진행하게 합니다.
아! 그리고 아래와 같이 노티파이 클래스에서 액터 클래스의 충돌검사 함수를 실행하기 위해서
인터페이스 클래스를 이용할 것 입니다.
IRPGAnimationAttackInterface* AttackPawn = Cast<IRPGAnimationAttackInterface>(MeshComp->GetOwner());
if (AttackPawn)
{
AttackPawn->AttackHitCheck();
}
충돌검사 함수를 진행할 캐릭터가 많아지면 그 캐릭터들의 해더를 모두 include 하기 보다는
인터페이스를 이용하여 한번에 관리하는 것이 여러가지 측면에서 효율적일 것 입니다!
인터페이스 클래스 생성
공격 충돌 체크 기능을 구현한다고 하면 이는 캐릭터만 사용할 기능이 아닌
몬스터 -> 캐릭터로 체크 될 수도 있고 함정같은것이 있다면 함정->캐릭터로 체크 될 수 있을 것 입니다.|
이 때 몬스터, 캐릭터, 함정들에게 서로의 헤더파일을 모두 넣어주기 보다는
각 클래스에서 인터페이스를 상속 받는다면 효율적인 구조가 될 것입니다.
먼저 Unreal Interface를 상속받은 RPGAnimationAttackInterface를 생성해 주었습니다.
RPGAnimationAttackInterface.h
// This class does not need to be modified.
UINTERFACE(MinimalAPI)
class URPGAnimationAttackInterface : public UInterface
{
GENERATED_BODY()
};
/**
*
*/
class RPG_API IRPGAnimationAttackInterface
{
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;
};
그리고 다음과 같이 두번째 클래스 영역에 순수 가상함수를 구현해 주었습니다.
첫번째 클래스 영역은 실제 인터페이스가 아닌 언리얼 시스템에 보이도록 하기 위해 존재하는 빈 클래스이므로
실제 구현은 두번째 클래스 영역에서 진행합니다.
애님 노티파이 클래스 생성
애님 노티파이를 상속받은 클래스를 생성해 주었습니다.
캐릭터나 몬스터의 공격 애니메이션에 노티파이의 이름을 생성한 클래스 이름과 똑같이 지정해 준다면
해당 노티파이 구간에 도달할 때 구현한 기능을 실행 시킬 수 있을 것 입니다.
AnimNotify_AttackHitCheck.h
UCLASS()
class RPG_API UAnimNotify_AttackHitCheck : public UAnimNotify
{
GENERATED_BODY()
protected:
virtual void Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, const FAnimNotifyEventReference& EventReference) override;
};
Notify 가상함수를 가져와 오버라이드 해 줍니다.
이 함수는 애니메이션 이벤트가 발생될 때 호출되는 함수입니다.
AnimNotify_AttackHitCheck.cpp
#include "RPGAnimationAttackInterface.h"
...
void UAnimNotify_AttackHitCheck::Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, const FAnimNotifyEventReference& EventReference)
{
Super::Notify(MeshComp, Animation, EventReference);
if (MeshComp)
{
IRPGAnimationAttackInterface* AttackPawn = Cast<IRPGAnimationAttackInterface>(MeshComp->GetOwner());
if (AttackPawn)
{
AttackPawn->AttackHitCheck();
}
}
}
먼저 아까 만든 인터페이스를 헤더파일로 include 해 줍니다.
그리고 Notify 함수를 구현하기 앞서 Super 키워드를 사용해 부모의 기능을 가져옵니다.
함수의 첫번째 매개변수는 애니메이션 이벤트가 발생한 오브젝트의 스켈레탈 메시 컴포넌트입니다.
만약 이를 정상적으로 불러왔다면 MeshComp->GetOwner()로 메시 컴포넌트가 속한 액터를 가져옵니다.
이후 Cast<IRPGAnimationAttackInterface> 형변환을 통해 액터가 해당 인터페이스를 완벽히 구현하고 있는지 확인하고
만약 구현 하였다면 해당 인터페이스에 대한 포인터를 반환해 AttackPawn에 저장합니다.
구현 되어있지 않다면 nullptr을 반환하게 됩니다.
성공적으로 포인터를 저장했다면 저장한 인터페이스에서 AttackHitCheck 함수를 호출합니다.
콜리전 추가
AttackAction이라는 이름으로 트레이스 채널을 하나 생성해 줍니다.
Default Response는 무시로 설정합니다.
AttackAction의 충돌 처리를 위한 RPGCapsule이라는 이름의 콜리전 프리셋을 생성해 줍니다.
이렇게 하는 이유는 AttackAction을 기본 Pawn 프리셋에서 충돌을 처리하게 할 수도 있지만 그렇게 하면
공격 처리를 원하지 않는 Pawn에서도 공격 체크가 되기 때문에 구분의 편의성을 위하여 따로 콜리전 프리셋을
생성하였습니다.
일단 AttackAction을 포함한 모든 타입의 충돌에 대하여 Block 처리를 해 주었습니다.
캐릭터 클래스 수정
RPGCharacter.h
#include "RPGAnimationAttackInterface.h"
...
UCLASS()
class RPG_API ARPGCharacter : public ACharacter, public IRPGAnimationAttackInterface
{
GENERATED_BODY()
...
// 공격 충돌 체크 인터페이스
protected:
virtual void AttackHitCheck() override;
캐릭터에서 인터페이스를 구현하기 위해 먼저 헤더파일에 인터페이스를 포함시켜 줍니다.
이후 클래스에 인터페이스를 상속시켜 줍니다.
그리고 인터페이스의 가상 함수를 오버라이드 하여 인터페이스를 완벽히 구현해 줍니다.
RPGCharacter.cpp
ARPGCharacter::ARPGCharacter()
{
...
GetCapsuleComponent()->SetCollisionProfileName(TEXT("RPGCapsule"));
...
GetMesh()->SetCollisionProfileName(TEXT("NoCollision"));
...
}
void ARPGCharacter::AttackHitCheck()
{
// 충돌검사 매개변수 설정
FCollisionQueryParams Params(SCENE_QUERY_STAT(Attack), false, this);
FHitResult OutHitResult;
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;
// 충돌검사
bool HitDetected = GetWorld()->SweepSingleByChannel(
OutHitResult,
Start,
End,
FQuat::Identity,
ECollisionChannel::ECC_GameTraceChannel1,
FCollisionShape::MakeSphere(AttackRadius),
Params);
if (HitDetected)
{
}
// 충돌판정 그리기
#if ENABLE_DRAW_DEBUG
FVector CapsuleOrigin = Start + (End - Start) * 0.5f;
float CapsuleHalfHeight = AttackRange * 0.5f;
FColor DrawColor = HitDetected ? FColor::Green : FColor::Red;
DrawDebugCapsule(GetWorld(), CapsuleOrigin, CapsuleHalfHeight, AttackRadius, FRotationMatrix::MakeFromZ(GetActorForwardVector()).ToQuat(), DrawColor, false, 5.0f);
#endif
}
캐릭터 캡슐의 콜리전을 아까 설정한 RPGCapsule로 설정하고
메쉬는 NoCollsion으로 설정합니다.
SweepSingleByChannel 함수를 사용하여 충돌 처리를 해보도록 할것인데,
이 함수이름은 {처리방법}{대상}{처리설정} 순으로 원하는 방식을 선택하여 이름이 정해졌습니다.
1. 처리방법은 캐릭터 전방에 구를 그려 충돌을 체크할 예정이기 때문에 Sweep을 사용하였습니다.
2. 몬스터와 1:1로 진행되는 전투를 구현할 예정이라 감지 대상은 Single을 선택하였습니다.
만약 여러 몬스터에 대해서 충돌 체크를 하려면 Multi을 넣어줘야 합니다.
3. 처리 설정은 아까 만든 AttackAction 트레이스 채널을 사용할 것 이기 때문에 ByChannel을 넣어줍니다.
이러한 과정에 따라 SweepSingleByChannel 이라는 함수명이 선택 되었습니다.
이 함수의 인자는
(결과값, 충돌검사 시작점, 끝점, 회전 고려 여부, 사용할 트레이스채널, 검사 시 사용할 모양, 검사 매개변수)
순으로 존재합니다.
시작 지점, 끝 지점
Start = GetActorLocation() + GetActorForwardVector() * GetCapsuleComponent()->GetScaledCapsuleRadius();
충돌 검사 시작 지점은 다음과 같은 방식으로 구현 하였습니다.
액터의 위치 + (액터의 전방 벡터 * 스케일된 캡슐 컴포넌트의 반지름) 을 구현하였고
액터의 위치의 전방 벡터 방향으로 스케일된 캡슐 컴포넌트의 반지름만큼 앞에 시작지점을 설정한다는 뜻 입니다.
End = Start + GetActorForwardVector() * AttackRange;
끝 지점 역시 비슷한 방식으로 시작 지점에 AttackRange로 저장된 40.0f 만큼을 전방 백터방향으로 더해주었습니다.
회전 고려 여부
회전은 고려하지 않는다는 의미로 FQuat::Identity으로 설정하였습니다.
충돌 채널
ECollisionChannel::ECC_GameTraceChannel1은 트레이스 채널로 만들었던 AttackAction을 뜻하는 것인데
이는 DefaultEngine.ini 파일을 열어보면
+DefaultChannelResponses=(
Channel=ECC_GameTraceChannel1,
DefaultResponse=ECR_Ignore,
bTraceType=True,
bStaticObject=False,
Name="AttackAction")
다음과 같이 AttackAction이 ECC_GameTraceChannel1로 설정 되어 있는것을 확인하고 설정하였습니다.
검사 시 사용할 모양
FCollisionShape::MakeSphere(AttackRadius)
충돌 검사 시 사용할 충돌 형태를 구체 모양으로 설정하고
이것의 반지름을 AttackRadius(50.0f) 으로 설정하였습니다.
검사 매개변수
FCollisionQueryParams Params(SCENE_QUERY_STAT(Attack), false, this);
첫번째 인자로 충돌 정보를 분석하기위한 SCENE_QUERY_STAT 매크로를 설정했고
나중에 이것을 식별하기 위해 Attack 이라는 이름을 설정하였습니다.
두번째 인자는 충돌 검사 대상을 복잡한 모양(메쉬의 세부 정보 등등)을 대상으로 할것이면 true
캡슐과 같이 단순한 형태를 대상으로 하면 false로 구분되는데
캡슐을 대상으로 충돌 검사를 진행할 것 이기 때문에 false로 지정해 주었습니다.
세번째 인자는 충돌 검사를 수행할 액터를 지정하는데 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(GetWorld(), CapsuleOrigin, CapsuleHalfHeight, AttackRadius, FRotationMatrix::MakeFromZ(GetActorForwardVector()).ToQuat(), DrawColor, false, 5.0f);
#endif
위와 같은 코드를 통해 충돌 검사에서 충돌판정이 수행되어
HitDetected가 true라면 초록색, false 빨간색 구가 그려지는 충돌 검사를 수행해 보았습니다.
RPGCapsule 콜리전과 충돌하지 않으면 빨간색,
충돌했다면 초록색 구가 만들어지는 것을 볼 수 있습니다.
다음에는 해당 코드에 이어서 캐릭터의 HP등등 스텟을 구현하고
공격 성공시 HP를 공격력만큼 감소시켜 HP가 0이 되면 사망하는 애니메이션을
출력 하는 것 까지 구현해 보도록 하겠습니다.
감사합니다.
'[게임 개발] 개발 일지 > RPG' 카테고리의 다른 글
10. 적 구현과 정찰 AI (0) | 2023.09.22 |
---|---|
9. 캐릭터 스탯과 체력 UI 구현 (0) | 2023.09.19 |
7. 연속 공격 구현 (0) | 2023.09.13 |
6. 캐릭터의 공격 애니메이션 구현 (0) | 2023.09.13 |
5. 애니메이션 트리밍 및 문제점 수정 (0) | 2023.09.12 |