Skip to content

Instantly share code, notes, and snippets.

@braustin20
Last active September 11, 2023 09:19
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save braustin20/82376c9e908153ed833d0c07d4e0048e to your computer and use it in GitHub Desktop.
Save braustin20/82376c9e908153ed833d0c07d4e0048e to your computer and use it in GitHub Desktop.
Unreal 4 wall running character source script
/**
WallRunCharacter.cpp
Primary player controls for wall running
@author Brandon Austin
@version 0.3 05/24/17
*/
#include "WallRun.h"
#include "WallRunCharacter.h"
//////////////////////////////////////////////////////////////////////////
// AWallRunCharacter
AWallRunCharacter::AWallRunCharacter()
{
//Set size for collision capsule
GetCapsuleComponent()->InitCapsuleSize(42.f, 96.0f);
//Set our turn rates for input
BaseTurnRate = 55.0f;
BaseLookUpRate = 55.0f;
//Don't rotate when the controller rotates. Let that just affect the camera.
bUseControllerRotationPitch = false;
bUseControllerRotationYaw = false;
bUseControllerRotationRoll = false;
//Wall run gravity values
WallRunGravityScale = 0.65f;
DefaultGravityScale = 1.8f;
WallJumpForce = 450.0f;
WallRunRange = 40.0f;
//Configure character movement
GetCharacterMovement()->bOrientRotationToMovement = false;
GetCharacterMovement()->bUseControllerDesiredRotation = true;
GetCharacterMovement()->RotationRate = FRotator(0.0f, 540.0f, 0.0f);
GetCharacterMovement()->JumpZVelocity = 600.f;
GetCharacterMovement()->AirControl = 0.1f;
GetCharacterMovement()->GravityScale = DefaultGravityScale;
//Create a camera boom (pulls in towards the player if there is a collision)
CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));
CameraBoom->SetupAttachment(RootComponent);
CameraBoom->TargetArmLength = 400.0f; // The camera follows at this distance behind the character
CameraBoom->bUsePawnControlRotation = true; // Rotate the arm based on the controller
CameraLockTargetSpeed = 5.0f;
//Create a follow camera
FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera"));
FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName); // Attach the camera to the end of the boom and let the boom adjust to match the controller orientation
FollowCamera->bUsePawnControlRotation = false; // Camera does not rotate relative to arm
}
//////////////////////////////////////////////////////////////////////////
// Input
void AWallRunCharacter::SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent)
{
//Set up gameplay key bindings
check(PlayerInputComponent);
PlayerInputComponent->BindAction("Jump", IE_Pressed, this, &ACharacter::Jump);
PlayerInputComponent->BindAction("Jump", IE_Released, this, &ACharacter::StopJumping);
PlayerInputComponent->BindAction("LockCamera", IE_Pressed, this, &AWallRunCharacter::ToggleCameraLock);
PlayerInputComponent->BindAction("LockCamera", IE_Released, this, &AWallRunCharacter::ToggleCameraLock);
PlayerInputComponent->BindAxis("MoveForward", this, &AWallRunCharacter::MoveForward);
PlayerInputComponent->BindAxis("MoveRight", this, &AWallRunCharacter::MoveRight);
// We have 2 versions of the rotation bindings to handle different kinds of devices differently
// "turn" handles devices that provide an absolute delta, such as a mouse.
// "turnrate" is for devices that we choose to treat as a rate of change, such as an analog joystick
PlayerInputComponent->BindAxis("Turn", this, &APawn::AddControllerYawInput);
PlayerInputComponent->BindAxis("TurnRate", this, &AWallRunCharacter::TurnAtRate);
PlayerInputComponent->BindAxis("LookUp", this, &APawn::AddControllerPitchInput);
PlayerInputComponent->BindAxis("LookUpRate", this, &AWallRunCharacter::LookUpAtRate);
}
void AWallRunCharacter::Tick(float DeltaSeconds)
{
if (GetCharacterMovement()->IsFalling() && bHoldingJump)
{
//Perform a trace left and right of the player
FHitResult HitOutR = CheckWall(true);
FHitResult HitOutL = CheckWall(false);
//Save previous hit normal
if (WallHit.bBlockingHit) PreviousWallHitNormal = WallHit.ImpactNormal;
//Determine if the left or right trace hit a closer wall
WallHit = HitOutR.Distance >= HitOutL.Distance ? HitOutR : HitOutL;
/** Used for testing degree difference between hit wall normals
float TestValue = FMath::RadiansToDegrees(FMath::Acos(FVector::DotProduct(PreviousWallHitNormal, WallHit.ImpactNormal)));
if(WallHit.bBlockingHit && TestValue != 0.0f) GEngine->AddOnScreenDebugMessage(1, 2.0f, FColor::Red, FString::SanitizeFloat(TestValue), false);
*/
//Verify that we have actually hit a wall with the closest trace
if (WallHit.bBlockingHit && !bWallRunning)
{
//GEngine->AddOnScreenDebugMessage(1, 2.0f, FColor::Green, TEXT("Touching Wall"), false);
ToggleWallRun(true);
}
else if (!WallHit.bBlockingHit && bWallRunning)
{
//GEngine->AddOnScreenDebugMessage(1, 2.0f, FColor::Red, TEXT("Not Touching Wall"), false);
ToggleWallRun(false);
}
}
//If we're either not falling or not holding the correct input, cancel the wall run if we're currently on the wall
else if (bWallRunning)
{
ToggleWallRun(false);
}
//If the camera locked input is triggered, and the goal actor is valid, lerp to the correct rotation
if (bCameraLocked && LevelGoalActor->IsValidLowLevelFast())
{
GetController()->SetControlRotation(FMath::RInterpTo(GetControlRotation(),
(LevelGoalActor->GetActorLocation() - GetActorLocation()).Rotation(), DeltaSeconds, CameraLockTargetSpeed));
}
Super::Tick(DeltaSeconds);
}
void AWallRunCharacter::ToggleCameraLock()
{
//Flip the boolean whenever the camera button is toggled
bCameraLocked = !bCameraLocked;
}
FHitResult AWallRunCharacter::CheckWall(bool bRight)
{
const FName TraceTag("WallTraceTag");
//Uncomment to see the trace shape from the player to the wall
//GetWorld()->DebugDrawTraceTag = TraceTag;
//Set up trace parameters
FCollisionQueryParams TraceParams = FCollisionQueryParams(TraceTag, true, this);
TraceParams.bTraceComplex = true;
TraceParams.bTraceAsyncScene = true;
TraceParams.bReturnPhysicalMaterial = false;
TraceParams.bFindInitialOverlaps = true;
FHitResult HitOut(ForceInit);
//Set up direction and end point for wall run sphere cast
float DirectionMultiplier = bRight ? WallRunRange : -1.0f * WallRunRange;
FVector End = GetActorLocation() + (GetActorForwardVector() * GetCapsuleComponent()->GetScaledCapsuleRadius()) + (GetActorRightVector() * DirectionMultiplier);
//Set up size/shape of trace
FCollisionShape TraceShape = FCollisionShape::MakeSphere(30.0f);
//Perform trace
GetWorld()->SweepSingleByChannel(
HitOut,
GetActorLocation(),
End,
GetActorRotation().Quaternion(),
ECollisionChannel::ECC_Visibility,
TraceShape,
TraceParams
);
//Return the result
return HitOut;
}
void AWallRunCharacter::ToggleWallRun(bool bEnable)
{
//Set the values needed for camera and gravity when wall running
if (bEnable)
{
GetCharacterMovement()->GravityScale = WallRunGravityScale;
APlayerController* PC = Cast<APlayerController>(GetController());
PC->PlayerCameraManager->ViewPitchMax = 20.0f;
PC->PlayerCameraManager->ViewPitchMin = -20.0f;
//If this is a newly hit wall, and we are moving downwards, cancel downward momentem temporarily
if (FVector::DotProduct(GetActorUpVector(), GetVelocity() / GetVelocity().Size()) < 0.0f && PreviousWallHitNormal != WallHit.ImpactNormal)
{
LaunchCharacter(GetActorUpVector() * 100.0f, false, true);
}
bWallRunning = true;
}
//Reset gravity and camera values for normal movement
else
{
GetCharacterMovement()->GravityScale = DefaultGravityScale;
APlayerController* PC = Cast<APlayerController>(GetController());
PC->PlayerCameraManager->ViewPitchMax = 89.900002;
PC->PlayerCameraManager->ViewPitchMin = -89.900002;
bWallRunning = false;
}
}
void AWallRunCharacter::Jump()
{
bHoldingJump = true;
Super::Jump();
}
void AWallRunCharacter::StopJumping()
{
//If the player is wall running when jump is released, perform a leap from the wall
if (bWallRunning)
{
//Get the vector from the player to the wall
FVector LaunchVector = WallHit.ImpactNormal;
LaunchVector.Normalize();
//Merge the wall's impact vector with the player's up vector to get a "Leap" from the wall
LaunchVector = (LaunchVector + (GetActorUpVector() * 1.0)) /2;
//Perform the wall jump using the player's wall jump force value and the launch vector
LaunchCharacter(LaunchVector * WallJumpForce, false, false);
//Uncomment to see a line trace showing the wall jump direction
//DrawDebugLine(GetWorld(), GetActorLocation(), (GetActorLocation() + (GetActorForwardVector() * GetCapsuleComponent()->GetScaledCapsuleRadius())) + (LaunchVector * (WallJumpForce / 25)), FColor::Red, false, 3.0f, 100, 5.0f);
}
bHoldingJump = false;
Super::StopJumping();
}
void AWallRunCharacter::TurnAtRate(float Rate)
{
if (!bCameraLocked || bWallRunning)
{
//Calculate delta for this frame from the rate information
AddControllerYawInput(Rate * BaseTurnRate * GetWorld()->GetDeltaSeconds());
}
}
void AWallRunCharacter::LookUpAtRate(float Rate)
{
if (!bCameraLocked || bWallRunning)
{
//Calculate delta for this frame from the rate information
AddControllerPitchInput(Rate * BaseLookUpRate * GetWorld()->GetDeltaSeconds());
}
}
void AWallRunCharacter::MoveForward(float Value)
{
if ((Controller != NULL) && (Value != 0.0f))
{
//Find out which way is forward
const FRotator Rotation = Controller->GetControlRotation();
const FRotator YawRotation(0, Rotation.Yaw, 0);
//Get forward vector
const FVector Direction = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
AddMovementInput(Direction, Value);
}
}
void AWallRunCharacter::MoveRight(float Value)
{
//NOTE: Disables strafing when wall running, debated on whether to leave this on or not.
//In place to prevent player from accidentally sliding off wall
if ( (Controller != NULL) && (Value != 0.0f) && !bWallRunning)
{
//Find out which way is right
const FRotator Rotation = Controller->GetControlRotation();
const FRotator YawRotation(0, Rotation.Yaw, 0);
//Get right vector
const FVector Direction = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);
//Add movement in that direction
AddMovementInput(Direction, Value);
}
}
/**
WallRunCharacter.h
Header file for primary player controls for wall running
@author Brandon Austin
@version 0.3 05/24/17
*/
#pragma once
#include "GameFramework/Character.h"
#include "WallRunCharacter.generated.h"
UCLASS(config=Game)
class AWallRunCharacter : public ACharacter
{
GENERATED_BODY()
/** Camera boom positioning the camera behind the character */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))
class USpringArmComponent* CameraBoom;
/** Follow camera */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))
class UCameraComponent* FollowCamera;
public:
AWallRunCharacter();
// Base turn rate, in deg/sec. Other scaling may affect final turn rate
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category=Camera)
float BaseTurnRate;
// Base look up/down rate, in deg/sec. Other scaling may affect final rate
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category=Camera)
float BaseLookUpRate;
// Has the camera been locked onto the objective
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera)
bool bCameraLocked;
// The speed at which the camera corrects to look at the objective
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Camera)
float CameraLockTargetSpeed;
//The end goal of the test level
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Gameplay)
AActor* LevelGoalActor;
//If the player is continuing to hold jump key
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = WallRun)
bool bHoldingJump;
//The extent of the wall trace
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = WallRun)
float WallRunRange;
//The gravity scale used while wall running
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = WallRun)
float WallRunGravityScale;
//The gravity scale used when in normal movement
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = WallRun)
float DefaultGravityScale;
//The force the player uses to jump off of a wall
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = WallRun)
float WallJumpForce;
//Is the player currently running on a wall
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = WallRun)
bool bWallRunning;
//The closest hit wall result
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = WallRun)
FHitResult WallHit;
//The normal vector of the previously hit wall
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = WallRun)
FVector PreviousWallHitNormal;
protected:
/** Called for forwards/backward input */
void MoveForward(float Value);
/** Called for side to side input */
void MoveRight(float Value);
/**
* Toggles camera lock when the camera binding is pressed
*/
void ToggleCameraLock();
/**
* Called via input to turn at a given rate.
* @param Rate This is a normalized rate, i.e. 1.0 means 100% of desired turn rate
*/
void TurnAtRate(float Rate);
/**
* Called via input to turn look up/down at a given rate.
* @param Rate This is a normalized rate, i.e. 1.0 means 100% of desired turn rate
*/
void LookUpAtRate(float Rate);
/**
* Sweeps a trace starting in the direction given to see if there is a wall beside the player. Will then recursively sweep in the opposite direction if not hit found.
* @param bRight Whether to do the first sweep in right/left direction
*/
FHitResult CheckWall(bool bRight);
/**
* Toggles player movement values when the player leaves a wall run by touching the ground or leaping off or starts a wall run.
*/
void ToggleWallRun(bool bEnable);
protected:
// APawn interface
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
virtual void Tick(float DeltaSeconds);
virtual void Jump();
virtual void StopJumping();
// End of APawn interface
public:
/** Returns CameraBoom subobject **/
FORCEINLINE class USpringArmComponent* GetCameraBoom() const { return CameraBoom; }
/** Returns FollowCamera subobject **/
FORCEINLINE class UCameraComponent* GetFollowCamera() const { return FollowCamera; }
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment