● 서론
이번에는 캐릭터의 방어 기능을 구현하고자 합니다!
제가 구현하고자 하는 방어 기능은 우클릭시 방어모션을 취하고
상대 몬스터가 공격하는 타이밍에 맞춰서 방어하면 몬스터에게 스턴 효과를 주고
잠시 카메라를 줌인하고 이펙트를 발생시키는 것입니다.
우선 우클릭을 눌러 방어 액션을 할 수 있도록 구현해 보겠습니다!
방어 몽타주 재생
구현 과정
인풋액션 추가 -> 매핑 컨텍스트 연결을 통해 코드상에서 구현하기 전 사전준비를 하였습니다.
void ARPGCharacter::Defense()
{
// 방어 예외 처리
// 공격 중일때
if (bIsAttacking)
{
return;
}
// 점프 중일 때
if (GetCharacterMovement()->MovementMode == MOVE_Falling)
{
return;
}
// 이미 방어 중일 때
if (bIsDefensing)
{
return;
}
// 몽타주 재생
if (AnimInstance)
{
AnimInstance->PlayDefenseMontage();
}
// 방어시 캐릭터의 상태 정보
bIsDefensing = true;
GetCharacterMovement()->SetMovementMode(EMovementMode::MOVE_None);
}
그리고 공격 액션의 구현과 거의 비슷한 형식으로 구현해 주었습니다.
현재 공격 중이거나 방어 중인 경우 리턴하게 설정하였고
몽타주 재생시에는 캐릭터가 움직이지 못합니다.
// void ARPGCharacter::MontageEnded(UAnimMontage* Montage, bool bInterrupted)
// 방어 몽타주 종료
if (Montage == AnimInstance->GetDefenseMontage())
{
// 방어 종료시 캐릭터의 상태정보
bIsDefensing = false;
GetCharacterMovement()->SetMovementMode(EMovementMode::MOVE_Walking);
}
또한 MontageEnded 함수에 다음과 같이 방어 몽타주가 종료 되었을때 캐릭터가 움직일 수 있게 설정했습니다.
이후 방어에 대한 몽타주를 만들어 주고 슬롯을 생성해 Output Pose로 연결되게 만들어 주면
방어에 대한 애니메이션 출력을 성공적으로 재생할 수 있습니다.
몽타주 재생 결과
다음과 같이 성공적으로 몽타주가 재생되는것을 확인할 수 있습니다!
방어 액션에 충돌 처리 적용하기
구현 전 설계
몬스터가 공격 할 때 타이밍에 맞춰서 방어하면 스턴을 거는 기능을 다음과 같이 구현할 예정입니다.
1. 몬스터의 공격 애니메이션에 AttackStart, AttackEnd 노티파이 생성,
각각의 노티파이는 몬스터의 IsAttacking 변수를 true/false로 전환시킨다.
2. 캐릭터의 방어 액션시 전방에 공격 기능처럼 방어 기능에 대한 인터페이스를 구현,
방어 몽타주의 노티파이를 이용해서 충돌처리 함수를 호출 한다.
3. 충돌된 몬스터의 IsAttaking 상태를 확인하여 true 일 경우 몬스터의 Stunned 변수를 true로 전환시킨다.
4. 비헤이비어 트리를 이용해 블랙보드의 Stunned가 true 일 경우 모든 행동을 중지시키고Stunned 노드를 작동시킨다.
5. Stunned 태스크 노드는 스턴에 대한 몽타주를 재생시킨다.
이후 타이머 함수고 5초뒤에 호출되어 스턴 몽타주를 정지시키고 스턴상태를 false로 만든다
우선 몬스터의 IsAttacking 변수가 몬스터의 노티파이에 따라 전환되게 구현 해보겠습니다.
몬스터의 IsAttacking 구현
먼저 방어를 통해 몬스터의 공격을 튕겨내게 하는것을 구현하고 싶어서 AttackStart와 AttackFinish 노티파이를
만들었습니다. 각각 몬스터의 isAttacking 변수를 true/false로 전환시키고 이를 이용해서 조건을 검사할 예정입니다.
두 노티파이 사이에서만 몬스터의 공격을 패링할 수 있고 만약 성공한다면 몽타주를 정지 시키기 때문에
AttackHitCheck 노티파이는 실행되지 않아서 데미지를 입지 않을 것 입니다.
캐릭터의 DefenseHitCheck를 통한 패링
캐릭터의 Defense 몽타주에 DefenseHitcheck라는 노티파이를 만들었습니다.
// 충돌검사 매개변수 설정
TArray<FHitResult> OutHitResults;
FCollisionQueryParams Params(SCENE_QUERY_STAT(Defense), false, this);
// 스텟 설정
const float DefenseRange = StatComp->GetDefenseRange();
const float DefenseRadius = DefenseRange * 0.5f;
// 방어 범위 설정
const FVector Start = GetActorLocation() + GetActorForwardVector() * GetCapsuleComponent()->GetScaledCapsuleRadius();
const FVector End = Start + GetActorForwardVector() * DefenseRange;
// 충돌검사
bool bHitDetected = GetWorld()->SweepMultiByChannel(
OutHitResults,
Start,
End,
FQuat::Identity,
ECollisionChannel::ECC_GameTraceChannel1,
FCollisionShape::MakeSphere(DefenseRadius),
Params);
if (bHitDetected)
{
for (auto const& OutHitResult : OutHitResults)
{
IRPGAnimationDefenseInterface* WidgetInterface;
WidgetInterface = Cast<IRPGAnimationDefenseInterface>(OutHitResult.GetActor());\
WidgetInterface->ApplyStun();
}
}
해당 노티파이는 캐릭터 내부의 다음과 같은 내용의 함수를 호출합니다.
캐릭터 전방의 충돌체크를 통해 얻은 액터에 대해서 방어 인터페이스를 통해 구현한 ApplyStun을 호출시킵니다.
구현하는 과정에서 TakeDamage를 통해서 충돌처리를 몬스터에게 전달할 것인지 인터페이스를 이용해서 스턴 적용
함수를 공유할 것인지 고민했고, 몬스터에게도 방어 패턴을 적용해 플레이어의 공격을 패링하는 시스템을 만들수도 있고
데미지를 주는 기능이 아니기 때문에 굳이 TakeDamage를 이용하지 않고 인터페이스를 이용하기로 하였습니다.
Apply Stun 함수는 다음과 같습니다.
void ARPGEnemy::ApplyStun()
{
if (AnimInstance->GetIsAttacking())
{
// 진행중인 몽타주 종료 후 스턴
AnimInstance->StopAllMontages(0.f);
AnimInstance->SetStunned(true);
// 스턴 해제를 위한 타이머
const float StunTime = 5.0f;
FTimerHandle StunTimerHandle;
GetWorldTimerManager().SetTimer(StunTimerHandle, this, &ARPGEnemy::EndStun, StunTime, false);
// 스턴 처리를 위해 블랙보드 컴포넌트 얻어옴
ARPGAIController* AIController = Cast<ARPGAIController>(Controller);
UBlackboardComponent* BlackboardComp = AIController->GetBlackboardComponent();
BlackboardComp->SetValueAsBool(FName(TEXT("Stunned")), true);
}
}
진행중인 모든 몽타주를 종료하고 Stunned 변수를 true로 만들어 캐릭터의 애님 인스턴스가
스턴 애니메이션을 실행하도록 만듭니다.
이후 StunTime만큼의 시간 뒤에 스턴 종료 함수를 호출하도록 타이머를 설정합니다.
또한 비헤이비어 트리에서도 처리를 해야 하기 때문에 블랙보드의 Stunned 값도 true로 변경해 주었습니다.
void ARPGEnemy::EndStun()
{
AnimInstance->StopAllMontages(0.0f);
AnimInstance->SetStunned(false);
ARPGAIController* AIController = Cast<ARPGAIController>(Controller);
UBlackboardComponent* BlackboardComp = AIController->GetBlackboardComponent();
BlackboardComp->SetValueAsBool(FName(TEXT("Stunned")), false);
}
스턴 종료를 위한 코드입니다.
Stunned 변수를 false로 만들어 스턴 애니메이션을 정지시키고 비헤이비어 트리도 정상화 시켜줍니다.
비헤이비어 트리 설정
비헤이비어 트리는 다음과 같이 설정하였습니다. Stunned에 대해서 데코레이터 노드를 만들어 스턴상태인 경우
아무런 AI도 수행하지 못하게 하였습니다. 다만 Detect 서비스는 작동하도록 하여 플레이어가 몬스터를 스턴 시키고
도망가면 몬스터가 캐릭터에 대한 인식을 해제하도록 설정하였습니다.
● 구현 결과
몬스터의 공격에 대한 패링이 정상적으로 진행되어 데미지를 입지 않고 몬스터의 공격 몽타주가 멈추는 것을
확인할 수 있습니다!
스턴을 걸고 몬스터의 Detect 범위 밖으로 나가면 Target 설정이 풀리고
몬스터는 일정시간 이후 스턴상태가 풀리는 것 또한 확인할 수 있었습니다!
마지막으로 스턴을 이용한 전투의 모습을 녹화해 보았습니다.
다음 포스팅에서는 패링 성공 시 이펙트나 소리같은 효과들을 적용시켜 패링 성공을 인식하기 쉽게
해보도록 하겠습니다.
감사합니다!
'[게임 개발] 개발 일지 > RPG' 카테고리의 다른 글
15. 피격 파티클 추가, 행동에 사운드 추가 (0) | 2023.09.30 |
---|---|
14. 파티클과 사운드를 이용한 패링 연출 효과 (0) | 2023.09.29 |
12. AI를 이용한 몬스터의 공격 (0) | 2023.09.25 |
11. AI를 이용한 캐릭터 탐지 (0) | 2023.09.24 |
10. 적 구현과 정찰 AI (0) | 2023.09.22 |