Initial Commit
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
|
||||
#include "SideScrollingAIController.h"
|
||||
#include "GameplayStateTreeModule/Public/Components/StateTreeAIComponent.h"
|
||||
|
||||
ASideScrollingAIController::ASideScrollingAIController()
|
||||
{
|
||||
// create the StateTree AI Component
|
||||
StateTreeAI = CreateDefaultSubobject<UStateTreeAIComponent>(TEXT("StateTreeAI"));
|
||||
check(StateTreeAI);
|
||||
|
||||
// ensure we start the StateTree when we possess the pawn
|
||||
bStartAILogicOnPossess = true;
|
||||
|
||||
// ensure we're attached to the possessed character.
|
||||
// this is necessary for EnvQueries to work correctly
|
||||
bAttachToPawn = true;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "AIController.h"
|
||||
#include "SideScrollingAIController.generated.h"
|
||||
|
||||
class UStateTreeAIComponent;
|
||||
|
||||
/**
|
||||
* A basic AI Controller capable of running StateTree
|
||||
*/
|
||||
UCLASS(abstract)
|
||||
class ASideScrollingAIController : public AAIController
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/** StateTree Component */
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "AI", meta = (AllowPrivateAccess = "true"))
|
||||
UStateTreeAIComponent* StateTreeAI;
|
||||
|
||||
public:
|
||||
|
||||
/** Constructor */
|
||||
ASideScrollingAIController();
|
||||
};
|
||||
@@ -0,0 +1,53 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
|
||||
#include "SideScrollingNPC.h"
|
||||
#include "Engine/World.h"
|
||||
#include "GameFramework/CharacterMovementComponent.h"
|
||||
#include "TimerManager.h"
|
||||
|
||||
ASideScrollingNPC::ASideScrollingNPC()
|
||||
{
|
||||
PrimaryActorTick.bCanEverTick = true;
|
||||
|
||||
GetCharacterMovement()->MaxWalkSpeed = 150.0f;
|
||||
}
|
||||
|
||||
void ASideScrollingNPC::EndPlay(EEndPlayReason::Type EndPlayReason)
|
||||
{
|
||||
Super::EndPlay(EndPlayReason);
|
||||
|
||||
// clear the deactivation timer
|
||||
GetWorld()->GetTimerManager().ClearTimer(DeactivationTimer);
|
||||
}
|
||||
|
||||
void ASideScrollingNPC::Interaction(AActor* Interactor)
|
||||
{
|
||||
// ignore if this NPC has already been deactivated
|
||||
if (bDeactivated)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// reset the deactivation flag
|
||||
bDeactivated = true;
|
||||
|
||||
// stop character movement immediately
|
||||
GetCharacterMovement()->StopMovementImmediately();
|
||||
|
||||
// launch the NPC away from the interactor
|
||||
FVector LaunchVector = Interactor->GetActorForwardVector() * LaunchImpulse;
|
||||
LaunchVector.Y = 0.0f;
|
||||
LaunchVector.Z = LaunchVerticalImpulse;
|
||||
|
||||
LaunchCharacter(LaunchVector, true, true);
|
||||
|
||||
// set up a timer to schedule reactivation
|
||||
GetWorld()->GetTimerManager().SetTimer(DeactivationTimer, this, &ASideScrollingNPC::ResetDeactivation, DeactivationTime, false);
|
||||
}
|
||||
|
||||
void ASideScrollingNPC::ResetDeactivation()
|
||||
{
|
||||
// reset the deactivation flag
|
||||
bDeactivated = false;
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GameFramework/Character.h"
|
||||
#include "SideScrollingInteractable.h"
|
||||
#include "SideScrollingNPC.generated.h"
|
||||
|
||||
/**
|
||||
* Simple platforming NPC
|
||||
* Its behaviors will be dictated by a possessing AI Controller
|
||||
* It can be temporarily deactivated through Actor interactions
|
||||
*/
|
||||
UCLASS(abstract)
|
||||
class ASideScrollingNPC : public ACharacter, public ISideScrollingInteractable
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
protected:
|
||||
|
||||
/** Horizontal impulse to apply to the NPC when it's interacted with */
|
||||
UPROPERTY(EditAnywhere, Category="NPC", meta = (ClampMin = 0, ClampMax = 10000, Units="cm/s"))
|
||||
float LaunchImpulse = 500.0f;
|
||||
|
||||
/** Vertical impulse to apply to the NPC when it's interacted with */
|
||||
UPROPERTY(EditAnywhere, Category="NPC", meta = (ClampMin = 0, ClampMax = 10000, Units="cm/s"))
|
||||
float LaunchVerticalImpulse = 500.0f;
|
||||
|
||||
/** Time that the NPC remains deactivated after being interacted with */
|
||||
UPROPERTY(EditAnywhere, Category="NPC", meta = (ClampMin = 0, ClampMax = 10, Units="s"))
|
||||
float DeactivationTime = 3.0f;
|
||||
|
||||
public:
|
||||
|
||||
/** If true, this NPC is deactivated and will not be interacted with */
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="NPC")
|
||||
bool bDeactivated = false;
|
||||
|
||||
/** Timer to reactivate the NPC */
|
||||
FTimerHandle DeactivationTimer;
|
||||
|
||||
public:
|
||||
|
||||
/** Constructor */
|
||||
ASideScrollingNPC();
|
||||
|
||||
public:
|
||||
|
||||
/** Cleanup */
|
||||
virtual void EndPlay(EEndPlayReason::Type EndPlayReason) override;
|
||||
|
||||
public:
|
||||
|
||||
// ~begin IInteractable interface
|
||||
|
||||
/** Performs an interaction triggered by another actor */
|
||||
virtual void Interaction(AActor* Interactor) override;
|
||||
|
||||
// ~end IInteractable interface
|
||||
|
||||
/** Reactivates the NPC */
|
||||
void ResetDeactivation();
|
||||
};
|
||||
@@ -0,0 +1,59 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
|
||||
#include "SideScrollingStateTreeUtility.h"
|
||||
#include "StateTreeExecutionContext.h"
|
||||
#include "StateTreeExecutionTypes.h"
|
||||
#include "AIController.h"
|
||||
#include "Kismet/GameplayStatics.h"
|
||||
|
||||
EStateTreeRunStatus FStateTreeGetPlayerTask::EnterState(FStateTreeExecutionContext& Context, const FStateTreeTransitionResult& Transition) const
|
||||
{
|
||||
// get the instance data
|
||||
FInstanceDataType& InstanceData = Context.GetInstanceData(*this);
|
||||
|
||||
// reset the selected target
|
||||
APawn* SelectedTarget = nullptr;
|
||||
float ClosestDistance = 1000000000000000000.0f;
|
||||
|
||||
// iterate through each local player
|
||||
const int32 NumPlayers = UGameplayStatics::GetNumLocalPlayerControllers(InstanceData.Controller.Get());
|
||||
|
||||
for (int32 i = 0; i < NumPlayers; ++i)
|
||||
{
|
||||
if (APawn* Current = UGameplayStatics::GetPlayerPawn(InstanceData.Controller.Get(), i))
|
||||
{
|
||||
// compute the distance to the target
|
||||
const float TargetDist = (Current->GetActorLocation() - InstanceData.NPC->GetActorLocation()).Size();
|
||||
|
||||
// if we haven't selected a target, or this one is closer to the current one, choose this pawn as target
|
||||
if (!SelectedTarget || TargetDist < ClosestDistance)
|
||||
{
|
||||
SelectedTarget = Current;
|
||||
ClosestDistance = TargetDist;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// set the selected target, assume out of range by default
|
||||
InstanceData.TargetPlayer = SelectedTarget;
|
||||
InstanceData.bValidTarget = false;
|
||||
|
||||
if (SelectedTarget)
|
||||
{
|
||||
// consider this a valid target if it's within range
|
||||
const float TargetDist = (SelectedTarget->GetActorLocation() - InstanceData.NPC->GetActorLocation()).Size();
|
||||
|
||||
InstanceData.bValidTarget = TargetDist < InstanceData.RangeMax;
|
||||
}
|
||||
|
||||
// succeed or fail depending on target validity
|
||||
return InstanceData.bValidTarget ? EStateTreeRunStatus::Succeeded : EStateTreeRunStatus::Failed;
|
||||
}
|
||||
|
||||
#if WITH_EDITOR
|
||||
FText FStateTreeGetPlayerTask::GetDescription(const FGuid& ID, FStateTreeDataView InstanceDataView, const IStateTreeBindingLookup& BindingLookup, EStateTreeNodeFormatting Formatting /*= EStateTreeNodeFormatting::Text*/) const
|
||||
{
|
||||
return FText::FromString("<b>Get Player</b>");
|
||||
}
|
||||
#endif // WITH_EDITOR
|
||||
@@ -0,0 +1,68 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "StateTreeTaskBase.h"
|
||||
|
||||
#include "SideScrollingStateTreeUtility.generated.h"
|
||||
|
||||
class AAIController;
|
||||
|
||||
/**
|
||||
* Instance data for the FStateTreeGetPlayerTask task
|
||||
*/
|
||||
USTRUCT()
|
||||
struct FStateTreeGetPlayerInstanceData
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/** NPC owning this task */
|
||||
UPROPERTY(VisibleAnywhere, Category="Context")
|
||||
TObjectPtr<APawn> NPC;
|
||||
|
||||
/** Holds the found player pawn */
|
||||
UPROPERTY(VisibleAnywhere, Category="Context")
|
||||
TObjectPtr<AAIController> Controller;
|
||||
|
||||
/** Holds the found player pawn */
|
||||
UPROPERTY(VisibleAnywhere, Category="Output")
|
||||
TObjectPtr<APawn> TargetPlayer;
|
||||
|
||||
/** Is the pawn close enough to be considered a valid target? */
|
||||
UPROPERTY(VisibleAnywhere, Category="Output")
|
||||
bool bValidTarget = false;
|
||||
|
||||
/** Max distance to be considered a valid target */
|
||||
UPROPERTY(EditAnywhere, Category="Parameter", meta = (ClampMin = 0, ClampMax = 10000, Units = "cm"))
|
||||
float RangeMax = 1000.0f;
|
||||
};
|
||||
|
||||
/**
|
||||
* StateTree task to get the player-controlled character
|
||||
*/
|
||||
USTRUCT(meta=(DisplayName="Get Player", Category="Side Scrolling"))
|
||||
struct FStateTreeGetPlayerTask : public FStateTreeTaskCommonBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
FStateTreeGetPlayerTask()
|
||||
{
|
||||
// disable tick
|
||||
bShouldCallTick = false;
|
||||
|
||||
// skip state change events if this is sustained
|
||||
bShouldStateChangeOnReselect = false;
|
||||
}
|
||||
|
||||
/* Ensure we're using the correct instance data struct */
|
||||
using FInstanceDataType = FStateTreeGetPlayerInstanceData;
|
||||
virtual const UStruct* GetInstanceDataType() const override { return FInstanceDataType::StaticStruct(); }
|
||||
|
||||
/** Runs when the owning state is entered */
|
||||
virtual EStateTreeRunStatus EnterState(FStateTreeExecutionContext& Context, const FStateTreeTransitionResult& Transition) const override;
|
||||
|
||||
#if WITH_EDITOR
|
||||
virtual FText GetDescription(const FGuid& ID, FStateTreeDataView InstanceDataView, const IStateTreeBindingLookup& BindingLookup, EStateTreeNodeFormatting Formatting = EStateTreeNodeFormatting::Text) const override;
|
||||
#endif // WITH_EDITOR
|
||||
};
|
||||
@@ -0,0 +1,46 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
|
||||
#include "SideScrollingJumpPad.h"
|
||||
#include "Components/BoxComponent.h"
|
||||
#include "GameFramework/Character.h"
|
||||
#include "GameFramework/CharacterMovementComponent.h"
|
||||
#include "Components/SceneComponent.h"
|
||||
|
||||
ASideScrollingJumpPad::ASideScrollingJumpPad()
|
||||
{
|
||||
PrimaryActorTick.bCanEverTick = false;
|
||||
|
||||
// create the root comp
|
||||
RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("Root"));
|
||||
|
||||
// create the bounding box
|
||||
Box = CreateDefaultSubobject<UBoxComponent>(TEXT("Box"));
|
||||
Box->SetupAttachment(RootComponent);
|
||||
|
||||
// configure the bounding box
|
||||
Box->SetBoxExtent(FVector(115.0f, 90.0f, 20.0f), false);
|
||||
Box->SetRelativeLocation(FVector(0.0f, 0.0f, 16.0f));
|
||||
|
||||
Box->SetCollisionObjectType(ECC_WorldDynamic);
|
||||
Box->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
|
||||
Box->SetCollisionResponseToAllChannels(ECR_Ignore);
|
||||
Box->SetCollisionResponseToChannel(ECC_Pawn, ECR_Overlap);
|
||||
|
||||
// add the overlap handler
|
||||
OnActorBeginOverlap.AddDynamic(this, &ASideScrollingJumpPad::BeginOverlap);
|
||||
}
|
||||
|
||||
void ASideScrollingJumpPad::BeginOverlap(AActor* OverlappedActor, AActor* OtherActor)
|
||||
{
|
||||
// were we overlapped by a character?
|
||||
if (ACharacter* OverlappingCharacter = Cast<ACharacter>(OtherActor))
|
||||
{
|
||||
// force the character to jump
|
||||
OverlappingCharacter->Jump();
|
||||
|
||||
// launch the character to override its vertical velocity
|
||||
FVector LaunchVelocity = FVector::UpVector * ZStrength;
|
||||
OverlappingCharacter->LaunchCharacter(LaunchVelocity, false, true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GameFramework/Actor.h"
|
||||
#include "SideScrollingJumpPad.generated.h"
|
||||
|
||||
class UBoxComponent;
|
||||
|
||||
/**
|
||||
* A simple jump pad that launches characters into the air
|
||||
*/
|
||||
UCLASS(abstract)
|
||||
class ASideScrollingJumpPad : public AActor
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/** Jump pad bounding box */
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Components", meta = (AllowPrivateAccess = "true"))
|
||||
UBoxComponent* Box;
|
||||
|
||||
protected:
|
||||
|
||||
/** Vertical velocity to set the character to when they use the jump pad */
|
||||
UPROPERTY(EditAnywhere, Category="Jump Pad", meta = (ClampMin=0, ClampMax=10000, Units="cm/s"))
|
||||
float ZStrength = 1000.0f;
|
||||
|
||||
public:
|
||||
|
||||
/** Constructor */
|
||||
ASideScrollingJumpPad();
|
||||
|
||||
protected:
|
||||
|
||||
UFUNCTION()
|
||||
void BeginOverlap(AActor* OverlappedActor, AActor* OtherActor);
|
||||
|
||||
};
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
|
||||
#include "SideScrollingMovingPlatform.h"
|
||||
#include "Components/SceneComponent.h"
|
||||
|
||||
ASideScrollingMovingPlatform::ASideScrollingMovingPlatform()
|
||||
{
|
||||
PrimaryActorTick.bCanEverTick = false;
|
||||
|
||||
// create the root comp
|
||||
RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("Root"));
|
||||
}
|
||||
|
||||
void ASideScrollingMovingPlatform::Interaction(AActor* Interactor)
|
||||
{
|
||||
// ignore interactions if we're already moving
|
||||
if (bMoving)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// raise the movement flag
|
||||
bMoving = true;
|
||||
|
||||
// pass control to BP for the actual movement
|
||||
BP_MoveToTarget();
|
||||
}
|
||||
|
||||
void ASideScrollingMovingPlatform::ResetInteraction()
|
||||
{
|
||||
// ignore if this is a one-shot platform
|
||||
if (bOneShot)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// reset the movement flag
|
||||
bMoving = false;
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GameFramework/Actor.h"
|
||||
#include "SideScrollingInteractable.h"
|
||||
#include "SideScrollingMovingPlatform.generated.h"
|
||||
|
||||
/**
|
||||
* Simple moving platform that can be triggered through interactions by other actors.
|
||||
* The actual movement is performed by Blueprint code through latent execution nodes.
|
||||
*/
|
||||
UCLASS(abstract)
|
||||
class ASideScrollingMovingPlatform : public AActor, public ISideScrollingInteractable
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
|
||||
/** Constructor */
|
||||
ASideScrollingMovingPlatform();
|
||||
|
||||
protected:
|
||||
|
||||
/** If this is true, the platform is mid-movement and will ignore further interactions */
|
||||
bool bMoving = false;
|
||||
|
||||
/** Destination of the platform in world space */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Moving Platform")
|
||||
FVector PlatformTarget;
|
||||
|
||||
/** Time for the platform to move to the destination */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Moving Platform", meta = (ClampMin = 0, ClampMax = 10, Units="s"))
|
||||
float MoveDuration = 5.0f;
|
||||
|
||||
/** If this is true, the platform will only move once. */
|
||||
UPROPERTY(EditAnywhere, Category="Moving Platform")
|
||||
bool bOneShot = false;
|
||||
|
||||
public:
|
||||
|
||||
// ~begin IInteractable interface
|
||||
|
||||
/** Performs an interaction triggered by another actor */
|
||||
virtual void Interaction(AActor* Interactor) override;
|
||||
|
||||
// ~end IInteractable interface
|
||||
|
||||
/** Resets the interaction state. Must be called from BP code to reset the platform */
|
||||
UFUNCTION(BlueprintCallable, Category="Moving Platform")
|
||||
virtual void ResetInteraction();
|
||||
|
||||
protected:
|
||||
|
||||
/** Allows Blueprint code to do the actual platform movement */
|
||||
UFUNCTION(BlueprintImplementableEvent, BlueprintCallable, Category="Moving Platform", meta = (DisplayName="Move to Target"))
|
||||
void BP_MoveToTarget();
|
||||
|
||||
};
|
||||
@@ -0,0 +1,55 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
|
||||
#include "SideScrollingPickup.h"
|
||||
#include "GameFramework/Character.h"
|
||||
#include "SideScrollingGameMode.h"
|
||||
#include "Components/SphereComponent.h"
|
||||
#include "Components/SceneComponent.h"
|
||||
#include "Engine/World.h"
|
||||
|
||||
ASideScrollingPickup::ASideScrollingPickup()
|
||||
{
|
||||
PrimaryActorTick.bCanEverTick = false;
|
||||
|
||||
// create the root comp
|
||||
RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("Root"));
|
||||
|
||||
// create the bounding sphere
|
||||
Sphere = CreateDefaultSubobject<USphereComponent>(TEXT("Collision"));
|
||||
Sphere->SetupAttachment(RootComponent);
|
||||
|
||||
Sphere->SetSphereRadius(100.0f);
|
||||
|
||||
Sphere->SetCollisionObjectType(ECC_WorldDynamic);
|
||||
Sphere->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
|
||||
Sphere->SetCollisionResponseToAllChannels(ECR_Ignore);
|
||||
Sphere->SetCollisionResponseToChannel(ECC_Pawn, ECR_Overlap);
|
||||
|
||||
// add the overlap handler
|
||||
OnActorBeginOverlap.AddDynamic(this, &ASideScrollingPickup::BeginOverlap);
|
||||
}
|
||||
|
||||
void ASideScrollingPickup::BeginOverlap(AActor* OverlappedActor, AActor* OtherActor)
|
||||
{
|
||||
// have we collided against a character?
|
||||
if (ACharacter* OverlappedCharacter = Cast<ACharacter>(OtherActor))
|
||||
{
|
||||
// is this the player character?
|
||||
if (OverlappedCharacter->IsPlayerControlled())
|
||||
{
|
||||
// get the game mode
|
||||
if (ASideScrollingGameMode* GM = Cast<ASideScrollingGameMode>(GetWorld()->GetAuthGameMode()))
|
||||
{
|
||||
// tell the game mode to process a pickup
|
||||
GM->ProcessPickup();
|
||||
|
||||
// disable collision so we don't get picked up again
|
||||
SetActorEnableCollision(false);
|
||||
|
||||
// Call the BP handler. It will be responsible for destroying the pickup
|
||||
BP_OnPickedUp();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GameFramework/Actor.h"
|
||||
#include "SideScrollingPickup.generated.h"
|
||||
|
||||
class USphereComponent;
|
||||
|
||||
/**
|
||||
* A simple side scrolling game pickup
|
||||
* Increments a counter on the GameMode
|
||||
*/
|
||||
UCLASS(abstract)
|
||||
class ASideScrollingPickup : public AActor
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/** Pickup bounding sphere */
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category ="Components", meta = (AllowPrivateAccess = "true"))
|
||||
USphereComponent* Sphere;
|
||||
|
||||
public:
|
||||
|
||||
/** Constructor */
|
||||
ASideScrollingPickup();
|
||||
|
||||
protected:
|
||||
|
||||
/** Handles pickup collision */
|
||||
UFUNCTION()
|
||||
void BeginOverlap(AActor* OverlappedActor, AActor* OtherActor);
|
||||
|
||||
/** Passes control to BP to play effects on pickup */
|
||||
UFUNCTION(BlueprintImplementableEvent, Category="Pickup", meta = (DisplayName = "On Picked Up"))
|
||||
void BP_OnPickedUp();
|
||||
};
|
||||
@@ -0,0 +1,59 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
|
||||
#include "SideScrollingSoftPlatform.h"
|
||||
#include "Components/SceneComponent.h"
|
||||
#include "Components/StaticMeshComponent.h"
|
||||
#include "Components/BoxComponent.h"
|
||||
#include "SideScrollingCharacter.h"
|
||||
|
||||
ASideScrollingSoftPlatform::ASideScrollingSoftPlatform()
|
||||
{
|
||||
PrimaryActorTick.bCanEverTick = true;
|
||||
|
||||
// create the root component
|
||||
RootComponent = Root = CreateDefaultSubobject<USceneComponent>(TEXT("Root"));
|
||||
|
||||
// create the mesh
|
||||
Mesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Mesh"));
|
||||
Mesh->SetupAttachment(Root);
|
||||
|
||||
Mesh->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
|
||||
Mesh->SetCollisionObjectType(ECC_WorldStatic);
|
||||
Mesh->SetCollisionResponseToAllChannels(ECR_Block);
|
||||
|
||||
// create the collision check box
|
||||
CollisionCheckBox = CreateDefaultSubobject<UBoxComponent>(TEXT("Collision Check Box"));
|
||||
CollisionCheckBox->SetupAttachment(Mesh);
|
||||
|
||||
CollisionCheckBox->SetRelativeLocation(FVector(0.0f, 0.0f, -40.0f));
|
||||
CollisionCheckBox->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
|
||||
CollisionCheckBox->SetCollisionObjectType(ECC_WorldDynamic);
|
||||
CollisionCheckBox->SetCollisionResponseToAllChannels(ECR_Ignore);
|
||||
CollisionCheckBox->SetCollisionResponseToChannel(ECC_Pawn, ECR_Overlap);
|
||||
|
||||
// subscribe to the overlap events
|
||||
CollisionCheckBox->OnComponentBeginOverlap.AddDynamic(this, &ASideScrollingSoftPlatform::OnSoftCollisionOverlap);
|
||||
}
|
||||
|
||||
void ASideScrollingSoftPlatform::OnSoftCollisionOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
|
||||
{
|
||||
// have we overlapped a character?
|
||||
if (ASideScrollingCharacter* Char = Cast<ASideScrollingCharacter>(OtherActor))
|
||||
{
|
||||
// disable the soft collision channel
|
||||
Char->SetSoftCollision(true);
|
||||
}
|
||||
}
|
||||
|
||||
void ASideScrollingSoftPlatform::NotifyActorEndOverlap(AActor* OtherActor)
|
||||
{
|
||||
Super::NotifyActorEndOverlap(OtherActor);
|
||||
|
||||
// have we overlapped a character?
|
||||
if (ASideScrollingCharacter* Char = Cast<ASideScrollingCharacter>(OtherActor))
|
||||
{
|
||||
// enable the soft collision channel
|
||||
Char->SetSoftCollision(false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GameFramework/Actor.h"
|
||||
#include "SideScrollingSoftPlatform.generated.h"
|
||||
|
||||
class USceneComponent;
|
||||
class UStaticMeshComponent;
|
||||
class UBoxComponent;
|
||||
|
||||
/**
|
||||
* A side scrolling game platform that the character can jump or drop through.
|
||||
*/
|
||||
UCLASS(abstract)
|
||||
class ASideScrollingSoftPlatform : public AActor
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/** Root component */
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category ="Components", meta = (AllowPrivateAccess = "true"))
|
||||
USceneComponent* Root;
|
||||
|
||||
/** Platform mesh. The part we collide against and see */
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category ="Components", meta = (AllowPrivateAccess = "true"))
|
||||
UStaticMeshComponent* Mesh;
|
||||
|
||||
/** Collision volume that toggles soft collision on the character when they're below the platform. */
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category ="Components", meta = (AllowPrivateAccess = "true"))
|
||||
UBoxComponent* CollisionCheckBox;
|
||||
|
||||
public:
|
||||
|
||||
/** Constructor */
|
||||
ASideScrollingSoftPlatform();
|
||||
|
||||
protected:
|
||||
|
||||
/** Handles soft collision check box overlaps */
|
||||
UFUNCTION()
|
||||
void OnSoftCollisionOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);
|
||||
|
||||
/** Restores soft collision state when overlap ends */
|
||||
virtual void NotifyActorEndOverlap(AActor* OtherActor) override;
|
||||
};
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
|
||||
#include "SideScrollingInteractable.h"
|
||||
|
||||
// Add default functionality here for any IInteractable functions that are not pure virtual.
|
||||
@@ -0,0 +1,31 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "UObject/Interface.h"
|
||||
#include "SideScrollingInteractable.generated.h"
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
UINTERFACE(MinimalAPI, NotBlueprintable)
|
||||
class USideScrollingInteractable : public UInterface
|
||||
{
|
||||
GENERATED_BODY()
|
||||
};
|
||||
|
||||
/**
|
||||
* Simple interface to allow Actors to interact without having knowledge of their internal implementation.
|
||||
*/
|
||||
class ISideScrollingInteractable
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
|
||||
/** Triggers an interaction by the provided Actor */
|
||||
UFUNCTION(BlueprintCallable, Category="Interactable")
|
||||
virtual void Interaction(AActor* Interactor) = 0;
|
||||
|
||||
};
|
||||
@@ -0,0 +1,105 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
|
||||
#include "SideScrollingCameraManager.h"
|
||||
#include "GameFramework/Pawn.h"
|
||||
#include "Engine/HitResult.h"
|
||||
#include "CollisionQueryParams.h"
|
||||
#include "Engine/World.h"
|
||||
|
||||
void ASideScrollingCameraManager::UpdateViewTarget(FTViewTarget& OutVT, float DeltaTime)
|
||||
{
|
||||
// ensure the view target is a pawn
|
||||
APawn* TargetPawn = Cast<APawn>(OutVT.Target);
|
||||
|
||||
// is our target valid?
|
||||
if (IsValid(TargetPawn))
|
||||
{
|
||||
// set the view target FOV and rotation
|
||||
OutVT.POV.Rotation = FRotator(0.0f, -90.0f, 0.0f);
|
||||
OutVT.POV.FOV = 65.0f;
|
||||
|
||||
// cache the current location
|
||||
FVector CurrentActorLocation = OutVT.Target->GetActorLocation();
|
||||
|
||||
// copy the current camera location
|
||||
FVector CurrentCameraLocation = GetCameraLocation();
|
||||
|
||||
// calculate the "zoom distance" - in reality the distance we want to keep to the target
|
||||
float CurrentY = CurrentZoom + CurrentActorLocation.Y;
|
||||
|
||||
// do first-time setup
|
||||
if (bSetup)
|
||||
{
|
||||
// lower the setup flag
|
||||
bSetup = false;
|
||||
|
||||
// initialize the camera viewpoint and return
|
||||
OutVT.POV.Location.X = CurrentActorLocation.X;
|
||||
OutVT.POV.Location.Y = CurrentY;
|
||||
OutVT.POV.Location.Z = CurrentActorLocation.Z + CameraZOffset;
|
||||
|
||||
// save the current camera height
|
||||
CurrentZ = OutVT.POV.Location.Z;
|
||||
|
||||
// skip the rest of the calculations
|
||||
return;
|
||||
}
|
||||
|
||||
// check if the camera needs to update its height
|
||||
bool bZUpdate = false;
|
||||
|
||||
// is the character moving vertically?
|
||||
if (FMath::IsNearlyZero(TargetPawn->GetVelocity().Z))
|
||||
{
|
||||
// determine if we need to do a height update
|
||||
bZUpdate = FMath::IsNearlyEqual(CurrentZ, CurrentCameraLocation.Z, 25.0f);
|
||||
|
||||
} else {
|
||||
|
||||
// run a trace below the character to determine if we need to do a height update
|
||||
FHitResult OutHit;
|
||||
|
||||
const FVector End = CurrentActorLocation + FVector(0.0f, 0.0f, -1000.0f);
|
||||
|
||||
FCollisionQueryParams QueryParams;
|
||||
QueryParams.AddIgnoredActor(TargetPawn);
|
||||
|
||||
// only update height if we're not about to hit ground
|
||||
bZUpdate = !GetWorld()->LineTraceSingleByChannel(OutHit, CurrentActorLocation, End, ECC_Visibility, QueryParams);
|
||||
|
||||
}
|
||||
|
||||
// do we need to do a height update?
|
||||
if (bZUpdate)
|
||||
{
|
||||
|
||||
// set the height goal from the actor location
|
||||
CurrentZ = CurrentActorLocation.Z;
|
||||
|
||||
} else {
|
||||
|
||||
// are we close enough to the target height?
|
||||
if (FMath::IsNearlyEqual(CurrentZ, CurrentActorLocation.Z, 100.0f))
|
||||
{
|
||||
// set the height goal from the actor location
|
||||
CurrentZ = CurrentActorLocation.Z;
|
||||
|
||||
} else {
|
||||
|
||||
// blend the height towards the actor location
|
||||
CurrentZ = FMath::FInterpTo(CurrentZ, CurrentActorLocation.Z, DeltaTime, 2.0f);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// clamp the X axis to the min and max camera bounds
|
||||
float CurrentX = FMath::Clamp(CurrentActorLocation.X, CameraXMinBounds, CameraXMaxBounds);
|
||||
|
||||
// blend towards the new camera location and update the output
|
||||
FVector TargetCameraLocation(CurrentX, CurrentY, CurrentZ);
|
||||
|
||||
OutVT.POV.Location = FMath::VInterpTo(CurrentCameraLocation, TargetCameraLocation, DeltaTime, 2.0f);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Camera/PlayerCameraManager.h"
|
||||
#include "SideScrollingCameraManager.generated.h"
|
||||
|
||||
/**
|
||||
* Simple side scrolling camera with smooth scrolling and horizontal bounds
|
||||
*/
|
||||
UCLASS()
|
||||
class ASideScrollingCameraManager : public APlayerCameraManager
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
|
||||
/** Overrides the default camera view target calculation */
|
||||
virtual void UpdateViewTarget(FTViewTarget& OutVT, float DeltaTime) override;
|
||||
|
||||
public:
|
||||
|
||||
/** How close we want to stay to the view target */
|
||||
UPROPERTY(EditAnywhere, Category="Side Scrolling Camera", meta=(ClampMin=0, ClampMax=10000, Units="cm"))
|
||||
float CurrentZoom = 1000.0f;
|
||||
|
||||
/** How far above the target do we want the camera to focus */
|
||||
UPROPERTY(EditAnywhere, Category="Side Scrolling Camera", meta=(ClampMin=0, ClampMax=10000, Units="cm"))
|
||||
float CameraZOffset = 100.0f;
|
||||
|
||||
/** Minimum camera scrolling bounds in world space */
|
||||
UPROPERTY(EditAnywhere, Category="Side Scrolling Camera", meta=(ClampMin=-100000, ClampMax=100000, Units="cm"))
|
||||
float CameraXMinBounds = -400.0f;
|
||||
|
||||
/** Maximum camera scrolling bounds in world space */
|
||||
UPROPERTY(EditAnywhere, Category="Side Scrolling Camera", meta=(ClampMin=-100000, ClampMax=100000, Units="cm"))
|
||||
float CameraXMaxBounds = 10000.0f;
|
||||
|
||||
protected:
|
||||
|
||||
/** Last cached camera vertical location. The camera only adjusts its height if necessary. */
|
||||
float CurrentZ = 0.0f;
|
||||
|
||||
/** First-time update camera setup flag */
|
||||
bool bSetup = true;
|
||||
};
|
||||
@@ -0,0 +1,350 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
|
||||
#include "SideScrollingCharacter.h"
|
||||
#include "GameFramework/CharacterMovementComponent.h"
|
||||
#include "Components/CapsuleComponent.h"
|
||||
#include "Camera/CameraComponent.h"
|
||||
#include "Components/InputComponent.h"
|
||||
#include "InputActionValue.h"
|
||||
#include "EnhancedInputComponent.h"
|
||||
#include "InputAction.h"
|
||||
#include "Engine/World.h"
|
||||
#include "SideScrollingInteractable.h"
|
||||
#include "Kismet/KismetMathLibrary.h"
|
||||
#include "TimerManager.h"
|
||||
|
||||
ASideScrollingCharacter::ASideScrollingCharacter()
|
||||
{
|
||||
PrimaryActorTick.bCanEverTick = true;
|
||||
|
||||
// create the camera component
|
||||
Camera = CreateDefaultSubobject<UCameraComponent>(TEXT("Camera"));
|
||||
Camera->SetupAttachment(RootComponent);
|
||||
|
||||
Camera->SetRelativeLocationAndRotation(FVector(0.0f, 300.0f, 0.0f), FRotator(0.0f, -90.0f, 0.0f));
|
||||
|
||||
// configure the collision capsule
|
||||
GetCapsuleComponent()->SetCapsuleSize(35.0f, 90.0f);
|
||||
|
||||
// configure the Pawn properties
|
||||
bUseControllerRotationYaw = false;
|
||||
|
||||
// configure the character movement component
|
||||
GetCharacterMovement()->GravityScale = 1.75f;
|
||||
GetCharacterMovement()->MaxAcceleration = 1500.0f;
|
||||
GetCharacterMovement()->BrakingFrictionFactor = 1.0f;
|
||||
GetCharacterMovement()->bUseSeparateBrakingFriction = true;
|
||||
GetCharacterMovement()->Mass = 500.0f;
|
||||
|
||||
GetCharacterMovement()->SetWalkableFloorAngle(75.0f);
|
||||
GetCharacterMovement()->MaxWalkSpeed = 500.0f;
|
||||
GetCharacterMovement()->MinAnalogWalkSpeed = 20.0f;
|
||||
GetCharacterMovement()->BrakingDecelerationWalking = 2000.0f;
|
||||
GetCharacterMovement()->bIgnoreBaseRotation = true;
|
||||
|
||||
GetCharacterMovement()->PerchRadiusThreshold = 15.0f;
|
||||
GetCharacterMovement()->LedgeCheckThreshold = 6.0f;
|
||||
|
||||
GetCharacterMovement()->JumpZVelocity = 750.0f;
|
||||
GetCharacterMovement()->AirControl = 1.0f;
|
||||
|
||||
GetCharacterMovement()->RotationRate = FRotator(0.0f, 750.0f, 0.0f);
|
||||
GetCharacterMovement()->bOrientRotationToMovement = true;
|
||||
|
||||
GetCharacterMovement()->SetPlaneConstraintNormal(FVector(0.0f, 1.0f, 0.0f));
|
||||
GetCharacterMovement()->bConstrainToPlane = true;
|
||||
|
||||
// enable double jump and coyote time
|
||||
JumpMaxCount = 3;
|
||||
}
|
||||
|
||||
void ASideScrollingCharacter::EndPlay(EEndPlayReason::Type EndPlayReason)
|
||||
{
|
||||
Super::EndPlay(EndPlayReason);
|
||||
|
||||
// clear the wall jump timer
|
||||
GetWorld()->GetTimerManager().ClearTimer(WallJumpTimer);
|
||||
}
|
||||
|
||||
void ASideScrollingCharacter::SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent)
|
||||
{
|
||||
Super::SetupPlayerInputComponent(PlayerInputComponent);
|
||||
|
||||
// Set up action bindings
|
||||
if (UEnhancedInputComponent* EnhancedInputComponent = Cast<UEnhancedInputComponent>(PlayerInputComponent))
|
||||
{
|
||||
// Jumping
|
||||
EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Started, this, &ASideScrollingCharacter::DoJumpStart);
|
||||
EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Completed, this, &ASideScrollingCharacter::DoJumpEnd);
|
||||
|
||||
// Interacting
|
||||
EnhancedInputComponent->BindAction(InteractAction, ETriggerEvent::Triggered, this, &ASideScrollingCharacter::DoInteract);
|
||||
|
||||
// Moving
|
||||
EnhancedInputComponent->BindAction(MoveAction, ETriggerEvent::Triggered, this, &ASideScrollingCharacter::Move);
|
||||
|
||||
// Dropping from platform
|
||||
EnhancedInputComponent->BindAction(DropAction, ETriggerEvent::Triggered, this, &ASideScrollingCharacter::Drop);
|
||||
EnhancedInputComponent->BindAction(DropAction, ETriggerEvent::Completed, this, &ASideScrollingCharacter::DropReleased);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void ASideScrollingCharacter::NotifyHit(class UPrimitiveComponent* MyComp, AActor* Other, class UPrimitiveComponent* OtherComp, bool bSelfMoved, FVector HitLocation, FVector HitNormal, FVector NormalImpulse, const FHitResult& Hit)
|
||||
{
|
||||
Super::NotifyHit(MyComp, Other, OtherComp, bSelfMoved, HitLocation, HitNormal, NormalImpulse, Hit);
|
||||
|
||||
// only apply push impulse if we're falling
|
||||
if (!GetCharacterMovement()->IsFalling())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// ensure the colliding component is valid
|
||||
if (OtherComp)
|
||||
{
|
||||
// ensure the component is movable and simulating physics
|
||||
if (OtherComp->Mobility == EComponentMobility::Movable && OtherComp->IsSimulatingPhysics())
|
||||
{
|
||||
const FVector PushDir = FVector(ActionValueY > 0.0f ? 1.0f : -1.0f, 0.0f, 0.0f);
|
||||
|
||||
// push the component away
|
||||
OtherComp->AddImpulse(PushDir * JumpPushImpulse, NAME_None, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ASideScrollingCharacter::Landed(const FHitResult& Hit)
|
||||
{
|
||||
// reset the double jump
|
||||
bHasDoubleJumped = false;
|
||||
}
|
||||
|
||||
void ASideScrollingCharacter::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();
|
||||
}
|
||||
}
|
||||
|
||||
void ASideScrollingCharacter::Move(const FInputActionValue& Value)
|
||||
{
|
||||
FVector2D MoveVector = Value.Get<FVector2D>();
|
||||
|
||||
// route the input
|
||||
DoMove(MoveVector.Y);
|
||||
}
|
||||
|
||||
void ASideScrollingCharacter::Drop(const FInputActionValue& Value)
|
||||
{
|
||||
// route the input
|
||||
DoDrop(Value.Get<float>());
|
||||
}
|
||||
|
||||
void ASideScrollingCharacter::DropReleased(const FInputActionValue& Value)
|
||||
{
|
||||
// reset the input
|
||||
DoDrop(0.0f);
|
||||
}
|
||||
|
||||
void ASideScrollingCharacter::DoMove(float Forward)
|
||||
{
|
||||
// is movement temporarily disabled after wall jumping?
|
||||
if (!bHasWallJumped)
|
||||
{
|
||||
// save the movement values
|
||||
ActionValueY = Forward;
|
||||
|
||||
// figure out the movement direction
|
||||
const FVector MoveDir = FVector(1.0f, Forward > 0.0f ? 0.1f : -0.1f, 0.0f);
|
||||
|
||||
// apply the movement input
|
||||
AddMovementInput(MoveDir, Forward);
|
||||
}
|
||||
}
|
||||
|
||||
void ASideScrollingCharacter::DoDrop(float Value)
|
||||
{
|
||||
// save the movement value
|
||||
DropValue = Value;
|
||||
}
|
||||
|
||||
void ASideScrollingCharacter::DoJumpStart()
|
||||
{
|
||||
// handle advanced jump behaviors
|
||||
MultiJump();
|
||||
}
|
||||
|
||||
void ASideScrollingCharacter::DoJumpEnd()
|
||||
{
|
||||
StopJumping();
|
||||
}
|
||||
|
||||
void ASideScrollingCharacter::DoInteract()
|
||||
{
|
||||
// do a sphere trace to look for interactive objects
|
||||
FHitResult OutHit;
|
||||
|
||||
const FVector Start = GetActorLocation();
|
||||
const FVector End = Start + FVector(100.0f, 0.0f, 0.0f);
|
||||
|
||||
FCollisionShape ColSphere;
|
||||
ColSphere.SetSphere(InteractionRadius);
|
||||
|
||||
FCollisionObjectQueryParams ObjectParams;
|
||||
ObjectParams.AddObjectTypesToQuery(ECC_Pawn);
|
||||
ObjectParams.AddObjectTypesToQuery(ECC_WorldDynamic);
|
||||
|
||||
FCollisionQueryParams QueryParams;
|
||||
QueryParams.AddIgnoredActor(this);
|
||||
|
||||
if (GetWorld()->SweepSingleByObjectType(OutHit, Start, End, FQuat::Identity, ObjectParams, ColSphere, QueryParams))
|
||||
{
|
||||
// have we hit an interactable?
|
||||
if (ISideScrollingInteractable* Interactable = Cast<ISideScrollingInteractable>(OutHit.GetActor()))
|
||||
{
|
||||
// interact
|
||||
Interactable->Interaction(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ASideScrollingCharacter::MultiJump()
|
||||
{
|
||||
// does the user want to drop to a lower platform?
|
||||
if (DropValue > 0.0f)
|
||||
{
|
||||
CheckForSoftCollision();
|
||||
return;
|
||||
}
|
||||
|
||||
// reset the drop value
|
||||
DropValue = 0.0f;
|
||||
|
||||
// if we're grounded, disregard advanced jump logic
|
||||
if (!GetCharacterMovement()->IsFalling())
|
||||
{
|
||||
Jump();
|
||||
return;
|
||||
}
|
||||
|
||||
// if we have a horizontal input, try for wall jump first
|
||||
if (!bHasWallJumped && !FMath::IsNearlyZero(ActionValueY))
|
||||
{
|
||||
// trace ahead of the character for walls
|
||||
FHitResult OutHit;
|
||||
|
||||
const FVector Start = GetActorLocation();
|
||||
const FVector End = Start + (FVector(ActionValueY > 0.0f ? 1.0f : -1.0f, 0.0f, 0.0f) * WallJumpTraceDistance);
|
||||
|
||||
FCollisionQueryParams QueryParams;
|
||||
QueryParams.AddIgnoredActor(this);
|
||||
|
||||
GetWorld()->LineTraceSingleByChannel(OutHit, Start, End, ECC_Visibility, QueryParams);
|
||||
|
||||
if (OutHit.bBlockingHit)
|
||||
{
|
||||
// rotate to the bounce direction
|
||||
const FRotator BounceRot = UKismetMathLibrary::MakeRotFromX(OutHit.ImpactNormal);
|
||||
SetActorRotation(FRotator(0.0f, BounceRot.Yaw, 0.0f));
|
||||
|
||||
// calculate the impulse vector
|
||||
FVector WallJumpImpulse = OutHit.ImpactNormal * WallJumpHorizontalImpulse;
|
||||
WallJumpImpulse.Z = GetCharacterMovement()->JumpZVelocity * WallJumpVerticalMultiplier;
|
||||
|
||||
// launch the character away from the wall
|
||||
LaunchCharacter(WallJumpImpulse, true, true);
|
||||
|
||||
// enable wall jump lockout for a bit
|
||||
bHasWallJumped = true;
|
||||
|
||||
// schedule wall jump lockout reset
|
||||
GetWorld()->GetTimerManager().SetTimer(WallJumpTimer, this, &ASideScrollingCharacter::ResetWallJump, DelayBetweenWallJumps, false);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// test for double jump only if we haven't already tested for wall jump
|
||||
if (!bHasWallJumped)
|
||||
{
|
||||
// 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();
|
||||
|
||||
// no coyote time jump
|
||||
} else {
|
||||
|
||||
// The movement component handles double jump but we still need to manage the flag for animation
|
||||
if (!bHasDoubleJumped)
|
||||
{
|
||||
// raise the double jump flag
|
||||
bHasDoubleJumped = true;
|
||||
|
||||
// let the CMC handle jump
|
||||
Jump();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ASideScrollingCharacter::CheckForSoftCollision()
|
||||
{
|
||||
// reset the drop value
|
||||
DropValue = 0.0f;
|
||||
|
||||
// trace down
|
||||
FHitResult OutHit;
|
||||
|
||||
const FVector Start = GetActorLocation();
|
||||
const FVector End = Start + (FVector::DownVector * SoftCollisionTraceDistance);
|
||||
|
||||
FCollisionObjectQueryParams ObjectParams;
|
||||
ObjectParams.AddObjectTypesToQuery(SoftCollisionObjectType);
|
||||
|
||||
FCollisionQueryParams QueryParams;
|
||||
QueryParams.AddIgnoredActor(this);
|
||||
|
||||
GetWorld()->LineTraceSingleByObjectType(OutHit, Start, End, ObjectParams, QueryParams);
|
||||
|
||||
// did we hit a soft floor?
|
||||
if (OutHit.GetActor())
|
||||
{
|
||||
// drop through the floor
|
||||
SetSoftCollision(true);
|
||||
}
|
||||
}
|
||||
|
||||
void ASideScrollingCharacter::ResetWallJump()
|
||||
{
|
||||
// reset the wall jump flag
|
||||
bHasWallJumped = false;
|
||||
}
|
||||
|
||||
void ASideScrollingCharacter::SetSoftCollision(bool bEnabled)
|
||||
{
|
||||
// enable or disable collision response to the soft collision channel
|
||||
GetCapsuleComponent()->SetCollisionResponseToChannel(SoftCollisionObjectType, bEnabled ? ECR_Ignore : ECR_Block);
|
||||
}
|
||||
|
||||
bool ASideScrollingCharacter::HasDoubleJumped() const
|
||||
{
|
||||
return bHasDoubleJumped;
|
||||
}
|
||||
|
||||
bool ASideScrollingCharacter::HasWallJumped() const
|
||||
{
|
||||
return bHasWallJumped;
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GameFramework/Character.h"
|
||||
#include "SideScrollingCharacter.generated.h"
|
||||
|
||||
class UCameraComponent;
|
||||
class UInputAction;
|
||||
struct FInputActionValue;
|
||||
|
||||
/**
|
||||
* A player-controllable character side scrolling game
|
||||
*/
|
||||
UCLASS(abstract)
|
||||
class ASideScrollingCharacter : public ACharacter
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/** Player camera */
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category ="Camera", meta = (AllowPrivateAccess = "true"))
|
||||
UCameraComponent* Camera;
|
||||
|
||||
protected:
|
||||
|
||||
/** Move Input Action */
|
||||
UPROPERTY(EditAnywhere, Category="Input")
|
||||
UInputAction* MoveAction;
|
||||
|
||||
/** Jump Input Action */
|
||||
UPROPERTY(EditAnywhere, Category="Input")
|
||||
UInputAction* JumpAction;
|
||||
|
||||
/** Drop from Platform Action */
|
||||
UPROPERTY(EditAnywhere, Category="Input")
|
||||
UInputAction* DropAction;
|
||||
|
||||
/** Interact Input Action */
|
||||
UPROPERTY(EditAnywhere, Category="Input")
|
||||
UInputAction* InteractAction;
|
||||
|
||||
/** Impulse to manually push physics objects while we're in midair */
|
||||
UPROPERTY(EditAnywhere, Category="Side Scrolling|Jump")
|
||||
float JumpPushImpulse = 600.0f;
|
||||
|
||||
/** Max distance that interactive objects can be triggered */
|
||||
UPROPERTY(EditAnywhere, Category="Side Scrolling|Interaction")
|
||||
float InteractionRadius = 200.0f;
|
||||
|
||||
/** Time to disable input after a wall jump to preserve momentum */
|
||||
UPROPERTY(EditAnywhere, Category="Side Scrolling|Wall Jump")
|
||||
float DelayBetweenWallJumps = 0.3f;
|
||||
|
||||
/** Distance to trace ahead of the character for wall jumps */
|
||||
UPROPERTY(EditAnywhere, Category="Side Scrolling|Wall Jump")
|
||||
float WallJumpTraceDistance = 50.0f;
|
||||
|
||||
/** Horizontal impulse to apply to the character during wall jumps */
|
||||
UPROPERTY(EditAnywhere, Category="Side Scrolling|Wall Jump")
|
||||
float WallJumpHorizontalImpulse = 500.0f;
|
||||
|
||||
/** Multiplies the jump Z velocity for wall jumps. */
|
||||
UPROPERTY(EditAnywhere, Category="Side Scrolling|Wall Jump")
|
||||
float WallJumpVerticalMultiplier = 1.4f;
|
||||
|
||||
/** Collision object type to use for soft collision traces (dropping down floors) */
|
||||
UPROPERTY(EditAnywhere, Category="Side Scrolling|Soft Platforms")
|
||||
TEnumAsByte<ECollisionChannel> SoftCollisionObjectType;
|
||||
|
||||
/** Distance to trace down during soft collision checks */
|
||||
UPROPERTY(EditAnywhere, Category="Side Scrolling|Soft Platforms")
|
||||
float SoftCollisionTraceDistance = 1000.0f;
|
||||
|
||||
/** 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="Side Scrolling|Coyote Time", meta = (ClampMin = 0, ClampMax = 5, Units = "s"))
|
||||
float MaxCoyoteTime = 0.16f;
|
||||
|
||||
/** Wall jump lockout timer */
|
||||
FTimerHandle WallJumpTimer;
|
||||
|
||||
/** Last captured horizontal movement input value */
|
||||
float ActionValueY = 0.0f;
|
||||
|
||||
/** Last captured platform drop axis value */
|
||||
float DropValue = 0.0f;
|
||||
|
||||
/** If true, this character has already wall jumped */
|
||||
bool bHasWallJumped = false;
|
||||
|
||||
/** If true, this character has already double jumped */
|
||||
bool bHasDoubleJumped = false;
|
||||
|
||||
/** If true, this character is moving along the side scrolling axis */
|
||||
bool bMovingHorizontally = false;
|
||||
|
||||
public:
|
||||
|
||||
/** Constructor */
|
||||
ASideScrollingCharacter();
|
||||
|
||||
protected:
|
||||
|
||||
/** Gameplay cleanup */
|
||||
virtual void EndPlay(EEndPlayReason::Type EndPlayReason) override;
|
||||
|
||||
/** Initialize input action bindings */
|
||||
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
|
||||
|
||||
/** Collision handling */
|
||||
virtual void NotifyHit(class UPrimitiveComponent* MyComp, AActor* Other, class UPrimitiveComponent* OtherComp, bool bSelfMoved, FVector HitLocation, FVector HitNormal, FVector NormalImpulse, const FHitResult& Hit) override;
|
||||
|
||||
/** Landing handling */
|
||||
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:
|
||||
|
||||
/** Called for movement input */
|
||||
void Move(const FInputActionValue& Value);
|
||||
|
||||
/** Called for drop from platform input */
|
||||
void Drop(const FInputActionValue& Value);
|
||||
|
||||
/** Called for drop from platform input release */
|
||||
void DropReleased(const FInputActionValue& Value);
|
||||
|
||||
public:
|
||||
|
||||
/** Handles move inputs from either controls or UI interfaces */
|
||||
UFUNCTION(BlueprintCallable, Category="Input")
|
||||
virtual void DoMove(float Forward);
|
||||
|
||||
/** Handles drop inputs from either controls or UI interfaces */
|
||||
UFUNCTION(BlueprintCallable, Category="Input")
|
||||
virtual void DoDrop(float Value);
|
||||
|
||||
/** 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();
|
||||
|
||||
/** Handles interact inputs from either controls or UI interfaces */
|
||||
UFUNCTION(BlueprintCallable, Category="Input")
|
||||
virtual void DoInteract();
|
||||
|
||||
protected:
|
||||
|
||||
/** Handles advanced jump logic */
|
||||
void MultiJump();
|
||||
|
||||
/** Checks for soft collision with platforms */
|
||||
void CheckForSoftCollision();
|
||||
|
||||
/** Resets wall jump lockout. Called from timer after a wall jump */
|
||||
void ResetWallJump();
|
||||
|
||||
public:
|
||||
|
||||
/** Sets the soft collision response. True passes, False blocks */
|
||||
void SetSoftCollision(bool bEnabled);
|
||||
|
||||
public:
|
||||
|
||||
/** Returns true if the character has just double jumped */
|
||||
UFUNCTION(BlueprintPure, Category="Side Scrolling")
|
||||
bool HasDoubleJumped() const;
|
||||
|
||||
/** Returns true if the character has just wall jumped */
|
||||
UFUNCTION(BlueprintPure, Category="Side Scrolling")
|
||||
bool HasWallJumped() const;
|
||||
};
|
||||
@@ -0,0 +1,77 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
|
||||
#include "SideScrollingGameMode.h"
|
||||
#include "Kismet/GameplayStatics.h"
|
||||
#include "Blueprint/UserWidget.h"
|
||||
#include "SideScrollingUI.h"
|
||||
#include "SideScrollingPickup.h"
|
||||
#include "Kismet/GameplayStatics.h"
|
||||
#include "GameFramework/PlayerStart.h"
|
||||
#include "Engine/World.h"
|
||||
|
||||
void ASideScrollingGameMode::BeginPlay()
|
||||
{
|
||||
Super::BeginPlay();
|
||||
|
||||
// create the game UI
|
||||
APlayerController* OwningPlayer = UGameplayStatics::GetPlayerController(GetWorld(), 0);
|
||||
|
||||
UserInterface = CreateWidget<USideScrollingUI>(OwningPlayer, UserInterfaceClass);
|
||||
|
||||
// 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* ASideScrollingGameMode::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;
|
||||
}
|
||||
|
||||
void ASideScrollingGameMode::ProcessPickup()
|
||||
{
|
||||
// increment the pickups counter
|
||||
++PickupsCollected;
|
||||
|
||||
if (UserInterface)
|
||||
{
|
||||
// if this is the first pickup we collect, show the UI
|
||||
if (PickupsCollected == 1)
|
||||
{
|
||||
|
||||
UserInterface->AddToViewport(0);
|
||||
}
|
||||
|
||||
// update the pickups counter on the UI
|
||||
UserInterface->UpdatePickups(PickupsCollected);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GameFramework/GameModeBase.h"
|
||||
#include "SideScrollingGameMode.generated.h"
|
||||
|
||||
class USideScrollingUI;
|
||||
|
||||
/**
|
||||
* Simple Side Scrolling Game Mode
|
||||
* Spawns and manages the game UI
|
||||
* Counts pickups collected by the player
|
||||
*/
|
||||
UCLASS(abstract)
|
||||
class ASideScrollingGameMode : public AGameModeBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
protected:
|
||||
|
||||
/** Class of UI widget to spawn when the game starts */
|
||||
UPROPERTY(EditAnywhere, Category="UI")
|
||||
TSubclassOf<USideScrollingUI> UserInterfaceClass;
|
||||
|
||||
/** User interface widget for the game */
|
||||
UPROPERTY(BlueprintReadOnly, Category="UI")
|
||||
TObjectPtr<USideScrollingUI> UserInterface;
|
||||
|
||||
/** Number of pickups collected by the player */
|
||||
UPROPERTY(BlueprintReadOnly, Category="Pickups")
|
||||
int32 PickupsCollected = 0;
|
||||
|
||||
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;
|
||||
|
||||
public:
|
||||
|
||||
/** Receives an interaction event from another actor */
|
||||
virtual void ProcessPickup();
|
||||
};
|
||||
@@ -0,0 +1,98 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
|
||||
#include "SideScrollingPlayerController.h"
|
||||
#include "EnhancedInputSubsystems.h"
|
||||
#include "InputMappingContext.h"
|
||||
#include "Kismet/GameplayStatics.h"
|
||||
#include "GameFramework/PlayerStart.h"
|
||||
#include "SideScrollingCharacter.h"
|
||||
#include "Engine/LocalPlayer.h"
|
||||
#include "Engine/World.h"
|
||||
#include "Blueprint/UserWidget.h"
|
||||
#include "KingshearthLegacy.h"
|
||||
#include "Widgets/Input/SVirtualJoystick.h"
|
||||
|
||||
void ASideScrollingPlayerController::BeginPlay()
|
||||
{
|
||||
Super::BeginPlay();
|
||||
}
|
||||
|
||||
void ASideScrollingPlayerController::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(LogKingshearthLegacy, Error, TEXT("Could not spawn mobile controls widget."));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void ASideScrollingPlayerController::OnPossess(APawn* InPawn)
|
||||
{
|
||||
Super::OnPossess(InPawn);
|
||||
|
||||
// subscribe to the pawn's OnDestroyed delegate
|
||||
InPawn->OnDestroyed.AddDynamic(this, &ASideScrollingPlayerController::OnPawnDestroyed);
|
||||
}
|
||||
|
||||
void ASideScrollingPlayerController::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 (ASideScrollingCharacter* RespawnedCharacter = GetWorld()->SpawnActor<ASideScrollingCharacter>(CharacterClass, SpawnTransform))
|
||||
{
|
||||
// possess the character
|
||||
Possess(RespawnedCharacter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ASideScrollingPlayerController::ShouldUseTouchControls() const
|
||||
{
|
||||
// are we on a mobile platform? Should we force touch?
|
||||
return SVirtualJoystick::ShouldDisplayTouchInterface() || bForceTouchControls;
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GameFramework/PlayerController.h"
|
||||
#include "EnhancedInput/Public/InputAction.h"
|
||||
#include "SideScrollingPlayerController.generated.h"
|
||||
|
||||
class ASideScrollingCharacter;
|
||||
class UInputMappingContext;
|
||||
|
||||
/**
|
||||
* A simple Side Scrolling Player Controller
|
||||
* Manages input mappings
|
||||
* Respawns the player pawn at the player start if it is destroyed
|
||||
*/
|
||||
UCLASS(abstract, Config="Game")
|
||||
class ASideScrollingPlayerController : 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<ASideScrollingCharacter> 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;
|
||||
|
||||
};
|
||||
@@ -0,0 +1,5 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
|
||||
#include "SideScrollingUI.h"
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Blueprint/UserWidget.h"
|
||||
#include "SideScrollingUI.generated.h"
|
||||
|
||||
/**
|
||||
* Simple Side Scrolling game UI
|
||||
* Displays and manages a pickup counter
|
||||
*/
|
||||
UCLASS(abstract)
|
||||
class USideScrollingUI : public UUserWidget
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
|
||||
/** Update the widget's pickup counter */
|
||||
UFUNCTION(BlueprintImplementableEvent, Category="UI")
|
||||
void UpdatePickups(int32 Amount);
|
||||
};
|
||||
Reference in New Issue
Block a user