diff --git a/Pawn_Unreal/Config/DefaultInput.ini b/Pawn_Unreal/Config/DefaultInput.ini
index 9b1348f..f2288dc 100644
--- a/Pawn_Unreal/Config/DefaultInput.ini
+++ b/Pawn_Unreal/Config/DefaultInput.ini
@@ -70,7 +70,9 @@ bCaptureMouseOnLaunch=True
bEnableLegacyInputScales=True
bEnableMotionControls=True
bFilterInputByPlatformUser=False
+bEnableInputDeviceSubsystem=True
bShouldFlushPressedKeysOnViewportFocusLost=True
+bEnableDynamicComponentInputBinding=True
bAlwaysShowTouchInterface=False
bShowConsoleOnFourFingerTap=True
bEnableGestureRecognizer=False
@@ -85,4 +87,5 @@ DefaultTouchInterface=/Engine/MobileResources/HUD/DefaultVirtualJoysticks.Defaul
-ConsoleKeys=Tilde
+ConsoleKeys=Tilde
+ConsoleKeys=Caret
++ConsoleKeys=²
diff --git a/Pawn_Unreal/Content/Characters/BP_Judy.uasset b/Pawn_Unreal/Content/Characters/BP_Judy.uasset
index b59cae4..1575cff 100644
--- a/Pawn_Unreal/Content/Characters/BP_Judy.uasset
+++ b/Pawn_Unreal/Content/Characters/BP_Judy.uasset
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:c23b59c00a787b2f5457ed34e65f89a34772dc484a8a02669cfd99f603721bf9
-size 174677
+oid sha256:bad47e6ba5d59a3b3b87c4f6f99513931d698e5bda42721987df04f4674c7faa
+size 225770
diff --git a/Pawn_Unreal/Content/Characters/Base/ABP_Character.uasset b/Pawn_Unreal/Content/Characters/Base/ABP_Character.uasset
index 3ef5d3a..7865e99 100644
--- a/Pawn_Unreal/Content/Characters/Base/ABP_Character.uasset
+++ b/Pawn_Unreal/Content/Characters/Base/ABP_Character.uasset
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:a27fe469a50cd6ade8e513410364d36f8b0fe70e9cd95bb6473d1080e62c2973
-size 266225
+oid sha256:94bbc78f7257a580831f357149d4572a7e5c492a494331c293fb6eaf7e54f308
+size 212741
diff --git a/Pawn_Unreal/Content/Input/BP_MainPlayerController.uasset b/Pawn_Unreal/Content/Input/BP_MainPlayerController.uasset
index a4d2ad0..db83481 100644
--- a/Pawn_Unreal/Content/Input/BP_MainPlayerController.uasset
+++ b/Pawn_Unreal/Content/Input/BP_MainPlayerController.uasset
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:5676f86cad9c61fe78944926907296751db39799f680ca324dee7fb11dd25656
-size 442497
+oid sha256:3dd40388fcf8b25c304a4abd4426a14b6cd29096ae5f1a7965974321c4de74cf
+size 414085
diff --git a/Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/0/91/BERD0JFDCI5RAYAPS4DY1P.uasset b/Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/0/91/BERD0JFDCI5RAYAPS4DY1P.uasset
new file mode 100644
index 0000000..26931dd
--- /dev/null
+++ b/Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/0/91/BERD0JFDCI5RAYAPS4DY1P.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:095b7423feef5e520436ca4ada169891edcdf403846c47993c4b3c326564c4c0
+size 4434
diff --git a/Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/0/XC/EES9G2P00B5EO5FPWE8N4U.uasset b/Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/0/XC/EES9G2P00B5EO5FPWE8N4U.uasset
new file mode 100644
index 0000000..7889f07
--- /dev/null
+++ b/Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/0/XC/EES9G2P00B5EO5FPWE8N4U.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:4b9aa98fa5ada2378494a0a3adb546a25770de0cc6cd8228a0001cd73ba5255e
+size 4320
diff --git a/Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/1/LE/RIS6ZA9R1KK7MS3EGVDKZV.uasset b/Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/1/LE/RIS6ZA9R1KK7MS3EGVDKZV.uasset
new file mode 100644
index 0000000..6086bba
--- /dev/null
+++ b/Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/1/LE/RIS6ZA9R1KK7MS3EGVDKZV.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:745affce1b7cb5725c8e39f2fc6dbaf9b2cd0bf0b1ccd607aee11be4bf396b4a
+size 4320
diff --git a/Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/3/JJ/2G4XZ4FX8KSV5ISWLBUX9T.uasset b/Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/3/JJ/2G4XZ4FX8KSV5ISWLBUX9T.uasset
deleted file mode 100644
index 583c3af..0000000
--- a/Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/3/JJ/2G4XZ4FX8KSV5ISWLBUX9T.uasset
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:8d175baeb9a56d342f674255c6628094ca2ea25ee0a1f4d1fb032dbb126a2356
-size 7349
diff --git a/Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/3/S1/Q0FDQMTTCM0M6RLWXOTPMF.uasset b/Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/3/S1/Q0FDQMTTCM0M6RLWXOTPMF.uasset
index 8f7ee9b..fa36887 100644
--- a/Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/3/S1/Q0FDQMTTCM0M6RLWXOTPMF.uasset
+++ b/Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/3/S1/Q0FDQMTTCM0M6RLWXOTPMF.uasset
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:18dce8ce6bda74815912bd1a7b7f18470fe3713cc6818760ba67ff11d1d030ee
+oid sha256:501e2333ad267b57cc6122c6d3a3e66e49189b324354a6a2807f4ba3115287a3
size 4441
diff --git a/Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/3/ZC/SPZ5ZSIG7BW3194FWIBEWC.uasset b/Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/3/ZC/SPZ5ZSIG7BW3194FWIBEWC.uasset
new file mode 100644
index 0000000..1f8e3db
--- /dev/null
+++ b/Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/3/ZC/SPZ5ZSIG7BW3194FWIBEWC.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:2f9ea1c60c5c4f236e2a2a0c625ee11dead9e78959268b6ff5ed5d2cb68b1034
+size 4318
diff --git a/Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/4/OH/3YZOO5HQPTMDBNJPPM4RV0.uasset b/Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/4/OH/3YZOO5HQPTMDBNJPPM4RV0.uasset
new file mode 100644
index 0000000..c73cd19
--- /dev/null
+++ b/Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/4/OH/3YZOO5HQPTMDBNJPPM4RV0.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:43e5a125722214af59e8d6753741d618e29e60b4cfe780767c3622a688d98755
+size 4320
diff --git a/Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/7/XC/WIQNHKPM06P8T5BPNH23RN.uasset b/Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/7/XC/WIQNHKPM06P8T5BPNH23RN.uasset
index 88647a4..505d785 100644
--- a/Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/7/XC/WIQNHKPM06P8T5BPNH23RN.uasset
+++ b/Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/7/XC/WIQNHKPM06P8T5BPNH23RN.uasset
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:8f3753b65840211d17718c61acd343257d9a8b37f9d86422e7db9583f3decdf2
+oid sha256:7c985f97632fe1b2d621e16da9e725c935156383ff605327acab5987664a9fba
size 4363
diff --git a/Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/9/RQ/JYNZEWOG6SXNE3HGUCSZIE.uasset b/Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/9/RQ/JYNZEWOG6SXNE3HGUCSZIE.uasset
new file mode 100644
index 0000000..156b9f7
--- /dev/null
+++ b/Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/9/RQ/JYNZEWOG6SXNE3HGUCSZIE.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:6d122dde87e7da821fd2442dac088f30bce0029e8966d9ee672bff2bbab0d9ac
+size 21547
diff --git a/Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/9/SL/CVWLA6QDNV8TESDOTZTWDN.uasset b/Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/9/SL/CVWLA6QDNV8TESDOTZTWDN.uasset
index 8d01cf2..f1022d4 100644
--- a/Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/9/SL/CVWLA6QDNV8TESDOTZTWDN.uasset
+++ b/Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/9/SL/CVWLA6QDNV8TESDOTZTWDN.uasset
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:a19f7876e98373728d76172520b2fce444ceada9fd15d4df2d5aa4d983453ad4
-size 20389
+oid sha256:ffce8c606a34176a459c81a435acba852d3aad8a529ae2e305aaf23d862de0e9
+size 20105
diff --git a/Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/A/BZ/XZUBN8NU0UZ2W8T2MBWCYN.uasset b/Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/A/BZ/XZUBN8NU0UZ2W8T2MBWCYN.uasset
new file mode 100644
index 0000000..1a4c3ef
--- /dev/null
+++ b/Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/A/BZ/XZUBN8NU0UZ2W8T2MBWCYN.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:c9e0625781eae5876357ad47e5ea94ecd9d495730c6a35b2c59c6e00ddc6207f
+size 4320
diff --git a/Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/A/U2/4ECO0ADAHJEURYSR8OYVEX.uasset b/Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/A/U2/4ECO0ADAHJEURYSR8OYVEX.uasset
new file mode 100644
index 0000000..9492931
--- /dev/null
+++ b/Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/A/U2/4ECO0ADAHJEURYSR8OYVEX.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:410a4983d36e9dfef96f23e75b4fd6cf2949d669e83f0aefc4afd75970809285
+size 4320
diff --git a/Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/C/BU/19EKBOJAPNXX8XDUPL0Z18.uasset b/Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/C/BU/19EKBOJAPNXX8XDUPL0Z18.uasset
index c1f09ba..7898209 100644
--- a/Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/C/BU/19EKBOJAPNXX8XDUPL0Z18.uasset
+++ b/Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/C/BU/19EKBOJAPNXX8XDUPL0Z18.uasset
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:ad5d6fa4613b64176b4e83451f7873c8d5b013b6203af1a7ed1333b0ddb9acdb
-size 9103
+oid sha256:3503b03398da6182b14796e8abfaa5fc3ac374d9421c8d5c763a4331fa7e1acc
+size 10539
diff --git a/Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/D/11/8KRHTI8SRC72YR9XJOITVN.uasset b/Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/D/11/8KRHTI8SRC72YR9XJOITVN.uasset
deleted file mode 100644
index aaf564c..0000000
--- a/Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/D/11/8KRHTI8SRC72YR9XJOITVN.uasset
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:221723698e9741459d92e8c08aa2d3bda0f62606727a4edf8a339d91ba924879
-size 46429
diff --git a/Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/D/VD/LJAQY01SJR93NPW0FPCPIL.uasset b/Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/D/VD/LJAQY01SJR93NPW0FPCPIL.uasset
new file mode 100644
index 0000000..b15ea29
--- /dev/null
+++ b/Pawn_Unreal/Content/__ExternalActors__/Maps/Dev/MAP_ControllerGym/D/VD/LJAQY01SJR93NPW0FPCPIL.uasset
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:3b4133eb7b55f9905d9e9547ab19e3acac3636c4821d3d48b7436b0cd5258f40
+size 4320
diff --git a/Pawn_Unreal/Pawn.sln.DotSettings b/Pawn_Unreal/Pawn.sln.DotSettings
new file mode 100644
index 0000000..ff71555
--- /dev/null
+++ b/Pawn_Unreal/Pawn.sln.DotSettings
@@ -0,0 +1,2 @@
+
+ True
\ No newline at end of file
diff --git a/Pawn_Unreal/Pawn.sln.DotSettings.user b/Pawn_Unreal/Pawn.sln.DotSettings.user
new file mode 100644
index 0000000..3da0f3b
--- /dev/null
+++ b/Pawn_Unreal/Pawn.sln.DotSettings.user
@@ -0,0 +1,2 @@
+
+ ForceIncluded
\ No newline at end of file
diff --git a/Pawn_Unreal/Source/Pawn/Private/Characters/PwnCharacterBase.cpp b/Pawn_Unreal/Source/Pawn/Private/Characters/PwnCharacterBase.cpp
new file mode 100644
index 0000000..c43099b
--- /dev/null
+++ b/Pawn_Unreal/Source/Pawn/Private/Characters/PwnCharacterBase.cpp
@@ -0,0 +1,18 @@
+#include "Characters/PwnCharacterBase.h"
+
+#include "Characters/PwnCharacterMovementComponent.h"
+
+APwnCharacterBase::APwnCharacterBase(const FObjectInitializer& ObjectInitializer)
+ : Super(ObjectInitializer.SetDefaultSubobjectClass(CharacterMovementComponentName)) {
+ PrimaryActorTick.bCanEverTick = true;
+
+ PwnCharacterMovementComponent = CastChecked(GetCharacterMovement());
+}
+
+void APwnCharacterBase::BeginPlay() {
+ Super::BeginPlay();
+}
+
+void APwnCharacterBase::Tick(float DeltaTime) {
+ Super::Tick(DeltaTime);
+}
diff --git a/Pawn_Unreal/Source/Pawn/Private/Characters/PwnCharacterMovementComponent.cpp b/Pawn_Unreal/Source/Pawn/Private/Characters/PwnCharacterMovementComponent.cpp
new file mode 100644
index 0000000..1122cbe
--- /dev/null
+++ b/Pawn_Unreal/Source/Pawn/Private/Characters/PwnCharacterMovementComponent.cpp
@@ -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();
+ 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
diff --git a/Pawn_Unreal/Source/Pawn/Private/GameplayModes/Combat/PwnCombatPlatformerPath.cpp b/Pawn_Unreal/Source/Pawn/Private/GameplayModes/Combat/PwnCombatPlatformerPath.cpp
new file mode 100644
index 0000000..19a06e4
--- /dev/null
+++ b/Pawn_Unreal/Source/Pawn/Private/GameplayModes/Combat/PwnCombatPlatformerPath.cpp
@@ -0,0 +1,30 @@
+#include "GameplayModes/Combat/PwnCombatPlatformerPath.h"
+
+#include "Components/SplineComponent.h"
+#include "GameplayModes/PwnGameplayModeSubsystem.h"
+
+APwnCombatPlatformerPath::APwnCombatPlatformerPath() {
+ PrimaryActorTick.bCanEverTick = false;
+
+ Spline = CreateDefaultSubobject(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();
+ check(Subsystem);
+ Subsystem->RegisterCombatPath(this);
+ Super::BeginPlay();
+}
+
+void APwnCombatPlatformerPath::EndPlay(const EEndPlayReason::Type EndPlayReason) {
+ if (UPwnGameplayModeSubsystem* Subsystem = GetWorld()->GetSubsystem()) {
+ Subsystem->UnregisterCombatPath(this);
+ }
+ Super::EndPlay(EndPlayReason);
+}
diff --git a/Pawn_Unreal/Source/Pawn/Private/GameplayModes/PwnGameplayModeSubsystem.cpp b/Pawn_Unreal/Source/Pawn/Private/GameplayModes/PwnGameplayModeSubsystem.cpp
index 8cdb663..bd771ad 100644
--- a/Pawn_Unreal/Source/Pawn/Private/GameplayModes/PwnGameplayModeSubsystem.cpp
+++ b/Pawn_Unreal/Source/Pawn/Private/GameplayModes/PwnGameplayModeSubsystem.cpp
@@ -1,5 +1,8 @@
#include "GameplayModes/PwnGameplayModeSubsystem.h"
+#include "Components/SplineComponent.h"
+#include "GameplayModes/Combat/PwnCombatPlatformerPath.h"
+
void UPwnGameplayModeSubsystem::Initialize(FSubsystemCollectionBase& Collection) {
Super::Initialize(Collection);
CurrentGameplayMode = EPwnGameplayMode::Narrative;
@@ -25,6 +28,46 @@ EPwnGameplayMode UPwnGameplayModeSubsystem::GetCurrentGameplayMode() const {
bool UPwnGameplayModeSubsystem::IsNarrativeMode() const {
return CurrentGameplayMode == EPwnGameplayMode::Narrative;
}
+
bool UPwnGameplayModeSubsystem::IsCombatMode() const {
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;
+}
diff --git a/Pawn_Unreal/Source/Pawn/Public/Characters/PwnCharacterBase.h b/Pawn_Unreal/Source/Pawn/Public/Characters/PwnCharacterBase.h
new file mode 100644
index 0000000..270a6dc
--- /dev/null
+++ b/Pawn_Unreal/Source/Pawn/Public/Characters/PwnCharacterBase.h
@@ -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 PwnCharacterMovementComponent;
+};
diff --git a/Pawn_Unreal/Source/Pawn/Public/Characters/PwnCharacterMovementComponent.h b/Pawn_Unreal/Source/Pawn/Public/Characters/PwnCharacterMovementComponent.h
new file mode 100644
index 0000000..6a2aa12
--- /dev/null
+++ b/Pawn_Unreal/Source/Pawn/Public/Characters/PwnCharacterMovementComponent.h
@@ -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 CombatPath;
+
+ UPROPERTY(BlueprintReadOnly)
+ float DistanceAlongSpline;
+
+ UPROPERTY(BlueprintReadOnly)
+ float DotDirection;
+
+ UPROPERTY(BlueprintReadWrite)
+ float Input;
+};
diff --git a/Pawn_Unreal/Source/Pawn/Public/GameplayModes/Combat/PwnCombatPlatformerPath.h b/Pawn_Unreal/Source/Pawn/Public/GameplayModes/Combat/PwnCombatPlatformerPath.h
new file mode 100644
index 0000000..4b570a9
--- /dev/null
+++ b/Pawn_Unreal/Source/Pawn/Public/GameplayModes/Combat/PwnCombatPlatformerPath.h
@@ -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 Spline;
+
+ UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Combat Platformer Path")
+ bool Reversed;
+
+ UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Combat Platformer Path")
+ bool SwapCamera;
+};
diff --git a/Pawn_Unreal/Source/Pawn/Public/GameplayModes/PwnGameplayModeSubsystem.h b/Pawn_Unreal/Source/Pawn/Public/GameplayModes/PwnGameplayModeSubsystem.h
index 9ee228d..a7b7334 100644
--- a/Pawn_Unreal/Source/Pawn/Public/GameplayModes/PwnGameplayModeSubsystem.h
+++ b/Pawn_Unreal/Source/Pawn/Public/GameplayModes/PwnGameplayModeSubsystem.h
@@ -4,6 +4,8 @@
#include "Subsystems/WorldSubsystem.h"
#include "PwnGameplayModeSubsystem.generated.h"
+class APwnCombatPlatformerPath;
+
UENUM(BlueprintType, DisplayName = "Gameplay Mode")
enum class EPwnGameplayMode : uint8 {
Narrative,
@@ -33,10 +35,24 @@ public:
UFUNCTION(BlueprintCallable, BlueprintPure, Category="Gameplay Modes")
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:
UPROPERTY(BlueprintAssignable)
FGameplayModeChangedDelegate OnGameplayModeChanged;
private:
+ UPROPERTY()
EPwnGameplayMode CurrentGameplayMode;
+
+ UPROPERTY()
+ TArray> CombatPaths;
};
diff --git a/Pawn_Unreal/Source/Pawn/Public/Utils/EngineUtils.h b/Pawn_Unreal/Source/Pawn/Public/Utils/EngineUtils.h
new file mode 100644
index 0000000..959acec
--- /dev/null
+++ b/Pawn_Unreal/Source/Pawn/Public/Utils/EngineUtils.h
@@ -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__);