├── .gitignore ├── External └── SM_SurfRamp.blend ├── Content └── Example │ ├── Input │ ├── IA_Jump.uasset │ ├── IA_Look.uasset │ ├── IA_Move.uasset │ ├── IA_Crouch.uasset │ ├── MovementExampleAssetData.uasset │ └── MovementExampleMappingContext.uasset │ ├── Maps │ └── MovementExample.umap │ └── Blueprints │ ├── ExampleKillbox.uasset │ ├── Tools │ ├── M_SurfRamp.uasset │ ├── SM_SurfRamp.uasset │ └── MovementExampleSurfRamp.uasset │ ├── MovementExamplePawn.uasset │ └── MovementExampleGameMode.uasset ├── Source ├── Private │ ├── FGMovementModule.cpp │ ├── FGDefines.cpp │ ├── Core │ │ ├── FGDataModel.cpp │ │ ├── FGMovementUtils.cpp │ │ ├── FGMoverComponent.cpp │ │ └── FGPawn.cpp │ ├── Example │ │ ├── FGExamplePawn.h │ │ ├── FGExampleAssetData.h │ │ └── FGExamplePawn.cpp │ ├── Transitions │ │ └── FGCrouchCheck.cpp │ ├── LayeredMoves │ │ └── FGLayeredMove_Crouch.cpp │ ├── FGCVars.cpp │ └── Modes │ │ ├── FGAirMode.cpp │ │ └── FGWalkMode.cpp ├── Public │ ├── FGMovementDefines.h │ ├── Transitions │ │ └── FGCrouchCheck.h │ ├── FGMovementCVars.h │ ├── Modes │ │ ├── FGAirMode.h │ │ └── FGWalkMode.h │ ├── Core │ │ ├── FGMoverComponent.h │ │ ├── FGDataModel.h │ │ ├── FGPawn.h │ │ └── FGMovementUtils.h │ └── LayeredMoves │ │ └── FGLayeredMove_Crouch.h └── FGMovement.Build.cs ├── FGMovement.uplugin ├── README.md └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | /.DS_Store 2 | /.idea 3 | /.vs 4 | /_ReSharper.Caches 5 | /Binaries 6 | /Intermediate 7 | -------------------------------------------------------------------------------- /External/SM_SurfRamp.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daftsoftware/DaftMover/HEAD/External/SM_SurfRamp.blend -------------------------------------------------------------------------------- /Content/Example/Input/IA_Jump.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daftsoftware/DaftMover/HEAD/Content/Example/Input/IA_Jump.uasset -------------------------------------------------------------------------------- /Content/Example/Input/IA_Look.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daftsoftware/DaftMover/HEAD/Content/Example/Input/IA_Look.uasset -------------------------------------------------------------------------------- /Content/Example/Input/IA_Move.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daftsoftware/DaftMover/HEAD/Content/Example/Input/IA_Move.uasset -------------------------------------------------------------------------------- /Content/Example/Input/IA_Crouch.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daftsoftware/DaftMover/HEAD/Content/Example/Input/IA_Crouch.uasset -------------------------------------------------------------------------------- /Content/Example/Maps/MovementExample.umap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daftsoftware/DaftMover/HEAD/Content/Example/Maps/MovementExample.umap -------------------------------------------------------------------------------- /Content/Example/Blueprints/ExampleKillbox.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daftsoftware/DaftMover/HEAD/Content/Example/Blueprints/ExampleKillbox.uasset -------------------------------------------------------------------------------- /Content/Example/Blueprints/Tools/M_SurfRamp.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daftsoftware/DaftMover/HEAD/Content/Example/Blueprints/Tools/M_SurfRamp.uasset -------------------------------------------------------------------------------- /Content/Example/Blueprints/Tools/SM_SurfRamp.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daftsoftware/DaftMover/HEAD/Content/Example/Blueprints/Tools/SM_SurfRamp.uasset -------------------------------------------------------------------------------- /Content/Example/Blueprints/MovementExamplePawn.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daftsoftware/DaftMover/HEAD/Content/Example/Blueprints/MovementExamplePawn.uasset -------------------------------------------------------------------------------- /Content/Example/Input/MovementExampleAssetData.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daftsoftware/DaftMover/HEAD/Content/Example/Input/MovementExampleAssetData.uasset -------------------------------------------------------------------------------- /Content/Example/Blueprints/MovementExampleGameMode.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daftsoftware/DaftMover/HEAD/Content/Example/Blueprints/MovementExampleGameMode.uasset -------------------------------------------------------------------------------- /Content/Example/Input/MovementExampleMappingContext.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daftsoftware/DaftMover/HEAD/Content/Example/Input/MovementExampleMappingContext.uasset -------------------------------------------------------------------------------- /Content/Example/Blueprints/Tools/MovementExampleSurfRamp.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daftsoftware/DaftMover/HEAD/Content/Example/Blueprints/Tools/MovementExampleSurfRamp.uasset -------------------------------------------------------------------------------- /Source/Private/FGMovementModule.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Daft Software 2 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 3 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 4 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 5 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 6 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 7 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 8 | // SOFTWARE. 9 | 10 | #include "Modules/ModuleInterface.h" 11 | 12 | class FFGMovementModule : public IModuleInterface {}; 13 | 14 | IMPLEMENT_MODULE(FFGMovementModule, FGMovement) -------------------------------------------------------------------------------- /FGMovement.uplugin: -------------------------------------------------------------------------------- 1 | { 2 | "FileVersion" : 3, 3 | "Version" : 1, 4 | "FriendlyName" : "FGMovement", 5 | "Description" : "Factory Game Movement Plugin.", 6 | "Category" : "Gameplay", 7 | "CreatedBy" : "Daft Software.", 8 | "CreatedByURL" : "https://github.com/daftsoftware", 9 | "EnabledByDefault" : true, 10 | "CanContainContent" : true, 11 | "IsBetaVersion" : false, 12 | "Installed" : false, 13 | "Modules" : 14 | [ 15 | { 16 | "Name" : "FGMovement", 17 | "Type" : "Runtime", 18 | "LoadingPhase" : "PreDefault" 19 | } 20 | ], 21 | "Plugins": [ 22 | { 23 | "Name": "Mover", 24 | "Enabled": true 25 | }, 26 | { 27 | "Name": "NetworkPrediction", 28 | "Enabled": true 29 | }, 30 | { 31 | "Name": "EnhancedInput", 32 | "Enabled": true 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /Source/Private/FGDefines.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Daft Software 2 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 3 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 4 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 5 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 6 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 7 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 8 | // SOFTWARE. 9 | 10 | #include "FGMovementDefines.h" 11 | 12 | namespace FG::Modes 13 | { 14 | const FLazyName Air = TEXT("Air"); 15 | const FLazyName Walk = TEXT("Walk"); 16 | } 17 | 18 | namespace FG::Blackboard 19 | { 20 | const FLazyName Dead = TEXT("Dead"); 21 | } 22 | -------------------------------------------------------------------------------- /Source/Public/FGMovementDefines.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Daft Software 2 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 3 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 4 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 5 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 6 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 7 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 8 | // SOFTWARE. 9 | 10 | #pragma once 11 | 12 | namespace FG::Modes 13 | { 14 | extern FGMOVEMENT_API const FLazyName Air; 15 | extern FGMOVEMENT_API const FLazyName Walk; 16 | } 17 | 18 | namespace FG::Blackboard 19 | { 20 | extern FGMOVEMENT_API const FLazyName Dead; 21 | } 22 | -------------------------------------------------------------------------------- /Source/Public/Transitions/FGCrouchCheck.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Daft Software 2 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 3 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 4 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 5 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 6 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 7 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 8 | // SOFTWARE. 9 | 10 | #pragma once 11 | 12 | #include "MovementModeTransition.h" 13 | #include "FGCrouchCheck.generated.h" 14 | 15 | UCLASS() 16 | class FGMOVEMENT_API UFGCrouchCheck : public UBaseMovementModeTransition 17 | { 18 | GENERATED_BODY() 19 | public: 20 | 21 | virtual void OnTrigger(const FSimulationTickParams& Params) override; 22 | virtual FTransitionEvalResult OnEvaluate(const FSimulationTickParams& Params) const override; 23 | }; 24 | -------------------------------------------------------------------------------- /Source/Public/FGMovementCVars.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Daft Software 2 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 3 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 4 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 5 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 6 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 7 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 8 | // SOFTWARE. 9 | 10 | #pragma once 11 | 12 | namespace FG::CVars 13 | { 14 | extern bool DrawMovementDebug; 15 | extern float JumpForce; 16 | extern float CrouchSpeedMult; 17 | extern float SprintSpeedMult; 18 | extern float GroundDamping; 19 | extern float AirDamping; 20 | extern float GroundAcceleration; 21 | extern float AirAcceleration; 22 | extern float GroundSpeed; 23 | extern float SlipFactor; 24 | extern float AirSpeed; 25 | extern float GravitySpeed; 26 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FGMovement 2 | 3 | Movement System being used for Factory Game. It's a work in progress but feel free to use it on your Unreal Game or for learning. It will become more battle tested as time goes on. 4 | 5 | ## Important Notices 6 | 7 | **You must be on Unreal Engine 5.4 or above to use FGMovement.** 8 | 9 | The following project config (Independent Interpolation) is currently required in `Config/DefaultNetworkPrediction.ini` to fix jittering: 10 | ``` 11 | [/Script/NetworkPrediction.NetworkPredictionSettingsObject] 12 | Settings=(PreferredTickingPolicy=Independent,ReplicatedManagerClassOverride=None,FixedTickFrameRate=60,bForceEngineFixTickForcePhysics=True,SimulatedProxyNetworkLOD=ForwardPredict,FixedTickInterpolationBufferedMS=100,IndependentTickInterpolationBufferedMS=100,IndependentTickInterpolationMaxBufferedMS=250,FixedTickInputSendCount=6,IndependentTickInputSendCount=6,MaximumRemoteInputFaultLimit=6) 13 | ``` 14 | ![image](https://github.com/daftsoftware/FGMovement/assets/9282017/69601534-a7be-4964-a175-ecc37f1f3ed9) 15 | -------------------------------------------------------------------------------- /Source/Public/Modes/FGAirMode.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Daft Software 2 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 3 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 4 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 5 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 6 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 7 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 8 | // SOFTWARE. 9 | 10 | #pragma once 11 | 12 | #include "MovementMode.h" 13 | #include "FGAirMode.generated.h" 14 | 15 | UCLASS() 16 | class FGMOVEMENT_API UFGAirMode final : public UBaseMovementMode 17 | { 18 | public: 19 | GENERATED_BODY() 20 | 21 | //~ Begin UBaseMovementMode 22 | void OnGenerateMove(const FMoverTickStartData& StartState, const FMoverTimeStep& TimeStep, FProposedMove& OutProposedMove) const override; 23 | void OnSimulationTick(const FSimulationTickParams& Params, FMoverTickEndData& OutputState) override; 24 | //~ End UBaseMovementMode 25 | 26 | }; 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Daft Software 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Source/Public/Core/FGMoverComponent.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Daft Software 2 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 3 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 4 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 5 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 6 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 7 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 8 | // SOFTWARE. 9 | 10 | #pragma once 11 | 12 | #include "MoverComponent.h" 13 | #include "FGMoverComponent.generated.h" 14 | 15 | UCLASS() 16 | class FGMOVEMENT_API UFGMoverComponent : public UMoverComponent 17 | { 18 | GENERATED_BODY() 19 | public: 20 | 21 | UFGMoverComponent(); 22 | 23 | virtual FVector GetFeetLocation(); 24 | 25 | //~ Begin UMoverComponent 26 | virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; 27 | virtual bool IsAirborne() const; 28 | virtual bool IsOnGround() const; 29 | //~ End UMoverComponent 30 | }; 31 | -------------------------------------------------------------------------------- /Source/FGMovement.Build.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Daft Software 2 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 3 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 4 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 5 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 6 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 7 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 8 | // SOFTWARE. 9 | 10 | using UnrealBuildTool; 11 | 12 | public class FGMovement : ModuleRules 13 | { 14 | public FGMovement(ReadOnlyTargetRules Target) : base(Target) 15 | { 16 | PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; 17 | 18 | PublicDependencyModuleNames.AddRange(new string[] 19 | { 20 | "Mover", 21 | "NetworkPrediction", 22 | }); 23 | 24 | PrivateDependencyModuleNames.AddRange(new string[] 25 | { 26 | "Core", 27 | "CoreUObject", 28 | "Engine", 29 | "InputCore", 30 | "NetCore", 31 | "EnhancedInput", 32 | }); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Source/Public/Modes/FGWalkMode.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Daft Software 2 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 3 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 4 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 5 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 6 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 7 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 8 | // SOFTWARE. 9 | 10 | #pragma once 11 | 12 | #include "MovementMode.h" 13 | #include "FGWalkMode.generated.h" 14 | 15 | struct FFGMoverInputCmd; 16 | 17 | UCLASS() 18 | class FGMOVEMENT_API UFGWalkMode final : public UBaseMovementMode 19 | { 20 | public: 21 | GENERATED_BODY() 22 | 23 | UFGWalkMode(); 24 | 25 | //~ Begin UBaseMovementMode 26 | void OnGenerateMove(const FMoverTickStartData& StartState, const FMoverTimeStep& TimeStep, FProposedMove& OutProposedMove) const override; 27 | void OnSimulationTick(const FSimulationTickParams& Params, FMoverTickEndData& OutputState) override; 28 | //~ End UBaseMovementMode 29 | 30 | bool TryJump(const FFGMoverInputCmd* InputCmd, FMoverTickEndData& OutputState); 31 | }; 32 | -------------------------------------------------------------------------------- /Source/Private/Core/FGDataModel.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Daft Software 2 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 3 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 4 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 5 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 6 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 7 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 8 | // SOFTWARE. 9 | 10 | #include "Core/FGDataModel.h" 11 | 12 | #include UE_INLINE_GENERATED_CPP_BY_NAME(FGDataModel) 13 | 14 | FFGMoverInputCmd::FFGMoverInputCmd() 15 | : bIsCrouchPressed(false) 16 | {} 17 | 18 | FMoverDataStructBase* FFGMoverInputCmd::Clone() const 19 | { 20 | FFGMoverInputCmd* CopyPtr = new FFGMoverInputCmd(*this); 21 | return CopyPtr; 22 | } 23 | 24 | bool FFGMoverInputCmd::NetSerialize(FArchive& Ar, UPackageMap* Map, bool& bOutSuccess) 25 | { 26 | Ar.SerializeBits(&bIsCrouchPressed, 1); 27 | return FCharacterDefaultInputs::NetSerialize(Ar, Map, bOutSuccess); 28 | } 29 | 30 | void FFGMoverInputCmd::ToString(FAnsiStringBuilderBase& Out) const 31 | { 32 | FCharacterDefaultInputs::ToString(Out); 33 | Out.Appendf("bIsCrouchPressed: %i\n", bIsCrouchPressed); 34 | } 35 | -------------------------------------------------------------------------------- /Source/Private/Example/FGExamplePawn.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Daft Software 2 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 3 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 4 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 5 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 6 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 7 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 8 | // SOFTWARE. 9 | 10 | #pragma once 11 | 12 | #include "Core/FGPawn.h" 13 | #include "FGExamplePawn.generated.h" 14 | 15 | class UFGExampleAssetData; 16 | 17 | /** 18 | * Example pawn implementation that inherits AFGPawn. 19 | * This class is not intended to be inherited from, it's just 20 | * an example of using the FG pawn class, to use it yourself 21 | * you should be inheriting AFGPawn yourself. 22 | */ 23 | UCLASS() 24 | class AFGExamplePawn final : public AFGPawn 25 | { 26 | GENERATED_BODY() 27 | public: 28 | 29 | //~ Begin FGPawn 30 | void SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) override; 31 | void PawnClientRestart() override; 32 | //~ End FGPawn 33 | 34 | UPROPERTY(EditDefaultsOnly) 35 | TSoftObjectPtr DefaultAssetData; 36 | }; 37 | -------------------------------------------------------------------------------- /Source/Public/LayeredMoves/FGLayeredMove_Crouch.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Daft Software 2 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 3 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 4 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 5 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 6 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 7 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 8 | // SOFTWARE. 9 | 10 | #pragma once 11 | 12 | #include "LayeredMove.h" 13 | #include "FGLayeredMove_Crouch.generated.h" 14 | 15 | USTRUCT() 16 | struct FGMOVEMENT_API FFGLayeredMove_Crouch : public FLayeredMoveBase 17 | { 18 | GENERATED_BODY() 19 | 20 | FFGLayeredMove_Crouch(); 21 | 22 | //~ Begin FLayeredMoveBase 23 | virtual bool GenerateMove(const FMoverTickStartData& StartState, const FMoverTimeStep& TimeStep, const UMoverComponent* MoverComp, UMoverBlackboard* SimBlackboard, FProposedMove& OutProposedMove) override; 24 | virtual FLayeredMoveBase* Clone() const override; 25 | virtual void NetSerialize(FArchive& Ar) override; 26 | virtual UScriptStruct* GetScriptStruct() const override; 27 | virtual FString ToSimpleString() const override; 28 | virtual void AddReferencedObjects(class FReferenceCollector& Collector) override; 29 | //~ End FLayeredMoveBase 30 | }; 31 | -------------------------------------------------------------------------------- /Source/Public/Core/FGDataModel.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Daft Software 2 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 3 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 4 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 5 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 6 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 7 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 8 | // SOFTWARE. 9 | 10 | #pragma once 11 | 12 | #include "MoverDataModelTypes.h" 13 | #include "FGDataModel.generated.h" 14 | 15 | USTRUCT() 16 | struct FFGMoverInputCmd : public FCharacterDefaultInputs 17 | { 18 | GENERATED_BODY() 19 | 20 | FFGMoverInputCmd(); 21 | 22 | UPROPERTY(BlueprintReadWrite, Category = Mover) 23 | bool bIsCrouchPressed; 24 | 25 | // @return newly allocated copy of this FCharacterDefaultInputs. Must be overridden by child classes 26 | virtual FMoverDataStructBase* Clone() const override; 27 | virtual bool NetSerialize(FArchive& Ar, UPackageMap* Map, bool& bOutSuccess) override; 28 | virtual UScriptStruct* GetScriptStruct() const override { return StaticStruct(); } 29 | virtual void ToString(FAnsiStringBuilderBase& Out) const override; 30 | virtual void AddReferencedObjects(FReferenceCollector& Collector) override { Super::AddReferencedObjects(Collector); } 31 | }; 32 | -------------------------------------------------------------------------------- /Source/Private/Transitions/FGCrouchCheck.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Daft Software 2 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 3 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 4 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 5 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 6 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 7 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 8 | // SOFTWARE. 9 | 10 | #include "Transitions/FGCrouchCheck.h" 11 | #include "LayeredMoves/FGLayeredMove_Crouch.h" 12 | #include "Core/FGDataModel.h" 13 | #include "FGMovementDefines.h" 14 | 15 | #include "MoverSimulationTypes.h" 16 | #include "MoverComponent.h" 17 | 18 | void UFGCrouchCheck::OnTrigger(const FSimulationTickParams& Params) 19 | { 20 | TSharedPtr DuckMove = MakeShared(); 21 | Params.MoverComponent->QueueLayeredMove(DuckMove); 22 | } 23 | 24 | FTransitionEvalResult UFGCrouchCheck::OnEvaluate(const FSimulationTickParams& Params) const 25 | { 26 | FTransitionEvalResult EvalResult = FTransitionEvalResult::NoTransition; 27 | 28 | if (const FFGMoverInputCmd* InputCmd = Params.StartState.InputCmd.InputCollection.FindDataByType()) 29 | { 30 | if (InputCmd->bIsCrouchPressed) 31 | { 32 | //EvalResult.NextMode = FG::Modes::Walk; 33 | } 34 | } 35 | 36 | return EvalResult; 37 | } 38 | -------------------------------------------------------------------------------- /Source/Private/Example/FGExampleAssetData.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Daft Software 2 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 3 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 4 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 5 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 6 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 7 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 8 | // SOFTWARE. 9 | 10 | #pragma once 11 | 12 | #include "Engine/DataAsset.h" 13 | #include "FGExampleAssetData.generated.h" 14 | 15 | class UInputMappingContext; 16 | class UInputAction; 17 | 18 | /** 19 | * Example Asset Data for setting up inputs on the example pawn. 20 | * This class is not intended to be inherited from, it's just 21 | * an example of setting up inputs. It's likely you would want 22 | * something more custom or tailored to your game. 23 | */ 24 | UCLASS() 25 | class UFGExampleAssetData final : public UDataAsset 26 | { 27 | public: 28 | GENERATED_BODY() 29 | 30 | UPROPERTY(EditDefaultsOnly, Category = "Input") 31 | TSoftObjectPtr InputMappingContext; 32 | 33 | UPROPERTY(EditDefaultsOnly, Category = "Input") 34 | TSoftObjectPtr InputActionMove; 35 | 36 | UPROPERTY(EditDefaultsOnly, Category = "Input") 37 | TSoftObjectPtr InputActionLook; 38 | 39 | UPROPERTY(EditDefaultsOnly, Category = "Input") 40 | TSoftObjectPtr InputActionJump; 41 | 42 | UPROPERTY(EditDefaultsOnly, Category = "Input") 43 | TSoftObjectPtr InputActionCrouch; 44 | }; 45 | -------------------------------------------------------------------------------- /Source/Private/LayeredMoves/FGLayeredMove_Crouch.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Daft Software 2 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 3 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 4 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 5 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 6 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 7 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 8 | // SOFTWARE. 9 | 10 | #include "LayeredMoves/FGLayeredMove_Crouch.h" 11 | #include "FGMovementCVars.h" 12 | #include "MoverComponent.h" 13 | #include "MoverDataModelTypes.h" 14 | 15 | #include UE_INLINE_GENERATED_CPP_BY_NAME(FGLayeredMove_Crouch) 16 | 17 | FFGLayeredMove_Crouch::FFGLayeredMove_Crouch() 18 | { 19 | DurationMs = 0.f; 20 | MixMode = EMoveMixMode::OverrideVelocity; 21 | } 22 | 23 | bool FFGLayeredMove_Crouch::GenerateMove(const FMoverTickStartData& StartState, const FMoverTimeStep& TimeStep, const UMoverComponent* MoverComp, UMoverBlackboard* SimBlackboard, FProposedMove& OutProposedMove) 24 | { 25 | const FMoverDefaultSyncState* SyncState = StartState.SyncState.SyncStateCollection.FindDataByType(); 26 | check(SyncState); 27 | 28 | checkf(MixMode == EMoveMixMode::OverrideVelocity, TEXT("Only OverrideVelocity is supported for crouch move.")); 29 | const FVector PriorVelocityWS = SyncState->GetVelocity_WorldSpace(); 30 | 31 | OutProposedMove.LinearVelocity = PriorVelocityWS * FG::CVars::CrouchSpeedMult; // Rescale velocity by crouch speed. 32 | 33 | return true; 34 | } 35 | 36 | FLayeredMoveBase* FFGLayeredMove_Crouch::Clone() const 37 | { 38 | FFGLayeredMove_Crouch* CopyPtr = new FFGLayeredMove_Crouch(*this); 39 | return CopyPtr; 40 | } 41 | 42 | void FFGLayeredMove_Crouch::NetSerialize(FArchive& Ar) 43 | { 44 | FLayeredMoveBase::NetSerialize(Ar); 45 | } 46 | 47 | UScriptStruct* FFGLayeredMove_Crouch::GetScriptStruct() const 48 | { 49 | return FFGLayeredMove_Crouch::StaticStruct(); 50 | } 51 | 52 | FString FFGLayeredMove_Crouch::ToSimpleString() const 53 | { 54 | return FString::Printf(TEXT("Crouch")); 55 | } 56 | 57 | void FFGLayeredMove_Crouch::AddReferencedObjects(FReferenceCollector& Collector) 58 | { 59 | FLayeredMoveBase::AddReferencedObjects(Collector); 60 | } 61 | -------------------------------------------------------------------------------- /Source/Private/Example/FGExamplePawn.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Daft Software 2 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 3 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 4 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 5 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 6 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 7 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 8 | // SOFTWARE. 9 | 10 | #include "FGExamplePawn.h" 11 | #include "FGExampleAssetData.h" 12 | #include "EnhancedInputComponent.h" 13 | #include "EnhancedInputSubsystems.h" 14 | #include "InputMappingContext.h" 15 | 16 | #include UE_INLINE_GENERATED_CPP_BY_NAME(FGExamplePawn) 17 | 18 | void AFGExamplePawn::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) 19 | { 20 | Super::SetupPlayerInputComponent(PlayerInputComponent); 21 | 22 | const UFGExampleAssetData* AssetData = DefaultAssetData.LoadSynchronous(); 23 | checkf(AssetData, TEXT("Invalid Input Data!")); 24 | 25 | if (auto* EIC = CastChecked(PlayerInputComponent)) 26 | { 27 | const UInputAction* MoveAction = AssetData->InputActionMove.LoadSynchronous(); 28 | EIC->BindAction(MoveAction, ETriggerEvent::Triggered, this, &ThisClass::Move); 29 | EIC->BindAction(MoveAction, ETriggerEvent::Completed, this, &ThisClass::MoveCompleted); 30 | 31 | const UInputAction* LookAction = AssetData->InputActionLook.LoadSynchronous(); 32 | EIC->BindAction(LookAction, ETriggerEvent::Triggered, this, &ThisClass::Look); 33 | 34 | const UInputAction* JumpAction = AssetData->InputActionJump.LoadSynchronous(); 35 | EIC->BindAction(JumpAction, ETriggerEvent::Triggered, this, &ThisClass::Jump); 36 | EIC->BindAction(JumpAction, ETriggerEvent::Completed, this, &ThisClass::JumpCompleted); 37 | 38 | const UInputAction* DuckAction = AssetData->InputActionCrouch.LoadSynchronous(); 39 | EIC->BindAction(DuckAction, ETriggerEvent::Triggered, this, &ThisClass::Crouch); 40 | EIC->BindAction(DuckAction, ETriggerEvent::Completed, this, &ThisClass::CrouchCompleted); 41 | } 42 | } 43 | 44 | void AFGExamplePawn::PawnClientRestart() 45 | { 46 | Super::PawnClientRestart(); 47 | 48 | const UFGExampleAssetData* AssetData = DefaultAssetData.LoadSynchronous(); 49 | checkf(AssetData, TEXT("Invalid Input Data!")); 50 | 51 | if (const auto* PC = Cast(GetController())) 52 | { 53 | if (auto* Subsystem = ULocalPlayer::GetSubsystem(PC->GetLocalPlayer())) 54 | { 55 | Subsystem->ClearAllMappings(); // PawnClientRestart can run multiple times, clear out leftover mappings. 56 | Subsystem->AddMappingContext(AssetData->InputMappingContext.LoadSynchronous(), 0); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Source/Public/Core/FGPawn.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Daft Software 2 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 3 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 4 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 5 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 6 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 7 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 8 | // SOFTWARE. 9 | 10 | #pragma once 11 | 12 | #include "InputActionValue.h" 13 | #include "MoverSimulationTypes.h" 14 | #include "GameFramework/Pawn.h" 15 | #include "FGPawn.generated.h" 16 | 17 | struct FFGMoverInputCmd; 18 | class UFGMoverComponent; 19 | class UCapsuleComponent; 20 | class UCameraComponent; 21 | 22 | /** 23 | * Custom implementation of a pawn that uses the FG movement component 24 | * without any additional logic. 25 | */ 26 | UCLASS() 27 | class FGMOVEMENT_API AFGPawn : public APawn, public IMoverInputProducerInterface 28 | { 29 | GENERATED_BODY() 30 | public: 31 | 32 | AFGPawn(); 33 | 34 | //~ Begin APawn 35 | virtual void Tick(float DeltaSeconds) override; 36 | //~ End APawn 37 | 38 | virtual void Move(const FInputActionValue& Value); 39 | virtual void MoveCompleted(const FInputActionValue& Value); 40 | virtual void Look(const FInputActionValue& Value); 41 | virtual void Jump(); 42 | virtual void JumpCompleted(); 43 | virtual void Crouch(); 44 | virtual void CrouchCompleted(); 45 | 46 | //~ Begin IMoverInputProducerInterface 47 | virtual void ProduceInput_Implementation(int32 SimTimeMs, FMoverInputCmdContext& OutInputCmd) override; 48 | //~ End IMoverInputProducerInterface 49 | 50 | UFGMoverComponent* GetMoverComponent() const { return MoverComponent; } 51 | UCapsuleComponent* GetCapsuleComponent() const { return CapsuleComponent; } 52 | UCameraComponent* GetCameraComponent() const { return CameraComponent; } 53 | 54 | private: 55 | 56 | UPROPERTY(Category = Movement, VisibleAnywhere, BlueprintReadOnly, meta = (AllowPrivateAccess = "true")) 57 | TObjectPtr MoverComponent; 58 | 59 | UPROPERTY(Category=Character, VisibleAnywhere, BlueprintReadOnly, meta=(AllowPrivateAccess = "true")) 60 | TObjectPtr CapsuleComponent; 61 | 62 | UPROPERTY(Category=Character, VisibleAnywhere, BlueprintReadOnly, meta=(AllowPrivateAccess = "true")) 63 | TObjectPtr CameraComponent; 64 | 65 | // @TODO: This seems redundant, why aren't we just caching an entire input cmd? 66 | FVector3d LastAffirmativeMoveInput = FVector3d::ZeroVector; // Movement input (intent or velocity) the last time we had one that wasn't zero 67 | FVector3d CachedMoveInputIntent = FVector3d::ZeroVector; 68 | FVector3d CachedMoveInputVelocity = FVector3d::ZeroVector; 69 | FRotator3d CachedTurnInput = FRotator3d::ZeroRotator; 70 | FRotator3d CachedLookInput = FRotator3d::ZeroRotator; 71 | bool JumpButtonDown = false; 72 | bool CrouchButtonDown = false; 73 | }; -------------------------------------------------------------------------------- /Source/Private/Core/FGMovementUtils.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Daft Software 2 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 3 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 4 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 5 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 6 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 7 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 8 | // SOFTWARE. 9 | 10 | #include "Core/FGMovementUtils.h" 11 | #include "FGMovementCVars.h" 12 | #include "FGMovementDefines.h" 13 | #include "MoveLibrary/MovementUtilsTypes.h" 14 | #include "MoverComponent.h" 15 | #include "Core/FGMoverComponent.h" 16 | #include "Kismet/KismetMathLibrary.h" 17 | #include "Logging/StructuredLog.h" 18 | #include "MoveLibrary/MovementUtils.h" 19 | 20 | #include UE_INLINE_GENERATED_CPP_BY_NAME(FGMovementUtils) 21 | 22 | void UFGMovementUtils::ApplyDamping(UFGMoverComponent* MoverComponent, FProposedMove& Move, float DeltaTime) 23 | { 24 | double Damper = 0.0; 25 | double IntentSpeed = 0.0; 26 | 27 | double Speed = Move.LinearVelocity.Size(); 28 | 29 | if(MoverComponent->IsOnGround()) 30 | { 31 | IntentSpeed = FG::CVars::GroundSpeed; 32 | Damper = FG::CVars::GroundDamping; 33 | } 34 | else if(MoverComponent->IsAirborne()) 35 | { 36 | IntentSpeed = FG::CVars::AirSpeed; 37 | Damper = FG::CVars::AirDamping; 38 | } 39 | 40 | double DragFactor = UKismetMathLibrary::NormalizeToRange(FMath::Max(FG::CVars::SlipFactor, Speed), 0.0, IntentSpeed); 41 | double Drag = Damper * DragFactor; // Drag is a function of speed and the damper. 42 | 43 | Move.LinearVelocity += -Move.LinearVelocity * Drag * DeltaTime; // Apply counter force. 44 | } 45 | 46 | void UFGMovementUtils::ApplyAcceleration(UFGMoverComponent* MoverComponent, FProposedMove& Move, float DeltaTime, FVector DirectionIntent, float DesiredSpeed) 47 | { 48 | double AccelerationConstant = 0.0; 49 | 50 | if(MoverComponent->IsOnGround()) 51 | { 52 | AccelerationConstant = FG::CVars::GroundAcceleration; 53 | } 54 | else if(MoverComponent->IsAirborne()) 55 | { 56 | AccelerationConstant = FG::CVars::AirAcceleration; 57 | } 58 | 59 | const double Acceleration = DesiredSpeed * AccelerationConstant * DeltaTime; 60 | const FVector DesiredVelocity = DirectionIntent * DesiredSpeed; 61 | 62 | const double ProjectedCurrentVelocity = Move.LinearVelocity | DirectionIntent; 63 | const double MissingSpeed = FMath::Max(DesiredSpeed - ProjectedCurrentVelocity, 0.0); 64 | const double ScaledAcceleration = Acceleration * (MissingSpeed / DesiredSpeed); 65 | 66 | Move.LinearVelocity += DirectionIntent * ScaledAcceleration; 67 | 68 | if(FG::CVars::DrawMovementDebug) 69 | { 70 | auto* MoverComp = Cast(MoverComponent); 71 | 72 | // Draw desired velocity. 73 | DrawDebugLine( 74 | MoverComp->GetWorld(), 75 | MoverComp->GetFeetLocation(), 76 | MoverComp->GetFeetLocation() + DesiredVelocity, 77 | FColor::Green, 78 | false, 79 | -1, 80 | 0, 81 | 1.0f); 82 | 83 | // Draw final velocity. 84 | DrawDebugLine( 85 | MoverComp->GetWorld(), 86 | MoverComp->GetFeetLocation(), 87 | MoverComp->GetFeetLocation() + Move.LinearVelocity, 88 | FColor::Emerald, 89 | false, 90 | -1, 91 | 0, 92 | 1.0f); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /Source/Private/FGCVars.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Daft Software 2 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 3 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 4 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 5 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 6 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 7 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 8 | // SOFTWARE. 9 | 10 | #include "FGMovementCVars.h" 11 | 12 | namespace FG::CVars 13 | { 14 | bool DrawMovementDebug = true; 15 | FAutoConsoleVariableRef CVarDrawMovementDebug( 16 | TEXT("FG.DrawMovementDebug"), 17 | DrawMovementDebug, 18 | TEXT("Draws debug helpers for FG Movement (0/1)."), 19 | ECVF_Default); 20 | 21 | float JumpForce = 300.0f; 22 | FAutoConsoleVariableRef CVarJumpForce( 23 | TEXT("FG.Move.JumpForce"), 24 | JumpForce, 25 | TEXT("Z Force to add on a player jump."), 26 | ECVF_Default 27 | ); 28 | 29 | float CrouchSpeedMult = 0.75f; 30 | FAutoConsoleVariableRef CVarCrouchSpeedMult( 31 | TEXT("FG.Move.CrouchSpeedMult"), 32 | CrouchSpeedMult, 33 | TEXT("Multiplier for walk speed when crouching."), 34 | ECVF_Default 35 | ); 36 | 37 | float SprintSpeedMult = 1.5f; 38 | FAutoConsoleVariableRef CVarSprintSpeedMult( 39 | TEXT("FG.Move.SprintSpeedMult"), 40 | SprintSpeedMult, 41 | TEXT("Multiplier for walk speed when sprinting."), 42 | ECVF_Default 43 | ); 44 | 45 | float GroundDamping = 6.0f; 46 | FAutoConsoleVariableRef CVarGroundDamping( 47 | TEXT("FG.Move.GroundDamping"), 48 | GroundDamping, 49 | TEXT("Ground damping amount, before additional damping is factored in."), 50 | ECVF_Default 51 | ); 52 | 53 | float AirDamping = 5.0f; 54 | FAutoConsoleVariableRef CVarAirDamping( 55 | TEXT("FG.Move.AirDamping"), 56 | AirDamping, 57 | TEXT("Air damping amount, before additional damping is factored in."), 58 | ECVF_Default 59 | ); 60 | 61 | float GroundAcceleration = 8.0f; 62 | FAutoConsoleVariableRef CVarGroundAcceleration( 63 | TEXT("FG.Move.GroundAcceleration"), 64 | GroundAcceleration, 65 | TEXT("Ground acceleration amount, providing ground control."), 66 | ECVF_Default 67 | ); 68 | 69 | float AirAcceleration = 2.0f; 70 | FAutoConsoleVariableRef CVarAirAcceleration( 71 | TEXT("FG.Move.AirAcceleration"), 72 | AirAcceleration, 73 | TEXT("Air acceleration amount, providing air control."), 74 | ECVF_Default 75 | ); 76 | 77 | float GroundSpeed = 1200.0f; 78 | FAutoConsoleVariableRef CVarGroundSpeed( 79 | TEXT("FG.Move.GroundSpeed"), 80 | GroundSpeed, 81 | TEXT("Constant walk speed to apply."), 82 | ECVF_Default 83 | ); 84 | 85 | float SlipFactor = 750.0f; 86 | FAutoConsoleVariableRef CVarSlipFactor( 87 | TEXT("FG.Move.SlipFactor"), 88 | SlipFactor, 89 | TEXT("Minimum speed threshold for slip (Drag force is scaled at this speed at minimum)."), 90 | ECVF_Default 91 | ); 92 | 93 | float AirSpeed = 1200.0f; 94 | FAutoConsoleVariableRef CVarAirSpeed( 95 | TEXT("FG.Move.AirSpeed"), 96 | AirSpeed, 97 | TEXT("Constant air speed to apply."), 98 | ECVF_Default 99 | ); 100 | 101 | float GravitySpeed = 800.0f; 102 | FAutoConsoleVariableRef CVarGravitySpeed( 103 | TEXT("FG.Move.GravitySpeed"), 104 | GravitySpeed, 105 | TEXT("Constant gravity speed to apply."), 106 | ECVF_Default 107 | ); 108 | } 109 | -------------------------------------------------------------------------------- /Source/Private/Core/FGMoverComponent.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Daft Software 2 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 3 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 4 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 5 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 6 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 7 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 8 | // SOFTWARE. 9 | 10 | #include "Core/FGMoverComponent.h" 11 | #include "Modes/FGWalkMode.h" 12 | #include "Modes/FGAirMode.h" 13 | #include "FGMovementDefines.h" 14 | #include "Components/CapsuleComponent.h" 15 | #include "Logging/StructuredLog.h" 16 | #include "MoveLibrary/FloorQueryUtils.h" 17 | #include "DrawDebugHelpers.h" 18 | #include "FGMovementCVars.h" 19 | 20 | #include UE_INLINE_GENERATED_CPP_BY_NAME(FGMoverComponent) 21 | 22 | UFGMoverComponent::UFGMoverComponent() 23 | { 24 | // Clear stock movement modes. 25 | MovementModes.Reset(); 26 | MovementModes.Add(FG::Modes::Walk, CreateDefaultSubobject(TEXT("FGWalkMode"))); 27 | MovementModes.Add(FG::Modes::Air, CreateDefaultSubobject(TEXT("FGAirMode"))); 28 | StartingMovementMode = FG::Modes::Air; 29 | } 30 | 31 | FVector UFGMoverComponent::GetFeetLocation() 32 | { 33 | if(auto* Capsule = Cast(UpdatedComponent)) 34 | { 35 | return Capsule->GetComponentLocation() + (-FVector::UpVector * Capsule->GetScaledCapsuleHalfHeight()); 36 | } 37 | return FVector::ZeroVector; 38 | } 39 | 40 | void UFGMoverComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) 41 | { 42 | Super::TickComponent(DeltaTime, TickType, ThisTickFunction); 43 | 44 | #if ENABLE_DRAW_DEBUG 45 | if(FG::CVars::DrawMovementDebug) 46 | { 47 | FFloorCheckResult LastFloorResult; 48 | GetSimBlackboard()->TryGet(CommonBlackboard::LastFloorResult, LastFloorResult); 49 | 50 | bool TouchingCollision = LastFloorResult.bWalkableFloor; 51 | 52 | FQuat PlaneRot = LastFloorResult.bWalkableFloor ? 53 | LastFloorResult.HitResult.ImpactNormal.ToOrientationQuat() : 54 | FVector::UpVector.ToOrientationQuat(); 55 | 56 | // Draw floor plane. 57 | DrawDebugBox( 58 | GetWorld(), 59 | GetFeetLocation(), 60 | FVector(0.0f, 50.0f, 50.0f), 61 | PlaneRot, 62 | TouchingCollision ? FColor::Green : FColor::Red, 63 | false, 64 | -1, 65 | 0, 66 | 1.0f); 67 | 68 | if(LastFloorResult.bWalkableFloor) 69 | { 70 | // Draw floor normal. 71 | DrawDebugLine( 72 | GetWorld(), 73 | GetFeetLocation(), 74 | GetFeetLocation() + LastFloorResult.HitResult.ImpactNormal * 50.f, 75 | FColor::Blue, 76 | false, 77 | -1, 78 | 0, 79 | 1.0f); 80 | } 81 | 82 | // Draw per frame velocity. 83 | DrawDebugLine( 84 | GetWorld(), 85 | GetFeetLocation(), 86 | GetFeetLocation() + (GetVelocity() * DeltaTime), 87 | FColor::White, 88 | false, 89 | -1, 90 | 0, 91 | 1.0f); 92 | } 93 | #endif 94 | 95 | UE_LOGFMT(LogMover, Display, "Current Movement Mode - {Name}", GetMovementModeName()); 96 | UE_LOGFMT(LogMover, Display, "IsOnGround - {Grounded}", IsOnGround()); 97 | } 98 | 99 | bool UFGMoverComponent::IsAirborne() const 100 | { 101 | if (bHasValidCachedState) 102 | { 103 | return CachedLastSyncState.MovementMode == FG::Modes::Air; 104 | } 105 | return false; 106 | } 107 | 108 | bool UFGMoverComponent::IsOnGround() const 109 | { 110 | if (bHasValidCachedState) 111 | { 112 | return CachedLastSyncState.MovementMode == FG::Modes::Walk; 113 | } 114 | 115 | return false; 116 | } 117 | -------------------------------------------------------------------------------- /Source/Public/Core/FGMovementUtils.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Daft Software 2 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 3 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 4 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 5 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 6 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 7 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 8 | // SOFTWARE. 9 | 10 | #pragma once 11 | 12 | #include "MoverSimulationTypes.h" 13 | #include "Kismet/BlueprintFunctionLibrary.h" 14 | #include "MoveLibrary/MovementRecord.h" 15 | #include "FGMovementUtils.generated.h" 16 | 17 | class UFGMoverComponent; 18 | struct FProposedMove; 19 | 20 | // @TODO: Remove or put into MovementUtils class. 21 | namespace FG 22 | { 23 | FORCEINLINE bool AttemptTeleport(USceneComponent* UpdatedComponent, const FVector& TeleportPos, const FRotator& TeleportRot, const FMoverDefaultSyncState& StartingSyncState, FMoverTickEndData& Output) 24 | { 25 | if (UpdatedComponent->GetOwner()->TeleportTo(TeleportPos, TeleportRot)) 26 | { 27 | FMoverDefaultSyncState& OutputSyncState = Output.SyncState.SyncStateCollection.FindOrAddMutableDataByType(); 28 | 29 | OutputSyncState.SetTransforms_WorldSpace(UpdatedComponent->GetComponentLocation(), 30 | UpdatedComponent->GetComponentRotation(), 31 | StartingSyncState.GetVelocity_WorldSpace(), 32 | nullptr); // no movement base 33 | 34 | UpdatedComponent->ComponentVelocity = StartingSyncState.GetVelocity_WorldSpace(); 35 | return true; 36 | } 37 | 38 | return false; 39 | } 40 | 41 | // TODO: replace this function with simply looking at/collapsing the MovementRecord 42 | FORCEINLINE void CaptureFinalState(USceneComponent* UpdatedComponent, FMovementRecord& Record, const FMoverDefaultSyncState& StartSyncState, FMoverDefaultSyncState& OutputSyncState, const float DeltaSeconds) 43 | { 44 | const FVector FinalLocation = UpdatedComponent->GetComponentLocation(); 45 | const FVector FinalVelocity = Record.GetRelevantVelocity(); 46 | 47 | // TODO: Update Main/large movement record with substeps from our local record 48 | 49 | OutputSyncState.SetTransforms_WorldSpace(FinalLocation, 50 | UpdatedComponent->GetComponentRotation(), 51 | FinalVelocity, 52 | nullptr); // no movement base 53 | 54 | UpdatedComponent->ComponentVelocity = FinalVelocity; 55 | } 56 | } 57 | 58 | /** 59 | * Mover utility functions. 60 | */ 61 | UCLASS() 62 | class FGMOVEMENT_API UFGMovementUtils : public UBlueprintFunctionLibrary 63 | { 64 | GENERATED_BODY() 65 | public: 66 | 67 | /** 68 | * Apply damper force to a proposed move, applying any ground friction or drag. 69 | * 70 | * @param MoverComponent - The mover component. 71 | * @param Move - The proposed move to apply damping to. 72 | * @param DeltaTime - Time passed since last tick. 73 | */ 74 | UFUNCTION(BlueprintCallable, Category = Mover) 75 | static void ApplyDamping(UFGMoverComponent* MoverComponent, FProposedMove& Move, float DeltaTime); 76 | 77 | /** 78 | * Project current velocity onto a direction intent, accelerating towards the 79 | * new direction and outputting the new velocity to a proposed move. 80 | * 81 | * @param MoverComponent - The mover component. 82 | * @param Move - The proposed move to apply acceleration to. 83 | * @param DeltaTime - Time passed since last tick. 84 | * @param DirectionIntent - The direction to accelerate in. 85 | * @param DesiredSpeed - The speed to accelerate to. 86 | */ 87 | UFUNCTION(BlueprintCallable, Category = Mover) 88 | static void ApplyAcceleration(UFGMoverComponent* MoverComponent, FProposedMove& Move, float DeltaTime, FVector DirectionIntent, float DesiredSpeed); 89 | }; -------------------------------------------------------------------------------- /Source/Private/Core/FGPawn.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Daft Software 2 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 3 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 4 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 5 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 6 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 7 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 8 | // SOFTWARE. 9 | 10 | #include "Core/FGPawn.h" 11 | #include "Core/FGMoverComponent.h" 12 | #include "Core/FGDataModel.h" 13 | #include "FGMovementDefines.h" 14 | #include "InputMappingContext.h" 15 | #include "Components/CapsuleComponent.h" 16 | #include "Camera/CameraComponent.h" 17 | #include "Logging/StructuredLog.h" 18 | 19 | #include UE_INLINE_GENERATED_CPP_BY_NAME(FGPawn) 20 | 21 | AFGPawn::AFGPawn() 22 | { 23 | SetReplicatingMovement(false); 24 | 25 | CapsuleComponent = CreateDefaultSubobject(TEXT("CapsuleComponent")); 26 | CapsuleComponent->SetHiddenInGame(false); 27 | CapsuleComponent->SetCollisionProfileName(UCollisionProfile::Pawn_ProfileName); 28 | CapsuleComponent->InitCapsuleSize(34.0f, 88.0f); 29 | CapsuleComponent->bDynamicObstacle = true; 30 | RootComponent = CapsuleComponent; 31 | 32 | CameraComponent = CreateDefaultSubobject(TEXT("CameraComponent")); 33 | CameraComponent->SetupAttachment(CapsuleComponent); 34 | CameraComponent->bUsePawnControlRotation = true; 35 | 36 | MoverComponent = CreateDefaultSubobject(TEXT("MoverComponent")); 37 | } 38 | 39 | void AFGPawn::Tick(float DeltaSeconds) 40 | { 41 | Super::Tick(DeltaSeconds); 42 | 43 | static constexpr double LookRateYaw = 150.0; // Degs/Sec 44 | static constexpr double LookRatePitch = 150.0; // Degs/Sec 45 | 46 | AddControllerYawInput(CachedLookInput.Yaw * LookRateYaw * DeltaSeconds); 47 | AddControllerPitchInput(CachedLookInput.Pitch * LookRatePitch * DeltaSeconds); 48 | 49 | CachedLookInput = FRotator3d::ZeroRotator; 50 | } 51 | 52 | void AFGPawn::Move(const FInputActionValue& Value) 53 | { 54 | const FVector MoveVector = Value.Get(); 55 | 56 | // @TODO: Base impl is clamping here, why? 57 | CachedMoveInputIntent = FVector3d(MoveVector); 58 | } 59 | 60 | void AFGPawn::MoveCompleted(const FInputActionValue& Value) 61 | { 62 | CachedMoveInputIntent = FVector::ZeroVector; 63 | } 64 | 65 | void AFGPawn::Look(const FInputActionValue& Value) 66 | { 67 | const FVector2D LookVector = Value.Get(); 68 | 69 | CachedLookInput.Yaw = CachedTurnInput.Yaw = LookVector.X; 70 | CachedLookInput.Pitch = CachedTurnInput.Pitch = LookVector.Y; 71 | } 72 | 73 | void AFGPawn::Jump() 74 | { 75 | JumpButtonDown = true; 76 | } 77 | 78 | void AFGPawn::JumpCompleted() 79 | { 80 | JumpButtonDown = false; 81 | } 82 | 83 | void AFGPawn::Crouch() 84 | { 85 | CrouchButtonDown = true; 86 | } 87 | 88 | void AFGPawn::CrouchCompleted() 89 | { 90 | CrouchButtonDown = false; 91 | } 92 | 93 | // Produce input is used to build an input cmd for the frame. 94 | void AFGPawn::ProduceInput_Implementation(int32 SimTimeMs, FMoverInputCmdContext& OutInputCmd) 95 | { 96 | FFGMoverInputCmd& CharacterInputs = OutInputCmd.InputCollection.FindOrAddMutableDataByType(); 97 | 98 | if (!GetController()) 99 | { 100 | if (GetLocalRole() == ENetRole::ROLE_Authority && GetRemoteRole() == ENetRole::ROLE_SimulatedProxy) 101 | { 102 | static const FFGMoverInputCmd DoNothingInput; 103 | CharacterInputs = DoNothingInput; 104 | } 105 | return; 106 | } 107 | 108 | FRotator IntentRotation = GetControlRotation(); 109 | IntentRotation.Pitch = 0.0f; 110 | IntentRotation.Roll = 0.0f; 111 | 112 | UE_LOGFMT(LogMover, Display, "Local - JumpButtonDown {Jumping}", JumpButtonDown); 113 | 114 | UE_LOGFMT(LogMover, Display, "Input - {Vector}", CachedMoveInputIntent.ToString()); 115 | 116 | CharacterInputs.ControlRotation = GetControlRotation(); 117 | CharacterInputs.bUsingMovementBase = false; 118 | CharacterInputs.OrientationIntent = IntentRotation.Vector(); 119 | CharacterInputs.bIsJumpPressed = JumpButtonDown; 120 | CharacterInputs.bIsCrouchPressed = CrouchButtonDown; 121 | CharacterInputs.SetMoveInput(EMoveInputType::DirectionalIntent, CachedMoveInputIntent); 122 | } 123 | -------------------------------------------------------------------------------- /Source/Private/Modes/FGAirMode.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Daft Software 2 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 3 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 4 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 5 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 6 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 7 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 8 | // SOFTWARE. 9 | 10 | #include "Modes/FGAirMode.h" 11 | 12 | #include "FGMovementCVars.h" 13 | #include "Core/FGDataModel.h" 14 | #include "Core/FGMovementUtils.h" 15 | #include "Core/FGMoverComponent.h" 16 | #include "FGMovementDefines.h" 17 | 18 | #include "Components/CapsuleComponent.h" 19 | #include "MoveLibrary/MovementUtils.h" 20 | #include "MoveLibrary/FloorQueryUtils.h" 21 | #include "Logging/StructuredLog.h" 22 | 23 | #include UE_INLINE_GENERATED_CPP_BY_NAME(FGAirMode) 24 | 25 | /** 26 | * Generate a single substep of movement for the mode - remember this is sub-stepping against 27 | * network prediction plugins tick and NOT the game thread tick. This function would typically 28 | * process a single input command, generating a proposed move to be processed in the next tick. 29 | * Note that a "Move" does not encapsulate movement for a simulation tick, but rather a "sub-tick". 30 | * 31 | * @param StartState - Inputs, sync state, and other data from the start of the tick. 32 | * @param TimeStep - The time step for this substep. 33 | * @param OutProposedMove - The proposed move to provide to the next tick that runs. 34 | */ 35 | void UFGAirMode::OnGenerateMove(const FMoverTickStartData& StartState, const FMoverTimeStep& TimeStep, FProposedMove& OutProposedMove) const 36 | { 37 | const FFGMoverInputCmd* CharacterInputs = StartState.InputCmd.InputCollection.FindDataByType(); 38 | const FMoverDefaultSyncState* StartingSyncState = StartState.SyncState.SyncStateCollection.FindDataByType(); 39 | check(StartingSyncState); 40 | 41 | OutProposedMove.LinearVelocity = StartingSyncState->GetVelocity_WorldSpace(); 42 | const float DeltaTime = TimeStep.StepMs * 0.001f; 43 | 44 | constexpr float TurningRateLimit = 5000.0f; 45 | 46 | // @TODO: This is going to break if the player isn't Z up. Works for now. 47 | OutProposedMove.AngularVelocity = UMovementUtils::ComputeAngularVelocity( 48 | StartingSyncState->GetOrientation_WorldSpace(), 49 | CharacterInputs->GetOrientationIntentDir_WorldSpace().ToOrientationRotator(), 50 | DeltaTime, 51 | TurningRateLimit); 52 | 53 | UE_LOGFMT(LogMover, Display, "Angular Velocity: {AngVel}", *OutProposedMove.AngularVelocity.ToString()); 54 | 55 | OutProposedMove.DirectionIntent = CharacterInputs->GetOrientationIntentDir_WorldSpace(); 56 | 57 | FVector MoveInputWS = OutProposedMove.DirectionIntent.ToOrientationRotator().RotateVector(CharacterInputs->GetMoveInput()); 58 | 59 | UFGMovementUtils::ApplyAcceleration(CastChecked(GetOuter()), OutProposedMove, DeltaTime, MoveInputWS, FG::CVars::AirSpeed); 60 | 61 | OutProposedMove.LinearVelocity -= FVector::UpVector * FG::CVars::GravitySpeed * DeltaTime; 62 | 63 | UE_LOGFMT(LogMover, Display, "Linear Velocity: {LinVel}", *OutProposedMove.LinearVelocity.ToString()); 64 | } 65 | 66 | /** 67 | * Actual network prediction plugin tick - Evolution of the sync state. 68 | * A DeltaTime is calculated from the last NPP tick, and then we figure out how many "sub-ticks" 69 | * happened in that time, aggregate them together using the MovementRecord, and then apply the 70 | * moves to the Pawn. This is where we actually see the moves from OnGenerateMove happen. 71 | * 72 | * @param Params - The parameters for the tick. 73 | * @param OutputState - The final state of the mover after the tick. 74 | */ 75 | void UFGAirMode::OnSimulationTick(const FSimulationTickParams& Params, FMoverTickEndData& OutputState) 76 | { 77 | const FMoverTickStartData& StartState = Params.StartState; 78 | USceneComponent* UpdatedComponent = Params.UpdatedComponent; 79 | UPrimitiveComponent* UpdatedPrimitive = Params.UpdatedPrimitive; 80 | FProposedMove ProposedMove = Params.ProposedMove; 81 | 82 | const FMoverDefaultSyncState* StartingSyncState = StartState.SyncState.SyncStateCollection.FindDataByType(); 83 | check(StartingSyncState); 84 | 85 | FMoverDefaultSyncState& OutputSyncState = OutputState.SyncState.SyncStateCollection.FindOrAddMutableDataByType(); 86 | 87 | const float DeltaSeconds = Params.TimeStep.StepMs * 0.001f; 88 | 89 | // Instantaneous movement changes that are executed and we exit before consuming any time 90 | if (ProposedMove.bHasTargetLocation && FG::AttemptTeleport(UpdatedComponent, ProposedMove.TargetLocation, UpdatedComponent->GetComponentRotation(), *StartingSyncState, OutputState)) 91 | { 92 | OutputState.MovementEndState.RemainingMs = Params.TimeStep.StepMs; // Give back all the time 93 | return; 94 | } 95 | 96 | FMovementRecord MoveRecord; 97 | MoveRecord.SetDeltaSeconds(DeltaSeconds); 98 | 99 | UMoverBlackboard* SimBlackboard = GetBlackboard_Mutable(); 100 | SimBlackboard->Invalidate(CommonBlackboard::LastFloorResult); // Flush last floor result. 101 | 102 | constexpr float FloorSweepDist = 1.f; 103 | constexpr float MaxWalkSlopeCosine = 0.71f; 104 | 105 | FFloorCheckResult NewFloor; 106 | 107 | // If we don't have cached floor information, we need to search for it again 108 | UFloorQueryUtils::FindFloor(UpdatedComponent, UpdatedPrimitive, FloorSweepDist, 109 | MaxWalkSlopeCosine, UpdatedPrimitive->GetComponentLocation(), NewFloor); 110 | 111 | SimBlackboard->Set(CommonBlackboard::LastFloorResult, NewFloor); 112 | 113 | OutputSyncState.MoveDirectionIntent = (ProposedMove.bHasDirIntent ? ProposedMove.DirectionIntent : FVector::ZeroVector); 114 | 115 | // Use the orientation intent directly. If no intent is provided, use last frame's orientation. Note that we are assuming rotation changes can't fail. 116 | const FRotator StartingOrient = StartingSyncState->GetOrientation_WorldSpace(); 117 | FRotator TargetOrient = StartingOrient; 118 | 119 | bool bIsOrientationChanging = false; 120 | 121 | // Apply orientation changes (if any) 122 | if (!ProposedMove.AngularVelocity.IsZero()) 123 | { 124 | TargetOrient += (ProposedMove.AngularVelocity * DeltaSeconds); 125 | bIsOrientationChanging = (TargetOrient != StartingOrient); 126 | } 127 | 128 | FVector MoveDelta = ProposedMove.LinearVelocity * DeltaSeconds; 129 | const FQuat OrientQuat = TargetOrient.Quaternion(); 130 | 131 | FHitResult Hit(1.f); 132 | 133 | if (!MoveDelta.IsNearlyZero() || bIsOrientationChanging) 134 | { 135 | UMovementUtils::TrySafeMoveUpdatedComponent(UpdatedComponent, UpdatedPrimitive, MoveDelta, OrientQuat, true, Hit, ETeleportType::None, MoveRecord); 136 | } 137 | 138 | if(Hit.bBlockingHit) 139 | { 140 | UMovementUtils::TryMoveToSlideAlongSurface( 141 | UpdatedComponent, 142 | UpdatedPrimitive, 143 | GetMoverComponent(), 144 | MoveDelta, 145 | 1.f - Hit.Time, 146 | OrientQuat, 147 | Hit.Normal, 148 | Hit, 149 | false, 150 | MoveRecord); 151 | } 152 | 153 | if(NewFloor.bWalkableFloor) 154 | { 155 | FMoverOnImpactParams ImpactParams(FG::Modes::Air, Hit, MoveDelta); 156 | GetMoverComponent()->HandleImpact(ImpactParams); 157 | OutputState.MovementEndState.NextModeName = FG::Modes::Walk; 158 | } 159 | 160 | FG::CaptureFinalState(UpdatedComponent, MoveRecord, *StartingSyncState, OutputSyncState, DeltaSeconds); 161 | } 162 | -------------------------------------------------------------------------------- /Source/Private/Modes/FGWalkMode.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Daft Software 2 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 3 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 4 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 5 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 6 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 7 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 8 | // SOFTWARE. 9 | 10 | #include "Modes/FGWalkMode.h" 11 | #include "Core/FGMovementUtils.h" 12 | #include "Core/FGDataModel.h" 13 | #include "LayeredMoves/FGLayeredMove_Crouch.h" 14 | #include "Transitions/FGCrouchCheck.h" 15 | #include "FGMovementCVars.h" 16 | #include "Core/FGMoverComponent.h" 17 | #include "FGMovementDefines.h" 18 | 19 | #include "DefaultMovementSet/LayeredMoves/BasicLayeredMoves.h" 20 | #include "MoveLibrary/MovementUtils.h" 21 | #include "MoveLibrary/FloorQueryUtils.h" 22 | #include "MoverComponent.h" 23 | #include "Components/CapsuleComponent.h" 24 | #include "Logging/StructuredLog.h" 25 | 26 | #include UE_INLINE_GENERATED_CPP_BY_NAME(FGWalkMode) 27 | 28 | UFGWalkMode::UFGWalkMode() 29 | { 30 | Transitions.Add(CreateDefaultSubobject(TEXT("DuckCheck"))); 31 | } 32 | 33 | /** 34 | * Generate a single substep of movement for the mode - remember this is sub-stepping against 35 | * network prediction plugins tick and NOT the game thread tick. This function would typically 36 | * process a single input command, generating a proposed move to be processed in the next tick. 37 | * Note that a "Move" does not encapsulate movement for a simulation tick, but rather a "sub-tick". 38 | * 39 | * @param StartState - Inputs, sync state, and other data from the start of the tick. 40 | * @param TimeStep - The time step for this substep. 41 | * @param OutProposedMove - The proposed move to provide to the next tick that runs. 42 | */ 43 | void UFGWalkMode::OnGenerateMove(const FMoverTickStartData& StartState, const FMoverTimeStep& TimeStep, FProposedMove& OutProposedMove) const 44 | { 45 | const FFGMoverInputCmd* CharacterInputs = StartState.InputCmd.InputCollection.FindDataByType(); 46 | const FMoverDefaultSyncState* StartingSyncState = StartState.SyncState.SyncStateCollection.FindDataByType(); 47 | check(StartingSyncState); 48 | 49 | OutProposedMove.LinearVelocity = StartingSyncState->GetVelocity_WorldSpace(); 50 | const float DeltaTime = TimeStep.StepMs * 0.001f; 51 | 52 | UFGMovementUtils::ApplyDamping(CastChecked(GetMoverComponent()), OutProposedMove, DeltaTime); 53 | 54 | constexpr float TurningRateLimit = 5000.0f; 55 | 56 | // @TODO: This is going to break if the player isn't Z up. Works for now. 57 | OutProposedMove.AngularVelocity = UMovementUtils::ComputeAngularVelocity( 58 | StartingSyncState->GetOrientation_WorldSpace(), 59 | CharacterInputs->GetOrientationIntentDir_WorldSpace().ToOrientationRotator(), 60 | DeltaTime, 61 | TurningRateLimit); 62 | 63 | UE_LOGFMT(LogMover, Display, "Angular Velocity: {AngVel}", *OutProposedMove.AngularVelocity.ToString()); 64 | 65 | OutProposedMove.DirectionIntent = CharacterInputs->GetOrientationIntentDir_WorldSpace(); 66 | 67 | FFloorCheckResult FloorResult; 68 | GetMoverComponent()->GetSimBlackboard()->TryGet(CommonBlackboard::LastFloorResult, FloorResult); 69 | 70 | FVector MoveInputWS = OutProposedMove.DirectionIntent.ToOrientationRotator().RotateVector(CharacterInputs->GetMoveInput()); 71 | 72 | FVector ProjectedMove = FVector::VectorPlaneProject(MoveInputWS, FloorResult.HitResult.ImpactNormal); 73 | ProjectedMove.Normalize(); 74 | 75 | UFGMovementUtils::ApplyAcceleration(CastChecked(GetMoverComponent()), OutProposedMove, DeltaTime, ProjectedMove, FG::CVars::GroundSpeed); 76 | 77 | UE_LOGFMT(LogMover, Display, "Linear Velocity: {LinVel}", *OutProposedMove.LinearVelocity.ToString()); 78 | } 79 | 80 | /** 81 | * Actual network prediction plugin tick - Evolution of the sync state. 82 | * A DeltaTime is calculated from the last NPP tick, and then we figure out how many "sub-ticks" 83 | * happened in that time, aggregate them together using the MovementRecord, and then apply the 84 | * moves to the Pawn. This is where we actually see the moves from OnGenerateMove happen. 85 | * 86 | * @param Params - Input parameters for the movement tick. 87 | * @param OutputState - The final state of the mover after the tick. 88 | */ 89 | void UFGWalkMode::OnSimulationTick(const FSimulationTickParams& Params, FMoverTickEndData& OutputState) 90 | { 91 | const FMoverTickStartData& StartState = Params.StartState; 92 | USceneComponent* UpdatedComponent = Params.UpdatedComponent; 93 | UPrimitiveComponent* UpdatedPrimitive = Params.UpdatedPrimitive; 94 | FProposedMove ProposedMove = Params.ProposedMove; 95 | 96 | const FFGMoverInputCmd* CharacterInputs = StartState.InputCmd.InputCollection.FindDataByType(); 97 | const FMoverDefaultSyncState* StartingSyncState = StartState.SyncState.SyncStateCollection.FindDataByType(); 98 | check(StartingSyncState); 99 | 100 | FMoverDefaultSyncState& OutputSyncState = OutputState.SyncState.SyncStateCollection.FindOrAddMutableDataByType(); 101 | 102 | const float DeltaSeconds = Params.TimeStep.StepMs * 0.001f; 103 | 104 | // Instantaneous movement changes that are executed and we exit before consuming any time 105 | if (ProposedMove.bHasTargetLocation && FG::AttemptTeleport(UpdatedComponent, ProposedMove.TargetLocation, UpdatedComponent->GetComponentRotation(), *StartingSyncState, OutputState)) 106 | { 107 | OutputState.MovementEndState.RemainingMs = Params.TimeStep.StepMs; // Give back all the time 108 | return; 109 | } 110 | 111 | if(TryJump(CharacterInputs, OutputState)) 112 | { 113 | OutputState.MovementEndState.NextModeName = FG::Modes::Air; 114 | } 115 | 116 | FMovementRecord MoveRecord; 117 | MoveRecord.SetDeltaSeconds(DeltaSeconds); 118 | 119 | UMoverBlackboard* SimBlackboard = GetBlackboard_Mutable(); 120 | SimBlackboard->Invalidate(CommonBlackboard::LastFloorResult); // Flush last floor result. 121 | 122 | constexpr float FloorSweepDist = 1.0f; 123 | constexpr float MaxWalkSlopeCosine = 0.71f; 124 | 125 | FFloorCheckResult NewFloor; 126 | 127 | // If we don't have cached floor information, we need to search for it again 128 | UFloorQueryUtils::FindFloor(UpdatedComponent, UpdatedPrimitive, FloorSweepDist, 129 | MaxWalkSlopeCosine, UpdatedPrimitive->GetComponentLocation(), NewFloor); 130 | 131 | SimBlackboard->Set(CommonBlackboard::LastFloorResult, NewFloor); 132 | 133 | OutputSyncState.MoveDirectionIntent = (ProposedMove.bHasDirIntent ? ProposedMove.DirectionIntent : FVector::ZeroVector); 134 | 135 | // Use the orientation intent directly. If no intent is provided, use last frame's orientation. Note that we are assuming rotation changes can't fail. 136 | const FRotator StartingOrient = StartingSyncState->GetOrientation_WorldSpace(); 137 | FRotator TargetOrient = StartingOrient; 138 | 139 | bool bIsOrientationChanging = false; 140 | 141 | // Apply orientation changes (if any) 142 | if (!ProposedMove.AngularVelocity.IsZero()) 143 | { 144 | TargetOrient += (ProposedMove.AngularVelocity * DeltaSeconds); 145 | bIsOrientationChanging = (TargetOrient != StartingOrient); 146 | } 147 | 148 | FVector MoveDelta = ProposedMove.LinearVelocity * DeltaSeconds; 149 | const FQuat OrientQuat = TargetOrient.Quaternion(); 150 | FHitResult Hit(1.f); 151 | 152 | if (!MoveDelta.IsNearlyZero() || bIsOrientationChanging) 153 | { 154 | UMovementUtils::TrySafeMoveUpdatedComponent(UpdatedComponent, UpdatedPrimitive, MoveDelta, OrientQuat, true, Hit, ETeleportType::None, MoveRecord); 155 | } 156 | 157 | if (NewFloor.bWalkableFloor && UpdatedPrimitive) 158 | { 159 | // @TODO: Stinky forward port from NPP implementation - Fix me. 160 | // In the NPP implementation we did a really dirty hack and basically just bodged the 161 | // slide vector to the expected travel distance. This means when you run into a wall 162 | // your movement speed isn't clamped depending on the angle of the wall. 163 | // However we can do it way better now, although it may mean not using this util. 164 | UMovementUtils::TryMoveToSlideAlongSurface( 165 | UpdatedComponent, 166 | UpdatedPrimitive, 167 | GetMoverComponent(), 168 | MoveDelta, 169 | 1.f - Hit.Time, 170 | OrientQuat, 171 | Hit.Normal, 172 | Hit, 173 | false, 174 | MoveRecord); 175 | } 176 | else 177 | { 178 | OutputState.MovementEndState.NextModeName = FG::Modes::Air; 179 | } 180 | 181 | FG::CaptureFinalState(UpdatedComponent, MoveRecord, *StartingSyncState, OutputSyncState, DeltaSeconds); 182 | } 183 | 184 | bool UFGWalkMode::TryJump(const FFGMoverInputCmd* InputCmd, FMoverTickEndData& OutputState) 185 | { 186 | if(!InputCmd->bIsJumpPressed) 187 | { 188 | return false; 189 | } 190 | 191 | TSharedPtr JumpMove = MakeShared(); 192 | JumpMove->UpwardsSpeed = FG::CVars::JumpForce; 193 | OutputState.SyncState.LayeredMoves.QueueLayeredMove(JumpMove); 194 | OutputState.MovementEndState.NextModeName = FG::Modes::Air; 195 | 196 | return true; 197 | } 198 | --------------------------------------------------------------------------------