
AnimNotify는 애니메이션과 함수 호출을 결합할 수 있는 매우 강력한 기능이다.
기본적으로 사운드 재생, 나이아가라 재생 등을 지원하고 있고

Notifies 트랙을 우클릭해서 NewNotify로 추가해 이름을 지정해주면

이런 노티파이가 생성된다.

Skeleton 에셋의 Animation Notifies탭에서 현재 어떤 노티파이들이 있는지 확인할 수 있다.

일치하는 스켈레톤의 애니메이션 블루프린트 이벤트 그래프에서 AnimNotify를 검색하면 이벤트 노드를 생성할 수 있다.

해당 애니메이션 몽타주가 재생되다가 Notify를 만나면 이게 호출되는 거다.
ShotgunShellReload는 BlueprintCallable로 열어놓은 함수다.
void UCombatComponent::ShotgunShellReload()
{
if (Character && Character->HasAuthority())
{
UpdateShotgunAmmoValues();
}
}
void UCombatComponent::UpdateShotgunAmmoValues()
{
if (Character == nullptr || EquippedWeapon == nullptr)
{
return;
}
if (CarriedAmmoMap.Contains(EquippedWeapon->GetWeaponType()))
{
CarriedAmmoMap[EquippedWeapon->GetWeaponType()] -= 1;
CarriedAmmo = CarriedAmmoMap[EquippedWeapon->GetWeaponType()];
}
Controller = Controller == nullptr ? Cast<ABlasterPlayerController>(Character->Controller) : Controller;
if (Controller)
{
Controller->SetHUDCarriedAmmo(CarriedAmmo);
}
EquippedWeapon->AddAmmo(1);
bool bNeedMoreReload = !EquippedWeapon->IsFull() && CarriedAmmo > 0;
JumpToShotgunMoreReload(bNeedMoreReload);
}
Shell 노티파이를 만나면 소지한 샷건 Ammo를 1발 줄이고, 장전된 Ammo를 1 늘린다.
그 다음 !EquippedWeapon->IsFull() && CarriedAmmo > 0 이 조건을 JumpToShotgunMoreReload의 매개변수로 보낸다.
void UCombatComponent::JumpToShotgunMoreReload(bool bNeedMoreReload)
{
UAnimInstance* AnimInstance = Character->GetMesh()->GetAnimInstance();
if (AnimInstance && Character->GetReloadMontage())
{
if (bNeedMoreReload)
{
AnimInstance->Montage_JumpToSection(FName("ShotgunStart"));
}
else
{
AnimInstance->Montage_JumpToSection(FName("ShotgunEnd"));
}
}
}
더 장전해야 하면 true, 더 장전할 수 없으면 false가 된다.
그 뒤 일치하는 섹션으로 보내 몽타주를 다시 재생한다.

자세히 보면 첫 Shell보다 미세하게 ShotgunStart 섹션이 앞서있다.
더 장전이 필요하면 저기로 가서 2번째 Shell 노티파이를 만나고
더이상 장전이 필요하지 않으면 ShotgunEnd 섹션으로 가게 된다.
강의에선 이걸 구현하지 않아서 샷건의 탄창이 아무리 커도 4발만 넣고 끝냈었는데, 나는 무한히 장전할 수 있도록 구현했다.
void UCombatComponent::OnRep_CarriedAmmo()
{
Controller = Controller == nullptr ? Cast<ABlasterPlayerController>(Character->Controller) : Controller;
if (Controller)
{
Controller->SetHUDCarriedAmmo(CarriedAmmo);
}
bool bJumpToShotgunEnd = CombatState == ECombatState::ECS_Reloading
&& EquippedWeapon != nullptr
&& EquippedWeapon->GetWeaponType() == EWeaponType::EWT_Shotgun
&& (CarriedAmmo == 0 || EquippedWeapon->IsEmpty());
JumpToShotgunMoreReload(!bJumpToShotgunEnd);
}
위 구현이 없으면 서버에서만 무한 장전이 작동하기 때문에 클라이언트도 알 수 있도록 OnRep에 작성해줬다.
bool UCombatComponent::CanFire()
{
if (EquippedWeapon == nullptr)
{
return false;
}
if (!EquippedWeapon->IsEmpty() && bCanFire && CombatState == ECombatState::ECS_Reloading && EquippedWeapon->GetWeaponType() == EWeaponType::EWT_Shotgun)
{
// 샷건 장전 중엔 예외로 사격 가능
return true;
}
return !EquippedWeapon->IsEmpty() && bCanFire && CombatState == ECombatState::ECS_Unoccupied;
}
그 외에 샷건 장전 중엔 예외로 사격 가능하도록 처리도 해줬다.

