x

Search in
Sort by:

Question Status:

Search help

  • Simple searches use one or more words. Separate the words with spaces (cat dog) to search cat,dog or both. Separate the words with plus signs (cat +dog) to search for items that may contain cat but must contain dog.
  • You can further refine your search on the search results page, where you can search by keywords, author, topic. These can be combined with each other. Examples
    • cat dog --matches anything with cat,dog or both
    • cat +dog --searches for cat +dog where dog is a mandatory term
    • cat -dog -- searches for cat excluding any result containing dog
    • [cats] —will restrict your search to results with topic named "cats"
    • [cats] [dogs] —will restrict your search to results with both topics, "cats", and "dogs"

Custom GroundMovementmode snagging on walkableAngleLimit

Alright, So I've been making a custom movement system were you have a mode were you can run up any curved surface, eventually being able to run upside down, terrain permitting.

My main issue is that when I try to rotate the actor to match the floor it's standing on the whole thing starts shaking like mad, and I've tested to make sure that the change orientation code isn't just constantly triggering.

Another problem is that somewhere along the way I've managed to bork the walkable floor angle limit. Instead of just halting any movement on a too steep slope my character freezes altogether.

I've been trying to keep all of my modification limited to the custom character movement component I've made, specifically a custom movement mode that was duplicated from MOVE_Walking.

Here's what I have:

URunMovementComponent.h: UENUM(BlueprintType) enum ECustomMovementMode { TESTMOVE_Running UMETA(DisplayName = "Running"), TESTMOVE_Rolling UMETA(DisplayName = "Rolling") };

 UCLASS()
 class RUNNERBUILD_API URunMovementComponent : public UCharacterMovementComponent
 {
     GENERATED_UCLASS_BODY()
 
 public:
 
     UPROPERTY(EditAnywhere, Category = "Running")
     float RunAngle;
 
 protected:
 
     //Init
     virtual void InitializeComponent() override;
 
 public:
 
     //void RunAlongFloor(const FVector& InVelocity, float DeltaSeconds, FStepDownResult* OutStepDownResult);
 
     void CalcRunVelocity(float deltaTime, float Friction, bool bfluid, float BreakingDeceleration);
 
     void PhysRUN(float deltaTime, int32 Iterations);
     void PhysROLL(float deltaTime, int32 Iterations);
 
 
     virtual void PhysCustom(float deltaTime, int32 Iterations) override;
 
     //Tick
     virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) override;
 };


URunMovementComponent.cpp excerpt:

 void URunMovementComponent::CalcRunVelocity(float DeltaTime, float Friction, bool bFluid, float BrakingDeceleration)
 {
     // Do not update velocity when using root motion or when SimulatedProxy - SimulatedProxy are repped their Velocity
     if (!HasValidData() || HasAnimRootMotion() || DeltaTime < MIN_TICK_TIME || (CharacterOwner && CharacterOwner->Role == ROLE_SimulatedProxy))
     {
         return;
     }
 
     Friction = FMath::Max(0.f, Friction);
     const float MaxAccel = GetMaxAcceleration();
     float MaxSpeed = GetMaxSpeed();
 
     // Check if path following requested movement
     bool bZeroRequestedAcceleration = true;
     FVector RequestedAcceleration = FVector::ZeroVector;
     float RequestedSpeed = 0.0f;
     if (ApplyRequestedMove(DeltaTime, MaxAccel, MaxSpeed, Friction, BrakingDeceleration, RequestedAcceleration, RequestedSpeed))
     {
         RequestedAcceleration = RequestedAcceleration.GetClampedToMaxSize(MaxAccel);
         bZeroRequestedAcceleration = false;
     }
 
     if (bForceMaxAccel)
     {
         // Force acceleration at full speed.
         // In consideration order for direction: Acceleration, then Velocity, then Pawn's rotation.
         if (Acceleration.SizeSquared() > SMALL_NUMBER)
         {
             Acceleration = Acceleration.GetSafeNormal() * MaxAccel;
         }
         else
         {
             Acceleration = MaxAccel * (Velocity.SizeSquared() < SMALL_NUMBER ? UpdatedComponent->GetForwardVector() : Velocity.GetSafeNormal());
         }
 
         AnalogInputModifier = 1.f;
     }
 
     // Path following above didn't care about the analog modifier, but we do for everything else below, so get the fully modified value.
     // Use max of requested speed and max speed if we modified the speed in ApplyRequestedMove above.
     MaxSpeed = FMath::Max3(RequestedSpeed, MaxSpeed * AnalogInputModifier, GetMinAnalogSpeed());
 
     // Apply braking or deceleration
     const bool bZeroAcceleration = Acceleration.IsZero();
     const bool bVelocityOverMax = IsExceedingMaxSpeed(MaxSpeed);
 
     // Only apply braking if there is no acceleration, or we are over our max speed and need to slow down to it.
     if ((bZeroAcceleration && bZeroRequestedAcceleration) || bVelocityOverMax)
     {
         const FVector OldVelocity = Velocity;
 
         const float ActualBrakingFriction = (bUseSeparateBrakingFriction ? BrakingFriction : Friction);
         ApplyVelocityBraking(DeltaTime, ActualBrakingFriction, BrakingDeceleration);
 
         // Don't allow braking to lower us below max speed if we started above it.
         if (bVelocityOverMax && Velocity.SizeSquared() < FMath::Square(MaxSpeed) && FVector::DotProduct(Acceleration, OldVelocity) > 0.0f)
         {
             Velocity = OldVelocity.GetSafeNormal() * MaxSpeed;
         }
     }
     else if (!bZeroAcceleration)
     {
         // Friction affects our ability to change direction. This is only done for input acceleration, not path following.
         const FVector AccelDir = Acceleration.GetSafeNormal();
         const float VelSize = Velocity.Size();
         Velocity = Velocity - (Velocity - AccelDir * VelSize) * FMath::Min(DeltaTime * Friction, 1.f);
     }
 
     // Apply fluid friction
     if (bFluid)
     {
         Velocity = Velocity * (1.f - FMath::Min(Friction * DeltaTime, 1.f));
     }
 
     // Apply acceleration
     const float NewMaxSpeed = (IsExceedingMaxSpeed(MaxSpeed)) ? Velocity.Size() : MaxSpeed;
     Velocity += Acceleration * DeltaTime;
     Velocity += RequestedAcceleration * DeltaTime;
     //Test to see if this stops one from achieving higher speeds.
     //Velocity = Velocity.GetClampedToMaxSize(NewMaxSpeed);
 
     if (bUseRVOAvoidance)
     {
         CalcAvoidanceVelocity(DeltaTime);
     }
 }
 
 void URunMovementComponent::PhysRUN(float deltaTime, int32 Iterations)
 {
     GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Yellow, TEXT("Running"));
 
     if (deltaTime < MIN_TICK_TIME)
     {
         return;
     }
 
     if (!CharacterOwner || (!CharacterOwner->Controller && !bRunPhysicsWithNoController && !HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity() && (CharacterOwner->Role != ROLE_SimulatedProxy)))
     {
         Acceleration = FVector::ZeroVector;
         Velocity = FVector::ZeroVector;
         return;
     }
 
     if (!UpdatedComponent->IsQueryCollisionEnabled())
     {
         SetMovementMode(MOVE_Walking);
         return;
     }
 
     devCode(ensureMsgf(!Velocity.ContainsNaN(), TEXT("PhysWalking: Velocity contains NaN before Iteration (%s)\n%s"), *GetPathNameSafe(this), *Velocity.ToString()));
 
     bJustTeleported = false;
     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->Role == ROLE_SimulatedProxy)))
     {
         Iterations++;
         bJustTeleported = false;
         const float timeTick = GetSimulationTimeStep(remainingTime, Iterations);
         remainingTime -= timeTick;
 
         // Save current values
         UPrimitiveComponent * const OldBase = GetMovementBase();
         const FVector PreviousBaseLocation = (OldBase != NULL) ? OldBase->GetComponentLocation() : FVector::ZeroVector;
         const FVector OldLocation = UpdatedComponent->GetComponentLocation();
         const FFindFloorResult OldFloor = CurrentFloor;
 
         RestorePreAdditiveRootMotionVelocity();
 
         // Ensure velocity is horizontal.
         MaintainHorizontalGroundVelocity();
         const FVector OldVelocity = Velocity;
         Acceleration.Z = 0.f;
 
         // Apply acceleration
         if (!HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity())
         {
             CalcRunVelocity(timeTick, GroundFriction, false, GetMaxBrakingDeceleration());
             devCode(ensureMsgf(!Velocity.ContainsNaN(), TEXT("PhysWalking: Velocity contains NaN after CalcVelocity (%s)\n%s"), *GetPathNameSafe(this), *Velocity.ToString()));
         }
 
         ApplyRootMotionToVelocity(timeTick);
         devCode(ensureMsgf(!Velocity.ContainsNaN(), TEXT("PhysWalking: Velocity contains NaN after Root Motion application (%s)\n%s"), *GetPathNameSafe(this), *Velocity.ToString()));
 
         if (IsFalling())
         {
             // Root motion could have put us into Falling.
             // No movement has taken place this movement tick so we pass on full time/past iteration count
             StartNewPhysics(remainingTime + timeTick, Iterations - 1);
             return;
         }
 
         // Compute move parameters
         const FVector MoveVelocity = Velocity;
         const FVector Delta = timeTick * MoveVelocity;
         const bool bZeroDelta = Delta.IsNearlyZero();
         FStepDownResult StepDownResult;
 
         if (bZeroDelta)
         {
             remainingTime = 0.f;
         }
         else
         {
             // try to move forward
             MoveAlongFloor(MoveVelocity, timeTick, &StepDownResult);
 
             if (IsFalling())
             {
                 // pawn decided to jump up
                 const float DesiredDist = Delta.Size();
                 if (DesiredDist > KINDA_SMALL_NUMBER)
                 {
                     const float ActualDist = (UpdatedComponent->GetComponentLocation() - OldLocation).Size2D();
                     remainingTime += timeTick * (1.f - FMath::Min(1.f, ActualDist / DesiredDist));
                 }
                 StartNewPhysics(remainingTime, Iterations);
                 return;
             }
             else if (IsSwimming()) //just entered water
             {
                 StartSwimming(OldLocation, OldVelocity, timeTick, remainingTime, Iterations);
                 return;
             }
         }
 
         // Update floor.
         // StepUp might have already done it for us.
         if (StepDownResult.bComputedFloor)
         {
             CurrentFloor = StepDownResult.FloorResult;
         }
         else
         {
             FindFloor(UpdatedComponent->GetComponentLocation(), CurrentFloor, bZeroDelta, NULL);
         }
 
         // check for ledges here
         const bool bCheckLedges = !CanWalkOffLedges();
         if (bCheckLedges && !CurrentFloor.IsWalkableFloor())
         {
             // calculate possible alternate movement
             const FVector GravDir = FVector(0.f, 0.f, -1.f);
             const FVector NewDelta = bTriedLedgeMove ? FVector::ZeroVector : GetLedgeMove(OldLocation, Delta, GravDir);
             if (!NewDelta.IsZero())
             {
                 // first revert this move
                 RevertMove(OldLocation, OldBase, PreviousBaseLocation, OldFloor, false);
 
                 // avoid repeated ledge moves if the first one fails
                 bTriedLedgeMove = true;
 
                 // Try new movement direction
                 Velocity = NewDelta / timeTick;
                 remainingTime += timeTick;
                 continue;
             }
             else
             {
                 // see if it is OK to jump
                 // @todo collision : only thing that can be problem is that oldbase has world collision on
                 bool bMustJump = bZeroDelta || (OldBase == NULL || (!OldBase->IsQueryCollisionEnabled() && MovementBaseUtility::IsDynamicBase(OldBase)));
                 if ((bMustJump || !bCheckedFall) && CheckFall(OldFloor, CurrentFloor.HitResult, Delta, OldLocation, remainingTime, timeTick, Iterations, bMustJump))
                 {
                     return;
                 }
                 bCheckedFall = true;
 
                 // revert this move
                 RevertMove(OldLocation, OldBase, PreviousBaseLocation, OldFloor, true);
                 remainingTime = 0.f;
                 break;
             }
         }
         else
         {
             // Validate the floor check
             if (CurrentFloor.IsWalkableFloor())
             {
                 if (ShouldCatchAir(OldFloor, CurrentFloor))
                 {
                     CharacterOwner->OnWalkingOffLedge(OldFloor.HitResult.ImpactNormal, OldFloor.HitResult.Normal, OldLocation, timeTick);
                     if (IsMovingOnGround())
                     {
                         // If still walking, then fall. If not, assume the user set a different mode they want to keep.
                         StartFalling(Iterations, remainingTime, timeTick, Delta, OldLocation);
                     }
                     return;
                 }
 
                 AdjustFloorHeight();
                 SetBase(CurrentFloor.HitResult.Component.Get(), CurrentFloor.HitResult.BoneName);
 
                 // Rotate Character to match floor.
                 FRotator CurrentRotation = FVector(CurrentFloor.HitResult.ImpactNormal).Rotation();
                 FRotator OldRotation = FVector(OldFloor.HitResult.ImpactNormal).Rotation();
 
                 // If the rotation hasn't changed then nothing needs to be done.
                 if (CurrentRotation != OldRotation)
                 {
                     CurrentRotation.Pitch -= OldRotation.Pitch;
                     CurrentRotation.Roll -= OldRotation.Roll;
                     CurrentRotation.Yaw = 0.0f;
 
                     GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Yellow, TEXT("Doing Something."));
 
                     GetCharacterOwner()->AddActorLocalRotation(CurrentRotation);
                 }
             }
             else if (CurrentFloor.HitResult.bStartPenetrating && remainingTime <= 0.f)
             {
                 // The floor check failed because it started in penetration
                 // We do not want to try to move downward because the downward sweep failed, rather we'd like to try to pop out of the floor.
                 FHitResult Hit(CurrentFloor.HitResult);
                 Hit.TraceEnd = Hit.TraceStart + FVector(0.f, 0.f, MAX_FLOOR_DIST);
                 const FVector RequestedAdjustment = GetPenetrationAdjustment(Hit);
                 ResolvePenetration(RequestedAdjustment, Hit, UpdatedComponent->GetComponentQuat());
                 bForceNextFloorCheck = true;
             }
 
             // check if just entered water
             if (IsSwimming())
             {
                 StartSwimming(OldLocation, Velocity, timeTick, remainingTime, Iterations);
                 return;
             }
 
             // See if we need to start falling.
             if (!CurrentFloor.IsWalkableFloor() && !CurrentFloor.HitResult.bStartPenetrating)
             {
                 const bool bMustJump = bJustTeleported || bZeroDelta || (OldBase == NULL || (!OldBase->IsQueryCollisionEnabled() && MovementBaseUtility::IsDynamicBase(OldBase)));
                 if ((bMustJump || !bCheckedFall) && CheckFall(OldFloor, CurrentFloor.HitResult, Delta, OldLocation, remainingTime, timeTick, Iterations, bMustJump))
                 {
                     return;
                 }
                 bCheckedFall = true;
             }
         }
 
         // Allow overlap events and such to change physics state and velocity
         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?
                 Velocity = (UpdatedComponent->GetComponentLocation() - OldLocation) / timeTick;
             }
         }
 
         // If we didn't move at all this iteration then abort (since future iterations will also be stuck).
         if (UpdatedComponent->GetComponentLocation() == OldLocation)
         {
             remainingTime = 0.f;
             break;
         }
     }
 
     if (IsMovingOnGround())
     {
         MaintainHorizontalGroundVelocity();
     }
 }
 
 void URunMovementComponent::PhysROLL(float deltaTime, int32 Iterations)
 {
     GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Yellow, TEXT("Rolling"));
 }
 
 void URunMovementComponent::PhysCustom(float deltaTime, int32 Iterations)
 {
     switch (CustomMovementMode)
     {
     case TESTMOVE_Running:
         PhysRUN(deltaTime, Iterations);
         break;
     case TESTMOVE_Rolling:
         PhysROLL(deltaTime, Iterations);
         break;
     default:
         break;
     }
 }

Sorry that there's so much to go through, but part of the problem is that I don't know where to start looking.

Product Version: UE 4.21
Tags:
more ▼

asked Jan 18 '19 at 11:13 PM in C++ Programming

avatar image

Hyperon_Ion
3 1 1 1

(comments are locked)
10|2000 characters needed characters left
Viewable by all users

1 answer: sort voted first

Ok, Managed to figure it out myself.

Turns out what hitting the angle limit does is the engine pushes the character back to the last point the floor was considered "walkable" and adjusts the velocity accordingly, but if you're in a custom movement mode that relies on a lot of the same stuff as MOVE_Walking does it get's confused when it calls the function IsMovingOnGround()

 bool UCharacterMovementComponent::IsMovingOnGround() const
 {
     return ((MovementMode == MOVE_Walking) || (MovementMode == MOVE_NavWalking)) && UpdatedComponent;
 }

As you can see the function only accounts for Unreal's "built-in" movement modes, so if you're making a custom movement mode that travels on the ground you either need to update IsMovingOnGround() with your custom movement mode or make a new physics process entirely.

Here's what I did to fix my code:

 bool URunMovementComponent::IsMovingOnGround() const
 {
     return ((MovementMode == MOVE_Walking) || (MovementMode == MOVE_NavWalking)
         || ((MovementMode == MOVE_Custom) && (CustomMovementMode == TESTMOVE_Running))) && UpdatedComponent;
 }
more ▼

answered Jan 21 '19 at 07:51 PM

avatar image

Hyperon_Ion
3 1 1 1

(comments are locked)
10|2000 characters needed characters left
Viewable by all users
Your answer
toggle preview:

Up to 5 attachments (including images) can be used with a maximum of 5.2 MB each and 5.2 MB total.

Follow this question

Once you sign in you will be able to subscribe for any updates here

Answers to this question