Last active
September 11, 2023 09:19
-
-
Save braustin20/82376c9e908153ed833d0c07d4e0048e to your computer and use it in GitHub Desktop.
Unreal 4 wall running character source script
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
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); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
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