Step 1: the Salty project, two modules, tests and the gym
Salty.uproject (UE 5.8) from the Third Person template with class redirects, the SaltyCore and Salty modules, the three build targets, USaltyAssetManager calling InitGlobalData, Config/Tags with the 23 root namespaces, Scripts/run-tests.sh, build.sh and Authoring/create_gym.py, L_Gym, Git LFS attributes and the placeholder test. Template variants kept as reference (D-41). The four Fab packs stay out of the repository for now (~10 GB). The editor serves the engine MCP plugin on 127.0.0.1:8000 through DefaultEditorPerProjectUserSettings.ini. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
0e61a77346
commit
4f2c55cd2a
@@ -0,0 +1,21 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
|
||||
#include "AnimNotify_EndDash.h"
|
||||
#include "PlatformingCharacter.h"
|
||||
#include "Components/SkeletalMeshComponent.h"
|
||||
|
||||
void UAnimNotify_EndDash::Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, const FAnimNotifyEventReference& EventReference)
|
||||
{
|
||||
// cast the owner to the attacker interface
|
||||
if (APlatformingCharacter* PlatformingCharacter = Cast<APlatformingCharacter>(MeshComp->GetOwner()))
|
||||
{
|
||||
// tell the actor to end the dash
|
||||
PlatformingCharacter->EndDash();
|
||||
}
|
||||
}
|
||||
|
||||
FString UAnimNotify_EndDash::GetNotifyName_Implementation() const
|
||||
{
|
||||
return FString("End Dash");
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Animation/AnimNotifies/AnimNotify.h"
|
||||
#include "AnimNotify_EndDash.generated.h"
|
||||
|
||||
/**
|
||||
* AnimNotify to finish the dash animation and restore player control
|
||||
*/
|
||||
UCLASS()
|
||||
class UAnimNotify_EndDash : public UAnimNotify
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
|
||||
/** Perform the Anim Notify */
|
||||
virtual void Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, const FAnimNotifyEventReference& EventReference) override;
|
||||
|
||||
/** Get the notify name */
|
||||
virtual FString GetNotifyName_Implementation() const override;
|
||||
};
|
||||
@@ -0,0 +1,373 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
|
||||
#include "PlatformingCharacter.h"
|
||||
|
||||
#include "Components/CapsuleComponent.h"
|
||||
#include "Engine/World.h"
|
||||
#include "GameFramework/CharacterMovementComponent.h"
|
||||
#include "GameFramework/SpringArmComponent.h"
|
||||
#include "Components/SkeletalMeshComponent.h"
|
||||
#include "Camera/CameraComponent.h"
|
||||
#include "EnhancedInputSubsystems.h"
|
||||
#include "EnhancedInputComponent.h"
|
||||
#include "TimerManager.h"
|
||||
#include "Engine/LocalPlayer.h"
|
||||
|
||||
APlatformingCharacter::APlatformingCharacter()
|
||||
{
|
||||
PrimaryActorTick.bCanEverTick = true;
|
||||
|
||||
// initialize the flags
|
||||
bHasWallJumped = false;
|
||||
bHasDoubleJumped = false;
|
||||
bHasDashed = false;
|
||||
bIsDashing = false;
|
||||
|
||||
// bind the dash montage ended delegate
|
||||
OnDashMontageEnded.BindUObject(this, &APlatformingCharacter::DashMontageEnded);
|
||||
|
||||
// enable press and hold jump
|
||||
JumpMaxHoldTime = 0.4f;
|
||||
|
||||
// set the jump max count to 3 so we can double jump and check for coyote time jumps
|
||||
JumpMaxCount = 3;
|
||||
|
||||
// Set size for collision capsule
|
||||
GetCapsuleComponent()->InitCapsuleSize(35.0f, 90.0f);
|
||||
|
||||
// don't rotate the mesh when the controller rotates
|
||||
bUseControllerRotationYaw = false;
|
||||
|
||||
// Configure character movement
|
||||
GetCharacterMovement()->GravityScale = 2.5f;
|
||||
GetCharacterMovement()->MaxAcceleration = 1500.0f;
|
||||
GetCharacterMovement()->BrakingFrictionFactor = 1.0f;
|
||||
GetCharacterMovement()->bUseSeparateBrakingFriction = true;
|
||||
|
||||
GetCharacterMovement()->GroundFriction = 4.0f;
|
||||
GetCharacterMovement()->MaxWalkSpeed = 750.0f;
|
||||
GetCharacterMovement()->MinAnalogWalkSpeed = 20.0f;
|
||||
GetCharacterMovement()->BrakingDecelerationWalking = 2500.0f;
|
||||
GetCharacterMovement()->PerchRadiusThreshold = 15.0f;
|
||||
|
||||
GetCharacterMovement()->JumpZVelocity = 350.0f;
|
||||
GetCharacterMovement()->BrakingDecelerationFalling = 750.0f;
|
||||
GetCharacterMovement()->AirControl = 1.0f;
|
||||
|
||||
GetCharacterMovement()->RotationRate = FRotator(0.0f, 500.0f, 0.0f);
|
||||
GetCharacterMovement()->bOrientRotationToMovement = true;
|
||||
|
||||
GetCharacterMovement()->NavAgentProps.AgentRadius = 42.0;
|
||||
GetCharacterMovement()->NavAgentProps.AgentHeight = 192.0;
|
||||
|
||||
// create the camera boom
|
||||
CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));
|
||||
CameraBoom->SetupAttachment(RootComponent);
|
||||
|
||||
CameraBoom->TargetArmLength = 400.0f;
|
||||
CameraBoom->bUsePawnControlRotation = true;
|
||||
CameraBoom->bEnableCameraLag = true;
|
||||
CameraBoom->CameraLagSpeed = 8.0f;
|
||||
CameraBoom->bEnableCameraRotationLag = true;
|
||||
CameraBoom->CameraRotationLagSpeed = 8.0f;
|
||||
|
||||
// create the orbiting camera
|
||||
FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera"));
|
||||
FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName);
|
||||
FollowCamera->bUsePawnControlRotation = false;
|
||||
}
|
||||
|
||||
void APlatformingCharacter::Move(const FInputActionValue& Value)
|
||||
{
|
||||
FVector2D MovementVector = Value.Get<FVector2D>();
|
||||
|
||||
// route the input
|
||||
DoMove(MovementVector.X, MovementVector.Y);
|
||||
}
|
||||
|
||||
void APlatformingCharacter::Look(const FInputActionValue& Value)
|
||||
{
|
||||
FVector2D LookAxisVector = Value.Get<FVector2D>();
|
||||
|
||||
// route the input
|
||||
DoLook(LookAxisVector.X, LookAxisVector.Y);
|
||||
}
|
||||
|
||||
|
||||
void APlatformingCharacter::Dash()
|
||||
{
|
||||
// route the input
|
||||
DoDash();
|
||||
}
|
||||
|
||||
void APlatformingCharacter::MultiJump()
|
||||
{
|
||||
// ignore jumps while dashing
|
||||
if(bIsDashing)
|
||||
return;
|
||||
|
||||
// are we already in the air?
|
||||
if (GetCharacterMovement()->IsFalling())
|
||||
{
|
||||
|
||||
// have we already wall jumped?
|
||||
if (!bHasWallJumped)
|
||||
{
|
||||
// run a sphere sweep to check if we're in front of a wall
|
||||
FHitResult OutHit;
|
||||
|
||||
const FVector TraceStart = GetActorLocation();
|
||||
const FVector TraceEnd = TraceStart + (GetActorForwardVector() * WallJumpTraceDistance);
|
||||
const FCollisionShape TraceShape = FCollisionShape::MakeSphere(WallJumpTraceRadius);
|
||||
|
||||
FCollisionQueryParams QueryParams;
|
||||
QueryParams.AddIgnoredActor(this);
|
||||
|
||||
if (GetWorld()->SweepSingleByChannel(OutHit, TraceStart, TraceEnd, FQuat(), ECollisionChannel::ECC_Visibility, TraceShape, QueryParams))
|
||||
{
|
||||
// rotate the character to face away from the wall, so we're correctly oriented for the next wall jump
|
||||
FRotator WallOrientation = OutHit.ImpactNormal.ToOrientationRotator();
|
||||
WallOrientation.Pitch = 0.0f;
|
||||
WallOrientation.Roll = 0.0f;
|
||||
|
||||
SetActorRotation(WallOrientation);
|
||||
|
||||
// apply a launch impulse to the character to perform the actual wall jump
|
||||
const FVector WallJumpImpulse = (OutHit.ImpactNormal * WallJumpBounceImpulse) + (FVector::UpVector * WallJumpVerticalImpulse);
|
||||
|
||||
LaunchCharacter(WallJumpImpulse, true, true);
|
||||
|
||||
// enable the jump trail
|
||||
SetJumpTrailState(true);
|
||||
|
||||
// raise the wall jump flag to prevent an immediate second wall jump
|
||||
bHasWallJumped = true;
|
||||
|
||||
GetWorld()->GetTimerManager().SetTimer(WallJumpTimer, this, &APlatformingCharacter::ResetWallJump, DelayBetweenWallJumps, false);
|
||||
}
|
||||
// no wall jump, try a double jump next
|
||||
else
|
||||
{
|
||||
// are we still within coyote time frames?
|
||||
if (GetWorld()->GetTimeSeconds() - LastFallTime < MaxCoyoteTime)
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("Coyote Jump"));
|
||||
|
||||
// use the built-in CMC functionality to do the jump
|
||||
Jump();
|
||||
|
||||
// enable the jump trail
|
||||
SetJumpTrailState(true);
|
||||
|
||||
// no coyote time jump
|
||||
} else {
|
||||
|
||||
// only double jump once while we're in the air
|
||||
if (!bHasDoubleJumped)
|
||||
{
|
||||
bHasDoubleJumped = true;
|
||||
|
||||
// use the built-in CMC functionality to do the double jump
|
||||
Jump();
|
||||
|
||||
// enable the jump trail
|
||||
SetJumpTrailState(true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
// we're grounded so just do a regular jump
|
||||
Jump();
|
||||
|
||||
// activate the jump trail
|
||||
SetJumpTrailState(true);
|
||||
}
|
||||
}
|
||||
|
||||
void APlatformingCharacter::ResetWallJump()
|
||||
{
|
||||
// reset the wall jump input lock
|
||||
bHasWallJumped = false;
|
||||
}
|
||||
|
||||
void APlatformingCharacter::DoMove(float Right, float Forward)
|
||||
{
|
||||
if (GetController() != nullptr)
|
||||
{
|
||||
// momentarily disable movement inputs if we've just wall jumped
|
||||
if (!bHasWallJumped)
|
||||
{
|
||||
// find out which way is forward
|
||||
const FRotator Rotation = GetController()->GetControlRotation();
|
||||
const FRotator YawRotation(0, Rotation.Yaw, 0);
|
||||
|
||||
// get forward vector
|
||||
const FVector ForwardDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
|
||||
|
||||
// get right vector
|
||||
const FVector RightDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);
|
||||
|
||||
// add movement
|
||||
AddMovementInput(ForwardDirection, Forward);
|
||||
AddMovementInput(RightDirection, Right);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void APlatformingCharacter::DoLook(float Yaw, float Pitch)
|
||||
{
|
||||
if (GetController() != nullptr)
|
||||
{
|
||||
// add yaw and pitch input to controller
|
||||
AddControllerYawInput(Yaw);
|
||||
AddControllerPitchInput(Pitch);
|
||||
}
|
||||
}
|
||||
|
||||
void APlatformingCharacter::DoDash()
|
||||
{
|
||||
// ignore the input if we've already dashed and have yet to reset
|
||||
if (bHasDashed)
|
||||
return;
|
||||
|
||||
// raise the dash flags
|
||||
bIsDashing = true;
|
||||
bHasDashed = true;
|
||||
|
||||
// disable gravity while dashing
|
||||
GetCharacterMovement()->GravityScale = 0.0f;
|
||||
|
||||
// reset the character velocity so we don't carry momentum into the dash
|
||||
GetCharacterMovement()->Velocity = FVector::ZeroVector;
|
||||
|
||||
// enable the jump trails
|
||||
SetJumpTrailState(true);
|
||||
|
||||
// play the dash montage
|
||||
if (UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance())
|
||||
{
|
||||
const float MontageLength = AnimInstance->Montage_Play(DashMontage, 1.0f, EMontagePlayReturnType::MontageLength, 0.0f, true);
|
||||
|
||||
// has the montage played successfully?
|
||||
if (MontageLength > 0.0f)
|
||||
{
|
||||
AnimInstance->Montage_SetEndDelegate(OnDashMontageEnded, DashMontage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void APlatformingCharacter::DoJumpStart()
|
||||
{
|
||||
// handle special jump cases
|
||||
MultiJump();
|
||||
}
|
||||
|
||||
void APlatformingCharacter::DoJumpEnd()
|
||||
{
|
||||
// stop jumping
|
||||
StopJumping();
|
||||
}
|
||||
|
||||
void APlatformingCharacter::DashMontageEnded(UAnimMontage* Montage, bool bInterrupted)
|
||||
{
|
||||
// Avoid resetting the dash if the previous ground dash interrupted the montage.
|
||||
if (bInterrupted && bIsDashing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// end the dash
|
||||
EndDash();
|
||||
}
|
||||
|
||||
void APlatformingCharacter::EndDash()
|
||||
{
|
||||
// restore gravity
|
||||
GetCharacterMovement()->GravityScale = 2.5f;
|
||||
|
||||
// reset the dashing flag
|
||||
bIsDashing = false;
|
||||
|
||||
// are we grounded after the dash?
|
||||
if (GetCharacterMovement()->IsMovingOnGround())
|
||||
{
|
||||
// reset the dash usage flag, since we won't receive a landed event
|
||||
bHasDashed = false;
|
||||
|
||||
// deactivate the jump trails
|
||||
SetJumpTrailState(false);
|
||||
}
|
||||
}
|
||||
|
||||
bool APlatformingCharacter::HasDoubleJumped() const
|
||||
{
|
||||
return bHasDoubleJumped;
|
||||
}
|
||||
|
||||
bool APlatformingCharacter::HasWallJumped() const
|
||||
{
|
||||
return bHasWallJumped;
|
||||
}
|
||||
|
||||
void APlatformingCharacter::EndPlay(const EEndPlayReason::Type EndPlayReason)
|
||||
{
|
||||
Super::EndPlay(EndPlayReason);
|
||||
|
||||
// clear the wall jump reset timer
|
||||
GetWorld()->GetTimerManager().ClearTimer(WallJumpTimer);
|
||||
}
|
||||
|
||||
void APlatformingCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
|
||||
{
|
||||
// Set up action bindings
|
||||
if (UEnhancedInputComponent* EnhancedInputComponent = Cast<UEnhancedInputComponent>(PlayerInputComponent))
|
||||
{
|
||||
|
||||
// Jumping
|
||||
EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Started, this, &APlatformingCharacter::DoJumpStart);
|
||||
EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Completed, this, &APlatformingCharacter::DoJumpEnd);
|
||||
|
||||
// Moving
|
||||
EnhancedInputComponent->BindAction(MoveAction, ETriggerEvent::Triggered, this, &APlatformingCharacter::Move);
|
||||
EnhancedInputComponent->BindAction(MouseLookAction, ETriggerEvent::Triggered, this, &APlatformingCharacter::Look);
|
||||
|
||||
// Looking
|
||||
EnhancedInputComponent->BindAction(LookAction, ETriggerEvent::Triggered, this, &APlatformingCharacter::Look);
|
||||
|
||||
// Dashing
|
||||
EnhancedInputComponent->BindAction(DashAction, ETriggerEvent::Triggered, this, &APlatformingCharacter::Dash);
|
||||
}
|
||||
}
|
||||
|
||||
void APlatformingCharacter::Landed(const FHitResult& Hit)
|
||||
{
|
||||
Super::Landed(Hit);
|
||||
|
||||
// reset the double jump and dash flags
|
||||
bHasDoubleJumped = false;
|
||||
bHasDashed = false;
|
||||
|
||||
// deactivate the jump trail
|
||||
SetJumpTrailState(false);
|
||||
}
|
||||
|
||||
void APlatformingCharacter::OnMovementModeChanged(EMovementMode PrevMovementMode, uint8 PreviousCustomMode /*= 0*/)
|
||||
{
|
||||
Super::OnMovementModeChanged(PrevMovementMode, PreviousCustomMode);
|
||||
|
||||
// are we falling?
|
||||
if (GetCharacterMovement()->MovementMode == EMovementMode::MOVE_Falling)
|
||||
{
|
||||
// save the game time when we started falling, so we can check it later for coyote time jumps
|
||||
LastFallTime = GetWorld()->GetTimeSeconds();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,194 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GameFramework/Character.h"
|
||||
#include "Animation/AnimInstance.h"
|
||||
#include "PlatformingCharacter.generated.h"
|
||||
|
||||
|
||||
class USpringArmComponent;
|
||||
class UCameraComponent;
|
||||
class UInputAction;
|
||||
struct FInputActionValue;
|
||||
class UAnimMontage;
|
||||
|
||||
/**
|
||||
* An enhanced Third Person Character with the following functionality:
|
||||
* - Platforming game character movement physics
|
||||
* - Press and Hold Jump
|
||||
* - Double Jump
|
||||
* - Wall Jump
|
||||
* - Dash
|
||||
*/
|
||||
UCLASS(abstract)
|
||||
class APlatformingCharacter : public ACharacter
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/** Camera boom positioning the camera behind the character */
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Components", meta = (AllowPrivateAccess = "true"))
|
||||
USpringArmComponent* CameraBoom;
|
||||
|
||||
/** Follow camera */
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Components", meta = (AllowPrivateAccess = "true"))
|
||||
UCameraComponent* FollowCamera;
|
||||
|
||||
protected:
|
||||
|
||||
/** Jump Input Action */
|
||||
UPROPERTY(EditAnywhere, Category="Input")
|
||||
UInputAction* JumpAction;
|
||||
|
||||
/** Move Input Action */
|
||||
UPROPERTY(EditAnywhere, Category="Input")
|
||||
UInputAction* MoveAction;
|
||||
|
||||
/** Look Input Action */
|
||||
UPROPERTY(EditAnywhere, Category="Input")
|
||||
UInputAction* LookAction;
|
||||
|
||||
/** Mouse Look Input Action */
|
||||
UPROPERTY(EditAnywhere, Category="Input")
|
||||
UInputAction* MouseLookAction;
|
||||
|
||||
/** Dash Input Action */
|
||||
UPROPERTY(EditAnywhere, Category="Input")
|
||||
UInputAction* DashAction;
|
||||
|
||||
public:
|
||||
|
||||
/** Constructor */
|
||||
APlatformingCharacter();
|
||||
|
||||
protected:
|
||||
|
||||
/** Called for movement input */
|
||||
void Move(const FInputActionValue& Value);
|
||||
|
||||
/** Called for looking input */
|
||||
void Look(const FInputActionValue& Value);
|
||||
|
||||
/** Called for dash input */
|
||||
void Dash();
|
||||
|
||||
/** Called for jump pressed to check for advanced multi-jump conditions */
|
||||
void MultiJump();
|
||||
|
||||
/** Resets the wall jump input lock */
|
||||
void ResetWallJump();
|
||||
|
||||
public:
|
||||
|
||||
/** Handles move inputs from either controls or UI interfaces */
|
||||
UFUNCTION(BlueprintCallable, Category="Input")
|
||||
virtual void DoMove(float Right, float Forward);
|
||||
|
||||
/** Handles look inputs from either controls or UI interfaces */
|
||||
UFUNCTION(BlueprintCallable, Category="Input")
|
||||
virtual void DoLook(float Yaw, float Pitch);
|
||||
|
||||
/** Handles dash inputs from either controls or UI interfaces */
|
||||
UFUNCTION(BlueprintCallable, Category="Input")
|
||||
virtual void DoDash();
|
||||
|
||||
/** Handles jump pressed inputs from either controls or UI interfaces */
|
||||
UFUNCTION(BlueprintCallable, Category="Input")
|
||||
virtual void DoJumpStart();
|
||||
|
||||
/** Handles jump pressed inputs from either controls or UI interfaces */
|
||||
UFUNCTION(BlueprintCallable, Category="Input")
|
||||
virtual void DoJumpEnd();
|
||||
|
||||
protected:
|
||||
|
||||
/** Called from a delegate when the dash montage ends */
|
||||
void DashMontageEnded(UAnimMontage* Montage, bool bInterrupted);
|
||||
|
||||
/** Passes control to Blueprint to enable or disable jump trails */
|
||||
UFUNCTION(BlueprintImplementableEvent, Category="Platforming")
|
||||
void SetJumpTrailState(bool bEnabled);
|
||||
|
||||
public:
|
||||
|
||||
/** Ends the dash state */
|
||||
void EndDash();
|
||||
|
||||
public:
|
||||
|
||||
/** Returns true if the character has just double jumped */
|
||||
UFUNCTION(BlueprintPure, Category="Platforming")
|
||||
bool HasDoubleJumped() const;
|
||||
|
||||
/** Returns true if the character has just wall jumped */
|
||||
UFUNCTION(BlueprintPure, Category="Platforming")
|
||||
bool HasWallJumped() const;
|
||||
|
||||
public:
|
||||
|
||||
/** EndPlay cleanup */
|
||||
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
|
||||
|
||||
/** Sets up input action bindings */
|
||||
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
|
||||
|
||||
/** Handle landings to reset dash and advanced jump state */
|
||||
virtual void Landed(const FHitResult& Hit) override;
|
||||
|
||||
/** Handle movement mode changes to keep track of coyote time jumps */
|
||||
virtual void OnMovementModeChanged(EMovementMode PrevMovementMode, uint8 PreviousCustomMode = 0) override;
|
||||
|
||||
protected:
|
||||
|
||||
/** movement state flag bits, packed into a uint8 for memory efficiency */
|
||||
uint8 bHasWallJumped : 1;
|
||||
uint8 bHasDoubleJumped : 1;
|
||||
uint8 bHasDashed : 1;
|
||||
uint8 bIsDashing : 1;
|
||||
|
||||
/** timer for wall jump input reset */
|
||||
FTimerHandle WallJumpTimer;
|
||||
|
||||
/** Dash montage ended delegate */
|
||||
FOnMontageEnded OnDashMontageEnded;
|
||||
|
||||
/** Distance to trace ahead of the character to look for walls to jump from */
|
||||
UPROPERTY(EditAnywhere, Category="Wall Jump", meta = (ClampMin = 0, ClampMax = 1000, Units = "cm"))
|
||||
float WallJumpTraceDistance = 50.0f;
|
||||
|
||||
/** Radius of the wall jump sphere trace check */
|
||||
UPROPERTY(EditAnywhere, Category="Wall Jump", meta = (ClampMin = 0, ClampMax = 100, Units = "cm"))
|
||||
float WallJumpTraceRadius = 25.0f;
|
||||
|
||||
/** Impulse to apply away from the wall when wall jumping */
|
||||
UPROPERTY(EditAnywhere, Category="Wall Jump", meta = (ClampMin = 0, ClampMax = 10000, Units = "cm/s"))
|
||||
float WallJumpBounceImpulse = 800.0f;
|
||||
|
||||
/** Vertical impulse to apply when wall jumping */
|
||||
UPROPERTY(EditAnywhere, Category="Wall Jump", meta = (ClampMin = 0, ClampMax = 10000, Units = "cm/s"))
|
||||
float WallJumpVerticalImpulse = 900.0f;
|
||||
|
||||
/** Time to ignore jump inputs after a wall jump */
|
||||
UPROPERTY(EditAnywhere, Category="Wall Jump", meta = (ClampMin = 0, ClampMax = 5, Units = "s"))
|
||||
float DelayBetweenWallJumps = 0.1f;
|
||||
|
||||
/** AnimMontage to use for the Dash action */
|
||||
UPROPERTY(EditAnywhere, Category="Dash")
|
||||
UAnimMontage* DashMontage;
|
||||
|
||||
/** Last recorded time when this character started falling */
|
||||
float LastFallTime = 0.0f;
|
||||
|
||||
/** Max amount of time that can pass since we started falling when we allow a regular jump */
|
||||
UPROPERTY(EditAnywhere, Category="Coyote Time", meta = (ClampMin = 0, ClampMax = 5, Units = "s"))
|
||||
float MaxCoyoteTime = 0.16f;
|
||||
|
||||
public:
|
||||
/** Returns CameraBoom subobject **/
|
||||
FORCEINLINE class USpringArmComponent* GetCameraBoom() const { return CameraBoom; }
|
||||
|
||||
/** Returns FollowCamera subobject **/
|
||||
FORCEINLINE class UCameraComponent* GetFollowCamera() const { return FollowCamera; }
|
||||
|
||||
};
|
||||
@@ -0,0 +1,53 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
|
||||
#include "Variant_Platforming/PlatformingGameMode.h"
|
||||
#include "Kismet/GameplayStatics.h"
|
||||
#include "GameFramework/PlayerStart.h"
|
||||
#include "Engine/World.h"
|
||||
|
||||
APlatformingGameMode::APlatformingGameMode()
|
||||
{
|
||||
// stub
|
||||
}
|
||||
|
||||
void APlatformingGameMode::BeginPlay()
|
||||
{
|
||||
Super::BeginPlay();
|
||||
|
||||
// create each additional local player.
|
||||
// Player 0 will be created automatically as part of regular game init
|
||||
for (int32 i = 2; i <= NumberOfLocalPlayers; ++i)
|
||||
{
|
||||
UGameplayStatics::CreatePlayer(GetWorld(), -1, true);
|
||||
}
|
||||
}
|
||||
|
||||
AActor* APlatformingGameMode::ChoosePlayerStart_Implementation(AController* Player)
|
||||
{
|
||||
// build the current player tag
|
||||
FName PlayerTag = FName(*FString::Printf(TEXT("Player%d"), CurrentPlayerStartAssignment));
|
||||
|
||||
// find all player starts with the matching player tag
|
||||
TArray<AActor*> PlayerStarts;
|
||||
|
||||
UGameplayStatics::GetAllActorsOfClassWithTag(GetWorld(), APlayerStart::StaticClass(), PlayerTag, PlayerStarts);
|
||||
|
||||
// increment the player start assignment index
|
||||
++CurrentPlayerStartAssignment;
|
||||
|
||||
// if no PlayerStarts were found, default to all PlayerStarts instead
|
||||
if (PlayerStarts.IsEmpty())
|
||||
{
|
||||
UGameplayStatics::GetAllActorsOfClass(GetWorld(), APlayerStart::StaticClass(), PlayerStarts);
|
||||
}
|
||||
|
||||
// have we found at least one PlayerStart?
|
||||
if (!PlayerStarts.IsEmpty())
|
||||
{
|
||||
return PlayerStarts[ FMath::RandRange(0, PlayerStarts.Num() - 1) ];
|
||||
}
|
||||
|
||||
// no PlayerStarts in the level
|
||||
return nullptr;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GameFramework/GameModeBase.h"
|
||||
#include "PlatformingGameMode.generated.h"
|
||||
|
||||
/**
|
||||
* Simple GameMode for a third person platforming game
|
||||
*/
|
||||
UCLASS()
|
||||
class APlatformingGameMode : public AGameModeBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
|
||||
/** Constructor */
|
||||
APlatformingGameMode();
|
||||
|
||||
protected:
|
||||
|
||||
/** Initialization */
|
||||
virtual void BeginPlay() override;
|
||||
|
||||
/** Assigns a PlayerStart to a specific player */
|
||||
virtual AActor* ChoosePlayerStart_Implementation(AController* Player) override;
|
||||
|
||||
protected:
|
||||
|
||||
/** Determines how many local players should be spawned on game start */
|
||||
UPROPERTY(EditDefaultsOnly, Category="Local Multiplayer", meta = (ClampMin = 1, ClampMax = 4))
|
||||
int32 NumberOfLocalPlayers = 1;
|
||||
|
||||
/** Used to assign players to different PlayerStarts in the level */
|
||||
int32 CurrentPlayerStartAssignment = 0;
|
||||
};
|
||||
@@ -0,0 +1,98 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
|
||||
#include "Variant_Platforming/PlatformingPlayerController.h"
|
||||
#include "EnhancedInputSubsystems.h"
|
||||
#include "InputMappingContext.h"
|
||||
#include "Kismet/GameplayStatics.h"
|
||||
#include "GameFramework/PlayerStart.h"
|
||||
#include "PlatformingCharacter.h"
|
||||
#include "Engine/LocalPlayer.h"
|
||||
#include "Engine/World.h"
|
||||
#include "Blueprint/UserWidget.h"
|
||||
#include "Salty.h"
|
||||
#include "Widgets/Input/SVirtualJoystick.h"
|
||||
|
||||
void APlatformingPlayerController::BeginPlay()
|
||||
{
|
||||
Super::BeginPlay();
|
||||
}
|
||||
|
||||
void APlatformingPlayerController::SetupInputComponent()
|
||||
{
|
||||
Super::SetupInputComponent();
|
||||
|
||||
// only add IMCs for local player controllers
|
||||
if (IsLocalPlayerController())
|
||||
{
|
||||
// add the input mapping context
|
||||
if (UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(GetLocalPlayer()))
|
||||
{
|
||||
for (UInputMappingContext* CurrentContext : DefaultMappingContexts)
|
||||
{
|
||||
Subsystem->AddMappingContext(CurrentContext, 0);
|
||||
}
|
||||
|
||||
// only add these IMCs if we're not using mobile touch input
|
||||
if (!ShouldUseTouchControls())
|
||||
{
|
||||
for (UInputMappingContext* CurrentContext : MobileExcludedMappingContexts)
|
||||
{
|
||||
Subsystem->AddMappingContext(CurrentContext, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// only spawn touch controls on local player controllers
|
||||
if (IsLocalPlayerController() && ShouldUseTouchControls())
|
||||
{
|
||||
// spawn the mobile controls widget
|
||||
MobileControlsWidget = CreateWidget<UUserWidget>(this, MobileControlsWidgetClass);
|
||||
|
||||
if (MobileControlsWidget)
|
||||
{
|
||||
// add the controls to the player screen
|
||||
MobileControlsWidget->AddToPlayerScreen(0);
|
||||
|
||||
} else {
|
||||
|
||||
UE_LOG(LogSalty, Error, TEXT("Could not spawn mobile controls widget."));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void APlatformingPlayerController::OnPossess(APawn* InPawn)
|
||||
{
|
||||
Super::OnPossess(InPawn);
|
||||
|
||||
// subscribe to the pawn's OnDestroyed delegate
|
||||
InPawn->OnDestroyed.AddDynamic(this, &APlatformingPlayerController::OnPawnDestroyed);
|
||||
}
|
||||
|
||||
void APlatformingPlayerController::OnPawnDestroyed(AActor* DestroyedActor)
|
||||
{
|
||||
// find the player start
|
||||
TArray<AActor*> ActorList;
|
||||
UGameplayStatics::GetAllActorsOfClass(GetWorld(), APlayerStart::StaticClass(), ActorList);
|
||||
|
||||
if (ActorList.Num() > 0)
|
||||
{
|
||||
// spawn a character at the player start
|
||||
const FTransform SpawnTransform = ActorList[0]->GetActorTransform();
|
||||
|
||||
if (APlatformingCharacter* RespawnedCharacter = GetWorld()->SpawnActor<APlatformingCharacter>(CharacterClass, SpawnTransform))
|
||||
{
|
||||
// possess the character
|
||||
Possess(RespawnedCharacter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool APlatformingPlayerController::ShouldUseTouchControls() const
|
||||
{
|
||||
// are we on a mobile platform? Should we force touch?
|
||||
return SVirtualJoystick::ShouldDisplayTouchInterface() || bForceTouchControls;
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GameFramework/PlayerController.h"
|
||||
#include "PlatformingPlayerController.generated.h"
|
||||
|
||||
class UInputMappingContext;
|
||||
class APlatformingCharacter;
|
||||
|
||||
/**
|
||||
* Simple Player Controller for a third person platforming game
|
||||
* Manages input mappings
|
||||
* Respawns the player character at the Player Start when it's destroyed
|
||||
*/
|
||||
UCLASS(abstract, Config="Game")
|
||||
class APlatformingPlayerController : public APlayerController
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
protected:
|
||||
|
||||
/** Input mapping context for this player */
|
||||
UPROPERTY(EditAnywhere, Category="Input|Input Mappings")
|
||||
TArray<UInputMappingContext*> DefaultMappingContexts;
|
||||
|
||||
/** Input Mapping Contexts */
|
||||
UPROPERTY(EditAnywhere, Category="Input|Input Mappings")
|
||||
TArray<UInputMappingContext*> MobileExcludedMappingContexts;
|
||||
|
||||
/** Mobile controls widget to spawn */
|
||||
UPROPERTY(EditAnywhere, Category="Input|Touch Controls")
|
||||
TSubclassOf<UUserWidget> MobileControlsWidgetClass;
|
||||
|
||||
/** Pointer to the mobile controls widget */
|
||||
UPROPERTY()
|
||||
TObjectPtr<UUserWidget> MobileControlsWidget;
|
||||
|
||||
/** If true, the player will use UMG touch controls even if not playing on mobile platforms */
|
||||
UPROPERTY(EditAnywhere, Config, Category = "Input|Touch Controls")
|
||||
bool bForceTouchControls = false;
|
||||
|
||||
/** Character class to respawn when the possessed pawn is destroyed */
|
||||
UPROPERTY(EditAnywhere, Category="Respawn")
|
||||
TSubclassOf<APlatformingCharacter> CharacterClass;
|
||||
|
||||
protected:
|
||||
|
||||
/** Gameplay initialization */
|
||||
virtual void BeginPlay() override;
|
||||
|
||||
/** Initialize input bindings */
|
||||
virtual void SetupInputComponent() override;
|
||||
|
||||
/** Pawn initialization */
|
||||
virtual void OnPossess(APawn* InPawn) override;
|
||||
|
||||
/** Called if the possessed pawn is destroyed */
|
||||
UFUNCTION()
|
||||
void OnPawnDestroyed(AActor* DestroyedActor);
|
||||
|
||||
/** Returns true if the player should use UMG touch controls */
|
||||
bool ShouldUseTouchControls() const;
|
||||
};
|
||||
Reference in New Issue
Block a user