Skip to content

Commit

Permalink
Fixed stand/crouch transition animation (#307)
Browse files Browse the repository at this point in the history
* #114 WIP on fix stand/crouch transition anim.

- Reload from crouch to stand currently with bug

* #114 Fix stand/crouch transition animation

* #114 Removed commented code

* #114 Change log level to verbose
  • Loading branch information
xthebat authored May 21, 2024
1 parent 051fdfe commit f9c4c4c
Show file tree
Hide file tree
Showing 16 changed files with 189 additions and 26 deletions.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file modified Content/Weapons/WeaponTableDefinitions.uasset
Binary file not shown.
79 changes: 74 additions & 5 deletions Source/Cloud9/Character/Components/Cloud9AnimationComponent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,27 +20,96 @@ UAnimInstance* UCloud9AnimationComponent::GetAnimInstance() const
return Mesh->GetAnimInstance();
}

bool UCloud9AnimationComponent::PlayMontage(UAnimMontage* Montage, float StartTime, float Rate) const
bool UCloud9AnimationComponent::PlayMontage(
UAnimMontage* NewBasePoseMontage,
UAnimMontage* NewOtherPoseMontage,
float StartTime,
float Rate)
{
OBJECT_RETURN_IF_FAIL(IsValid(Montage), false, Error, "Montage is invalid");
OBJECT_RETURN_IF_FAIL(IsValid(NewBasePoseMontage), false, Error, "Montage is invalid");

let AnimInstance = GetAnimInstance();
OBJECT_RETURN_IF_FAIL(
IsValid(AnimInstance), false,
Error, "AnimInstance is invalid for montage '%s'",
*Montage->GetName());
*NewBasePoseMontage->GetName());

OBJECT_RETURN_IF_FAIL(
AnimInstance->Montage_Play(Montage, Rate, EMontagePlayReturnType::MontageLength, StartTime), false,
Error, "Can't play montage '%s'", *Montage->GetName()
AnimInstance->Montage_Play(NewBasePoseMontage, Rate, EMontagePlayReturnType::MontageLength, StartTime), false,
Error, "Can't play montage '%s'", *NewBasePoseMontage->GetName()
);

let MontageInstance = AnimInstance->GetActiveInstanceForMontage(NewBasePoseMontage);
let InstanceID = MontageInstance->GetInstanceID();
MontageInstance->OnMontageEnded.BindLambda(
[this, InstanceID](UAnimMontage* Montage, bool IsInterrupted)
{
OBJECT_VERBOSE(
"Montage stopped=%s InstanceID=%d Interrupted=%d",
*Montage->GetName(), InstanceID, IsInterrupted);
Montages.Remove(InstanceID);
}
);
Montages.Add(InstanceID, NewOtherPoseMontage);
OBJECT_VERBOSE("Montage started=%s InstanceID=%d", *NewBasePoseMontage->GetName(), InstanceID);

return true;
}

void UCloud9AnimationComponent::StopAllMontages(float BlendOut) const
{
let AnimInstance = GetAnimInstance();
OBJECT_VOID_IF_FAIL(IsValid(AnimInstance), Error, "AnimInstance is invalid");
AnimInstance->StopAllMontages(BlendOut);
}

bool UCloud9AnimationComponent::IsAnyMontagePlaying() const
{
let AnimInstance = GetAnimInstance();
OBJECT_RETURN_IF_FAIL(IsValid(AnimInstance), false, Error, "AnimInstance is invalid");
return AnimInstance->IsAnyMontagePlaying();
}

void UCloud9AnimationComponent::StopMontage(const UAnimMontage* Montage) const
{
let AnimInstance = GetAnimInstance();
OBJECT_VOID_IF_FAIL(IsValid(AnimInstance), Error, "AnimInstance is invalid");

if (let MontageInstance = AnimInstance->GetActiveInstanceForMontage(Montage))
{
MontageInstance->Stop(Montage->BlendOut);
}
}

UAnimMontage* UCloud9AnimationComponent::StopMontage(int32 InstanceID) const
{
let AnimInstance = GetAnimInstance();
OBJECT_RETURN_IF_FAIL(IsValid(AnimInstance), nullptr, Error, "AnimInstance is invalid");

if (let MontageInstance = AnimInstance->GetMontageInstanceForID(InstanceID))
{
if (let Montage = MontageInstance->Montage; IsValid(Montage))
{
MontageInstance->Stop(Montage->BlendOut);
return Montage;
}
}

return nullptr;
}

void UCloud9AnimationComponent::PoseChanged()
{
let AnimInstance = GetAnimInstance();
OBJECT_VOID_IF_FAIL(IsValid(AnimInstance), Error, "AnimInstance is invalid");

// Prevent modification during iteration
for (let Copy = Montages; let [InstanceID, OtherMontage] : Copy)
{
let MontageInstance = AnimInstance->GetMontageInstanceForID(InstanceID);
let Position = MontageInstance->GetPosition();
let BaseMontage = StopMontage(InstanceID);
OBJECT_VERBOSE("Pose changed ActiveMontage=%s InstanceID=%d", *BaseMontage->GetName(), InstanceID);
PlayMontage(OtherMontage, BaseMontage, Position);
}
}
23 changes: 22 additions & 1 deletion Source/Cloud9/Character/Components/Cloud9AnimationComponent.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,28 @@ class CLOUD9_API UCloud9AnimationComponent : public UActorComponent

UAnimInstance* GetAnimInstance() const;

bool PlayMontage(UAnimMontage* Montage, float StartTime = 0.0f, float Rate = 1.0f) const;
bool PlayMontage(
UAnimMontage* NewBasePoseMontage,
UAnimMontage* NewOtherPoseMontage,
float StartTime = 0.0f,
float Rate = 1.0f);

bool IsAnyMontagePlaying() const;
void StopMontage(const UAnimMontage* Montage) const;
UAnimMontage* StopMontage(int32 InstanceID) const;
void StopAllMontages(float BlendOut) const;

// TODO: Change with delegate and to untethered PlayerController with AnimationComponent
void PoseChanged();

protected:
// This was made in an account that several montages can be played in same time,
// but easier way is just stop all other montages for weapon deploy and reload
// when the character pose changed.
//
// Currently left as is in order future modification.

/** Montage instance ID to another pose montage */
UPROPERTY()
TMap<int32, UAnimMontage*> Montages;
};
7 changes: 7 additions & 0 deletions Source/Cloud9/Contollers/Cloud9KeyboardController.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

#include "Cloud9KeyboardController.h"

#include "Cloud9/Character/Components/Cloud9AnimationComponent.h"
#include "GameFramework/SpringArmComponent.h"

#include "Cloud9/Game/Cloud9DeveloperSettings.h"
Expand Down Expand Up @@ -147,6 +148,9 @@ void UCloud9KeyboardController::OnCrouchPressed()
if (let Pawn = GetCloud9Pawn(); IsValid(Pawn))
{
Pawn->Crouch(false);
let AnimationComponent = Pawn->GetAnimationComponent();
OBJECT_VOID_IF_FAIL(IsValid(AnimationComponent), Error, "AnimationComponent is invalid");
AnimationComponent->PoseChanged();
}
}

Expand All @@ -155,6 +159,9 @@ void UCloud9KeyboardController::OnCrouchReleased()
if (let Pawn = GetCloud9Pawn(); IsValid(Pawn))
{
Pawn->UnCrouch(false);
let AnimationComponent = Pawn->GetAnimationComponent();
OBJECT_VOID_IF_FAIL(IsValid(AnimationComponent), Error, "AnimationComponent is invalid");
AnimationComponent->PoseChanged();
}
}

Expand Down
2 changes: 1 addition & 1 deletion Source/Cloud9/Weapon/Classes/Cloud9WeaponBase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ bool ACloud9WeaponBase::UpdateWeaponAttachment(EWeaponSlot NewSlot, EWeaponBond
CharacterMesh->GetSocketByName(SocketName), false,
Error, "Socket not found in character mesh for '%s'", SLOT_NAME);

OBJECT_DISPLAY(
OBJECT_VERBOSE(
"Update attachment to character '%s' into socket '%s'",
*Character->GetName(), *SocketName.ToString());

Expand Down
43 changes: 35 additions & 8 deletions Source/Cloud9/Weapon/Classes/Cloud9WeaponFirearm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -208,11 +208,12 @@ void ACloud9WeaponFirearm::Tick(float DeltaSeconds)
OBJECT_VOID_IF_FAIL(IsValid(AnimComponent), Error, "AnimComponent isn't valid");

let WeaponInfo = WeaponDefinition.GetWeaponInfo<FFirearmWeaponInfo>();
let PoseMontages = WeaponDefinition.GetPoseMontages(Character->bIsCrouched);
let BasePoseMontages = WeaponDefinition.GetPoseMontages(Character->bIsCrouched);
let OtherPoseMontages = WeaponDefinition.GetPoseMontages(not Character->bIsCrouched);
let CommonData = WeaponDefinition.GetCommonData();

let HasSecondaryAction = PoseMontages->bHasSecondaryAction;
let HasLoopedReload = PoseMontages->bHasReloadLoop;
let HasSecondaryAction = BasePoseMontages->bHasSecondaryAction;
let HasLoopedReload = BasePoseMontages->bHasReloadLoop;

if (WeaponState.IsActionActive(EWeaponAction::ReloadStart))
{
Expand All @@ -221,8 +222,14 @@ void ACloud9WeaponFirearm::Tick(float DeltaSeconds)
WeaponInfo->ReloadTime,
[&]
{
// Stop current montage action to if change pose
AnimComponent->StopAllMontages(0.0f);

return UpdateReloadAmmo(WeaponInfo->Type == EWeaponType::Shotgun)
and AnimComponent->PlayMontage(PoseMontages->ReloadMontage);
and AnimComponent->PlayMontage(
BasePoseMontages->ReloadMontage,
OtherPoseMontages->ReloadMontage
);
},
[this, HasLoopedReload]
{
Expand Down Expand Up @@ -253,7 +260,10 @@ void ACloud9WeaponFirearm::Tick(float DeltaSeconds)
[&]
{
return UpdateReloadAmmo(true) and
AnimComponent->PlayMontage(PoseMontages->ReloadLoopMontage);
AnimComponent->PlayMontage(
BasePoseMontages->ReloadLoopMontage,
OtherPoseMontages->ReloadLoopMontage
);
}
);
}
Expand All @@ -263,7 +273,13 @@ void ACloud9WeaponFirearm::Tick(float DeltaSeconds)
ExecuteAction(
EWeaponAction::ReloadLoop,
WeaponInfo->ReloadEndTime,
[&] { return AnimComponent->PlayMontage(PoseMontages->ReloadEndMontage); }
[&]
{
return AnimComponent->PlayMontage(
BasePoseMontages->ReloadEndMontage,
OtherPoseMontages->ReloadEndMontage
);
}
);

WeaponState.ClearAction(EWeaponAction::ReloadEnd);
Expand All @@ -274,7 +290,15 @@ void ACloud9WeaponFirearm::Tick(float DeltaSeconds)
ExecuteAction(
EWeaponAction::Deploy,
WeaponInfo->DeployTime,
[&] { return AnimComponent->PlayMontage(PoseMontages->DeployMontage); },
[&]
{
// Stop current montage action to if change pose
AnimComponent->StopAllMontages(0.0f);

return AnimComponent->PlayMontage(
BasePoseMontages->DeployMontage,
OtherPoseMontages->DeployMontage);
},
[this] { WeaponState.ClearAction(EWeaponAction::Deploy); }
);
}
Expand All @@ -301,7 +325,10 @@ void ACloud9WeaponFirearm::Tick(float DeltaSeconds)
Error, "Weapon fire failure status=%d", Status);

OBJECT_RETURN_IF_FAIL(
AnimComponent->PlayMontage(PoseMontages->PrimaryActionMontage), false,
AnimComponent->PlayMontage(
BasePoseMontages->PrimaryActionMontage,
OtherPoseMontages->PrimaryActionMontage
), false,
Error, "No montage for primary action specified"
);

Expand Down
32 changes: 26 additions & 6 deletions Source/Cloud9/Weapon/Classes/Cloud9WeaponGrenade.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -145,14 +145,21 @@ void ACloud9WeaponGrenade::Tick(float DeltaSeconds)
let AnimComponent = Character->GetAnimationComponent();
OBJECT_VOID_IF_FAIL(IsValid(AnimComponent), Error, "AnimComponent isn't valid");

let PoseMontages = WeaponDefinition.GetPoseMontages(Character->bIsCrouched);
let BasePoseMontages = WeaponDefinition.GetPoseMontages(Character->bIsCrouched);
let OtherPoseMontages = WeaponDefinition.GetPoseMontages(not Character->bIsCrouched);

if (WeaponState.IsActionActive(EWeaponAction::Deploy))
{
ExecuteAction(
EWeaponAction::Deploy,
GetWeaponInfo()->DeployTime,
[&] { return AnimComponent->PlayMontage(PoseMontages->DeployMontage); },
[&]
{
return AnimComponent->PlayMontage(
BasePoseMontages->DeployMontage,
OtherPoseMontages->DeployMontage
);
},
[this] { WeaponState.ClearAction(EWeaponAction::Deploy); }
);
}
Expand All @@ -161,7 +168,13 @@ void ACloud9WeaponGrenade::Tick(float DeltaSeconds)
ExecuteAction(
EWeaponAction::PrimaryStart,
GetWeaponInfo()->PinpullTime,
[&] { return AnimComponent->PlayMontage(PoseMontages->PinpullPrimaryActionMontage); },
[&]
{
return AnimComponent->PlayMontage(
BasePoseMontages->PinpullPrimaryActionMontage,
OtherPoseMontages->PinpullPrimaryActionMontage
);
},
[this]
{
WeaponState.ClearAction(EWeaponAction::PrimaryStart);
Expand All @@ -173,8 +186,9 @@ void ACloud9WeaponGrenade::Tick(float DeltaSeconds)
{
// Play hold frame of montage
AnimComponent->PlayMontage(
PoseMontages->PinpullPrimaryActionMontage,
PoseMontages->PinpullPrimaryActionHoldTiming);
BasePoseMontages->PinpullPrimaryActionMontage,
OtherPoseMontages->PinpullPrimaryActionMontage,
BasePoseMontages->PinpullPrimaryActionHoldTiming);

if (WeaponState.IsActionActive(EWeaponAction::PrimaryEnd))
{
Expand All @@ -194,7 +208,13 @@ void ACloud9WeaponGrenade::Tick(float DeltaSeconds)
ExecuteAction(
EWeaponAction::PrimaryEnd,
GetWeaponInfo()->ThrowTime,
[&] { return AnimComponent->PlayMontage(PoseMontages->PrimaryActionMontage); },
[&]
{
return AnimComponent->PlayMontage(
BasePoseMontages->PrimaryActionMontage,
OtherPoseMontages->PrimaryActionMontage
);
},
[this]
{
WeaponState.ClearAction(EWeaponAction::PrimaryEnd);
Expand Down
29 changes: 24 additions & 5 deletions Source/Cloud9/Weapon/Classes/Cloud9WeaponMelee.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,16 +78,23 @@ void ACloud9WeaponMelee::Tick(float DeltaSeconds)
OBJECT_VOID_IF_FAIL(IsValid(AnimComponent), Error, "AnimComponent isn't valid");

let WeaponInfo = WeaponDefinition.GetWeaponInfo<FMeleeWeaponInfo>();
let PoseMontages = WeaponDefinition.GetPoseMontages(Character->bIsCrouched);
let BasePoseMontages = WeaponDefinition.GetPoseMontages(Character->bIsCrouched);
let OtherPoseMontages = WeaponDefinition.GetPoseMontages(not Character->bIsCrouched);

let HasSecondaryAction = PoseMontages->bHasSecondaryAction;
let HasSecondaryAction = BasePoseMontages->bHasSecondaryAction;

if (WeaponState.IsActionActive(EWeaponAction::Deploy))
{
ExecuteAction(
EWeaponAction::Deploy,
WeaponInfo->DeployTime,
[&] { return AnimComponent->PlayMontage(PoseMontages->DeployMontage); },
[&]
{
return AnimComponent->PlayMontage(
BasePoseMontages->DeployMontage,
OtherPoseMontages->DeployMontage
);
},
[this] { WeaponState.ClearAction(EWeaponAction::Deploy); }
);
}
Expand All @@ -100,7 +107,13 @@ void ACloud9WeaponMelee::Tick(float DeltaSeconds)
ExecuteAction(
EWeaponAction::PrimaryLoop,
WeaponInfo->SlashCycleTime,
[&] { return AnimComponent->PlayMontage(PoseMontages->PrimaryActionMontage); }
[&]
{
return AnimComponent->PlayMontage(
BasePoseMontages->PrimaryActionMontage,
OtherPoseMontages->PrimaryActionMontage
);
}
);
}
else if (WeaponState.IsActionActive(EWeaponAction::PrimaryEnd))
Expand All @@ -112,7 +125,13 @@ void ACloud9WeaponMelee::Tick(float DeltaSeconds)
ExecuteAction(
EWeaponAction::Secondary,
WeaponInfo->StabCycleTime,
[&] { return AnimComponent->PlayMontage(PoseMontages->SecondaryActionMontage); }
[&]
{
return AnimComponent->PlayMontage(
BasePoseMontages->SecondaryActionMontage,
OtherPoseMontages->SecondaryActionMontage
);
}
);

// no auto stab
Expand Down

0 comments on commit f9c4c4c

Please sign in to comment.