Semi-functional gravity direction changing
This commit is contained in:
		
							parent
							
								
									f7a263d2c2
								
							
						
					
					
						commit
						01f88914cd
					
				
							
								
								
									
										
											BIN
										
									
								
								GravityStomp/Content/Characters/BP_GSCharacter.uasset
									 (Stored with Git LFS)
									
									
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								GravityStomp/Content/Characters/BP_GSCharacter.uasset
									 (Stored with Git LFS)
									
									
									
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								GravityStomp/Content/Input/Actions/IA_ChangeGravity.uasset
									 (Stored with Git LFS)
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								GravityStomp/Content/Input/Actions/IA_ChangeGravity.uasset
									 (Stored with Git LFS)
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								GravityStomp/Content/Input/IMC_Default.uasset
									 (Stored with Git LFS)
									
									
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								GravityStomp/Content/Input/IMC_Default.uasset
									 (Stored with Git LFS)
									
									
									
									
								
							
										
											Binary file not shown.
										
									
								
							@ -7,7 +7,10 @@
 | 
				
			|||||||
		{
 | 
							{
 | 
				
			||||||
			"Name": "GravityStompGame",
 | 
								"Name": "GravityStompGame",
 | 
				
			||||||
			"Type": "Runtime",
 | 
								"Type": "Runtime",
 | 
				
			||||||
			"LoadingPhase": "Default"
 | 
								"LoadingPhase": "Default",
 | 
				
			||||||
 | 
								"AdditionalDependencies": [
 | 
				
			||||||
 | 
									"Engine"
 | 
				
			||||||
 | 
								]
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			"Name": "GravityStompEditor",
 | 
								"Name": "GravityStompEditor",
 | 
				
			||||||
 | 
				
			|||||||
@ -9,13 +9,15 @@
 | 
				
			|||||||
#include "GameFramework/SpringArmComponent.h"
 | 
					#include "GameFramework/SpringArmComponent.h"
 | 
				
			||||||
#include "EnhancedInputComponent.h"
 | 
					#include "EnhancedInputComponent.h"
 | 
				
			||||||
#include "EnhancedInputSubsystems.h"
 | 
					#include "EnhancedInputSubsystems.h"
 | 
				
			||||||
 | 
					#include "GSCharacterMovementComponent.h"
 | 
				
			||||||
#include "Kismet/KismetMathLibrary.h"
 | 
					#include "Kismet/KismetMathLibrary.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//////////////////////////////////////////////////////////////////////////
 | 
					//////////////////////////////////////////////////////////////////////////
 | 
				
			||||||
// AGravityStompCharacter
 | 
					// AGravityStompCharacter
 | 
				
			||||||
 | 
					
 | 
				
			||||||
AGSCharacter::AGSCharacter()
 | 
					AGSCharacter::AGSCharacter(const FObjectInitializer& ObjectInitializer)
 | 
				
			||||||
 | 
					    : Super(ObjectInitializer.SetDefaultSubobjectClass<UGSCharacterMovementComponent>(ACharacter::CharacterMovementComponentName))
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	// Set size for collision capsule
 | 
						// Set size for collision capsule
 | 
				
			||||||
	GetCapsuleComponent()->InitCapsuleSize(42.f, 96.0f);
 | 
						GetCapsuleComponent()->InitCapsuleSize(42.f, 96.0f);
 | 
				
			||||||
@ -80,6 +82,7 @@ void AGSCharacter::SetupPlayerInputComponent(class UInputComponent* PlayerInputC
 | 
				
			|||||||
	{
 | 
						{
 | 
				
			||||||
		//Moving
 | 
							//Moving
 | 
				
			||||||
		EnhancedInputComponent->BindAction(MoveAction, ETriggerEvent::Triggered, this, &AGSCharacter::Move);
 | 
							EnhancedInputComponent->BindAction(MoveAction, ETriggerEvent::Triggered, this, &AGSCharacter::Move);
 | 
				
			||||||
 | 
							EnhancedInputComponent->BindAction(ChangeGravityAction, ETriggerEvent::Triggered, this, &AGSCharacter::ChangeGravityDirection);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -94,3 +97,11 @@ void AGSCharacter::Move(const FInputActionValue& Value)
 | 
				
			|||||||
	AddMovementInput(FVector::UpVector, MovementVector.Y);
 | 
						AddMovementInput(FVector::UpVector, MovementVector.Y);
 | 
				
			||||||
	AddMovementInput(FVector::RightVector, MovementVector.X);
 | 
						AddMovementInput(FVector::RightVector, MovementVector.X);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void AGSCharacter::ChangeGravityDirection(const FInputActionValue& Value)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						FVector2D GravityDirection = Value.Get<FVector2D>();
 | 
				
			||||||
 | 
						FVector NewCharacterUpDirection(0.0f, -GravityDirection.X, -GravityDirection.Y);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						GetCharacterMovement<UGSCharacterMovementComponent>()->SetCharacterUpDirection(NewCharacterUpDirection);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -27,16 +27,22 @@ class AGSCharacter : public ACharacter
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	/** Move Input Action */
 | 
						/** Move Input Action */
 | 
				
			||||||
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, meta = (AllowPrivateAccess = "true"))
 | 
						UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, meta = (AllowPrivateAccess = "true"))
 | 
				
			||||||
	class UInputAction* MoveAction;;
 | 
						class UInputAction* MoveAction;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, meta = (AllowPrivateAccess = "true"))
 | 
				
			||||||
 | 
						class UInputAction* ChangeGravityAction;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public:
 | 
					public:
 | 
				
			||||||
	AGSCharacter();
 | 
						AGSCharacter(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
protected:
 | 
					protected:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/** Called for movement input */
 | 
						/** Called for movement input */
 | 
				
			||||||
	void Move(const FInputActionValue& Value);
 | 
						void Move(const FInputActionValue& Value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/** Called when the player changes the gravity direction */
 | 
				
			||||||
 | 
						void ChangeGravityDirection(const FInputActionValue& Value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
protected:
 | 
					protected:
 | 
				
			||||||
	// APawn interface
 | 
						// APawn interface
 | 
				
			||||||
	virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
 | 
						virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
 | 
				
			||||||
 | 
				
			|||||||
@ -0,0 +1,355 @@
 | 
				
			|||||||
 | 
					// Fill out your copyright notice in the Description page of Project Settings.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "Character/GSCharacterMovementComponent.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// UE includes
 | 
				
			||||||
 | 
					#include "GameFramework/Character.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// These are defined in CharacterMovementComponent.cpp and inaccessible here. Just copy and paste too make the PhysFalling work
 | 
				
			||||||
 | 
					namespace CharacterMovementConstants
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// MAGIC NUMBERS
 | 
				
			||||||
 | 
						const float VERTICAL_SLOPE_NORMAL_Z = 0.001f; // Slope is vertical if Abs(Normal.Z) <= this threshold. Accounts for precision problems that sometimes angle normals slightly off horizontal for vertical surface.
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace CharacterMovementCVars
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    int32 ForceJumpPeakSubstep = 1;
 | 
				
			||||||
 | 
						FAutoConsoleVariableRef CVarForceJumpPeakSubstep(
 | 
				
			||||||
 | 
							TEXT("gs.ForceJumpPeakSubstep"),
 | 
				
			||||||
 | 
							ForceJumpPeakSubstep,
 | 
				
			||||||
 | 
							TEXT("If 1, force a jump substep to always reach the peak position of a jump, which can often be cut off as framerate lowers."),
 | 
				
			||||||
 | 
							ECVF_Default);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					UGSCharacterMovementComponent::UGSCharacterMovementComponent()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void UGSCharacterMovementComponent::PhysFalling(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;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							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 = CharacterUpDirection * 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);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 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 (CharacterMovementCVars::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);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 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);
 | 
				
			||||||
 | 
											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();
 | 
				
			||||||
 | 
									if (/*CharacterMovementCVars::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;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									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.
 | 
				
			||||||
 | 
											if (bHasLimitedAirControl && Hit.Normal.Z > CharacterMovementConstants::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;
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											// 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);
 | 
				
			||||||
 | 
													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;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void UGSCharacterMovementComponent::SetCharacterUpDirection(FVector NewUpDirection)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						NewUpDirection.Normalize();
 | 
				
			||||||
 | 
						CharacterUpDirection = NewUpDirection;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,29 @@
 | 
				
			|||||||
 | 
					// Fill out your copyright notice in the Description page of Project Settings.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// UE includes
 | 
				
			||||||
 | 
					#include "CoreMinimal.h"
 | 
				
			||||||
 | 
					#include "GameFramework/CharacterMovementComponent.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "GSCharacterMovementComponent.generated.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 *	Subclass of CharacterMovementComponent which allows for a custom gravity direction to be applied to the character while falling
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					UCLASS()
 | 
				
			||||||
 | 
					class GRAVITYSTOMPGAME_API UGSCharacterMovementComponent : public UCharacterMovementComponent
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						GENERATED_BODY()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						UGSCharacterMovementComponent();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						virtual void PhysFalling(float deltaTime, int32 Iterations) override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						UFUNCTION(BlueprintCallable)
 | 
				
			||||||
 | 
						void SetCharacterUpDirection(FVector NewUpDirection);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private:
 | 
				
			||||||
 | 
						FVector CharacterUpDirection = FVector::UpVector;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
		Loading…
	
		Reference in New Issue
	
	Block a user