Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed stand/crouch transition animation #307

Merged
merged 4 commits into from
May 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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