-
Notifications
You must be signed in to change notification settings - Fork 571
/
ALSCharacterAnimInstance.cpp
942 lines (828 loc) · 41.8 KB
/
ALSCharacterAnimInstance.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
// Copyright: Copyright (C) 2022 Doğa Can Yanıkoğlu
// Source Code: https://github.com/dyanikoglu/ALS-Community
#include "Character/Animation/ALSCharacterAnimInstance.h"
#include "Character/ALSBaseCharacter.h"
#include "Library/ALSMathLibrary.h"
#include "Components/ALSDebugComponent.h"
#include "Curves/CurveVector.h"
#include "Components/CapsuleComponent.h"
#include "GameFramework/CharacterMovementComponent.h"
static const FName NAME_BasePose_CLF(TEXT("BasePose_CLF"));
static const FName NAME_BasePose_N(TEXT("BasePose_N"));
static const FName NAME_Enable_FootIK_R(TEXT("Enable_FootIK_R"));
static const FName NAME_Enable_FootIK_L(TEXT("Enable_FootIK_L"));
static const FName NAME_Enable_HandIK_L(TEXT("Enable_HandIK_L"));
static const FName NAME_Enable_HandIK_R(TEXT("Enable_HandIK_R"));
static const FName NAME_Enable_Transition(TEXT("Enable_Transition"));
static const FName NAME_FootLock_L(TEXT("FootLock_L"));
static const FName NAME_FootLock_R(TEXT("FootLock_R"));
static const FName NAME_Grounded___Slot(TEXT("Grounded Slot"));
static const FName NAME_Layering_Arm_L(TEXT("Layering_Arm_L"));
static const FName NAME_Layering_Arm_L_Add(TEXT("Layering_Arm_L_Add"));
static const FName NAME_Layering_Arm_L_LS(TEXT("Layering_Arm_L_LS"));
static const FName NAME_Layering_Arm_R(TEXT("Layering_Arm_R"));
static const FName NAME_Layering_Arm_R_Add(TEXT("Layering_Arm_R_Add"));
static const FName NAME_Layering_Arm_R_LS(TEXT("Layering_Arm_R_LS"));
static const FName NAME_Layering_Hand_L(TEXT("Layering_Hand_L"));
static const FName NAME_Layering_Hand_R(TEXT("Layering_Hand_R"));
static const FName NAME_Layering_Head_Add(TEXT("Layering_Head_Add"));
static const FName NAME_Layering_Spine_Add(TEXT("Layering_Spine_Add"));
static const FName NAME_Mask_AimOffset(TEXT("Mask_AimOffset"));
static const FName NAME_Mask_LandPrediction(TEXT("Mask_LandPrediction"));
static const FName NAME__ALSCharacterAnimInstance__RotationAmount(TEXT("RotationAmount"));
static const FName NAME_VB___foot_target_l(TEXT("VB foot_target_l"));
static const FName NAME_VB___foot_target_r(TEXT("VB foot_target_r"));
static const FName NAME_W_Gait(TEXT("W_Gait"));
static const FName NAME__ALSCharacterAnimInstance__root(TEXT("root"));
void UALSCharacterAnimInstance::NativeInitializeAnimation()
{
Super::NativeInitializeAnimation();
Character = Cast<AALSBaseCharacter>(TryGetPawnOwner());
if (Character)
{
Character->OnJumpedDelegate.AddUniqueDynamic(this, &UALSCharacterAnimInstance::OnJumped);
}
}
void UALSCharacterAnimInstance::NativeBeginPlay()
{
// it seems to be that the player pawn components are not really initialized
// when the call to NativeInitializeAnimation() happens.
// This is the reason why it is tried here to get the ALS debug component.
if (APawn* Owner = TryGetPawnOwner())
{
ALSDebugComponent = Owner->FindComponentByClass<UALSDebugComponent>();
}
}
void UALSCharacterAnimInstance::NativeUpdateAnimation(float DeltaSeconds)
{
Super::NativeUpdateAnimation(DeltaSeconds);
if (!Character || DeltaSeconds == 0.0f)
{
return;
}
// Update rest of character information. Others are reflected into anim bp when they're set inside character class
CharacterInformation.MovementInputAmount = Character->GetMovementInputAmount();
CharacterInformation.bHasMovementInput = Character->HasMovementInput();
CharacterInformation.bIsMoving = Character->IsMoving();
CharacterInformation.Acceleration = Character->GetAcceleration();
CharacterInformation.AimYawRate = Character->GetAimYawRate();
CharacterInformation.Speed = Character->GetSpeed();
CharacterInformation.Velocity = Character->GetCharacterMovement()->Velocity;
CharacterInformation.MovementInput = Character->GetMovementInput();
CharacterInformation.AimingRotation = Character->GetAimingRotation();
CharacterInformation.CharacterActorRotation = Character->GetActorRotation();
CharacterInformation.ViewMode = Character->GetViewMode();
CharacterInformation.PrevMovementState = Character->GetPrevMovementState();
LayerBlendingValues.OverlayOverrideState = Character->GetOverlayOverrideState();
MovementState = Character->GetMovementState();
MovementAction = Character->GetMovementAction();
Stance = Character->GetStance();
RotationMode = Character->GetRotationMode();
Gait = Character->GetGait();
OverlayState = Character->GetOverlayState();
GroundedEntryState = Character->GetGroundedEntryState();
UpdateAimingValues(DeltaSeconds);
UpdateLayerValues();
UpdateFootIK(DeltaSeconds);
if (MovementState.Grounded())
{
// Check If Moving Or Not & Enable Movement Animations if IsMoving and HasMovementInput, or if the Speed is greater than 150.
const bool bPrevShouldMove = Grounded.bShouldMove;
Grounded.bShouldMove = ShouldMoveCheck();
if (bPrevShouldMove == false && Grounded.bShouldMove)
{
// Do When Starting To Move
TurnInPlaceValues.ElapsedDelayTime = 0.0f;
Grounded.bRotateL = false;
Grounded.bRotateR = false;
}
if (Grounded.bShouldMove)
{
// Do While Moving
UpdateMovementValues(DeltaSeconds);
UpdateRotationValues();
}
else
{
// Do While Not Moving
if (CanRotateInPlace())
{
RotateInPlaceCheck();
}
else
{
Grounded.bRotateL = false;
Grounded.bRotateR = false;
}
if (CanTurnInPlace())
{
TurnInPlaceCheck(DeltaSeconds);
}
else
{
TurnInPlaceValues.ElapsedDelayTime = 0.0f;
}
if (CanDynamicTransition())
{
DynamicTransitionCheck();
}
}
}
else if (MovementState.InAir())
{
// Do While InAir
UpdateInAirValues(DeltaSeconds);
}
else if (MovementState.Ragdoll())
{
// Do While Ragdolling
UpdateRagdollValues();
}
}
void UALSCharacterAnimInstance::PlayTransition(const FALSDynamicMontageParams& Parameters)
{
PlaySlotAnimationAsDynamicMontage(Parameters.Animation, NAME_Grounded___Slot,
Parameters.BlendInTime, Parameters.BlendOutTime, Parameters.PlayRate, 1,
0.0f, Parameters.StartTime);
}
void UALSCharacterAnimInstance::PlayTransitionChecked(const FALSDynamicMontageParams& Parameters)
{
if (Stance.Standing() && !Grounded.bShouldMove)
{
PlayTransition(Parameters);
}
}
void UALSCharacterAnimInstance::PlayDynamicTransition(float ReTriggerDelay, FALSDynamicMontageParams Parameters)
{
if (bCanPlayDynamicTransition)
{
bCanPlayDynamicTransition = false;
// Play Dynamic Additive Transition Animation
PlayTransition(Parameters);
UWorld* World = GetWorld();
check(World);
World->GetTimerManager().SetTimer(PlayDynamicTransitionTimer, this,
&UALSCharacterAnimInstance::PlayDynamicTransitionDelay,
ReTriggerDelay, false);
}
}
bool UALSCharacterAnimInstance::ShouldMoveCheck() const
{
return (CharacterInformation.bIsMoving && CharacterInformation.bHasMovementInput) ||
CharacterInformation.Speed > 150.0f;
}
bool UALSCharacterAnimInstance::CanRotateInPlace() const
{
return RotationMode.Aiming() ||
CharacterInformation.ViewMode == EALSViewMode::FirstPerson;
}
bool UALSCharacterAnimInstance::CanTurnInPlace() const
{
return RotationMode.LookingDirection() &&
CharacterInformation.ViewMode == EALSViewMode::ThirdPerson &&
GetCurveValue(NAME_Enable_Transition) >= 0.99f;
}
bool UALSCharacterAnimInstance::CanDynamicTransition() const
{
return GetCurveValue(NAME_Enable_Transition) >= 0.99f;
}
void UALSCharacterAnimInstance::PlayDynamicTransitionDelay()
{
bCanPlayDynamicTransition = true;
}
void UALSCharacterAnimInstance::OnJumpedDelay()
{
InAir.bJumped = false;
}
void UALSCharacterAnimInstance::OnPivotDelay()
{
Grounded.bPivot = false;
}
void UALSCharacterAnimInstance::UpdateAimingValues(float DeltaSeconds)
{
// Interp the Aiming Rotation value to achieve smooth aiming rotation changes.
// Interpolating the rotation before calculating the angle ensures the value is not affected by changes
// in actor rotation, allowing slow aiming rotation changes with fast actor rotation changes.
AimingValues.SmoothedAimingRotation = FMath::RInterpTo(AimingValues.SmoothedAimingRotation,
CharacterInformation.AimingRotation, DeltaSeconds,
Config.SmoothedAimingRotationInterpSpeed);
// Calculate the Aiming angle and Smoothed Aiming Angle by getting
// the delta between the aiming rotation and the actor rotation.
FRotator Delta = CharacterInformation.AimingRotation - CharacterInformation.CharacterActorRotation;
Delta.Normalize();
AimingValues.AimingAngle.X = Delta.Yaw;
AimingValues.AimingAngle.Y = Delta.Pitch;
Delta = AimingValues.SmoothedAimingRotation - CharacterInformation.CharacterActorRotation;
Delta.Normalize();
SmoothedAimingAngle.X = Delta.Yaw;
SmoothedAimingAngle.Y = Delta.Pitch;
if (!RotationMode.VelocityDirection())
{
// Clamp the Aiming Pitch Angle to a range of 1 to 0 for use in the vertical aim sweeps.
AimingValues.AimSweepTime = FMath::GetMappedRangeValueClamped<float, float>({-90.0f, 90.0f}, {1.0f, 0.0f},
AimingValues.AimingAngle.Y);
// Use the Aiming Yaw Angle divided by the number of spine+pelvis bones to get the amount of spine rotation
// needed to remain facing the camera direction.
AimingValues.SpineRotation.Roll = 0.0f;
AimingValues.SpineRotation.Pitch = 0.0f;
AimingValues.SpineRotation.Yaw = AimingValues.AimingAngle.X / 4.0f;
}
else if (CharacterInformation.bHasMovementInput)
{
// Get the delta between the Movement Input rotation and Actor rotation and map it to a range of 0-1.
// This value is used in the aim offset behavior to make the character look toward the Movement Input.
Delta = CharacterInformation.MovementInput.ToOrientationRotator() - CharacterInformation.CharacterActorRotation;
Delta.Normalize();
const float InterpTarget = FMath::GetMappedRangeValueClamped<float, float>({-180.0f, 180.0f}, {0.0f, 1.0f}, Delta.Yaw);
AimingValues.InputYawOffsetTime = FMath::FInterpTo(AimingValues.InputYawOffsetTime, InterpTarget,
DeltaSeconds, Config.InputYawOffsetInterpSpeed);
}
// Separate the Aiming Yaw Angle into 3 separate Yaw Times. These 3 values are used in the Aim Offset behavior
// to improve the blending of the aim offset when rotating completely around the character.
// This allows you to keep the aiming responsive but still smoothly blend from left to right or right to left.
AimingValues.LeftYawTime = FMath::GetMappedRangeValueClamped<float, float>({0.0f, 180.0f}, {0.5f, 0.0f},
FMath::Abs(SmoothedAimingAngle.X));
AimingValues.RightYawTime = FMath::GetMappedRangeValueClamped<float, float>({0.0f, 180.0f}, {0.5f, 1.0f},
FMath::Abs(SmoothedAimingAngle.X));
AimingValues.ForwardYawTime = FMath::GetMappedRangeValueClamped<float, float>({-180.0f, 180.0f}, {0.0f, 1.0f},
SmoothedAimingAngle.X);
}
void UALSCharacterAnimInstance::UpdateLayerValues()
{
// Get the Aim Offset weight by getting the opposite of the Aim Offset Mask.
LayerBlendingValues.EnableAimOffset = FMath::Lerp(1.0f, 0.0f, GetCurveValue(NAME_Mask_AimOffset));
// Set the Base Pose weights
LayerBlendingValues.BasePose_N = GetCurveValue(NAME_BasePose_N);
LayerBlendingValues.BasePose_CLF = GetCurveValue(NAME_BasePose_CLF);
// Set the Additive amount weights for each body part
LayerBlendingValues.Spine_Add = GetCurveValue(NAME_Layering_Spine_Add);
LayerBlendingValues.Head_Add = GetCurveValue(NAME_Layering_Head_Add);
LayerBlendingValues.Arm_L_Add = GetCurveValue(NAME_Layering_Arm_L_Add);
LayerBlendingValues.Arm_R_Add = GetCurveValue(NAME_Layering_Arm_R_Add);
// Set the Hand Override weights
LayerBlendingValues.Hand_R = GetCurveValue(NAME_Layering_Hand_R);
LayerBlendingValues.Hand_L = GetCurveValue(NAME_Layering_Hand_L);
// Blend and set the Hand IK weights to ensure they only are weighted if allowed by the Arm layers.
LayerBlendingValues.EnableHandIK_L = FMath::Lerp(0.0f, GetCurveValue(NAME_Enable_HandIK_L),
GetCurveValue(NAME_Layering_Arm_L));
LayerBlendingValues.EnableHandIK_R = FMath::Lerp(0.0f, GetCurveValue(NAME_Enable_HandIK_R),
GetCurveValue(NAME_Layering_Arm_R));
// Set whether the arms should blend in mesh space or local space.
// The Mesh space weight will always be 1 unless the Local Space (LS) curve is fully weighted.
LayerBlendingValues.Arm_L_LS = GetCurveValue(NAME_Layering_Arm_L_LS);
LayerBlendingValues.Arm_L_MS = static_cast<float>(1 - FMath::FloorToInt(LayerBlendingValues.Arm_L_LS));
LayerBlendingValues.Arm_R_LS = GetCurveValue(NAME_Layering_Arm_R_LS);
LayerBlendingValues.Arm_R_MS = static_cast<float>(1 - FMath::FloorToInt(LayerBlendingValues.Arm_R_LS));
}
void UALSCharacterAnimInstance::UpdateFootIK(float DeltaSeconds)
{
FVector FootOffsetLTarget = FVector::ZeroVector;
FVector FootOffsetRTarget = FVector::ZeroVector;
// Update Foot Locking values.
SetFootLocking(DeltaSeconds, NAME_Enable_FootIK_L, NAME_FootLock_L,
IkFootL_BoneName, FootIKValues.FootLock_L_Alpha, FootIKValues.UseFootLockCurve_L,
FootIKValues.FootLock_L_Location, FootIKValues.FootLock_L_Rotation);
SetFootLocking(DeltaSeconds, NAME_Enable_FootIK_R, NAME_FootLock_R,
IkFootR_BoneName, FootIKValues.FootLock_R_Alpha, FootIKValues.UseFootLockCurve_R,
FootIKValues.FootLock_R_Location, FootIKValues.FootLock_R_Rotation);
if (MovementState.InAir())
{
// Reset IK Offsets if In Air
SetPelvisIKOffset(DeltaSeconds, FVector::ZeroVector, FVector::ZeroVector);
ResetIKOffsets(DeltaSeconds);
}
else if (!MovementState.Ragdoll())
{
// Update all Foot Lock and Foot Offset values when not In Air
SetFootOffsets(DeltaSeconds, NAME_Enable_FootIK_L, IkFootL_BoneName, NAME__ALSCharacterAnimInstance__root,
FootOffsetLTarget,
FootIKValues.FootOffset_L_Location, FootIKValues.FootOffset_L_Rotation);
SetFootOffsets(DeltaSeconds, NAME_Enable_FootIK_R, IkFootR_BoneName, NAME__ALSCharacterAnimInstance__root,
FootOffsetRTarget,
FootIKValues.FootOffset_R_Location, FootIKValues.FootOffset_R_Rotation);
SetPelvisIKOffset(DeltaSeconds, FootOffsetLTarget, FootOffsetRTarget);
}
}
void UALSCharacterAnimInstance::SetFootLocking(float DeltaSeconds, FName EnableFootIKCurve, FName FootLockCurve,
FName IKFootBone, float& CurFootLockAlpha, bool& UseFootLockCurve,
FVector& CurFootLockLoc, FRotator& CurFootLockRot)
{
if (GetCurveValue(EnableFootIKCurve) <= 0.0f)
{
return;
}
// Step 1: Set Local FootLock Curve value
float FootLockCurveVal;
if (UseFootLockCurve)
{
UseFootLockCurve = FMath::Abs(GetCurveValue(NAME__ALSCharacterAnimInstance__RotationAmount)) <= 0.001f ||
Character->GetLocalRole() != ROLE_AutonomousProxy;
FootLockCurveVal = GetCurveValue(FootLockCurve) * (1.f / GetSkelMeshComponent()->AnimUpdateRateParams->UpdateRate);
}
else
{
UseFootLockCurve = GetCurveValue(FootLockCurve) >= 0.99f;
FootLockCurveVal = 0.0f;
}
// Step 2: Only update the FootLock Alpha if the new value is less than the current, or it equals 1. This makes it
// so that the foot can only blend out of the locked position or lock to a new position, and never blend in.
if (FootLockCurveVal >= 0.99f || FootLockCurveVal < CurFootLockAlpha)
{
CurFootLockAlpha = FootLockCurveVal;
}
// Step 3: If the Foot Lock curve equals 1, save the new lock location and rotation in component space as the target.
if (CurFootLockAlpha >= 0.99f)
{
const FTransform& OwnerTransform =
GetOwningComponent()->GetSocketTransform(IKFootBone, RTS_Component);
CurFootLockLoc = OwnerTransform.GetLocation();
CurFootLockRot = OwnerTransform.Rotator();
}
// Step 4: If the Foot Lock Alpha has a weight,
// update the Foot Lock offsets to keep the foot planted in place while the capsule moves.
if (CurFootLockAlpha > 0.0f)
{
SetFootLockOffsets(DeltaSeconds, CurFootLockLoc, CurFootLockRot);
}
}
void UALSCharacterAnimInstance::SetFootLockOffsets(float DeltaSeconds, FVector& LocalLoc, FRotator& LocalRot)
{
FRotator RotationDifference = FRotator::ZeroRotator;
// Use the delta between the current and last updated rotation to find how much the foot should be rotated
// to remain planted on the ground.
if (Character->GetCharacterMovement()->IsMovingOnGround())
{
RotationDifference = CharacterInformation.CharacterActorRotation - Character->GetCharacterMovement()->
GetLastUpdateRotation();
RotationDifference.Normalize();
}
// Get the distance traveled between frames relative to the mesh rotation
// to find how much the foot should be offset to remain planted on the ground.
const FVector& LocationDifference = GetOwningComponent()->GetComponentRotation().UnrotateVector(
CharacterInformation.Velocity * DeltaSeconds);
// Subtract the location difference from the current local location and rotate
// it by the rotation difference to keep the foot planted in component space.
LocalLoc = (LocalLoc - LocationDifference).RotateAngleAxis(RotationDifference.Yaw, FVector::DownVector);
// Subtract the Rotation Difference from the current Local Rotation to get the new local rotation.
FRotator Delta = LocalRot - RotationDifference;
Delta.Normalize();
LocalRot = Delta;
}
void UALSCharacterAnimInstance::SetPelvisIKOffset(float DeltaSeconds, FVector FootOffsetLTarget,
FVector FootOffsetRTarget)
{
// Calculate the Pelvis Alpha by finding the average Foot IK weight. If the alpha is 0, clear the offset.
FootIKValues.PelvisAlpha =
(GetCurveValue(NAME_Enable_FootIK_L) + GetCurveValue(NAME_Enable_FootIK_R)) / 2.0f;
if (FootIKValues.PelvisAlpha > 0.0f)
{
// Step 1: Set the new Pelvis Target to be the lowest Foot Offset
const FVector PelvisTarget = FootOffsetLTarget.Z < FootOffsetRTarget.Z ? FootOffsetLTarget : FootOffsetRTarget;
// Step 2: Interp the Current Pelvis Offset to the new target value.
//Interpolate at different speeds based on whether the new target is above or below the current one.
const float InterpSpeed = PelvisTarget.Z > FootIKValues.PelvisOffset.Z ? 10.0f : 15.0f;
FootIKValues.PelvisOffset =
FMath::VInterpTo(FootIKValues.PelvisOffset, PelvisTarget, DeltaSeconds, InterpSpeed);
}
else
{
FootIKValues.PelvisOffset = FVector::ZeroVector;
}
}
void UALSCharacterAnimInstance::ResetIKOffsets(float DeltaSeconds)
{
// Interp Foot IK offsets back to 0
FootIKValues.FootOffset_L_Location = FMath::VInterpTo(FootIKValues.FootOffset_L_Location,
FVector::ZeroVector, DeltaSeconds, 15.0f);
FootIKValues.FootOffset_R_Location = FMath::VInterpTo(FootIKValues.FootOffset_R_Location,
FVector::ZeroVector, DeltaSeconds, 15.0f);
FootIKValues.FootOffset_L_Rotation = FMath::RInterpTo(FootIKValues.FootOffset_L_Rotation,
FRotator::ZeroRotator, DeltaSeconds, 15.0f);
FootIKValues.FootOffset_R_Rotation = FMath::RInterpTo(FootIKValues.FootOffset_R_Rotation,
FRotator::ZeroRotator, DeltaSeconds, 15.0f);
}
void UALSCharacterAnimInstance::SetFootOffsets(float DeltaSeconds, FName EnableFootIKCurve, FName IKFootBone,
FName RootBone, FVector& CurLocationTarget, FVector& CurLocationOffset,
FRotator& CurRotationOffset)
{
// Only update Foot IK offset values if the Foot IK curve has a weight. If it equals 0, clear the offset values.
if (GetCurveValue(EnableFootIKCurve) <= 0)
{
CurLocationOffset = FVector::ZeroVector;
CurRotationOffset = FRotator::ZeroRotator;
return;
}
// Step 1: Trace downward from the foot location to find the geometry.
// If the surface is walkable, save the Impact Location and Normal.
USkeletalMeshComponent* OwnerComp = GetOwningComponent();
FVector IKFootFloorLoc = OwnerComp->GetSocketLocation(IKFootBone);
IKFootFloorLoc.Z = OwnerComp->GetSocketLocation(RootBone).Z;
UWorld* World = GetWorld();
check(World);
FCollisionQueryParams Params;
Params.AddIgnoredActor(Character);
const FVector TraceStart = IKFootFloorLoc + FVector(0.0, 0.0, Config.IK_TraceDistanceAboveFoot);
const FVector TraceEnd = IKFootFloorLoc - FVector(0.0, 0.0, Config.IK_TraceDistanceBelowFoot);
FHitResult HitResult;
const bool bHit = World->LineTraceSingleByChannel(HitResult,
TraceStart,
TraceEnd,
ECC_Visibility, Params);
if (ALSDebugComponent && ALSDebugComponent->GetShowTraces())
{
UALSDebugComponent::DrawDebugLineTraceSingle(
World,
TraceStart,
TraceEnd,
EDrawDebugTrace::Type::ForOneFrame,
bHit,
HitResult,
FLinearColor::Red,
FLinearColor::Green,
5.0f);
}
FRotator TargetRotOffset = FRotator::ZeroRotator;
if (Character->GetCharacterMovement()->IsWalkable(HitResult))
{
FVector ImpactPoint = HitResult.ImpactPoint;
FVector ImpactNormal = HitResult.ImpactNormal;
// Step 1.1: Find the difference in location from the Impact point and the expected (flat) floor location.
// These values are offset by the normal multiplied by the
// foot height to get better behavior on angled surfaces.
CurLocationTarget = (ImpactPoint + ImpactNormal * Config.FootHeight) -
(IKFootFloorLoc + FVector(0, 0, Config.FootHeight));
// Step 1.2: Calculate the Rotation offset by getting the Atan2 of the Impact Normal.
TargetRotOffset.Pitch = -FMath::RadiansToDegrees(FMath::Atan2(ImpactNormal.X, ImpactNormal.Z));
TargetRotOffset.Roll = FMath::RadiansToDegrees(FMath::Atan2(ImpactNormal.Y, ImpactNormal.Z));
}
// Step 2: Interp the Current Location Offset to the new target value.
// Interpolate at different speeds based on whether the new target is above or below the current one.
const float InterpSpeed = CurLocationOffset.Z > CurLocationTarget.Z ? 30.f : 15.0f;
CurLocationOffset = FMath::VInterpTo(CurLocationOffset, CurLocationTarget, DeltaSeconds, InterpSpeed);
// Step 3: Interp the Current Rotation Offset to the new target value.
CurRotationOffset = FMath::RInterpTo(CurRotationOffset, TargetRotOffset, DeltaSeconds, 30.0f);
}
void UALSCharacterAnimInstance::RotateInPlaceCheck()
{
// Step 1: Check if the character should rotate left or right by checking if the Aiming Angle exceeds the threshold.
Grounded.bRotateL = AimingValues.AimingAngle.X < RotateInPlace.RotateMinThreshold;
Grounded.bRotateR = AimingValues.AimingAngle.X > RotateInPlace.RotateMaxThreshold;
// Step 2: If the character should be rotating, set the Rotate Rate to scale with the Aim Yaw Rate.
// This makes the character rotate faster when moving the camera faster.
if (Grounded.bRotateL || Grounded.bRotateR)
{
Grounded.RotateRate = FMath::GetMappedRangeValueClamped<float, float>(
{RotateInPlace.AimYawRateMinRange, RotateInPlace.AimYawRateMaxRange},
{RotateInPlace.MinPlayRate, RotateInPlace.MaxPlayRate},
CharacterInformation.AimYawRate);
}
}
void UALSCharacterAnimInstance::TurnInPlaceCheck(float DeltaSeconds)
{
// Step 1: Check if Aiming angle is outside of the Turn Check Min Angle, and if the Aim Yaw Rate is below the Aim Yaw Rate Limit.
// If so, begin counting the Elapsed Delay Time. If not, reset the Elapsed Delay Time.
// This ensures the conditions remain true for a sustained period of time before turning in place.
if (FMath::Abs(AimingValues.AimingAngle.X) <= TurnInPlaceValues.TurnCheckMinAngle ||
CharacterInformation.AimYawRate >= TurnInPlaceValues.AimYawRateLimit)
{
TurnInPlaceValues.ElapsedDelayTime = 0.0f;
return;
}
TurnInPlaceValues.ElapsedDelayTime += DeltaSeconds;
const float ClampedAimAngle = FMath::GetMappedRangeValueClamped<float, float>({TurnInPlaceValues.TurnCheckMinAngle, 180.0f},
{
TurnInPlaceValues.MinAngleDelay,
TurnInPlaceValues.MaxAngleDelay
},
AimingValues.AimingAngle.X);
// Step 2: Check if the Elapsed Delay time exceeds the set delay (mapped to the turn angle range). If so, trigger a Turn In Place.
if (TurnInPlaceValues.ElapsedDelayTime > ClampedAimAngle)
{
FRotator TurnInPlaceYawRot = CharacterInformation.AimingRotation;
TurnInPlaceYawRot.Roll = 0.0f;
TurnInPlaceYawRot.Pitch = 0.0f;
TurnInPlace(TurnInPlaceYawRot, 1.0f, 0.0f, false);
}
}
void UALSCharacterAnimInstance::DynamicTransitionCheck()
{
// Check each foot to see if the location difference between the IK_Foot bone and its desired / target location
// (determined via a virtual bone) exceeds a threshold. If it does, play an additive transition animation on that foot.
// The currently set transition plays the second half of a 2 foot transition animation, so that only a single foot moves.
// Because only the IK_Foot bone can be locked, the separate virtual bone allows the system to know its desired location when locked.
FTransform SocketTransformA = GetOwningComponent()->GetSocketTransform(IkFootL_BoneName, RTS_Component);
FTransform SocketTransformB = GetOwningComponent()->GetSocketTransform(
NAME_VB___foot_target_l, RTS_Component);
float Distance = (SocketTransformB.GetLocation() - SocketTransformA.GetLocation()).Size();
if (Distance > Config.DynamicTransitionThreshold)
{
FALSDynamicMontageParams Params;
Params.Animation = TransitionAnim_R;
Params.BlendInTime = 0.2f;
Params.BlendOutTime = 0.2f;
Params.PlayRate = 1.5f;
Params.StartTime = 0.8f;
PlayDynamicTransition(0.1f, Params);
}
SocketTransformA = GetOwningComponent()->GetSocketTransform(IkFootR_BoneName, RTS_Component);
SocketTransformB = GetOwningComponent()->GetSocketTransform(NAME_VB___foot_target_r, RTS_Component);
Distance = (SocketTransformB.GetLocation() - SocketTransformA.GetLocation()).Size();
if (Distance > Config.DynamicTransitionThreshold)
{
FALSDynamicMontageParams Params;
Params.Animation = TransitionAnim_L;
Params.BlendInTime = 0.2f;
Params.BlendOutTime = 0.2f;
Params.PlayRate = 1.5f;
Params.StartTime = 0.8f;
PlayDynamicTransition(0.1f, Params);
}
}
void UALSCharacterAnimInstance::UpdateMovementValues(float DeltaSeconds)
{
// Interp and set the Velocity Blend.
const FALSVelocityBlend& TargetBlend = CalculateVelocityBlend();
VelocityBlend.F = FMath::FInterpTo(VelocityBlend.F, TargetBlend.F, DeltaSeconds, Config.VelocityBlendInterpSpeed);
VelocityBlend.B = FMath::FInterpTo(VelocityBlend.B, TargetBlend.B, DeltaSeconds, Config.VelocityBlendInterpSpeed);
VelocityBlend.L = FMath::FInterpTo(VelocityBlend.L, TargetBlend.L, DeltaSeconds, Config.VelocityBlendInterpSpeed);
VelocityBlend.R = FMath::FInterpTo(VelocityBlend.R, TargetBlend.R, DeltaSeconds, Config.VelocityBlendInterpSpeed);
// Set the Diagonal Scale Amount.
Grounded.DiagonalScaleAmount = CalculateDiagonalScaleAmount();
// Set the Relative Acceleration Amount and Interp the Lean Amount.
RelativeAccelerationAmount = CalculateRelativeAccelerationAmount();
LeanAmount.LR = FMath::FInterpTo(LeanAmount.LR, RelativeAccelerationAmount.Y, DeltaSeconds,
Config.GroundedLeanInterpSpeed);
LeanAmount.FB = FMath::FInterpTo(LeanAmount.FB, RelativeAccelerationAmount.X, DeltaSeconds,
Config.GroundedLeanInterpSpeed);
// Set the Walk Run Blend
Grounded.WalkRunBlend = CalculateWalkRunBlend();
// Set the Stride Blend
Grounded.StrideBlend = CalculateStrideBlend();
// Set the Standing and Crouching Play Rates
Grounded.StandingPlayRate = CalculateStandingPlayRate();
Grounded.CrouchingPlayRate = CalculateCrouchingPlayRate();
}
void UALSCharacterAnimInstance::UpdateRotationValues()
{
// Set the Movement Direction
MovementDirection = CalculateMovementDirection();
// Set the Yaw Offsets. These values influence the "YawOffset" curve in the AnimGraph and are used to offset
// the characters rotation for more natural movement. The curves allow for fine control over how the offset
// behaves for each movement direction.
FRotator Delta = CharacterInformation.Velocity.ToOrientationRotator() - CharacterInformation.AimingRotation;
Delta.Normalize();
const FVector& FBOffset = YawOffset_FB->GetVectorValue(Delta.Yaw);
Grounded.FYaw = FBOffset.X;
Grounded.BYaw = FBOffset.Y;
const FVector& LROffset = YawOffset_LR->GetVectorValue(Delta.Yaw);
Grounded.LYaw = LROffset.X;
Grounded.RYaw = LROffset.Y;
}
void UALSCharacterAnimInstance::UpdateInAirValues(float DeltaSeconds)
{
// Update the fall speed. Setting this value only while in the air allows you to use it within the AnimGraph for the landing strength.
// If not, the Z velocity would return to 0 on landing.
InAir.FallSpeed = CharacterInformation.Velocity.Z;
// Set the Land Prediction weight.
InAir.LandPrediction = CalculateLandPrediction();
// Interp and set the In Air Lean Amount
const FALSLeanAmount& InAirLeanAmount = CalculateAirLeanAmount();
LeanAmount.LR = FMath::FInterpTo(LeanAmount.LR, InAirLeanAmount.LR, DeltaSeconds, Config.GroundedLeanInterpSpeed);
LeanAmount.FB = FMath::FInterpTo(LeanAmount.FB, InAirLeanAmount.FB, DeltaSeconds, Config.GroundedLeanInterpSpeed);
}
void UALSCharacterAnimInstance::UpdateRagdollValues()
{
// Scale the Flail Rate by the velocity length. The faster the ragdoll moves, the faster the character will flail.
const float VelocityLength = GetOwningComponent()->GetPhysicsLinearVelocity(NAME__ALSCharacterAnimInstance__root).Size();
FlailRate = FMath::GetMappedRangeValueClamped<float, float>({0.0f, 1000.0f}, {0.0f, 1.0f}, VelocityLength);
}
float UALSCharacterAnimInstance::GetAnimCurveClamped(const FName& Name, float Bias, float ClampMin,
float ClampMax) const
{
return FMath::Clamp(GetCurveValue(Name) + Bias, ClampMin, ClampMax);
}
FALSVelocityBlend UALSCharacterAnimInstance::CalculateVelocityBlend() const
{
// Calculate the Velocity Blend. This value represents the velocity amount of the actor in each direction (normalized so that
// diagonals equal .5 for each direction), and is used in a BlendMulti node to produce better
// directional blending than a standard blendspace.
const FVector LocRelativeVelocityDir =
CharacterInformation.CharacterActorRotation.UnrotateVector(CharacterInformation.Velocity.GetSafeNormal(0.1f));
const float Sum = FMath::Abs(LocRelativeVelocityDir.X) + FMath::Abs(LocRelativeVelocityDir.Y) +
FMath::Abs(LocRelativeVelocityDir.Z);
const FVector RelativeDir = LocRelativeVelocityDir / Sum;
FALSVelocityBlend Result;
Result.F = FMath::Clamp(RelativeDir.X, 0.0f, 1.0f);
Result.B = FMath::Abs(FMath::Clamp(RelativeDir.X, -1.0f, 0.0f));
Result.L = FMath::Abs(FMath::Clamp(RelativeDir.Y, -1.0f, 0.0f));
Result.R = FMath::Clamp(RelativeDir.Y, 0.0f, 1.0f);
return Result;
}
FVector UALSCharacterAnimInstance::CalculateRelativeAccelerationAmount() const
{
// Calculate the Relative Acceleration Amount. This value represents the current amount of acceleration / deceleration
// relative to the actor rotation. It is normalized to a range of -1 to 1 so that -1 equals the Max Braking Deceleration,
// and 1 equals the Max Acceleration of the Character Movement Component.
if (FVector::DotProduct(CharacterInformation.Acceleration, CharacterInformation.Velocity) > 0.0f)
{
const float MaxAcc = Character->GetCharacterMovement()->GetMaxAcceleration();
return CharacterInformation.CharacterActorRotation.UnrotateVector(
CharacterInformation.Acceleration.GetClampedToMaxSize(MaxAcc) / MaxAcc);
}
const float MaxBrakingDec = Character->GetCharacterMovement()->GetMaxBrakingDeceleration();
return
CharacterInformation.CharacterActorRotation.UnrotateVector(
CharacterInformation.Acceleration.GetClampedToMaxSize(MaxBrakingDec) / MaxBrakingDec);
}
float UALSCharacterAnimInstance::CalculateStrideBlend() const
{
// Calculate the Stride Blend. This value is used within the blendspaces to scale the stride (distance feet travel)
// so that the character can walk or run at different movement speeds.
// It also allows the walk or run gait animations to blend independently while still matching the animation speed to
// the movement speed, preventing the character from needing to play a half walk+half run blend.
// The curves are used to map the stride amount to the speed for maximum control.
const float CurveTime = CharacterInformation.Speed / GetOwningComponent()->GetComponentScale().Z;
const float ClampedGait = GetAnimCurveClamped(NAME_W_Gait, -1.0, 0.0f, 1.0f);
const float LerpedStrideBlend =
FMath::Lerp(StrideBlend_N_Walk->GetFloatValue(CurveTime), StrideBlend_N_Run->GetFloatValue(CurveTime),
ClampedGait);
return FMath::Lerp(LerpedStrideBlend, StrideBlend_C_Walk->GetFloatValue(CharacterInformation.Speed),
GetCurveValue(NAME_BasePose_CLF));
}
float UALSCharacterAnimInstance::CalculateWalkRunBlend() const
{
// Calculate the Walk Run Blend. This value is used within the Blendspaces to blend between walking and running.
return Gait.Walking() ? 0.0f : 1.0;
}
float UALSCharacterAnimInstance::CalculateStandingPlayRate() const
{
// Calculate the Play Rate by dividing the Character's speed by the Animated Speed for each gait.
// The lerps are determined by the "W_Gait" anim curve that exists on every locomotion cycle so
// that the play rate is always in sync with the currently blended animation.
// The value is also divided by the Stride Blend and the mesh scale so that the play rate increases as the stride or scale gets smaller
const float LerpedSpeed = FMath::Lerp(CharacterInformation.Speed / Config.AnimatedWalkSpeed,
CharacterInformation.Speed / Config.AnimatedRunSpeed,
GetAnimCurveClamped(NAME_W_Gait, -1.0f, 0.0f, 1.0f));
const float SprintAffectedSpeed = FMath::Lerp(LerpedSpeed, CharacterInformation.Speed / Config.AnimatedSprintSpeed,
GetAnimCurveClamped(NAME_W_Gait, -2.0f, 0.0f, 1.0f));
return FMath::Clamp((SprintAffectedSpeed / Grounded.StrideBlend) / GetOwningComponent()->GetComponentScale().Z,
0.0f, 3.0f);
}
float UALSCharacterAnimInstance::CalculateDiagonalScaleAmount() const
{
// Calculate the Diagonal Scale Amount. This value is used to scale the Foot IK Root bone to make the Foot IK bones
// cover more distance on the diagonal blends. Without scaling, the feet would not move far enough on the diagonal
// direction due to the linear translational blending of the IK bones. The curve is used to easily map the value.
return DiagonalScaleAmountCurve->GetFloatValue(FMath::Abs(VelocityBlend.F + VelocityBlend.B));
}
float UALSCharacterAnimInstance::CalculateCrouchingPlayRate() const
{
// Calculate the Crouching Play Rate by dividing the Character's speed by the Animated Speed.
// This value needs to be separate from the standing play rate to improve the blend from crouch to stand while in motion.
return FMath::Clamp(
CharacterInformation.Speed / Config.AnimatedCrouchSpeed / Grounded.StrideBlend / GetOwningComponent()->
GetComponentScale().Z,
0.0f, 2.0f);
}
float UALSCharacterAnimInstance::CalculateLandPrediction() const
{
// Calculate the land prediction weight by tracing in the velocity direction to find a walkable surface the character
// is falling toward, and getting the 'Time' (range of 0-1, 1 being maximum, 0 being about to land) till impact.
// The Land Prediction Curve is used to control how the time affects the final weight for a smooth blend.
if (InAir.FallSpeed >= -200.0f)
{
return 0.0f;
}
const UCapsuleComponent* CapsuleComp = Character->GetCapsuleComponent();
const FVector& CapsuleWorldLoc = CapsuleComp->GetComponentLocation();
const float VelocityZ = CharacterInformation.Velocity.Z;
FVector VelocityClamped = CharacterInformation.Velocity;
VelocityClamped.Z = FMath::Clamp(VelocityZ, -4000.0f, -200.0f);
VelocityClamped.Normalize();
const FVector TraceLength = VelocityClamped * FMath::GetMappedRangeValueClamped<float, float>(
{0.0f, -4000.0f}, {50.0f, 2000.0f}, VelocityZ);
UWorld* World = GetWorld();
check(World);
FCollisionQueryParams Params;
Params.AddIgnoredActor(Character);
FHitResult HitResult;
const FCollisionShape CapsuleCollisionShape = FCollisionShape::MakeCapsule(CapsuleComp->GetUnscaledCapsuleRadius(),
CapsuleComp->GetUnscaledCapsuleHalfHeight());
const bool bHit = World->SweepSingleByChannel(HitResult, CapsuleWorldLoc, CapsuleWorldLoc + TraceLength, FQuat::Identity,
ECC_Visibility, CapsuleCollisionShape, Params);
if (ALSDebugComponent && ALSDebugComponent->GetShowTraces())
{
UALSDebugComponent::DrawDebugCapsuleTraceSingle(World,
CapsuleWorldLoc,
CapsuleWorldLoc + TraceLength,
CapsuleCollisionShape,
EDrawDebugTrace::Type::ForOneFrame,
bHit,
HitResult,
FLinearColor::Red,
FLinearColor::Green,
5.0f);
}
if (Character->GetCharacterMovement()->IsWalkable(HitResult))
{
return FMath::Lerp(LandPredictionCurve->GetFloatValue(HitResult.Time), 0.0f,
GetCurveValue(NAME_Mask_LandPrediction));
}
return 0.0f;
}
FALSLeanAmount UALSCharacterAnimInstance::CalculateAirLeanAmount() const
{
// Use the relative Velocity direction and amount to determine how much the character should lean while in air.
// The Lean In Air curve gets the Fall Speed and is used as a multiplier to smoothly reverse the leaning direction
// when transitioning from moving upwards to moving downwards.
FALSLeanAmount CalcLeanAmount;
const FVector& UnrotatedVel = CharacterInformation.CharacterActorRotation.UnrotateVector(
CharacterInformation.Velocity) / 350.0f;
FVector2D InversedVect(UnrotatedVel.Y, UnrotatedVel.X);
InversedVect *= LeanInAirCurve->GetFloatValue(InAir.FallSpeed);
CalcLeanAmount.LR = InversedVect.X;
CalcLeanAmount.FB = InversedVect.Y;
return CalcLeanAmount;
}
EALSMovementDirection UALSCharacterAnimInstance::CalculateMovementDirection() const
{
// Calculate the Movement Direction. This value represents the direction the character is moving relative to the camera
// during the Looking Direction / Aiming rotation modes, and is used in the Cycle Blending Anim Layers to blend to the
// appropriate directional states.
if (Gait.Sprinting() || RotationMode.VelocityDirection())
{
return EALSMovementDirection::Forward;
}
FRotator Delta = CharacterInformation.Velocity.ToOrientationRotator() - CharacterInformation.AimingRotation;
Delta.Normalize();
return UALSMathLibrary::CalculateQuadrant(MovementDirection, 70.0f, -70.0f, 110.0f, -110.0f, 5.0f, Delta.Yaw);
}
void UALSCharacterAnimInstance::TurnInPlace(FRotator TargetRotation, float PlayRateScale, float StartTime,
bool OverrideCurrent)
{
// Step 1: Set Turn Angle
FRotator Delta = TargetRotation - CharacterInformation.CharacterActorRotation;
Delta.Normalize();
const float TurnAngle = Delta.Yaw;
// Step 2: Choose Turn Asset based on the Turn Angle and Stance
FALSTurnInPlaceAsset TargetTurnAsset;
if (Stance.Standing())
{
if (FMath::Abs(TurnAngle) < TurnInPlaceValues.Turn180Threshold)
{
TargetTurnAsset = TurnAngle < 0.0f
? TurnInPlaceValues.N_TurnIP_L90
: TurnInPlaceValues.N_TurnIP_R90;
}
else
{
TargetTurnAsset = TurnAngle < 0.0f
? TurnInPlaceValues.N_TurnIP_L180
: TurnInPlaceValues.N_TurnIP_R180;
}
}
else
{
if (FMath::Abs(TurnAngle) < TurnInPlaceValues.Turn180Threshold)
{
TargetTurnAsset = TurnAngle < 0.0f
? TurnInPlaceValues.CLF_TurnIP_L90
: TurnInPlaceValues.CLF_TurnIP_R90;
}
else
{
TargetTurnAsset = TurnAngle < 0.0f
? TurnInPlaceValues.CLF_TurnIP_L180
: TurnInPlaceValues.CLF_TurnIP_R180;
}
}
// Step 3: If the Target Turn Animation is not playing or set to be overriden, play the turn animation as a dynamic montage.
if (!OverrideCurrent && IsPlayingSlotAnimation(TargetTurnAsset.Animation, TargetTurnAsset.SlotName))
{
return;
}
PlaySlotAnimationAsDynamicMontage(TargetTurnAsset.Animation, TargetTurnAsset.SlotName, 0.2f, 0.2f,
TargetTurnAsset.PlayRate * PlayRateScale, 1, 0.0f, StartTime);
// Step 4: Scale the rotation amount (gets scaled in AnimGraph) to compensate for turn angle (If Allowed) and play rate.
if (TargetTurnAsset.ScaleTurnAngle)
{
Grounded.RotationScale = (TurnAngle / TargetTurnAsset.AnimatedAngle) * TargetTurnAsset.PlayRate * PlayRateScale;
}
else
{
Grounded.RotationScale = TargetTurnAsset.PlayRate * PlayRateScale;
}
}
void UALSCharacterAnimInstance::OnJumped()
{
InAir.bJumped = true;
InAir.JumpPlayRate = FMath::GetMappedRangeValueClamped<float, float>({0.0f, 600.0f}, {1.2f, 1.5f}, CharacterInformation.Speed);
GetWorld()->GetTimerManager().SetTimer(OnJumpedTimer, this,
&UALSCharacterAnimInstance::OnJumpedDelay, 0.1f, false);
}
void UALSCharacterAnimInstance::OnPivot()
{
Grounded.bPivot = CharacterInformation.Speed < Config.TriggerPivotSpeedLimit;
GetWorld()->GetTimerManager().SetTimer(OnPivotTimer, this,
&UALSCharacterAnimInstance::OnPivotDelay, 0.1f, false);
}