First iteration of spline movement mechanic in C++

This commit is contained in:
Maxime Maurin 2023-11-21 19:05:26 +01:00
parent f70079c04f
commit 2bfd6d4f15
30 changed files with 575 additions and 18 deletions

View File

@ -70,7 +70,9 @@ bCaptureMouseOnLaunch=True
bEnableLegacyInputScales=True bEnableLegacyInputScales=True
bEnableMotionControls=True bEnableMotionControls=True
bFilterInputByPlatformUser=False bFilterInputByPlatformUser=False
bEnableInputDeviceSubsystem=True
bShouldFlushPressedKeysOnViewportFocusLost=True bShouldFlushPressedKeysOnViewportFocusLost=True
bEnableDynamicComponentInputBinding=True
bAlwaysShowTouchInterface=False bAlwaysShowTouchInterface=False
bShowConsoleOnFourFingerTap=True bShowConsoleOnFourFingerTap=True
bEnableGestureRecognizer=False bEnableGestureRecognizer=False
@ -85,4 +87,5 @@ DefaultTouchInterface=/Engine/MobileResources/HUD/DefaultVirtualJoysticks.Defaul
-ConsoleKeys=Tilde -ConsoleKeys=Tilde
+ConsoleKeys=Tilde +ConsoleKeys=Tilde
+ConsoleKeys=Caret +ConsoleKeys=Caret
+ConsoleKeys=²

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,2 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/UserDictionary/Words/=Platformer/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

View File

@ -0,0 +1,2 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=8C1CE801_002DA611_002D3A51_002DBA22_002DD1481FF14826_002Fdl_003ASource_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003FProgram_0020Files_003FEpic_0020Games_003FUE_005F5_002E2_003FEngine_003FSource_002Fd_003ARuntime_002Fd_003AEngine_002Fd_003APrivate_002Fd_003AComponents_002Ff_003ACharacterMovementComponent_002Ecpp/@EntryIndexedValue">ForceIncluded</s:String></wpf:ResourceDictionary>

View File

@ -0,0 +1,18 @@
#include "Characters/PwnCharacterBase.h"
#include "Characters/PwnCharacterMovementComponent.h"
APwnCharacterBase::APwnCharacterBase(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer.SetDefaultSubobjectClass<UPwnCharacterMovementComponent>(CharacterMovementComponentName)) {
PrimaryActorTick.bCanEverTick = true;
PwnCharacterMovementComponent = CastChecked<UPwnCharacterMovementComponent>(GetCharacterMovement());
}
void APwnCharacterBase::BeginPlay() {
Super::BeginPlay();
}
void APwnCharacterBase::Tick(float DeltaTime) {
Super::Tick(DeltaTime);
}

View File

@ -0,0 +1,299 @@
#include "Characters/PwnCharacterMovementComponent.h"
#include "Characters/PwnCharacterBase.h"
#include "Components/SplineComponent.h"
#include "GameplayModes/PwnGameplayModeSubsystem.h"
#include "GameplayModes/Combat/PwnCombatPlatformerPath.h"
#include "Utils/EngineUtils.h"
UPwnCharacterMovementComponent::UPwnCharacterMovementComponent() {
PrimaryComponentTick.bCanEverTick = true;
}
void UPwnCharacterMovementComponent::BeginPlay() {
Super::BeginPlay();
}
float UPwnCharacterMovementComponent::GetMaxBrakingDeceleration() const {
if (MovementMode == MOVE_Custom) {
switch (CustomMovementMode) {
case SplineWalking: return BrakingDecelerationWalking;
case SplineFalling: return BrakingDecelerationFalling;
default: break;
}
}
return Super::GetMaxBrakingDeceleration();
}
float UPwnCharacterMovementComponent::GetMaxSpeed() const {
if (MovementMode == MOVE_Custom) {
switch (CustomMovementMode) {
case SplineWalking: return IsCrouching() ? MaxWalkSpeedCrouched : MaxWalkSpeed;;
case SplineFalling: return MaxWalkSpeed;
default: break;
}
}
return Super::GetMaxSpeed();
}
bool UPwnCharacterMovementComponent::IsMovingOnGround() const {
return Super::IsMovingOnGround() || IsCustomMovementMode(SplineWalking);
}
void UPwnCharacterMovementComponent::EnterSplineFollowMode() {
SetMovementMode(MOVE_Custom, SplineWalking);
const UPwnGameplayModeSubsystem* Subsystem = GetWorld()->GetSubsystem<UPwnGameplayModeSubsystem>();
check(Subsystem);
FVector OutLocation;
APwnCombatPlatformerPath* OutCombatPath;
float OutDistanceAloneSpline;
if (Subsystem->FindClosestCombatPathLocation(UpdatedComponent->GetComponentLocation(), OutLocation, OutCombatPath, OutDistanceAloneSpline)) {
DistanceAlongSpline = OutDistanceAloneSpline;
CombatPath = OutCombatPath;
DotDirection = CombatPath->Reversed ? -1.0f : 1.0f;
}
}
void UPwnCharacterMovementComponent::ExitSplineFollowMode() {
SetMovementMode(MOVE_Walking);
}
bool UPwnCharacterMovementComponent::IsCustomMovementMode(const ECustomMovementMode Mode) const {
return MovementMode == MOVE_Custom && CustomMovementMode == Mode;
}
void UPwnCharacterMovementComponent::PhysCustom(const float DeltaTime, const int32 Iterations) {
Super::PhysCustom(DeltaTime, Iterations);
switch (CustomMovementMode) {
case SplineWalking: PhysSplineFollow(DeltaTime, Iterations);
break;
default:
UE_LOG(LogTemp, Fatal, TEXT("Invalid custom movement mode"));
}
}
UE_DISABLE_OPTIMIZATION
void UPwnCharacterMovementComponent::PhysSplineFollow(const float DeltaTime, int32 Iterations) {
if (DeltaTime < MIN_TICK_TIME) {
return;
}
if (!CharacterOwner || (!CharacterOwner->Controller && !bRunPhysicsWithNoController && !HasAnimRootMotion() && !CurrentRootMotion.
HasOverrideVelocity() && (CharacterOwner->GetLocalRole() != ROLE_SimulatedProxy))) {
Acceleration = FVector::ZeroVector;
Velocity = FVector::ZeroVector;
return;
}
if (!UpdatedComponent->IsQueryCollisionEnabled()) {
SetMovementMode(MOVE_Walking);
return;
}
bJustTeleported = false;
bool bCheckedFall = false;
bool bTriedLedgeMove = false;
float RemainingTime = DeltaTime;
// Perform the move
while ((RemainingTime >= MIN_TICK_TIME) && (Iterations < MaxSimulationIterations) && CharacterOwner && (CharacterOwner->Controller ||
bRunPhysicsWithNoController || HasAnimRootMotion() || CurrentRootMotion.HasOverrideVelocity() || (CharacterOwner->GetLocalRole() ==
ROLE_SimulatedProxy))) {
Iterations++;
bJustTeleported = false;
const float TimeTick = GetSimulationTimeStep(RemainingTime, Iterations);
RemainingTime -= TimeTick;
// Save current values
UPrimitiveComponent* const OldBase = GetMovementBase();
const FVector PreviousBaseLocation = OldBase != nullptr ? OldBase->GetComponentLocation() : FVector::ZeroVector;
const FVector OldLocation = UpdatedComponent->GetComponentLocation();
const FFindFloorResult OldFloor = CurrentFloor;
const FVector OldVelocity = Velocity;
RestorePreAdditiveRootMotionVelocity();
MaintainHorizontalGroundVelocity();
Acceleration.Z = 0.f;
if (!HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity()) {
CalcVelocity(TimeTick, GroundFriction, false, GetMaxBrakingDeceleration());
}
float VelocityDirection = FMath::Sign(Velocity.X) * DotDirection;
float NewDistanceAlongSpline = DistanceAlongSpline + VelocityDirection * Velocity.Size() * TimeTick;
NewDistanceAlongSpline = FMath::Clamp(NewDistanceAlongSpline, 0.0f, CombatPath->Spline->GetSplineLength());
FVector TargetLocation = CombatPath->Spline->GetLocationAtDistanceAlongSpline(NewDistanceAlongSpline, ESplineCoordinateSpace::World);
TargetLocation.Z = OldLocation.Z;
FVector Direction = (TargetLocation - OldLocation).GetSafeNormal();
FHitResult MyHit;
float DistanceOffset = NewDistanceAlongSpline - DistanceAlongSpline;
FVector Delta2 = TargetLocation - OldLocation;
SafeMoveUpdatedComponent(Delta2, UpdatedComponent->GetComponentQuat(), true, MyHit);
if (MyHit.Time < 1.0f) {
HandleImpact(MyHit, TimeTick, Delta2);
//DistanceAlongSpline += InputDotDirection * Velocity.Size() * TimeTick * MyHit.Distance;
} else {
DistanceAlongSpline = NewDistanceAlongSpline;
}
if (OldLocation == UpdatedComponent->GetComponentLocation()) {
Velocity = FVector::Zero();
}
//continue;
//Velocity = OldVelocity;
// Apply acceleration
if (!HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity()) {
//CalcVelocity(TimeTick, GroundFriction, false, GetMaxBrakingDeceleration());
}
if (IsFalling()) {
// Root motion could have put us into Falling.
// No movement has taken place this movement tick so we pass on full time/past iteration count
StartNewPhysics(RemainingTime + TimeTick, Iterations - 1);
return;
}
// Compute move parameters
const FVector MoveVelocity = Velocity;
const FVector Delta = TimeTick * MoveVelocity;
const bool bZeroDelta = Delta.IsNearlyZero();
FStepDownResult StepDownResult;
if (bZeroDelta) {
RemainingTime = 0.f;
} else {
/*// try to move forward
MoveAlongFloor(MoveVelocity, TimeTick, &StepDownResult);
if (IsFalling()) {
// pawn decided to jump up
const float DesiredDist = Delta.Size();
if (DesiredDist > UE_KINDA_SMALL_NUMBER) {
const float ActualDist = (UpdatedComponent->GetComponentLocation() - OldLocation).Size2D();
RemainingTime += TimeTick * (1.f - FMath::Min(1.f, ActualDist / DesiredDist));
}
StartNewPhysics(RemainingTime, Iterations);
return;
} else if (IsSwimming()) //just entered water
{
StartSwimming(OldLocation, OldVelocity, TimeTick, RemainingTime, Iterations);
return;
}*/
}
continue;
// Update floor.
// StepUp might have already done it for us.
if (StepDownResult.bComputedFloor) {
CurrentFloor = StepDownResult.FloorResult;
} else {
FindFloor(UpdatedComponent->GetComponentLocation(), CurrentFloor, bZeroDelta, NULL);
}
// check for ledges here
const bool bCheckLedges = !CanWalkOffLedges();
if (bCheckLedges && !CurrentFloor.IsWalkableFloor()) {
// calculate possible alternate movement
const FVector GravDir = FVector(0.f, 0.f, -1.f);
const FVector NewDelta = bTriedLedgeMove ? FVector::ZeroVector : GetLedgeMove(OldLocation, Delta, GravDir);
if (!NewDelta.IsZero()) {
// first revert this move
RevertMove(OldLocation, OldBase, PreviousBaseLocation, OldFloor, false);
// avoid repeated ledge moves if the first one fails
bTriedLedgeMove = true;
// Try new movement direction
Velocity = NewDelta / TimeTick;
RemainingTime += TimeTick;
continue;
} else {
// see if it is OK to jump
// @todo collision : only thing that can be problem is that oldbase has world collision on
bool bMustJump = bZeroDelta || (OldBase == NULL || (!OldBase->IsQueryCollisionEnabled() &&
MovementBaseUtility::IsDynamicBase(OldBase)));
if ((bMustJump || !bCheckedFall) && CheckFall(OldFloor, CurrentFloor.HitResult, Delta, OldLocation, RemainingTime, TimeTick,
Iterations, bMustJump)) {
return;
}
bCheckedFall = true;
// revert this move
RevertMove(OldLocation, OldBase, PreviousBaseLocation, OldFloor, true);
RemainingTime = 0.f;
break;
}
} else {
// Validate the floor check
if (CurrentFloor.IsWalkableFloor()) {
if (ShouldCatchAir(OldFloor, CurrentFloor)) {
HandleWalkingOffLedge(OldFloor.HitResult.ImpactNormal, OldFloor.HitResult.Normal, OldLocation, TimeTick);
if (IsMovingOnGround()) {
// If still walking, then fall. If not, assume the user set a different mode they want to keep.
StartFalling(Iterations, RemainingTime, TimeTick, Delta, OldLocation);
}
return;
}
AdjustFloorHeight();
SetBase(CurrentFloor.HitResult.Component.Get(), CurrentFloor.HitResult.BoneName);
} else if (CurrentFloor.HitResult.bStartPenetrating && RemainingTime <= 0.f) {
// The floor check failed because it started in penetration
// We do not want to try to move downward because the downward sweep failed, rather we'd like to try to pop out of the floor.
FHitResult Hit(CurrentFloor.HitResult);
Hit.TraceEnd = Hit.TraceStart + FVector(0.f, 0.f, MAX_FLOOR_DIST);
const FVector RequestedAdjustment = GetPenetrationAdjustment(Hit);
ResolvePenetration(RequestedAdjustment, Hit, UpdatedComponent->GetComponentQuat());
bForceNextFloorCheck = true;
}
// check if just entered water
if (IsSwimming()) {
StartSwimming(OldLocation, Velocity, TimeTick, RemainingTime, Iterations);
return;
}
// See if we need to start falling.
if (!CurrentFloor.IsWalkableFloor() && !CurrentFloor.HitResult.bStartPenetrating) {
const bool bMustJump = bJustTeleported || bZeroDelta || (OldBase == NULL || (!OldBase->IsQueryCollisionEnabled() &&
MovementBaseUtility::IsDynamicBase(OldBase)));
if ((bMustJump || !bCheckedFall) && CheckFall(OldFloor, CurrentFloor.HitResult, Delta, OldLocation, RemainingTime, TimeTick,
Iterations, bMustJump)) {
return;
}
bCheckedFall = true;
}
}
// Allow overlap events and such to change physics state and velocity
/*if (IsMovingOnGround()) {
// Make velocity reflect actual move
if (!bJustTeleported && !HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity() && TimeTick >= MIN_TICK_TIME) {
// TODO-RootMotionSource: Allow this to happen during partial override Velocity, but only set allowed axes?
Velocity = (UpdatedComponent->GetComponentLocation() - OldLocation) / TimeTick;
MaintainHorizontalGroundVelocity();
}
}
// If we didn't move at all this iteration then abort (since future iterations will also be stuck).
if (UpdatedComponent->GetComponentLocation() == OldLocation) {
RemainingTime = 0.f;
break;
}*/
}
if (IsMovingOnGround()) {
MaintainHorizontalGroundVelocity();
}
}
UE_ENABLE_OPTIMIZATION

View File

@ -0,0 +1,30 @@
#include "GameplayModes/Combat/PwnCombatPlatformerPath.h"
#include "Components/SplineComponent.h"
#include "GameplayModes/PwnGameplayModeSubsystem.h"
APwnCombatPlatformerPath::APwnCombatPlatformerPath() {
PrimaryActorTick.bCanEverTick = false;
Spline = CreateDefaultSubobject<USplineComponent>(TEXT("Spline"));
SetRootComponent(Spline);
#if WITH_EDITORONLY_DATA
Spline->EditorSelectedSplineSegmentColor = FColor::FromHex("EBA30AFF");
Spline->EditorUnselectedSplineSegmentColor = FColor::Red;
#endif
}
void APwnCombatPlatformerPath::BeginPlay() {
UPwnGameplayModeSubsystem* Subsystem = GetWorld()->GetSubsystem<UPwnGameplayModeSubsystem>();
check(Subsystem);
Subsystem->RegisterCombatPath(this);
Super::BeginPlay();
}
void APwnCombatPlatformerPath::EndPlay(const EEndPlayReason::Type EndPlayReason) {
if (UPwnGameplayModeSubsystem* Subsystem = GetWorld()->GetSubsystem<UPwnGameplayModeSubsystem>()) {
Subsystem->UnregisterCombatPath(this);
}
Super::EndPlay(EndPlayReason);
}

View File

@ -1,5 +1,8 @@
#include "GameplayModes/PwnGameplayModeSubsystem.h" #include "GameplayModes/PwnGameplayModeSubsystem.h"
#include "Components/SplineComponent.h"
#include "GameplayModes/Combat/PwnCombatPlatformerPath.h"
void UPwnGameplayModeSubsystem::Initialize(FSubsystemCollectionBase& Collection) { void UPwnGameplayModeSubsystem::Initialize(FSubsystemCollectionBase& Collection) {
Super::Initialize(Collection); Super::Initialize(Collection);
CurrentGameplayMode = EPwnGameplayMode::Narrative; CurrentGameplayMode = EPwnGameplayMode::Narrative;
@ -25,6 +28,46 @@ EPwnGameplayMode UPwnGameplayModeSubsystem::GetCurrentGameplayMode() const {
bool UPwnGameplayModeSubsystem::IsNarrativeMode() const { bool UPwnGameplayModeSubsystem::IsNarrativeMode() const {
return CurrentGameplayMode == EPwnGameplayMode::Narrative; return CurrentGameplayMode == EPwnGameplayMode::Narrative;
} }
bool UPwnGameplayModeSubsystem::IsCombatMode() const { bool UPwnGameplayModeSubsystem::IsCombatMode() const {
return CurrentGameplayMode == EPwnGameplayMode::Combat; return CurrentGameplayMode == EPwnGameplayMode::Combat;
} }
void UPwnGameplayModeSubsystem::RegisterCombatPath(APwnCombatPlatformerPath* CombatPath) {
CombatPaths.Add(CombatPath);
}
void UPwnGameplayModeSubsystem::UnregisterCombatPath(APwnCombatPlatformerPath* CombatPath) {
CombatPaths.Remove(CombatPath);
}
bool UPwnGameplayModeSubsystem::FindClosestCombatPathLocation(const FVector& Location, FVector& OutCombatPathLocation,
APwnCombatPlatformerPath*& OutCombatPath, float& OutDistanceAlongSpline) const {
float ShortestDistance = FLT_MAX;
float ClosestKey = 0.0f;
FVector ClosestLocation = FVector::ZeroVector;
APwnCombatPlatformerPath* ClosestCombatPath = nullptr;
bool Found = false;
for (APwnCombatPlatformerPath* CombatPath : CombatPaths) {
const USplineComponent* CurrentSpline = CombatPath->Spline;
const float CurrentKey = CurrentSpline->FindInputKeyClosestToWorldLocation(Location);
FVector CurrentLocation = CurrentSpline->GetLocationAtSplineInputKey(CurrentKey, ESplineCoordinateSpace::World);
const float CurrentDistance = FVector::DistSquared(Location, CurrentLocation);
if (CurrentDistance < ShortestDistance) {
ShortestDistance = CurrentDistance;
ClosestKey = CurrentKey;
ClosestLocation = CurrentLocation;
ClosestCombatPath = CombatPath;
Found = true;
}
}
if (Found) {
OutCombatPathLocation = ClosestLocation;
OutCombatPath = ClosestCombatPath;
OutDistanceAlongSpline = OutCombatPath->Spline->GetDistanceAlongSplineAtSplineInputKey(ClosestKey);
}
return Found;
}

View File

@ -0,0 +1,25 @@
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "PwnCharacterBase.generated.h"
class UPwnCharacterMovementComponent;
UCLASS()
class PAWN_API APwnCharacterBase : public ACharacter {
GENERATED_BODY()
public:
APwnCharacterBase(const FObjectInitializer& ObjectInitializer);
protected:
virtual void BeginPlay() override;
public:
virtual void Tick(float DeltaTime) override;
protected:
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Components")
TObjectPtr<UPwnCharacterMovementComponent> PwnCharacterMovementComponent;
};

View File

@ -0,0 +1,59 @@
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "PwnCharacterMovementComponent.generated.h"
class APwnCombatPlatformerPath;
UENUM(BlueprintType)
enum ECustomMovementMode : uint8 {
None UMETA(Hidden),
SplineWalking,
SplineFalling
};
UCLASS(meta=(BlueprintSpawnableComponent))
class PAWN_API UPwnCharacterMovementComponent : public UCharacterMovementComponent {
GENERATED_BODY()
public:
UPwnCharacterMovementComponent();
protected:
virtual void BeginPlay() override;
virtual float GetMaxBrakingDeceleration() const override;
virtual float GetMaxSpeed() const override;
virtual bool IsMovingOnGround() const override;
public:
UFUNCTION(BlueprintCallable, Category="CharacterMovement")
void EnterSplineFollowMode();
UFUNCTION(BlueprintCallable, Category="CharacterMovement")
void ExitSplineFollowMode();
UFUNCTION(BlueprintCallable, Category="CharacterMovement")
bool IsCustomMovementMode(const ECustomMovementMode Mode) const;
virtual void PhysCustom(const float DeltaTime, int32 Iterations) override;
private:
void PhysSplineFollow(const float DeltaTime, int32 Iterations);
public:
UPROPERTY(BlueprintReadOnly)
TObjectPtr<APwnCombatPlatformerPath> CombatPath;
UPROPERTY(BlueprintReadOnly)
float DistanceAlongSpline;
UPROPERTY(BlueprintReadOnly)
float DotDirection;
UPROPERTY(BlueprintReadWrite)
float Input;
};

View File

@ -0,0 +1,30 @@
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "PwnCombatPlatformerPath.generated.h"
class USplineComponent;
UCLASS()
class PAWN_API APwnCombatPlatformerPath : public AActor {
GENERATED_BODY()
public:
APwnCombatPlatformerPath();
protected:
virtual void BeginPlay() override;
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
public:
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Components")
TObjectPtr<USplineComponent> Spline;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Combat Platformer Path")
bool Reversed;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Combat Platformer Path")
bool SwapCamera;
};

View File

@ -4,6 +4,8 @@
#include "Subsystems/WorldSubsystem.h" #include "Subsystems/WorldSubsystem.h"
#include "PwnGameplayModeSubsystem.generated.h" #include "PwnGameplayModeSubsystem.generated.h"
class APwnCombatPlatformerPath;
UENUM(BlueprintType, DisplayName = "Gameplay Mode") UENUM(BlueprintType, DisplayName = "Gameplay Mode")
enum class EPwnGameplayMode : uint8 { enum class EPwnGameplayMode : uint8 {
Narrative, Narrative,
@ -33,10 +35,24 @@ public:
UFUNCTION(BlueprintCallable, BlueprintPure, Category="Gameplay Modes") UFUNCTION(BlueprintCallable, BlueprintPure, Category="Gameplay Modes")
bool IsCombatMode() const; bool IsCombatMode() const;
UFUNCTION()
void RegisterCombatPath(APwnCombatPlatformerPath* CombatPath);
UFUNCTION()
void UnregisterCombatPath(APwnCombatPlatformerPath* CombatPath);
UFUNCTION()
bool FindClosestCombatPathLocation(const FVector& Location, FVector& OutCombatPathLocation, APwnCombatPlatformerPath*& OutCombatPath,
float& OutDistanceAlongSpline) const;
public: public:
UPROPERTY(BlueprintAssignable) UPROPERTY(BlueprintAssignable)
FGameplayModeChangedDelegate OnGameplayModeChanged; FGameplayModeChangedDelegate OnGameplayModeChanged;
private: private:
UPROPERTY()
EPwnGameplayMode CurrentGameplayMode; EPwnGameplayMode CurrentGameplayMode;
UPROPERTY()
TArray<TObjectPtr<APwnCombatPlatformerPath>> CombatPaths;
}; };

View File

@ -0,0 +1,9 @@
#pragma once
#define PRINT_STRING(Key, Time, Color, Content, ...) GEngine->AddOnScreenDebugMessage(Key, Time, Color, FString::Printf(TEXT(Content), __VA_ARGS__));
#define PRINT_STRING_GENERIC(Color, Content, ...) PRINT_STRING(-1, 4.0f, Color, Content, __VA_ARGS__);
#define PRINT_STRING_WHITE(Content, ...) PRINT_STRING_GENERIC(FColor::White, Content, __VA_ARGS__);
#define PRINT_STRING_BLUE(Content, ...) PRINT_STRING_GENERIC(FColor::Blue, Content, __VA_ARGS__);
#define PRINT_STRING_RED(Content, ...) PRINT_STRING_GENERIC(FColor::Red, Content, __VA_ARGS__);
#define PRINT_STRING_YELLOW(Content, ...) PRINT_STRING_GENERIC(FColor::Yellow, Content, __VA_ARGS__);
#define PRINT_STRING_GREEN(Content, ...) PRINT_STRING_GENERIC(FColor::Green, Content, __VA_ARGS__);