초기 설계에서는 VGMissionSandBag에서 모든 코드를 작성하고 bool 플래그로 반격 여부를 설정하게 했습니다. 그러나 반격 관련 로직을 그대로 작성하기에는 코드량이 2배가 되는 상황에 반복기능은 자식 클래스를 만들어 그곳에서 작성하는 것이 좋겠다는 판단이 들었습니다. 때문에 자식 클래스로 VGMissionCounterSandbag을 추가했습니다.
반격 로직의 흐름은 다음과 같습니다.
StartCounter -> UpdateCounter -> OnCounterHitTimerExpired -> UpdateCounterReturning -> FinishCounter
StartCounter - 반격 시작(Server Only)
UpdateCounter - 숙이기(Both)
OnCounterHitTimerExpired - 반격 타이머 종료 후 상태를 Returning으로 전환
UpdateCounterReturning - 복귀(Both)
FinishCounter - (Server Only)
초반에는 동작의 분리와 서버/클라이언트 동작 분리가 부족하여 제대로 동작하지 못하였고, 몇번의 수정 끝에 위의 흐름으로 고정되었습니다.
반격 상태를 enum class로 정의하여 위의 함수들과 함께 사용했습니다.
enum class EVGSandbagCounterState : uint8
{
Idle UMETA(DisplayName = "Idle"), // 대기
Countering UMETA(DisplayName = "Countering"), // 회전 + 숙임 진행 / 충돌 판정 활성화 중
Hitting UMETA(DisplayName = "Hitting"), // 충돌 판정 활성화 중
Returning UMETA(DisplayName = "Returning"), // 원래 자세로 복귀 중
};
피격 시마다 현재 피격 횟수를 증가시켜서 지정된 요구 수에 도달하면 피격 횟수를 초기화하고 반격을 실행하는 방식입니다.

공격자 정보가 더 먼저 저장되어야 하는데 데미지 처리가 먼저 진행되며 변화된 Health 값에 따른 처리가 진행되는 데 이때 반격 로직도 같이 수행이 되어 등록된 공격자 없이 반격을 수행하게 됩니다.
// [VGMissionSandbag.cpp]
float AVGMissionSandbag::TakeDamage(float DamageAmount, ...)
{
float ActualDamage = Super::TakeDamage(DamageAmount, ...);
if (StatComponent)
{
// 1. 여기서 OnHPChanged가 즉시 호출됨
// -> AVGMissionCounterSandbag::OnHPChanged 실행
// -> StartCounter() 실행 (이때 LastAttacker는 아직 NULL!)
StatComponent->ApplyDamageToStat(ActualDamage, EventInstigator);
}
if (HasAuthority() && EventInstigator)
{
// 2. 반격 로직이 다 끝난 "뒤에" 공격자 정보가 저장됨
LastAttacker = Cast<AVGCharacterBase>(EventInstigator->GetPawn());
}
return ActualDamage;
}
수정된 코드
float AVGMissionSandbag::TakeDamage(float DamageAmount, ...)
{
float ActualDamage = Super::TakeDamage(DamageAmount, ...);
if (HasAuthority() && EventInstigator)
{
LastAttacker = Cast<AVGCharacterBase>(EventInstigator->GetPawn());
}
if (StatComponent)
{
StatComponent->ApplyDamageToStat(ActualDamage, EventInstigator);
}
return ActualDamage;
}
이번에는 순수 BP로 구현하는 데 성공했습니다. 중간에 빌드가 꼬여서 상호작용이 되지 않는 오류가 발생했습니다. 임시 파일을 삭제한 후 다시 빌드를 하니 문제가 해결되었지만 당시에는 원인을 파악하기 위해 3~4시간 동안 BP와 관련 C++ 코드를 계속 찾아보고 있었습니다.
상호작용 시 상호작용한 Character에게만 위젯을 출력하는 액터를 만들었습니다.
현재 작성된 코드 상황상 Inteact는 서버에서만 호출이 되며, 호출 이후의 처리는 변경된 값을 Replicate해서 해결합니다.
이번 액터에서도 마찬가지로 서버에서 상호작용한 PlayerCharacter 목록을 갱신하고, RepNotify로 복제받은 목록에 자신(Clinet)의 PlayerCharacter가 있다면 위젯을 활성화 하는 식으로 구현했습니다.
아래는 상호작용한 클라이언트에서만 위젯이 활성화 되는 모습이고

아래는 메쉬와 텍스처를 적용한 버전입니다.

미션 아이템 획득 시 부착에 사용되는 소켓이 기본 소켓이다 보니 위치나 각도가 이상한 경우가 자주 있었습니다.
이를 해결하기 위해 각 미션 아이템을 위한 소켓을 추가하였습니다. 한 소켓을 여러 아이템이 공유하게 만들어 보려 했으나 아이템 메쉬의 크기와 모양이 너무 제각각이라 실패했습니다.
최종적으로 4종의 아이템의 전용 소켓을 왼손, 오른손 본 아래에 생성하였습니다.
작업을 하면서 신기하게 느꼈던 것이 하나의 SKM(스켈레탈 메쉬)만 수정했는데 캐릭터가 사용하는 모든 SKM에 소켓이 적용되었습니다. 이에 대해서는 내일 알아보는 것도 좋을 것 같습니다.