Spline movement works for walking and falling
This commit is contained in:
parent
2bfd6d4f15
commit
75c325c4cc
BIN
Pawn_Unreal/Content/Characters/BP_Judy.uasset
(Stored with Git LFS)
BIN
Pawn_Unreal/Content/Characters/BP_Judy.uasset
(Stored with Git LFS)
Binary file not shown.
BIN
Pawn_Unreal/Content/Input/IMC_Judy.uasset
(Stored with Git LFS)
BIN
Pawn_Unreal/Content/Input/IMC_Judy.uasset
(Stored with Git LFS)
Binary file not shown.
BIN
Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/0/91/BERD0JFDCI5RAYAPS4DY1P.uasset
(Stored with Git LFS)
BIN
Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/0/91/BERD0JFDCI5RAYAPS4DY1P.uasset
(Stored with Git LFS)
Binary file not shown.
BIN
Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/0/XC/EES9G2P00B5EO5FPWE8N4U.uasset
(Stored with Git LFS)
BIN
Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/0/XC/EES9G2P00B5EO5FPWE8N4U.uasset
(Stored with Git LFS)
Binary file not shown.
BIN
Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/1/LE/RIS6ZA9R1KK7MS3EGVDKZV.uasset
(Stored with Git LFS)
BIN
Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/1/LE/RIS6ZA9R1KK7MS3EGVDKZV.uasset
(Stored with Git LFS)
Binary file not shown.
BIN
Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/1/ZO/2T6IY0BJD55BZXPJC2K1VW.uasset
(Stored with Git LFS)
BIN
Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/1/ZO/2T6IY0BJD55BZXPJC2K1VW.uasset
(Stored with Git LFS)
Binary file not shown.
BIN
Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/3/7A/SCU1D1LU7J4NS7X4MENA7G.uasset
(Stored with Git LFS)
Normal file
BIN
Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/3/7A/SCU1D1LU7J4NS7X4MENA7G.uasset
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/3/S1/Q0FDQMTTCM0M6RLWXOTPMF.uasset
(Stored with Git LFS)
BIN
Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/3/S1/Q0FDQMTTCM0M6RLWXOTPMF.uasset
(Stored with Git LFS)
Binary file not shown.
BIN
Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/3/ZC/SPZ5ZSIG7BW3194FWIBEWC.uasset
(Stored with Git LFS)
BIN
Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/3/ZC/SPZ5ZSIG7BW3194FWIBEWC.uasset
(Stored with Git LFS)
Binary file not shown.
BIN
Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/3/ZX/V7QFRP3DMWRT8NZCJQYVRB.uasset
(Stored with Git LFS)
Normal file
BIN
Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/3/ZX/V7QFRP3DMWRT8NZCJQYVRB.uasset
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/4/OH/3YZOO5HQPTMDBNJPPM4RV0.uasset
(Stored with Git LFS)
BIN
Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/4/OH/3YZOO5HQPTMDBNJPPM4RV0.uasset
(Stored with Git LFS)
Binary file not shown.
BIN
Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/5/V3/7TDZ89SOYDGI26YWXQ04D4.uasset
(Stored with Git LFS)
Normal file
BIN
Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/5/V3/7TDZ89SOYDGI26YWXQ04D4.uasset
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/9/IH/5T09JNLLSNG3U1KPWNFK3T.uasset
(Stored with Git LFS)
Normal file
BIN
Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/9/IH/5T09JNLLSNG3U1KPWNFK3T.uasset
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/9/RQ/JYNZEWOG6SXNE3HGUCSZIE.uasset
(Stored with Git LFS)
BIN
Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/9/RQ/JYNZEWOG6SXNE3HGUCSZIE.uasset
(Stored with Git LFS)
Binary file not shown.
BIN
Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/A/BZ/XZUBN8NU0UZ2W8T2MBWCYN.uasset
(Stored with Git LFS)
BIN
Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/A/BZ/XZUBN8NU0UZ2W8T2MBWCYN.uasset
(Stored with Git LFS)
Binary file not shown.
BIN
Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/A/U2/4ECO0ADAHJEURYSR8OYVEX.uasset
(Stored with Git LFS)
BIN
Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/A/U2/4ECO0ADAHJEURYSR8OYVEX.uasset
(Stored with Git LFS)
Binary file not shown.
BIN
Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/B/0Z/X5N2DTGOJMHQWHOBBKFNDX.uasset
(Stored with Git LFS)
Normal file
BIN
Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/B/0Z/X5N2DTGOJMHQWHOBBKFNDX.uasset
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/C/6U/C2O2TW7RAVDUQGZS7QLKMX.uasset
(Stored with Git LFS)
Normal file
BIN
Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/C/6U/C2O2TW7RAVDUQGZS7QLKMX.uasset
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/C/BU/19EKBOJAPNXX8XDUPL0Z18.uasset
(Stored with Git LFS)
BIN
Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/C/BU/19EKBOJAPNXX8XDUPL0Z18.uasset
(Stored with Git LFS)
Binary file not shown.
BIN
Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/D/VD/LJAQY01SJR93NPW0FPCPIL.uasset
(Stored with Git LFS)
BIN
Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/D/VD/LJAQY01SJR93NPW0FPCPIL.uasset
(Stored with Git LFS)
Binary file not shown.
BIN
Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/E/86/NKWQISSIQGRX07O716DOXO.uasset
(Stored with Git LFS)
Normal file
BIN
Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/E/86/NKWQISSIQGRX07O716DOXO.uasset
(Stored with Git LFS)
Normal file
Binary file not shown.
@ -40,7 +40,48 @@ bool UPwnCharacterMovementComponent::IsMovingOnGround() const {
|
||||
return Super::IsMovingOnGround() || IsCustomMovementMode(SplineWalking);
|
||||
}
|
||||
|
||||
bool UPwnCharacterMovementComponent::IsFalling() const {
|
||||
return Super::IsFalling() || IsCustomMovementMode(SplineFalling);
|
||||
}
|
||||
|
||||
void UPwnCharacterMovementComponent::OnMovementModeChanged(EMovementMode PreviousMovementMode, uint8 PreviousCustomMode) {
|
||||
Super::OnMovementModeChanged(PreviousMovementMode, PreviousCustomMode);
|
||||
|
||||
// React to changes in the movement mode.
|
||||
if (MovementMode == MOVE_Custom && CustomMovementMode == SplineWalking)
|
||||
{
|
||||
// make sure we update our new floor/base on initial entry of the walking physics
|
||||
FindFloor(UpdatedComponent->GetComponentLocation(), CurrentFloor, false);
|
||||
AdjustFloorHeight();
|
||||
SetBaseFromFloor(CurrentFloor);
|
||||
}
|
||||
}
|
||||
|
||||
void UPwnCharacterMovementComponent::UpdateCharacterStateBeforeMovement(float DeltaSeconds) {
|
||||
if (MovementMode == MOVE_Falling && IsFollowingSpline) {
|
||||
SetMovementMode(MOVE_Custom, SplineFalling);
|
||||
} else if (MovementMode == MOVE_Walking && IsFollowingSpline) {
|
||||
SetMovementMode(MOVE_Custom, SplineWalking);
|
||||
}
|
||||
|
||||
Super::UpdateCharacterStateBeforeMovement(DeltaSeconds);
|
||||
}
|
||||
|
||||
void UPwnCharacterMovementComponent::PhysCustom(const float DeltaTime, const int32 Iterations) {
|
||||
Super::PhysCustom(DeltaTime, Iterations);
|
||||
|
||||
switch (CustomMovementMode) {
|
||||
case SplineWalking: PhysSplineWalking(DeltaTime, Iterations);
|
||||
break;
|
||||
case SplineFalling: PhysSplineFalling(DeltaTime, Iterations);
|
||||
break;
|
||||
default:
|
||||
UE_LOG(LogTemp, Error, TEXT("Invalid custom movement mode for PhysCustom call."));
|
||||
}
|
||||
}
|
||||
|
||||
void UPwnCharacterMovementComponent::EnterSplineFollowMode() {
|
||||
IsFollowingSpline = true;
|
||||
SetMovementMode(MOVE_Custom, SplineWalking);
|
||||
|
||||
const UPwnGameplayModeSubsystem* Subsystem = GetWorld()->GetSubsystem<UPwnGameplayModeSubsystem>();
|
||||
@ -53,10 +94,14 @@ void UPwnCharacterMovementComponent::EnterSplineFollowMode() {
|
||||
DistanceAlongSpline = OutDistanceAloneSpline;
|
||||
CombatPath = OutCombatPath;
|
||||
DotDirection = CombatPath->Reversed ? -1.0f : 1.0f;
|
||||
FVector TargetLocation = CombatPath->Spline->GetLocationAtDistanceAlongSpline(DistanceAlongSpline, ESplineCoordinateSpace::World);
|
||||
TargetLocation.Z = UpdatedComponent->GetComponentLocation().Z;
|
||||
UpdatedComponent->SetWorldLocation(TargetLocation);
|
||||
}
|
||||
}
|
||||
|
||||
void UPwnCharacterMovementComponent::ExitSplineFollowMode() {
|
||||
IsFollowingSpline = false;
|
||||
SetMovementMode(MOVE_Walking);
|
||||
}
|
||||
|
||||
@ -64,20 +109,44 @@ bool UPwnCharacterMovementComponent::IsCustomMovementMode(const ECustomMovementM
|
||||
return MovementMode == MOVE_Custom && CustomMovementMode == Mode;
|
||||
}
|
||||
|
||||
void UPwnCharacterMovementComponent::PhysCustom(const float DeltaTime, const int32 Iterations) {
|
||||
Super::PhysCustom(DeltaTime, Iterations);
|
||||
|
||||
switch (CustomMovementMode) {
|
||||
case SplineWalking: PhysSplineFollow(DeltaTime, Iterations);
|
||||
break;
|
||||
default:
|
||||
UE_LOG(LogTemp, Fatal, TEXT("Invalid custom movement mode"));
|
||||
}
|
||||
void UPwnCharacterMovementComponent::RecomputeTangentAndAcceleration() {
|
||||
Tangent2D = CombatPath->Spline->GetTangentAtDistanceAlongSpline(DistanceAlongSpline, ESplineCoordinateSpace::World).GetSafeNormal2D();
|
||||
// Recalculate acceleration so the input is relative to the spline
|
||||
Acceleration = Tangent2D * Acceleration.Size2D() * FMath::Sign(Acceleration.X);
|
||||
}
|
||||
|
||||
UE_DISABLE_OPTIMIZATION
|
||||
void UPwnCharacterMovementComponent::UpdatePawnVelocity(const float TimeTick) {
|
||||
const FVector OldVelocity = Velocity;
|
||||
const FVector OldLocation = UpdatedComponent->GetComponentLocation();
|
||||
const FVector VelocityNormal2D = Velocity.GetSafeNormal2D();
|
||||
const float VelocitySize2D = Velocity.Size2D();
|
||||
|
||||
void UPwnCharacterMovementComponent::PhysSplineFollow(const float DeltaTime, int32 Iterations) {
|
||||
float VelocityDirection = Tangent2D.Dot(VelocityNormal2D);
|
||||
if (VelocityNormal2D.IsNearlyZero()) {
|
||||
VelocityDirection = 0;
|
||||
}
|
||||
VelocityDirection = FMath::Sign(VelocityDirection); // -1, 0 or 1
|
||||
|
||||
float NewDistanceAlongSpline = DistanceAlongSpline + VelocityDirection * VelocitySize2D * TimeTick;
|
||||
NewDistanceAlongSpline = FMath::Clamp(NewDistanceAlongSpline, 0.0f, CombatPath->Spline->GetSplineLength());
|
||||
|
||||
FVector TargetLocation = CombatPath->Spline->GetLocationAtDistanceAlongSpline(NewDistanceAlongSpline, ESplineCoordinateSpace::World);
|
||||
TargetLocation.Z = OldLocation.Z;
|
||||
|
||||
const FVector Direction = (TargetLocation - OldLocation).GetSafeNormal2D();
|
||||
Velocity = VelocitySize2D * Direction;
|
||||
Velocity.Z = OldVelocity.Z;
|
||||
|
||||
DistanceAlongSpline = NewDistanceAlongSpline;
|
||||
}
|
||||
|
||||
void UPwnCharacterMovementComponent::UpdateDistanceAlongSpline() {
|
||||
// Maintain coherent distance along spline with location
|
||||
const float InputKey = CombatPath->Spline->FindInputKeyClosestToWorldLocation(UpdatedComponent->GetComponentLocation());
|
||||
DistanceAlongSpline = CombatPath->Spline->GetDistanceAlongSplineAtSplineInputKey(InputKey);
|
||||
}
|
||||
|
||||
void UPwnCharacterMovementComponent::PhysSplineWalking(const float DeltaTime, int32 Iterations) {
|
||||
if (DeltaTime < MIN_TICK_TIME) {
|
||||
return;
|
||||
}
|
||||
@ -98,6 +167,7 @@ void UPwnCharacterMovementComponent::PhysSplineFollow(const float DeltaTime, int
|
||||
bool bCheckedFall = false;
|
||||
bool bTriedLedgeMove = false;
|
||||
float RemainingTime = DeltaTime;
|
||||
|
||||
// Perform the move
|
||||
while ((RemainingTime >= MIN_TICK_TIME) && (Iterations < MaxSimulationIterations) && CharacterOwner && (CharacterOwner->Controller ||
|
||||
bRunPhysicsWithNoController || HasAnimRootMotion() || CurrentRootMotion.HasOverrideVelocity() || (CharacterOwner->GetLocalRole() ==
|
||||
@ -109,51 +179,27 @@ void UPwnCharacterMovementComponent::PhysSplineFollow(const float DeltaTime, int
|
||||
|
||||
// Save current values
|
||||
UPrimitiveComponent* const OldBase = GetMovementBase();
|
||||
const FVector PreviousBaseLocation = OldBase != nullptr ? OldBase->GetComponentLocation() : FVector::ZeroVector;
|
||||
const FVector PreviousBaseLocation = (OldBase != nullptr) ? OldBase->GetComponentLocation() : FVector::ZeroVector;
|
||||
const FVector OldLocation = UpdatedComponent->GetComponentLocation();
|
||||
const FFindFloorResult OldFloor = CurrentFloor;
|
||||
const FVector OldVelocity = Velocity;
|
||||
|
||||
RestorePreAdditiveRootMotionVelocity();
|
||||
|
||||
// Ensure velocity is horizontal.
|
||||
MaintainHorizontalGroundVelocity();
|
||||
const FVector OldVelocity = Velocity;
|
||||
|
||||
RecomputeTangentAndAcceleration(); /* -- PAWN MODIFICATIONS -- */
|
||||
Acceleration.Z = 0.f;
|
||||
|
||||
// Apply acceleration
|
||||
if (!HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity()) {
|
||||
CalcVelocity(TimeTick, GroundFriction, false, GetMaxBrakingDeceleration());
|
||||
}
|
||||
|
||||
float VelocityDirection = FMath::Sign(Velocity.X) * DotDirection;
|
||||
float NewDistanceAlongSpline = DistanceAlongSpline + VelocityDirection * Velocity.Size() * TimeTick;
|
||||
NewDistanceAlongSpline = FMath::Clamp(NewDistanceAlongSpline, 0.0f, CombatPath->Spline->GetSplineLength());
|
||||
ApplyRootMotionToVelocity(TimeTick);
|
||||
|
||||
FVector TargetLocation = CombatPath->Spline->GetLocationAtDistanceAlongSpline(NewDistanceAlongSpline, ESplineCoordinateSpace::World);
|
||||
TargetLocation.Z = OldLocation.Z;
|
||||
|
||||
FVector Direction = (TargetLocation - OldLocation).GetSafeNormal();
|
||||
|
||||
FHitResult MyHit;
|
||||
|
||||
float DistanceOffset = NewDistanceAlongSpline - DistanceAlongSpline;
|
||||
FVector Delta2 = TargetLocation - OldLocation;
|
||||
SafeMoveUpdatedComponent(Delta2, UpdatedComponent->GetComponentQuat(), true, MyHit);
|
||||
if (MyHit.Time < 1.0f) {
|
||||
HandleImpact(MyHit, TimeTick, Delta2);
|
||||
//DistanceAlongSpline += InputDotDirection * Velocity.Size() * TimeTick * MyHit.Distance;
|
||||
} else {
|
||||
DistanceAlongSpline = NewDistanceAlongSpline;
|
||||
}
|
||||
|
||||
if (OldLocation == UpdatedComponent->GetComponentLocation()) {
|
||||
Velocity = FVector::Zero();
|
||||
}
|
||||
|
||||
//continue;
|
||||
|
||||
//Velocity = OldVelocity;
|
||||
// Apply acceleration
|
||||
if (!HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity()) {
|
||||
//CalcVelocity(TimeTick, GroundFriction, false, GetMaxBrakingDeceleration());
|
||||
}
|
||||
UpdatePawnVelocity(TimeTick); /* -- PAWN MODIFICATIONS -- */
|
||||
|
||||
if (IsFalling()) {
|
||||
// Root motion could have put us into Falling.
|
||||
@ -171,7 +217,7 @@ void UPwnCharacterMovementComponent::PhysSplineFollow(const float DeltaTime, int
|
||||
if (bZeroDelta) {
|
||||
RemainingTime = 0.f;
|
||||
} else {
|
||||
/*// try to move forward
|
||||
// try to move forward
|
||||
MoveAlongFloor(MoveVelocity, TimeTick, &StepDownResult);
|
||||
|
||||
if (IsFalling()) {
|
||||
@ -187,10 +233,9 @@ void UPwnCharacterMovementComponent::PhysSplineFollow(const float DeltaTime, int
|
||||
{
|
||||
StartSwimming(OldLocation, OldVelocity, TimeTick, RemainingTime, Iterations);
|
||||
return;
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
// Update floor.
|
||||
// StepUp might have already done it for us.
|
||||
if (StepDownResult.bComputedFloor) {
|
||||
@ -214,6 +259,9 @@ void UPwnCharacterMovementComponent::PhysSplineFollow(const float DeltaTime, int
|
||||
|
||||
// Try new movement direction
|
||||
Velocity = NewDelta / TimeTick;
|
||||
|
||||
UpdatePawnVelocity(TimeTick); /* -- PAWN MODIFICATIONS -- */
|
||||
|
||||
RemainingTime += TimeTick;
|
||||
continue;
|
||||
} else {
|
||||
@ -275,7 +323,7 @@ void UPwnCharacterMovementComponent::PhysSplineFollow(const float DeltaTime, int
|
||||
}
|
||||
|
||||
// Allow overlap events and such to change physics state and velocity
|
||||
/*if (IsMovingOnGround()) {
|
||||
if (IsMovingOnGround()) {
|
||||
// Make velocity reflect actual move
|
||||
if (!bJustTeleported && !HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity() && TimeTick >= MIN_TICK_TIME) {
|
||||
// TODO-RootMotionSource: Allow this to happen during partial override Velocity, but only set allowed axes?
|
||||
@ -288,12 +336,328 @@ void UPwnCharacterMovementComponent::PhysSplineFollow(const float DeltaTime, int
|
||||
if (UpdatedComponent->GetComponentLocation() == OldLocation) {
|
||||
RemainingTime = 0.f;
|
||||
break;
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
if (IsMovingOnGround()) {
|
||||
MaintainHorizontalGroundVelocity();
|
||||
}
|
||||
|
||||
UpdateDistanceAlongSpline(); /* -- PAWN MODIFICATIONS -- */
|
||||
}
|
||||
|
||||
UE_DISABLE_OPTIMIZATION
|
||||
|
||||
void UPwnCharacterMovementComponent::PhysSplineFalling(const float DeltaTime, int32 Iterations) {
|
||||
if (DeltaTime < MIN_TICK_TIME) {
|
||||
return;
|
||||
}
|
||||
|
||||
FVector FallAcceleration = GetFallingLateralAcceleration(DeltaTime);
|
||||
FallAcceleration.Z = 0.f;
|
||||
const bool bHasLimitedAirControl = ShouldLimitAirControl(DeltaTime, FallAcceleration);
|
||||
|
||||
float RemainingTime = DeltaTime;
|
||||
while ((RemainingTime >= MIN_TICK_TIME) && (Iterations < MaxSimulationIterations)) {
|
||||
Iterations++;
|
||||
float TimeTick = GetSimulationTimeStep(RemainingTime, Iterations);
|
||||
RemainingTime -= TimeTick;
|
||||
|
||||
const FVector OldLocation = UpdatedComponent->GetComponentLocation();
|
||||
const FQuat PawnRotation = UpdatedComponent->GetComponentQuat();
|
||||
bJustTeleported = false;
|
||||
|
||||
RecomputeTangentAndAcceleration(); /* -- PAWN MODIFICATIONS -- */
|
||||
|
||||
const FVector OldVelocityWithRootMotion = Velocity;
|
||||
|
||||
RestorePreAdditiveRootMotionVelocity();
|
||||
|
||||
const FVector OldVelocity = Velocity;
|
||||
|
||||
// Apply input
|
||||
const float MaxDecel = GetMaxBrakingDeceleration();
|
||||
if (!HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity()) {
|
||||
// Compute Velocity
|
||||
{
|
||||
// Acceleration = FallAcceleration for CalcVelocity(), but we restore it after using it.
|
||||
TGuardValue<FVector> RestoreAcceleration(Acceleration, FallAcceleration);
|
||||
Velocity.Z = 0.f;
|
||||
CalcVelocity(TimeTick, FallingLateralFriction, false, MaxDecel);
|
||||
Velocity.Z = OldVelocity.Z;
|
||||
}
|
||||
}
|
||||
|
||||
// Compute current gravity
|
||||
const FVector Gravity(0.f, 0.f, GetGravityZ());
|
||||
float GravityTime = TimeTick;
|
||||
|
||||
// If jump is providing force, gravity may be affected.
|
||||
bool bEndingJumpForce = false;
|
||||
if (CharacterOwner->JumpForceTimeRemaining > 0.0f) {
|
||||
// Consume some of the force time. Only the remaining time (if any) is affected by gravity when bApplyGravityWhileJumping=false.
|
||||
const float JumpForceTime = FMath::Min(CharacterOwner->JumpForceTimeRemaining, TimeTick);
|
||||
GravityTime = bApplyGravityWhileJumping ? TimeTick : FMath::Max(0.0f, TimeTick - JumpForceTime);
|
||||
|
||||
// Update Character state
|
||||
CharacterOwner->JumpForceTimeRemaining -= JumpForceTime;
|
||||
if (CharacterOwner->JumpForceTimeRemaining <= 0.0f) {
|
||||
CharacterOwner->ResetJumpState();
|
||||
bEndingJumpForce = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply gravity
|
||||
Velocity = NewFallVelocity(Velocity, Gravity, GravityTime);
|
||||
|
||||
//UE_LOG(LogCharacterMovement, Log, TEXT("dt=(%.6f) OldLocation=(%s) OldVelocity=(%s) OldVelocityWithRootMotion=(%s) NewVelocity=(%s)"), timeTick, *(UpdatedComponent->GetComponentLocation()).ToString(), *OldVelocity.ToString(), *OldVelocityWithRootMotion.ToString(), *Velocity.ToString());
|
||||
ApplyRootMotionToVelocity(TimeTick);
|
||||
DecayFormerBaseVelocity(TimeTick);
|
||||
|
||||
int32 ForceJumpPeakSubstep = 1; /* -- PAWN MODIFICATIONS -- */
|
||||
|
||||
// See if we need to sub-step to exactly reach the apex. This is important for avoiding "cutting off the top" of the trajectory as framerate varies.
|
||||
if (ForceJumpPeakSubstep && OldVelocityWithRootMotion.Z > 0.f && Velocity.Z <= 0.f && NumJumpApexAttempts <
|
||||
MaxJumpApexAttemptsPerSimulation) {
|
||||
const FVector DerivedAccel = (Velocity - OldVelocityWithRootMotion) / TimeTick;
|
||||
if (!FMath::IsNearlyZero(DerivedAccel.Z)) {
|
||||
const float TimeToApex = -OldVelocityWithRootMotion.Z / DerivedAccel.Z;
|
||||
|
||||
// The time-to-apex calculation should be precise, and we want to avoid adding a substep when we are basically already at the apex from the previous iteration's work.
|
||||
const float ApexTimeMinimum = 0.0001f;
|
||||
if (TimeToApex >= ApexTimeMinimum && TimeToApex < TimeTick) {
|
||||
const FVector ApexVelocity = OldVelocityWithRootMotion + (DerivedAccel * TimeToApex);
|
||||
Velocity = ApexVelocity;
|
||||
Velocity.Z = 0.f; // Should be nearly zero anyway, but this makes apex notifications consistent.
|
||||
|
||||
// We only want to move the amount of time it takes to reach the apex, and refund the unused time for next iteration.
|
||||
const float TimeToRefund = (TimeTick - TimeToApex);
|
||||
|
||||
RemainingTime += TimeToRefund;
|
||||
TimeTick = TimeToApex;
|
||||
Iterations--;
|
||||
NumJumpApexAttempts++;
|
||||
|
||||
// Refund time to any active Root Motion Sources as well
|
||||
for (TSharedPtr<FRootMotionSource> RootMotionSource : CurrentRootMotion.RootMotionSources) {
|
||||
const float RewoundRMSTime = FMath::Max(0.0f, RootMotionSource->GetTime() - TimeToRefund);
|
||||
RootMotionSource->SetTime(RewoundRMSTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bNotifyApex && (Velocity.Z < 0.f)) {
|
||||
// Just passed jump apex since now going down
|
||||
bNotifyApex = false;
|
||||
NotifyJumpApex();
|
||||
}
|
||||
|
||||
// Compute change in position (using midpoint integration method).
|
||||
FVector Adjusted = 0.5f * (OldVelocityWithRootMotion + Velocity) * TimeTick;
|
||||
|
||||
// Special handling if ending the jump force where we didn't apply gravity during the jump.
|
||||
if (bEndingJumpForce && !bApplyGravityWhileJumping) {
|
||||
// We had a portion of the time at constant speed then a portion with acceleration due to gravity.
|
||||
// Account for that here with a more correct change in position.
|
||||
const float NonGravityTime = FMath::Max(0.f, TimeTick - GravityTime);
|
||||
Adjusted = (OldVelocityWithRootMotion * NonGravityTime) + (0.5f * (OldVelocityWithRootMotion + Velocity) * GravityTime);
|
||||
}
|
||||
|
||||
UpdatePawnVelocity(TimeTick); /* -- PAWN MODIFICATIONS -- */
|
||||
|
||||
// Move
|
||||
FHitResult Hit(1.f);
|
||||
SafeMoveUpdatedComponent(Adjusted, PawnRotation, true, Hit);
|
||||
|
||||
if (!HasValidData()) {
|
||||
return;
|
||||
}
|
||||
|
||||
float LastMoveTimeSlice = TimeTick;
|
||||
float SubTimeTickRemaining = TimeTick * (1.f - Hit.Time);
|
||||
|
||||
if (IsSwimming()) //just entered water
|
||||
{
|
||||
RemainingTime += SubTimeTickRemaining;
|
||||
StartSwimming(OldLocation, OldVelocity, TimeTick, RemainingTime, Iterations);
|
||||
return;
|
||||
} else if (Hit.bBlockingHit) {
|
||||
if (IsValidLandingSpot(UpdatedComponent->GetComponentLocation(), Hit)) {
|
||||
RemainingTime += SubTimeTickRemaining;
|
||||
ProcessLanded(Hit, RemainingTime, Iterations);
|
||||
return;
|
||||
} else {
|
||||
// Compute impact deflection based on final velocity, not integration step.
|
||||
// This allows us to compute a new velocity from the deflected vector, and ensures the full gravity effect is included in the slide result.
|
||||
Adjusted = Velocity * TimeTick;
|
||||
|
||||
// See if we can convert a normally invalid landing spot (based on the hit result) to a usable one.
|
||||
if (!Hit.bStartPenetrating && ShouldCheckForValidLandingSpot(TimeTick, Adjusted, Hit)) {
|
||||
const FVector PawnLocation = UpdatedComponent->GetComponentLocation();
|
||||
FFindFloorResult FloorResult;
|
||||
FindFloor(PawnLocation, FloorResult, false);
|
||||
if (FloorResult.IsWalkableFloor() && IsValidLandingSpot(PawnLocation, FloorResult.HitResult)) {
|
||||
RemainingTime += SubTimeTickRemaining;
|
||||
ProcessLanded(FloorResult.HitResult, RemainingTime, Iterations);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
HandleImpact(Hit, LastMoveTimeSlice, Adjusted);
|
||||
|
||||
// If we've changed physics mode, abort.
|
||||
if (!HasValidData() || !IsFalling()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Limit air control based on what we hit.
|
||||
// We moved to the impact point using air control, but may want to deflect from there based on a limited air control acceleration.
|
||||
FVector VelocityNoAirControl = OldVelocity;
|
||||
FVector AirControlAccel = Acceleration;
|
||||
if (bHasLimitedAirControl) {
|
||||
// Compute VelocityNoAirControl
|
||||
{
|
||||
// Find velocity *without* acceleration.
|
||||
TGuardValue<FVector> RestoreAcceleration(Acceleration, FVector::ZeroVector);
|
||||
TGuardValue<FVector> RestoreVelocity(Velocity, OldVelocity);
|
||||
Velocity.Z = 0.f;
|
||||
CalcVelocity(TimeTick, FallingLateralFriction, false, MaxDecel);
|
||||
|
||||
UpdatePawnVelocity(TimeTick); /* -- PAWN MODIFICATIONS -- */
|
||||
|
||||
VelocityNoAirControl = FVector(Velocity.X, Velocity.Y, OldVelocity.Z);
|
||||
VelocityNoAirControl = NewFallVelocity(VelocityNoAirControl, Gravity, GravityTime);
|
||||
}
|
||||
|
||||
const bool bCheckLandingSpot = false; // we already checked above.
|
||||
AirControlAccel = (Velocity - VelocityNoAirControl) / TimeTick;
|
||||
const FVector AirControlDeltaV = LimitAirControl(LastMoveTimeSlice, AirControlAccel, Hit, bCheckLandingSpot) * LastMoveTimeSlice;
|
||||
Adjusted = (VelocityNoAirControl + AirControlDeltaV) * LastMoveTimeSlice;
|
||||
}
|
||||
|
||||
const FVector OldHitNormal = Hit.Normal;
|
||||
const FVector OldHitImpactNormal = Hit.ImpactNormal;
|
||||
FVector Delta = ComputeSlideVector(Adjusted, 1.f - Hit.Time, OldHitNormal, Hit);
|
||||
|
||||
// Compute velocity after deflection (only gravity component for RootMotion)
|
||||
const UPrimitiveComponent* HitComponent = Hit.GetComponent();
|
||||
int32 UseTargetVelocityOnImpact = 1; /* -- PAWN MODIFICATIONS -- */
|
||||
if (UseTargetVelocityOnImpact && !Velocity.IsNearlyZero() && MovementBaseUtility::IsSimulatedBase(HitComponent)) {
|
||||
const FVector ContactVelocity = MovementBaseUtility::GetMovementBaseVelocity(HitComponent, NAME_None) +
|
||||
MovementBaseUtility::GetMovementBaseTangentialVelocity(HitComponent, NAME_None, Hit.ImpactPoint);
|
||||
const FVector NewVelocity = Velocity - Hit.ImpactNormal * FVector::DotProduct(Velocity - ContactVelocity, Hit.ImpactNormal);
|
||||
Velocity = HasAnimRootMotion() || CurrentRootMotion.HasOverrideVelocityWithIgnoreZAccumulate()
|
||||
? FVector(Velocity.X, Velocity.Y, NewVelocity.Z)
|
||||
: NewVelocity;
|
||||
} else if (SubTimeTickRemaining > UE_KINDA_SMALL_NUMBER && !bJustTeleported) {
|
||||
const FVector NewVelocity = (Delta / SubTimeTickRemaining);
|
||||
Velocity = HasAnimRootMotion() || CurrentRootMotion.HasOverrideVelocityWithIgnoreZAccumulate()
|
||||
? FVector(Velocity.X, Velocity.Y, NewVelocity.Z)
|
||||
: NewVelocity;
|
||||
}
|
||||
|
||||
//UpdatePawnVelocity(TimeTick); /* -- PAWN MODIFICATIONS -- */
|
||||
|
||||
if (SubTimeTickRemaining > UE_KINDA_SMALL_NUMBER && (Delta | Adjusted) > 0.f) {
|
||||
// Move in deflected direction.
|
||||
SafeMoveUpdatedComponent(Delta, PawnRotation, true, Hit);
|
||||
|
||||
if (Hit.bBlockingHit) {
|
||||
// hit second wall
|
||||
LastMoveTimeSlice = SubTimeTickRemaining;
|
||||
SubTimeTickRemaining = SubTimeTickRemaining * (1.f - Hit.Time);
|
||||
|
||||
if (IsValidLandingSpot(UpdatedComponent->GetComponentLocation(), Hit)) {
|
||||
RemainingTime += SubTimeTickRemaining;
|
||||
ProcessLanded(Hit, RemainingTime, Iterations);
|
||||
return;
|
||||
}
|
||||
|
||||
HandleImpact(Hit, LastMoveTimeSlice, Delta);
|
||||
|
||||
// If we've changed physics mode, abort.
|
||||
if (!HasValidData() || !IsFalling()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Act as if there was no air control on the last move when computing new deflection.
|
||||
const float VERTICAL_SLOPE_NORMAL_Z = 0.001f; /* -- PAWN MODIFICATIONS -- */
|
||||
if (bHasLimitedAirControl && Hit.Normal.Z > VERTICAL_SLOPE_NORMAL_Z) {
|
||||
const FVector LastMoveNoAirControl = VelocityNoAirControl * LastMoveTimeSlice;
|
||||
Delta = ComputeSlideVector(LastMoveNoAirControl, 1.f, OldHitNormal, Hit);
|
||||
}
|
||||
|
||||
FVector PreTwoWallDelta = Delta;
|
||||
TwoWallAdjust(Delta, Hit, OldHitNormal);
|
||||
|
||||
// Limit air control, but allow a slide along the second wall.
|
||||
if (bHasLimitedAirControl) {
|
||||
const bool bCheckLandingSpot = false; // we already checked above.
|
||||
const FVector AirControlDeltaV = LimitAirControl(SubTimeTickRemaining, AirControlAccel, Hit, bCheckLandingSpot) *
|
||||
SubTimeTickRemaining;
|
||||
|
||||
// Only allow if not back in to first wall
|
||||
if (FVector::DotProduct(AirControlDeltaV, OldHitNormal) > 0.f) {
|
||||
Delta += (AirControlDeltaV * SubTimeTickRemaining);
|
||||
}
|
||||
}
|
||||
|
||||
// Compute velocity after deflection (only gravity component for RootMotion)
|
||||
if (SubTimeTickRemaining > UE_KINDA_SMALL_NUMBER && !bJustTeleported) {
|
||||
const FVector NewVelocity = (Delta / SubTimeTickRemaining);
|
||||
Velocity = HasAnimRootMotion() || CurrentRootMotion.HasOverrideVelocityWithIgnoreZAccumulate()
|
||||
? FVector(Velocity.X, Velocity.Y, NewVelocity.Z)
|
||||
: NewVelocity;
|
||||
//UpdatePawnVelocity(TimeTick); /* -- PAWN MODIFICATIONS -- */
|
||||
}
|
||||
|
||||
// bDitch=true means that pawn is straddling two slopes, neither of which it can stand on
|
||||
bool bDitch = ((OldHitImpactNormal.Z > 0.f) && (Hit.ImpactNormal.Z > 0.f) && (FMath::Abs(Delta.Z) <= UE_KINDA_SMALL_NUMBER) &&
|
||||
((Hit.ImpactNormal | OldHitImpactNormal) < 0.f));
|
||||
SafeMoveUpdatedComponent(Delta, PawnRotation, true, Hit);
|
||||
if (Hit.Time == 0.f) {
|
||||
// if we are stuck then try to side step
|
||||
FVector SideDelta = (OldHitNormal + Hit.ImpactNormal).GetSafeNormal2D();
|
||||
if (SideDelta.IsNearlyZero()) {
|
||||
SideDelta = FVector(OldHitNormal.Y, -OldHitNormal.X, 0).GetSafeNormal();
|
||||
}
|
||||
SafeMoveUpdatedComponent(SideDelta, PawnRotation, true, Hit);
|
||||
}
|
||||
|
||||
if (bDitch || IsValidLandingSpot(UpdatedComponent->GetComponentLocation(), Hit) || Hit.Time == 0.f) {
|
||||
RemainingTime = 0.f;
|
||||
ProcessLanded(Hit, RemainingTime, Iterations);
|
||||
return;
|
||||
} else if (GetPerchRadiusThreshold() > 0.f && Hit.Time == 1.f && OldHitImpactNormal.Z >= GetWalkableFloorZ()) {
|
||||
// We might be in a virtual 'ditch' within our perch radius. This is rare.
|
||||
const FVector PawnLocation = UpdatedComponent->GetComponentLocation();
|
||||
const float ZMovedDist = FMath::Abs(PawnLocation.Z - OldLocation.Z);
|
||||
const float MovedDist2DSq = (PawnLocation - OldLocation).SizeSquared2D();
|
||||
if (ZMovedDist <= 0.2f * TimeTick && MovedDist2DSq <= 4.f * TimeTick) {
|
||||
Velocity.X += 0.25f * GetMaxSpeed() * (RandomStream.FRand() - 0.5f);
|
||||
Velocity.Y += 0.25f * GetMaxSpeed() * (RandomStream.FRand() - 0.5f);
|
||||
Velocity.Z = FMath::Max<float>(JumpZVelocity * 0.25f, 1.f);
|
||||
|
||||
//UpdatePawnVelocity(TimeTick); /* -- PAWN MODIFICATIONS -- */
|
||||
|
||||
Delta = Velocity * TimeTick;
|
||||
|
||||
SafeMoveUpdatedComponent(Delta, PawnRotation, true, Hit);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Velocity.SizeSquared2D() <= UE_KINDA_SMALL_NUMBER * 10.f) {
|
||||
Velocity.X = 0.f;
|
||||
Velocity.Y = 0.f;
|
||||
}
|
||||
}
|
||||
|
||||
UpdateDistanceAlongSpline(); /* -- PAWN MODIFICATIONS -- */
|
||||
}
|
||||
|
||||
UE_ENABLE_OPTIMIZATION
|
||||
|
||||
@ -29,6 +29,14 @@ protected:
|
||||
|
||||
virtual bool IsMovingOnGround() const override;
|
||||
|
||||
virtual bool IsFalling() const override;
|
||||
|
||||
virtual void OnMovementModeChanged(EMovementMode PreviousMovementMode, uint8 PreviousCustomMode) override;
|
||||
|
||||
virtual void UpdateCharacterStateBeforeMovement(float DeltaSeconds) override;
|
||||
|
||||
virtual void PhysCustom(const float DeltaTime, int32 Iterations) override;
|
||||
|
||||
public:
|
||||
UFUNCTION(BlueprintCallable, Category="CharacterMovement")
|
||||
void EnterSplineFollowMode();
|
||||
@ -39,10 +47,16 @@ public:
|
||||
UFUNCTION(BlueprintCallable, Category="CharacterMovement")
|
||||
bool IsCustomMovementMode(const ECustomMovementMode Mode) const;
|
||||
|
||||
virtual void PhysCustom(const float DeltaTime, int32 Iterations) override;
|
||||
|
||||
private:
|
||||
void PhysSplineFollow(const float DeltaTime, int32 Iterations);
|
||||
void RecomputeTangentAndAcceleration();
|
||||
|
||||
void UpdatePawnVelocity(const float TimeTick);
|
||||
|
||||
void UpdateDistanceAlongSpline();
|
||||
|
||||
void PhysSplineWalking(const float DeltaTime, int32 Iterations);
|
||||
|
||||
void PhysSplineFalling(const float DeltaTime, int32 Iterations);
|
||||
|
||||
public:
|
||||
UPROPERTY(BlueprintReadOnly)
|
||||
@ -54,6 +68,9 @@ public:
|
||||
UPROPERTY(BlueprintReadOnly)
|
||||
float DotDirection;
|
||||
|
||||
UPROPERTY(BlueprintReadWrite)
|
||||
float Input;
|
||||
UPROPERTY(Transient)
|
||||
bool IsFollowingSpline;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
FVector Tangent2D;
|
||||
};
|
||||
|
||||
@ -7,3 +7,6 @@
|
||||
#define PRINT_STRING_RED(Content, ...) PRINT_STRING_GENERIC(FColor::Red, Content, __VA_ARGS__);
|
||||
#define PRINT_STRING_YELLOW(Content, ...) PRINT_STRING_GENERIC(FColor::Yellow, Content, __VA_ARGS__);
|
||||
#define PRINT_STRING_GREEN(Content, ...) PRINT_STRING_GENERIC(FColor::Green, Content, __VA_ARGS__);
|
||||
|
||||
#define BOOL_TO_TEXT(Bool) ((Bool) ? TEXT("True") : TEXT("False"))
|
||||
#define BOOL_TO_STR(Bool) ((Bool) ? "True" : "False")
|
||||
Loading…
x
Reference in New Issue
Block a user