I am working on a multiplayer TPS game and am currently developing the reload functionality. However, I encountered the following problem: when the host (server) reloads, the reload animation plays normally on both the client (other users) and the server, and the ammo count updates correctly. But when another user (client) tries to reload, there is a bug where AnimNotify triggers multiple times, causing the ammo count to be updated incorrectly.
Specifically, I placed an AnimNotify
at the end of the reload Montage animation. Normally, each time the animation plays, it triggers the AnimNotify
, which then updates the ammo count once. However, in the abnormal situation when the client reloads, the animation triggers the ammo update twice, meaning the AnimNotify
is triggered twice within a short time during a single animation playback.
Here is the relevant logic:
void UCombatComponent::Reload()
{
// This part is triggered by user input
if (CarriedAmmo > 0 && CombatState != ECombatState::ECS_Reloading
&& EquippedWeapon
&& EquippedWeapon->GetAmmo() < EquippedWeapon->GetMagCapacity())
{
// If the user is on the client, send a ServerRPC request; on the server, it's treated as a normal function call
ServerReload();
}
}
void UCombatComponent::ServerReload_Implementation()
{
if (Character && EquippedWeapon && Character->HasAuthority())
{
CombatState = ECombatState::ECS_Reloading;
HandleReload();
}
}
When the client reloads, the server receives the request, updates the state, and handles the reload:
void UCombatComponent::HandleReload()
{
if (Character)
{
Character->PlayReloadMontage();
}
if (Character->HasAuthority())
{
UE_LOG(LogTemp, Log, TEXT("HandleReloadOnServer"));
}
}
The server plays the Montage animation:
void APlayerCharacter::PlayReloadMontage() const
{
if (Combat == nullptr || Combat->EquippedWeapon == nullptr) return;
if (HasAuthority())
{
UE_LOG(LogTemp, Log, TEXT("PlayMontageOnServer"));
}
if (UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance(); AnimInstance
&& ReloadMontage && !AnimInstance->Montage_IsPlaying(ReloadMontage))
{
AnimInstance->Montage_Play(ReloadMontage);
FName SectionName;
switch (Combat->EquippedWeapon->GetWeaponType())
{
case EWeaponType::EWT_AssultRifle:
SectionName = FName("Rifle");
break;
case EWeaponType::EWT_RocketLauncher:
SectionName = FName("Rifle");
break;
case EWeaponType::EWT_Pistol:
SectionName = FName("Pistol");
break;
case EWeaponType::EWT_SubmachineGun:
SectionName = FName("Rifle");
break;
case EWeaponType::EWT_ShotGun:
SectionName = FName("ShotGun");
break;
case EWeaponType::EWT_SniperRifle:
SectionName = FName("SniperRifle");
break;
case EWeaponType::EWT_GrenadeLauncher:
SectionName = FName("Rifle");
break;
default:
break;
}
AnimInstance->Montage_JumpToSection(SectionName);
}
}
Then, when the Montage animation reaches the end, the AnimNotify
placed at the end is triggered. The AnimNotify
triggers:
void UCombatComponent::FinishReloading()
{
UE_LOG(LogTemp, Log, TEXT("%d"), Character->GetLocalRole());
UE_LOG(LogTemp, Log, TEXT("Finish Reloading ■■■■ ■■■■ why????????????"));
if (Character == nullptr) return;
if (Character->HasAuthority() && CombatState == ECombatState::ECS_Reloading)
{
UE_LOG(LogTemp, Log, TEXT("Finish Reloading HasAuthority ■■■■ ■■■■ why????????????"));
CombatState = ECombatState::ECS_Unoccupied;
UpdateAmmoValue();
}
}
void UCombatComponent::UpdateAmmoValue()
{
UE_LOG(LogTemp, Log, TEXT("Update Ammo"));
if (EquippedWeapon == nullptr || Character == nullptr) return;
int32 ReloadAmount = AmountToReload();
if (CarriedAmmoMap.Contains(EquippedWeapon->GetWeaponType()))
{
CarriedAmmoMap[EquippedWeapon->GetWeaponType()] -= ReloadAmount;
CarriedAmmo = CarriedAmmoMap[EquippedWeapon->GetWeaponType()];
}
EquippedWeapon->AddAmmo(ReloadAmount);
Controller = Controller == nullptr ? Cast<AMyPlayerController>(Character->Controller) : Controller;
if (Controller)
{
Controller->SetHUDCarriedAmmo(CarriedAmmo);
}
}
At this point, the server updates the ammo count. According to debug logs, FinishReloading
is triggered twice even though PlayReloadMontage
is only triggered once. This means the AnimNotify
is being triggered twice. While this doesn’t affect the final ammo count in this case, it causes issues with the shotgun later, where the reload animation results in two bullets being loaded instead of one, affecting gameplay.
I don’t understand why the AnimNotify
is triggered multiple times in these two cases, even though the Montage is only played once. Can anyone help me figure this out?