From 68050f0f7d1bdffb1c1385dc65d18252f210ae29 Mon Sep 17 00:00:00 2001 From: Ezekiel Warren Date: Thu, 12 Dec 2024 15:32:49 -0800 Subject: [PATCH 01/15] feat: mass entity lerp and more processors --- .../BP_EcsactEntityMassSpawner.uasset | Bin 25359 -> 25131 bytes unreal-cpp-net-fps/Plugins/EcsactNet | 2 +- .../EcsactEntityMassSpawner.cpp | 134 +++++------------- .../Fragments/EcsactFragments.h | 9 ++ .../Fragments/LerpPositionFragment.h | 14 ++ .../Fragments/LerpPositionParameters.h | 19 +++ .../EcsactPositionReadProcessor.cpp | 44 ------ .../Processors/EcsactPositionReadProcessor.h | 25 ---- .../Processors/LerpPositionProcessor.cpp | 58 ++++++++ .../Processors/LerpPositionProcessor.h | 22 +++ .../Processors/StreamPositionProcessor.cpp | 60 ++++++++ .../Processors/StreamPositionProcessor.h | 22 +++ .../Processors/SyncPositionProcessor.cpp | 51 +++++++ .../Processors/SyncPositionProcessor.h | 22 +++ .../Processors/TickProcessor.cpp | 117 --------------- .../Processors/TickProcessor.h | 23 --- .../EcsactUnrealFps/Tasks/FollowPlayer.cpp | 4 + .../Traits/LerpPositionTrait.cpp | 16 +++ .../Traits/LerpPositionTrait.h | 20 +++ 19 files changed, 355 insertions(+), 307 deletions(-) create mode 100644 unreal-cpp-net-fps/Source/EcsactUnrealFps/Fragments/LerpPositionFragment.h create mode 100644 unreal-cpp-net-fps/Source/EcsactUnrealFps/Fragments/LerpPositionParameters.h delete mode 100644 unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/EcsactPositionReadProcessor.cpp delete mode 100644 unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/EcsactPositionReadProcessor.h create mode 100644 unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/LerpPositionProcessor.cpp create mode 100644 unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/LerpPositionProcessor.h create mode 100644 unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/StreamPositionProcessor.cpp create mode 100644 unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/StreamPositionProcessor.h create mode 100644 unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/SyncPositionProcessor.cpp create mode 100644 unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/SyncPositionProcessor.h delete mode 100644 unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/TickProcessor.cpp delete mode 100644 unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/TickProcessor.h create mode 100644 unreal-cpp-net-fps/Source/EcsactUnrealFps/Traits/LerpPositionTrait.cpp create mode 100644 unreal-cpp-net-fps/Source/EcsactUnrealFps/Traits/LerpPositionTrait.h diff --git a/unreal-cpp-net-fps/Content/EntityMass/BP_EcsactEntityMassSpawner.uasset b/unreal-cpp-net-fps/Content/EntityMass/BP_EcsactEntityMassSpawner.uasset index 5739aa7c395d66d587b0d2f8438b107666165099..fcba1656d33f14b3e28dd0f9222d1f1dd4299f09 100644 GIT binary patch delta 478 zcmeA_#<=N)vgg!*K8A$O(<2(ur^ zH1z_D{s%HkAk2JWd#=qa5X;=X!Ls*(LPcy4p9&ydAd@FohMoh_ zo7=0seP;3ZB=7uA$pAk|l<>ez z2i_geDI6_g}*(8z_&* zul|ICL}xW?5!p<8ym#Kf)Pi^L{08yPknGZswdfTeu>L07?y zZLQ_)H48CJb_qefQLVWZgU;iLhle50#soPWi3y?{jS2!MtBR-wC0>=dfU2-8UYYB~ yH)pFbz^kkn;S?bl4yl1aD69meP*4g=YFHL|Iq0m+)qt}-e;hc=Hw1upHR}%^%;7Tt diff --git a/unreal-cpp-net-fps/Plugins/EcsactNet b/unreal-cpp-net-fps/Plugins/EcsactNet index 5f68619..4e3d06e 160000 --- a/unreal-cpp-net-fps/Plugins/EcsactNet +++ b/unreal-cpp-net-fps/Plugins/EcsactNet @@ -1 +1 @@ -Subproject commit 5f6861971e45f19208526aab83d69494a36fe08b +Subproject commit 4e3d06eceac68f63d1f808da628636d6a5aa360c diff --git a/unreal-cpp-net-fps/Source/EcsactUnrealFps/EcsactEntityMassSpawner.cpp b/unreal-cpp-net-fps/Source/EcsactUnrealFps/EcsactEntityMassSpawner.cpp index 0323fcb..ccc8950 100644 --- a/unreal-cpp-net-fps/Source/EcsactUnrealFps/EcsactEntityMassSpawner.cpp +++ b/unreal-cpp-net-fps/Source/EcsactUnrealFps/EcsactEntityMassSpawner.cpp @@ -48,7 +48,7 @@ auto UEcsactEntityMassSpawner::CreateMassEntities(int count) -> void { }) .AddComponent(example::fps::MassEntity{}) .AddComponent(example::fps::Velocity{}) - .AddComponent(example::fps::Toggle{.streaming = true}) + .AddComponent(example::fps::Toggle{.streaming = StreamEntities}) .OnCreate(TDelegate::CreateLambda( // [](auto entity) { UE_LOG( @@ -122,14 +122,6 @@ auto UEcsactEntityMassSpawner::UpdatePosition_Implementation( for(auto EntityHandle : EntityHandles) { check(EntityHandle.IsValid()); check(EntityHandle.IsSet()); - UE_LOG( - LogTemp, - Warning, - TEXT("Ecsact Position updated to %f, %f, %f"), - Position.X, - Position.Y, - Position.Z - ); if(EntityManager.IsProcessing()) { EntityManager.Defer().PushCommand( [EntityHandle, vec](auto& EntityManager) { @@ -194,76 +186,37 @@ auto UEcsactEntityMassSpawner::Spawn( Position.Y ); - if(EntityManager.IsProcessing()) { - EntityManager.AddFragmentToEntity( - EntityHandle, - FEcsactEntityFragment::StaticStruct(), - [Entity](void* fragment, const UScriptStruct& FragmentType) { - FEcsactEntityFragment* EntityFragment = - static_cast(fragment); - EntityFragment->SetId(static_cast(Entity)); - } - ); - - EntityManager.AddFragmentToEntity( - EntityHandle, - FEcsactPositionFragment::StaticStruct(), - [&vec](void* fragment, const UScriptStruct& FragmentType) { - auto* PositionFragment = - static_cast(fragment); - PositionFragment->SetPosition(vec); - } - ); - - EntityManager.AddFragmentToEntity( - EntityHandle, - FEcsactStreamFragment::StaticStruct(), - [this](void* fragment, const UScriptStruct& FragmentType) { - auto* StreamFragment = static_cast(fragment); - StreamFragment->SetStream(StreamEntities); - } - ); - } else { - EntityManager.Defer().PushCommand( - [EntityHandle, Entity, ShouldStream = StreamEntities, vec]( - auto& EntityManager - ) { - EntityManager.AddFragmentToEntity( - EntityHandle, - FEcsactEntityFragment::StaticStruct(), - [Entity](void* fragment, const UScriptStruct& FragmentType) { - FEcsactEntityFragment* EntityFragment = - static_cast(fragment); - EntityFragment->SetId(static_cast(Entity)); - } - ); + EntityManager.AddFragmentToEntity( + EntityHandle, + FEcsactEntityFragment::StaticStruct(), + [Entity](void* fragment, const UScriptStruct& FragmentType) { + FEcsactEntityFragment* EntityFragment = + static_cast(fragment); + EntityFragment->SetId(static_cast(Entity)); + } + ); - EntityManager.AddFragmentToEntity( - EntityHandle, - FEcsactPositionFragment::StaticStruct(), - [&vec](void* fragment, const UScriptStruct& FragmentType) { - auto* PositionFragment = - static_cast(fragment); - PositionFragment->SetPosition(vec); - } - ); + EntityManager.AddFragmentToEntity( + EntityHandle, + FEcsactPositionFragment::StaticStruct(), + [&vec](void* fragment, const UScriptStruct& FragmentType) { + auto* PositionFragment = + static_cast(fragment); + PositionFragment->SetPosition(vec); + } + ); - EntityManager.AddFragmentToEntity( - EntityHandle, - FEcsactStreamFragment::StaticStruct(), - [ShouldStream](void* fragment, const UScriptStruct& FragmentType) { - auto* StreamFragment = - static_cast(fragment); - StreamFragment->SetStream(ShouldStream); - } - ); - } - ); - } + EntityManager.AddFragmentToEntity( + EntityHandle, + FEcsactStreamFragment::StaticStruct(), + [this](void* fragment, const UScriptStruct& FragmentType) { + auto* StreamFragment = static_cast(fragment); + StreamFragment->SetStream(StreamEntities); + } + ); } - EntityManager.FlushCommands(); - MassEntities.Add(Entity, NewEntityHandles); + MassEntities.Add(Entity, NewEntityHandles); EntityPools.Remove(Entity); } @@ -275,36 +228,23 @@ auto UEcsactEntityMassSpawner::UpdateToggle_Implementation( // return; } - UWorld* world = GetWorld(); - auto MassEntitySubsystem = world->GetSubsystem(); + auto* world = GetWorld(); + auto MassEntitySubsystem = world->GetSubsystem(); - FMassEntityManager& EntityManager = - MassEntitySubsystem->GetMutableEntityManager(); + auto& EntityManager = MassEntitySubsystem->GetMutableEntityManager(); auto EcsactEntity = static_cast(Entity); - - if(!MassEntities.Contains(EcsactEntity)) { + auto EntityHandles = MassEntities.Find(EcsactEntity); + if(!EntityHandles) { UE_LOG(LogTemp, Error, TEXT("Unknown Entity not found (Mass Spawner)")); return; } - auto EntityHandles = *MassEntities.Find(EcsactEntity); - - for(auto EntityHandle : EntityHandles) { - if(EntityManager.IsProcessing()) { - EntityManager.Defer().PushCommand( - [EntityHandle, Streaming = Toggle.Streaming](auto& EntityManager) { - auto* StreamFragment = - EntityManager.template GetFragmentDataPtr( - EntityHandle - ); - StreamFragment->SetStream(static_cast(Streaming)); - } - ); + for(auto Entity : *EntityHandles) { + if(Toggle.Streaming) { + EntityManager.Defer().AddTag(Entity); } else { - auto* StreamFragent = - EntityManager.GetFragmentDataPtr(EntityHandle); - StreamFragent->SetStream(static_cast(Toggle.Streaming)); + EntityManager.Defer().RemoveTag(Entity); } } } diff --git a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Fragments/EcsactFragments.h b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Fragments/EcsactFragments.h index 756f76a..2d450f3 100644 --- a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Fragments/EcsactFragments.h +++ b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Fragments/EcsactFragments.h @@ -89,3 +89,12 @@ struct ECSACTUNREALFPS_API FEcsactPositionFragment : public FMassFragment { private: FVector Position; }; + +USTRUCT() + +/** + * Tag indiciating that this mass entity should be streamed. + */ +struct ECSACTUNREALFPS_API FEcsactStreamTag : public FMassTag { + GENERATED_BODY() +}; diff --git a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Fragments/LerpPositionFragment.h b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Fragments/LerpPositionFragment.h new file mode 100644 index 0000000..aa8aa81 --- /dev/null +++ b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Fragments/LerpPositionFragment.h @@ -0,0 +1,14 @@ +#pragma once + +#include "LerpPositionFragment.generated.h" + +USTRUCT() + +struct ECSACTUNREALFPS_API FLerpPositionFragment : public FMassFragment { + GENERATED_BODY() // nolint + + UPROPERTY() + FVector DesiredPosition; + + FLerpPositionFragment() = default; +}; diff --git a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Fragments/LerpPositionParameters.h b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Fragments/LerpPositionParameters.h new file mode 100644 index 0000000..334a51f --- /dev/null +++ b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Fragments/LerpPositionParameters.h @@ -0,0 +1,19 @@ +#pragma once + +#include "MassEntityTypes.h" +#include "LerpPositionParameters.generated.h" + +USTRUCT() + +struct ECSACTUNREALFPS_API FLerpPositionParameters + : public FMassSharedFragment { + GENERATED_BODY() + + /** How fast position should be interpolated */ + UPROPERTY( + EditAnywhere, + Category = "General", + meta = (ClampMin = "0.0", ForceUnits = "cm") + ) + float Speed = 400.f; +}; diff --git a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/EcsactPositionReadProcessor.cpp b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/EcsactPositionReadProcessor.cpp deleted file mode 100644 index 8959a53..0000000 --- a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/EcsactPositionReadProcessor.cpp +++ /dev/null @@ -1,44 +0,0 @@ -#include "EcsactPositionReadProcessor.h" - -#include "MassArchetypeTypes.h" -#include "MassProcessingTypes.h" -#include "MassRequirements.h" -#include "MassSignalSubsystem.h" -#include "MassStateTreeFragments.h" -#include "MassCommonFragments.h" -#include "MassExecutionContext.h" -#include "MassStateTreeTypes.h" -#include "EcsactUnreal/EcsactExecution.h" -#include "EcsactUnreal/EcsactRunner.h" -#include "../Fragments/EcsactFragments.h" - -UEcsactPositionReadProcessor::UEcsactPositionReadProcessor() { - ProcessingPhase = EMassProcessingPhase::PostPhysics; -} - -void UEcsactPositionReadProcessor::ConfigureQueries() { - EntityQuery.AddSubsystemRequirement( - EMassFragmentAccess::ReadWrite - ); - EntityQuery.RegisterWithProcessor(*this); - EntityQuery.AddRequirement( - EMassFragmentAccess::None - ); - EntityQuery.AddRequirement(EMassFragmentAccess::ReadWrite - ); - EntityQuery.AddRequirement( - EMassFragmentAccess::ReadOnly - ); - EntityQuery.AddRequirement( - EMassFragmentAccess::ReadOnly - ); - EntityQuery.AddRequirement( - EMassFragmentAccess::ReadOnly - ); -} - -void UEcsactPositionReadProcessor::Execute( - FMassEntityManager& EntityManager, - FMassExecutionContext& Context -) { -} diff --git a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/EcsactPositionReadProcessor.h b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/EcsactPositionReadProcessor.h deleted file mode 100644 index 653d32d..0000000 --- a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/EcsactPositionReadProcessor.h +++ /dev/null @@ -1,25 +0,0 @@ - -#pragma once - -#include "CoreMinimal.h" -#include "MassProcessor.h" -#include "EcsactPositionReadProcessor.generated.h" - -UCLASS() - -// TODO(Kelwan): We shouldn't check an `if` statement in the Tick Processor. -// When the condition is changed, a different processor should run for whether a -// streamed entity is streaming or reading from Ecsact. -class ECSACTUNREALFPS_API UEcsactPositionReadProcessor : public UMassProcessor { - GENERATED_BODY() // nolint - -protected: - UEcsactPositionReadProcessor(); - virtual void ConfigureQueries() override; - virtual void Execute( - FMassEntityManager& EntityManager, - FMassExecutionContext& Context - ) override; - - FMassEntityQuery EntityQuery; -}; diff --git a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/LerpPositionProcessor.cpp b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/LerpPositionProcessor.cpp new file mode 100644 index 0000000..8d85b48 --- /dev/null +++ b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/LerpPositionProcessor.cpp @@ -0,0 +1,58 @@ +#include "LerpPositionProcessor.h" +#include "EcsactUnrealFps/Fragments/LerpPositionFragment.h" +#include "EcsactUnrealFps/Fragments/LerpPositionParameters.h" +#include "MassCommonFragments.h" +#include "MassExecutionContext.h" +#include "MassRequirements.h" +#include "Math/UnrealMathUtility.h" + +ULerpPositionProcessor::ULerpPositionProcessor() { + ProcessingPhase = EMassProcessingPhase::PostPhysics; +} + +auto ULerpPositionProcessor::ConfigureQueries() -> void { + using EMassFragmentAccess::ReadOnly; + using EMassFragmentAccess::ReadWrite; + using EMassFragmentPresence::All; + + EntityQuery // + .AddRequirement(ReadWrite, All) + .AddRequirement(ReadOnly, All) + .AddConstSharedRequirement(All); + + RegisterQuery(EntityQuery); +} + +auto ULerpPositionProcessor::Execute( + FMassEntityManager& EntityManager, + FMassExecutionContext& Context +) -> void { + EntityQuery.ForEachEntityChunk( + EntityManager, + Context, + [](FMassExecutionContext& Context) { + const auto num_entities = Context.GetNumEntities(); + + auto transform_fragments = + Context.GetMutableFragmentView(); + auto lerp_pos_fragments = + Context.GetFragmentView(); + + const auto& lerp_pos_params = + Context.GetConstSharedFragment(); + + for(auto i = 0; num_entities > i; ++i) { + auto& transform = transform_fragments[i].GetMutableTransform(); + auto lerp_pos = lerp_pos_fragments[i]; + + auto new_location = FMath::Lerp( + transform.GetLocation(), + lerp_pos.DesiredPosition, + Context.GetDeltaTimeSeconds() * lerp_pos_params.Speed + ); + + transform.SetLocation(new_location); + } + } + ); +} diff --git a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/LerpPositionProcessor.h b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/LerpPositionProcessor.h new file mode 100644 index 0000000..4852141 --- /dev/null +++ b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/LerpPositionProcessor.h @@ -0,0 +1,22 @@ +#pragma once + +#include "CoreMinimal.h" +#include "MassProcessor.h" +#include "LerpPositionProcessor.generated.h" + +UCLASS() + +class ECSACTUNREALFPS_API ULerpPositionProcessor : public UMassProcessor { + GENERATED_BODY() // nolint + +protected: + ULerpPositionProcessor(); + + auto ConfigureQueries() -> void override; + auto Execute( + FMassEntityManager& EntityManager, + FMassExecutionContext& Context + ) -> void override; + + FMassEntityQuery EntityQuery; +}; diff --git a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/StreamPositionProcessor.cpp b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/StreamPositionProcessor.cpp new file mode 100644 index 0000000..51fb72e --- /dev/null +++ b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/StreamPositionProcessor.cpp @@ -0,0 +1,60 @@ +#include "StreamPositionProcessor.h" +#include "EcsactUnrealFps/Fragments/EcsactFragments.h" +#include "EcsactUnrealFps/EcsactUnrealFps__ecsact__ue.h" +#include "EcsactUnreal/EcsactExecution.h" +#include "EcsactUnreal/EcsactRunner.h" +#include "MassCommonFragments.h" +#include "MassExecutionContext.h" +#include "MassRequirements.h" + +UStreamPositionProcessor::UStreamPositionProcessor() { + ProcessingPhase = EMassProcessingPhase::PostPhysics; +} + +auto UStreamPositionProcessor::ConfigureQueries() -> void { + using EMassFragmentAccess::ReadOnly; + using EMassFragmentAccess::ReadWrite; + using EMassFragmentPresence::All; + + EntityQuery // + .AddRequirement(ReadOnly, All) + .AddRequirement(ReadOnly, All) + .AddTagRequirement(All); + + RegisterQuery(EntityQuery); +} + +auto UStreamPositionProcessor::Execute( + FMassEntityManager& EntityManager, + FMassExecutionContext& Context +) -> void { + auto runner = EcsactUnrealExecution::Runner(GetWorld()).Get(); + if(!runner) { + return; + } + + EntityQuery.ForEachEntityChunk( + EntityManager, + Context, + [runner](FMassExecutionContext& Context) { + const auto num_entities = Context.GetNumEntities(); + + auto ecsact_entity_fragments = + Context.GetFragmentView(); + auto transform_fragments = Context.GetFragmentView(); + + for(auto i = 0; num_entities > i; ++i) { + auto ecsact_entity = ecsact_entity_fragments[i].GetId(); + auto entity_loc = transform_fragments[i].GetTransform().GetLocation(); + runner->Stream( + ecsact_entity, + example::fps::Position{ + .x = static_cast(entity_loc.X), + .y = static_cast(entity_loc.Y), + .z = static_cast(entity_loc.Z), + } + ); + } + } + ); +} diff --git a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/StreamPositionProcessor.h b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/StreamPositionProcessor.h new file mode 100644 index 0000000..d952b6f --- /dev/null +++ b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/StreamPositionProcessor.h @@ -0,0 +1,22 @@ +#pragma once + +#include "CoreMinimal.h" +#include "MassProcessor.h" +#include "StreamPositionProcessor.generated.h" + +UCLASS() + +class ECSACTUNREALFPS_API UStreamPositionProcessor : public UMassProcessor { + GENERATED_BODY() // nolint + +protected: + UStreamPositionProcessor(); + + auto ConfigureQueries() -> void override; + auto Execute( + FMassEntityManager& EntityManager, + FMassExecutionContext& Context + ) -> void override; + + FMassEntityQuery EntityQuery; +}; diff --git a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/SyncPositionProcessor.cpp b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/SyncPositionProcessor.cpp new file mode 100644 index 0000000..d674c69 --- /dev/null +++ b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/SyncPositionProcessor.cpp @@ -0,0 +1,51 @@ +#include "SyncPositionProcessor.h" +#include "EcsactUnrealFps/Fragments/EcsactFragments.h" +#include "EcsactUnrealFps/EcsactUnrealFps__ecsact__ue.h" +#include "EcsactUnreal/EcsactExecution.h" +#include "EcsactUnreal/EcsactRunner.h" +#include "EcsactUnrealFps/Fragments/LerpPositionFragment.h" +#include "MassCommonFragments.h" +#include "MassExecutionContext.h" +#include "MassRequirements.h" + +USyncPositionProcessor::USyncPositionProcessor() { + ProcessingPhase = EMassProcessingPhase::PostPhysics; +} + +auto USyncPositionProcessor::ConfigureQueries() -> void { + using EMassFragmentAccess::ReadOnly; + using EMassFragmentAccess::ReadWrite; + using EMassFragmentPresence::All; + using EMassFragmentPresence::None; + + EntityQuery // + .AddRequirement(ReadOnly, All) + .AddRequirement(ReadWrite, All) + .AddTagRequirement(None); + + RegisterQuery(EntityQuery); +} + +auto USyncPositionProcessor::Execute( + FMassEntityManager& EntityManager, + FMassExecutionContext& Context +) -> void { + EntityQuery.ForEachEntityChunk( + EntityManager, + Context, + [](FMassExecutionContext& Context) { + const auto num_entities = Context.GetNumEntities(); + + auto pos_fragments = Context.GetFragmentView(); + auto lerp_pos_fragments = + Context.GetMutableFragmentView(); + + for(auto i = 0; num_entities > i; ++i) { + auto entity_pos = pos_fragments[i].GetPosition(); + auto& lerp_pos = lerp_pos_fragments[i]; + lerp_pos.DesiredPosition = + FVector{entity_pos.X, entity_pos.Y, entity_pos.Z}; + } + } + ); +} diff --git a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/SyncPositionProcessor.h b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/SyncPositionProcessor.h new file mode 100644 index 0000000..e5eda78 --- /dev/null +++ b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/SyncPositionProcessor.h @@ -0,0 +1,22 @@ +#pragma once + +#include "CoreMinimal.h" +#include "MassProcessor.h" +#include "SyncPositionProcessor.generated.h" + +UCLASS() + +class ECSACTUNREALFPS_API USyncPositionProcessor : public UMassProcessor { + GENERATED_BODY() // nolint + +protected: + USyncPositionProcessor(); + + auto ConfigureQueries() -> void override; + auto Execute( + FMassEntityManager& EntityManager, + FMassExecutionContext& Context + ) -> void override; + + FMassEntityQuery EntityQuery; +}; diff --git a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/TickProcessor.cpp b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/TickProcessor.cpp deleted file mode 100644 index 3205d32..0000000 --- a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/TickProcessor.cpp +++ /dev/null @@ -1,117 +0,0 @@ -#include "TickProcessor.h" -#include "MassArchetypeTypes.h" -#include "MassProcessingTypes.h" -#include "MassRequirements.h" -#include "MassSignalSubsystem.h" -#include "MassStateTreeFragments.h" -#include "MassCommonFragments.h" -#include "MassExecutionContext.h" -#include "MassStateTreeTypes.h" -#include "EcsactUnreal/EcsactExecution.h" -#include "EcsactUnreal/EcsactRunner.h" -#include "../Fragments/EcsactFragments.h" -#include "../EcsactUnrealFps.ecsact.hh" - -UTickProcessor::UTickProcessor() { - ProcessingPhase = EMassProcessingPhase::PostPhysics; -} - -void UTickProcessor::ConfigureQueries() { - EntityQuery.AddSubsystemRequirement( - EMassFragmentAccess::ReadWrite - ); - EntityQuery.RegisterWithProcessor(*this); - EntityQuery.AddRequirement( - EMassFragmentAccess::None - ); - EntityQuery.AddRequirement(EMassFragmentAccess::ReadWrite - ); - EntityQuery.AddRequirement( - EMassFragmentAccess::ReadOnly - ); - EntityQuery.AddRequirement( - EMassFragmentAccess::ReadOnly - ); - EntityQuery.AddRequirement( - EMassFragmentAccess::ReadOnly - ); -} - -void UTickProcessor::Execute( - FMassEntityManager& EntityManager, - FMassExecutionContext& Context -) { - auto runner = EcsactUnrealExecution::Runner(GetWorld()).Get(); - if(!runner) { - return; - } - - EntityQuery.ForEachEntityChunk( - EntityManager, - Context, - [runner](FMassExecutionContext& Context) { - UMassSignalSubsystem& SignalSubsystem = - Context.GetMutableSubsystemChecked(); - - auto Entities = Context.GetEntities(); - SignalSubsystem.SignalEntities( - UE::Mass::Signals::StateTreeActivate, - Entities - ); - - const auto NumEntities = Context.GetNumEntities(); - auto StreamFragments = Context.GetFragmentView(); - auto EntityFragments = Context.GetFragmentView(); - auto TransformFragments = - Context.GetMutableFragmentView(); - auto PositionFragments = - Context.GetFragmentView(); - - for(int32 i = 0; NumEntities > i; ++i) { - const bool ShouldStream = StreamFragments[i].ShouldStream(); - - if(ShouldStream) { - auto TransformLoc = - TransformFragments[i].GetTransform().GetLocation(); - auto Entity = EntityFragments[i].GetId(); - - runner->Stream( - Entity, - example::fps::Position{ - .x = static_cast(TransformLoc.X), - .y = static_cast(TransformLoc.Y), - .z = static_cast(TransformLoc.Z), - } - ); - } else { - auto& Transform = TransformFragments[i].GetMutableTransform(); - auto Position = PositionFragments[i].GetPosition(); - - // const auto& Location = Transform.GetLocation(); - - // UE_LOG( - // LogTemp, - // Log, - // TEXT("Current transform location of entity %i: %f, %f, %f"), - // i, - // Location.X, - // Location.Y, - // Location.Z - // ); - - // UE_LOG( - // LogTemp, - // Log, - // TEXT("Setting position to entity %i: %f, %f, %f"), - // i, - // Position.X, - // Position.Y, - // Position.Z - // ); - - Transform.SetLocation(Position); - } - } - } - ); -} diff --git a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/TickProcessor.h b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/TickProcessor.h deleted file mode 100644 index 7900a03..0000000 --- a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/TickProcessor.h +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once - -#include "CoreMinimal.h" -#include "MassProcessor.h" -#include "TickProcessor.generated.h" - -UCLASS() - -// TODO(Kelwan): Rename this class, it's not clear what it does. Move some logic -// to other processes -class ECSACTUNREALFPS_API UTickProcessor : public UMassProcessor { - GENERATED_BODY() // nolint - -protected: - UTickProcessor(); - virtual void ConfigureQueries() override; - virtual void Execute( - FMassEntityManager& EntityManager, - FMassExecutionContext& Context - ) override; - - FMassEntityQuery EntityQuery; -}; diff --git a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Tasks/FollowPlayer.cpp b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Tasks/FollowPlayer.cpp index 31e9030..193b645 100644 --- a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Tasks/FollowPlayer.cpp +++ b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Tasks/FollowPlayer.cpp @@ -54,6 +54,10 @@ EStateTreeRunStatus FFollowPlayer::Tick( // FStateTreeExecutionContext& Context, const float DeltaTime ) const { + if(!Context.GetExternalData(StreamFragmentHandle).ShouldStream()) { + return EStateTreeRunStatus::Running; + } + MoveToPlayerPosition(Context); return EStateTreeRunStatus::Running; diff --git a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Traits/LerpPositionTrait.cpp b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Traits/LerpPositionTrait.cpp new file mode 100644 index 0000000..32c580c --- /dev/null +++ b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Traits/LerpPositionTrait.cpp @@ -0,0 +1,16 @@ +#include "LerpPositionTrait.h" +#include "MassEntityUtils.h" +#include "EcsactUnrealFps/Fragments/LerpPositionFragment.h" +#include "MassEntityTemplateRegistry.h" + +auto ULerpPositionTrait::BuildTemplate( // + FMassEntityTemplateBuildContext& BuildContext, + const UWorld& World +) const -> void { + auto& entity_manager = UE::Mass::Utils::GetEntityManagerChecked(World); + + BuildContext.AddFragment(); + BuildContext.AddConstSharedFragment( + entity_manager.GetOrCreateConstSharedFragment(PositionParameters) + ); +} diff --git a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Traits/LerpPositionTrait.h b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Traits/LerpPositionTrait.h new file mode 100644 index 0000000..12a0e15 --- /dev/null +++ b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Traits/LerpPositionTrait.h @@ -0,0 +1,20 @@ +#pragma once + +#include "MassEntityTraitBase.h" +#include "EcsactUnrealFps/Fragments/LerpPositionParameters.h" +#include "LerpPositionTrait.generated.h" + +UCLASS(meta = (DisplayName = "Avoidance")) +class ECSACTUNREALFPS_API ULerpPositionTrait : public UMassEntityTraitBase { + GENERATED_BODY() + +protected: + + auto BuildTemplate( // + FMassEntityTemplateBuildContext& BuildContext, + const UWorld& World + ) const -> void override; + + UPROPERTY(EditAnywhere, Category="") + FLerpPositionParameters PositionParameters; +}; From 5a39dd88247b4fc6cf60db73c246a99e5985e45b Mon Sep 17 00:00:00 2001 From: Ezekiel Warren Date: Thu, 12 Dec 2024 16:31:53 -0800 Subject: [PATCH 02/15] chore: wip --- .../EcsactEntityMassSpawner.cpp | 91 +++++++++++++------ .../EcsactUnrealFps/EcsactEntityMassSpawner.h | 27 +++--- .../EcsactUnrealFps/EcsactUnrealFps.ecsact | 8 +- .../EcsactUnrealFps/EcsactUnrealFps.ecsact.hh | 4 +- .../EcsactUnrealFps__ecsact__ue.cpp | 28 +++--- .../EcsactUnrealFps__ecsact__ue.h | 28 +++--- 6 files changed, 109 insertions(+), 77 deletions(-) diff --git a/unreal-cpp-net-fps/Source/EcsactUnrealFps/EcsactEntityMassSpawner.cpp b/unreal-cpp-net-fps/Source/EcsactUnrealFps/EcsactEntityMassSpawner.cpp index ccc8950..27cb496 100644 --- a/unreal-cpp-net-fps/Source/EcsactUnrealFps/EcsactEntityMassSpawner.cpp +++ b/unreal-cpp-net-fps/Source/EcsactUnrealFps/EcsactEntityMassSpawner.cpp @@ -62,45 +62,82 @@ auto UEcsactEntityMassSpawner::CreateMassEntities(int count) -> void { } } -auto UEcsactEntityMassSpawner::InitMassentity_Implementation( - int32 Entity, - FExampleFpsMassentity MEntityTag +auto UEcsactEntityMassSpawner::EntityCreated_Implementation( // + int32 Entity ) -> void { - UE_LOG(LogTemp, Warning, TEXT("Mass Entity found, adding to Mass Spawner")); + checkSlow(!MassEntities.Contains(static_cast(Entity))); - auto EcsactEntity = static_cast(Entity); - auto& EntityPool = EntityPools.FindOrAdd(EcsactEntity); + auto* world = GetWorld(); - using std::get; + const FMassEntityTemplate& entity_template = + MassEntityConfigAsset->GetOrCreateEntityTemplate(*world); + auto new_entity_handles = TArray{}; - const auto& Position = get>(EntityPool); - auto& TMassEntity = get>(EntityPool); + auto mass_spawner = world->GetSubsystem(); + auto& entity_manager = + world->GetSubsystem()->GetMutableEntityManager(); - TMassEntity = MEntityTag; - if(Position == std::nullopt) { - return; + mass_spawner->SpawnEntities(entity_template, 1, new_entity_handles); + for(auto entity_handle : new_entity_handles) { + entity_manager.AddFragmentToEntity( + entity_handle, + FEcsactEntityFragment::StaticStruct(), + [Entity](void* fragment, const UScriptStruct&) { + static_cast(fragment)->SetId( + static_cast(Entity) + ); + } + ); } - Spawn(EcsactEntity, *Position); + + MassEntities.Add(static_cast(Entity), new_entity_handles); } -auto UEcsactEntityMassSpawner::InitPosition_Implementation( - int32 Entity, - FExampleFpsPosition Position +auto UEcsactEntityMassSpawner::EntityDestroyed_Implementation( // + int32 Entity ) -> void { - auto EcsactEntity = static_cast(Entity); - auto& EntityPool = EntityPools.FindOrAdd(EcsactEntity); + checkSlow(MassEntities.Contains(static_cast(Entity))); - using std::get; + auto* world = GetWorld(); + auto& entity_manager = + world->GetSubsystem()->GetMutableEntityManager(); - const auto& TMassEntity = - get>(EntityPool); - auto& TPosition = get>(EntityPool); + auto old_entity_handles = TArray{}; + MassEntities.RemoveAndCopyValue( + static_cast(Entity), + old_entity_handles + ); - TPosition = Position; - if(TMassEntity == std::nullopt) { - return; + for(auto entity_handle : old_entity_handles) { + entity_manager.Defer().DestroyEntity(entity_handle); } - Spawn(EcsactEntity, Position); +} + +auto UEcsactEntityMassSpawner::InitEnemy_Implementation( + int32 Entity, + FExampleFpsMassentity MEntityTag +) -> void { +} + +auto UEcsactEntityMassSpawner::InitPosition_Implementation( + int32 Entity, + FExampleFpsPosition Position +) -> void { + checkSlow(MassEntities.Contains(static_cast(Entity))); + + auto* world = GetWorld(); + auto& entity_manager = + world->GetSubsystem()->GetMutableEntityManager(); + + auto entity_handles = + MassEntities.FindChecked(static_cast(Entity)); + entity_manager.Defer().PushCommand( + [entity_handles](FMassEntityManager& entity_manager) { + for(auto entity_handle : entity_handles) { + // entity_manager.AddFragmentToEntity + } + } + ); } auto UEcsactEntityMassSpawner::UpdatePosition_Implementation( @@ -120,8 +157,6 @@ auto UEcsactEntityMassSpawner::UpdatePosition_Implementation( auto vec = FVector{Position.X, Position.Y, Position.Z}; for(auto EntityHandle : EntityHandles) { - check(EntityHandle.IsValid()); - check(EntityHandle.IsSet()); if(EntityManager.IsProcessing()) { EntityManager.Defer().PushCommand( [EntityHandle, vec](auto& EntityManager) { diff --git a/unreal-cpp-net-fps/Source/EcsactUnrealFps/EcsactEntityMassSpawner.h b/unreal-cpp-net-fps/Source/EcsactUnrealFps/EcsactEntityMassSpawner.h index 502ec37..c3ea331 100644 --- a/unreal-cpp-net-fps/Source/EcsactUnrealFps/EcsactEntityMassSpawner.h +++ b/unreal-cpp-net-fps/Source/EcsactUnrealFps/EcsactEntityMassSpawner.h @@ -22,29 +22,21 @@ class UEcsactEntityMassSpawner : public UExampleFpsEcsactRunnerSubsystem { UPROPERTY(EditAnywhere) UMassEntityConfigAsset* MassEntityConfigAsset; - UPROPERTY(EditAnywhere) - TArray SpawnDataGenerators; - TMap> MassEntities; - using EntityPoolT = std::tuple< - std::optional, - std::optional>; - - // NOTE: Temporary until more general solution is implemented - TMap EntityPools; - - void Spawn(ecsact_entity_id Entity, const FExampleFpsPosition& Position); - public: UFUNCTION(BlueprintCallable) void CreateMassEntities(int count); UPROPERTY(EditAnywhere, BlueprintReadWrite) bool StreamEntities; - auto InitMassentity_Implementation( // - int32 Entity, - FExampleFpsMassentity MassEntity + auto EntityCreated_Implementation(int32 Entity) -> void override; + + auto EntityDestroyed_Implementation(int32 Entity) -> void override; + + auto InitEnemy_Implementation( // + int32 Entity, + FExampleFpsEnemy Enemy ) -> void override; auto InitPosition_Implementation( // @@ -57,6 +49,11 @@ class UEcsactEntityMassSpawner : public UExampleFpsEcsactRunnerSubsystem { FExampleFpsPosition Position ) -> void override; + auto InitToggle_Implementation( // + int32 Entity, + FExampleFpsToggle Toggle + ) -> void override; + auto UpdateToggle_Implementation( // int32 Entity, FExampleFpsToggle Toggle diff --git a/unreal-cpp-net-fps/Source/EcsactUnrealFps/EcsactUnrealFps.ecsact b/unreal-cpp-net-fps/Source/EcsactUnrealFps/EcsactUnrealFps.ecsact index 1320c3b..840a50d 100644 --- a/unreal-cpp-net-fps/Source/EcsactUnrealFps/EcsactUnrealFps.ecsact +++ b/unreal-cpp-net-fps/Source/EcsactUnrealFps/EcsactUnrealFps.ecsact @@ -6,7 +6,7 @@ component PusherExpired; component Rotation(stream) { f32 pitch; f32 yaw; f32 roll; } component Position(stream) { f32 x; f32 y; f32 z; } -component MassEntity; +component Enemy; component Velocity { f32 x; @@ -46,7 +46,7 @@ action Push { adds Pusher; system PushEntities { - include MassEntity; + include Enemy; readonly Position; readwrite Toggle; adds Pushing; @@ -70,7 +70,7 @@ action Move { component RemovePushingTag; system ApplyPush { - include MassEntity; + include Enemy; readwrite Pushing; readwrite Velocity; } @@ -97,7 +97,7 @@ system TogglePushedEntities { } system RemovePushing { - include MassEntity; + include Enemy; removes RemovePushingTag; removes Pushing; } diff --git a/unreal-cpp-net-fps/Source/EcsactUnrealFps/EcsactUnrealFps.ecsact.hh b/unreal-cpp-net-fps/Source/EcsactUnrealFps/EcsactUnrealFps.ecsact.hh index e1c0b20..3eb2b68 100644 --- a/unreal-cpp-net-fps/Source/EcsactUnrealFps/EcsactUnrealFps.ecsact.hh +++ b/unreal-cpp-net-fps/Source/EcsactUnrealFps/EcsactUnrealFps.ecsact.hh @@ -45,11 +45,11 @@ struct Position { float z; auto operator<=>(const example::fps::Position&) const = default; }; -struct MassEntity { +struct Enemy { static constexpr bool transient = false; static constexpr bool has_assoc_fields = false; static constexpr auto id = static_cast(6); - auto operator<=>(const example::fps::MassEntity&) const = default; + auto operator<=>(const example::fps::Enemy&) const = default; }; struct Velocity { static constexpr bool transient = false; diff --git a/unreal-cpp-net-fps/Source/EcsactUnrealFps/EcsactUnrealFps__ecsact__ue.cpp b/unreal-cpp-net-fps/Source/EcsactUnrealFps/EcsactUnrealFps__ecsact__ue.cpp index bd52cb3..bdcf738 100644 --- a/unreal-cpp-net-fps/Source/EcsactUnrealFps/EcsactUnrealFps__ecsact__ue.cpp +++ b/unreal-cpp-net-fps/Source/EcsactUnrealFps/EcsactUnrealFps__ecsact__ue.cpp @@ -27,8 +27,8 @@ FExampleFpsPosition FExampleFpsPosition::FromEcsactComponentData(const void* com result.Z = static_cast(component_data)->z; return result; } -FExampleFpsMassentity FExampleFpsMassentity::FromEcsactComponentData(const void* component_data) { - auto result = FExampleFpsMassentity{}; +FExampleFpsEnemy FExampleFpsEnemy::FromEcsactComponentData(const void* component_data) { + auto result = FExampleFpsEnemy{}; return result; } FExampleFpsVelocity FExampleFpsVelocity::FromEcsactComponentData(const void* component_data) { @@ -80,9 +80,9 @@ UExampleFpsEcsactRunnerSubsystem::UExampleFpsEcsactRunnerSubsystem() { InitComponentFns[5] = &ThisClass::RawInitPosition; UpdateComponentFns[5] = &ThisClass::RawUpdatePosition; RemoveComponentFns[5] = &ThisClass::RawRemovePosition; - InitComponentFns[6] = &ThisClass::RawInitMassentity; - UpdateComponentFns[6] = &ThisClass::RawUpdateMassentity; - RemoveComponentFns[6] = &ThisClass::RawRemoveMassentity; + InitComponentFns[6] = &ThisClass::RawInitEnemy; + UpdateComponentFns[6] = &ThisClass::RawUpdateEnemy; + RemoveComponentFns[6] = &ThisClass::RawRemoveEnemy; InitComponentFns[7] = &ThisClass::RawInitVelocity; UpdateComponentFns[7] = &ThisClass::RawUpdateVelocity; RemoveComponentFns[7] = &ThisClass::RawRemoveVelocity; @@ -158,14 +158,14 @@ void UExampleFpsEcsactRunnerSubsystem::RawUpdatePosition(int32 entity, const voi void UExampleFpsEcsactRunnerSubsystem::RawRemovePosition(int32 entity, const void* component) { RemovePosition(entity, FExampleFpsPosition::FromEcsactComponentData(component)); } -void UExampleFpsEcsactRunnerSubsystem::RawInitMassentity(int32 entity, const void* component) { - InitMassentity(entity, FExampleFpsMassentity::FromEcsactComponentData(component)); +void UExampleFpsEcsactRunnerSubsystem::RawInitEnemy(int32 entity, const void* component) { + InitEnemy(entity, FExampleFpsEnemy::FromEcsactComponentData(component)); } -void UExampleFpsEcsactRunnerSubsystem::RawUpdateMassentity(int32 entity, const void* component) { - UpdateMassentity(entity, FExampleFpsMassentity::FromEcsactComponentData(component)); +void UExampleFpsEcsactRunnerSubsystem::RawUpdateEnemy(int32 entity, const void* component) { + UpdateEnemy(entity, FExampleFpsEnemy::FromEcsactComponentData(component)); } -void UExampleFpsEcsactRunnerSubsystem::RawRemoveMassentity(int32 entity, const void* component) { - RemoveMassentity(entity, FExampleFpsMassentity::FromEcsactComponentData(component)); +void UExampleFpsEcsactRunnerSubsystem::RawRemoveEnemy(int32 entity, const void* component) { + RemoveEnemy(entity, FExampleFpsEnemy::FromEcsactComponentData(component)); } void UExampleFpsEcsactRunnerSubsystem::RawInitVelocity(int32 entity, const void* component) { InitVelocity(entity, FExampleFpsVelocity::FromEcsactComponentData(component)); @@ -272,15 +272,15 @@ void UExampleFpsEcsactRunnerSubsystem::RemovePosition_Implementation(int32 Entit } -void UExampleFpsEcsactRunnerSubsystem::InitMassentity_Implementation(int32 Entity, FExampleFpsMassentity Massentity) { +void UExampleFpsEcsactRunnerSubsystem::InitEnemy_Implementation(int32 Entity, FExampleFpsEnemy Enemy) { } -void UExampleFpsEcsactRunnerSubsystem::UpdateMassentity_Implementation(int32 Entity, FExampleFpsMassentity Massentity) { +void UExampleFpsEcsactRunnerSubsystem::UpdateEnemy_Implementation(int32 Entity, FExampleFpsEnemy Enemy) { } -void UExampleFpsEcsactRunnerSubsystem::RemoveMassentity_Implementation(int32 Entity, FExampleFpsMassentity Massentity) { +void UExampleFpsEcsactRunnerSubsystem::RemoveEnemy_Implementation(int32 Entity, FExampleFpsEnemy Enemy) { } diff --git a/unreal-cpp-net-fps/Source/EcsactUnrealFps/EcsactUnrealFps__ecsact__ue.h b/unreal-cpp-net-fps/Source/EcsactUnrealFps/EcsactUnrealFps__ecsact__ue.h index 7992096..e90fb63 100644 --- a/unreal-cpp-net-fps/Source/EcsactUnrealFps/EcsactUnrealFps__ecsact__ue.h +++ b/unreal-cpp-net-fps/Source/EcsactUnrealFps/EcsactUnrealFps__ecsact__ue.h @@ -64,10 +64,10 @@ struct FExampleFpsPosition { }; USTRUCT(BlueprintType) -struct FExampleFpsMassentity { +struct FExampleFpsEnemy { GENERATED_BODY() - static FExampleFpsMassentity FromEcsactComponentData(const void*); + static FExampleFpsEnemy FromEcsactComponentData(const void*); }; @@ -153,9 +153,9 @@ class UExampleFpsEcsactRunnerSubsystem : public UEcsactRunnerSubsystem { void RawInitPosition(int32 Entity, const void* Component); void RawUpdatePosition(int32 Entity, const void* Component); void RawRemovePosition(int32 Entity, const void* Component); - void RawInitMassentity(int32 Entity, const void* Component); - void RawUpdateMassentity(int32 Entity, const void* Component); - void RawRemoveMassentity(int32 Entity, const void* Component); + void RawInitEnemy(int32 Entity, const void* Component); + void RawUpdateEnemy(int32 Entity, const void* Component); + void RawRemoveEnemy(int32 Entity, const void* Component); void RawInitVelocity(int32 Entity, const void* Component); void RawUpdateVelocity(int32 Entity, const void* Component); void RawRemoveVelocity(int32 Entity, const void* Component); @@ -225,15 +225,15 @@ class UExampleFpsEcsactRunnerSubsystem : public UEcsactRunnerSubsystem { UFUNCTION(BlueprintNativeEvent, Category = "Ecsact Runner", meta = (DisplayName = "Remove example.fps.Position")) void RemovePosition(int32 Entity, FExampleFpsPosition Position); virtual void RemovePosition_Implementation(int32 Entity, FExampleFpsPosition Position); - UFUNCTION(BlueprintNativeEvent, Category = "Ecsact Runner", meta = (DisplayName = "Init example.fps.MassEntity")) - void InitMassentity(int32 Entity, FExampleFpsMassentity Massentity); - virtual void InitMassentity_Implementation(int32 Entity, FExampleFpsMassentity Massentity); - UFUNCTION(BlueprintNativeEvent, Category = "Ecsact Runner", meta = (DisplayName = "Update example.fps.MassEntity")) - void UpdateMassentity(int32 Entity, FExampleFpsMassentity Massentity); - virtual void UpdateMassentity_Implementation(int32 Entity, FExampleFpsMassentity Massentity); - UFUNCTION(BlueprintNativeEvent, Category = "Ecsact Runner", meta = (DisplayName = "Remove example.fps.MassEntity")) - void RemoveMassentity(int32 Entity, FExampleFpsMassentity Massentity); - virtual void RemoveMassentity_Implementation(int32 Entity, FExampleFpsMassentity Massentity); + UFUNCTION(BlueprintNativeEvent, Category = "Ecsact Runner", meta = (DisplayName = "Init example.fps.Enemy")) + void InitEnemy(int32 Entity, FExampleFpsEnemy Enemy); + virtual void InitEnemy_Implementation(int32 Entity, FExampleFpsEnemy Enemy); + UFUNCTION(BlueprintNativeEvent, Category = "Ecsact Runner", meta = (DisplayName = "Update example.fps.Enemy")) + void UpdateEnemy(int32 Entity, FExampleFpsEnemy Enemy); + virtual void UpdateEnemy_Implementation(int32 Entity, FExampleFpsEnemy Enemy); + UFUNCTION(BlueprintNativeEvent, Category = "Ecsact Runner", meta = (DisplayName = "Remove example.fps.Enemy")) + void RemoveEnemy(int32 Entity, FExampleFpsEnemy Enemy); + virtual void RemoveEnemy_Implementation(int32 Entity, FExampleFpsEnemy Enemy); UFUNCTION(BlueprintNativeEvent, Category = "Ecsact Runner", meta = (DisplayName = "Init example.fps.Velocity")) void InitVelocity(int32 Entity, FExampleFpsVelocity Velocity); virtual void InitVelocity_Implementation(int32 Entity, FExampleFpsVelocity Velocity); From f14e2d3f711e6191b47f19085525cf39ade8063f Mon Sep 17 00:00:00 2001 From: Ezekiel Warren Date: Thu, 12 Dec 2024 22:32:12 -0800 Subject: [PATCH 03/15] chore: wip --- .../EcsactEntityMassSpawner.cpp | 161 ++++-------------- .../generated/EcsactUnrealFps.ecsact.hh | 4 +- 2 files changed, 38 insertions(+), 127 deletions(-) diff --git a/unreal-cpp-net-fps/Source/EcsactUnrealFps/EcsactEntityMassSpawner.cpp b/unreal-cpp-net-fps/Source/EcsactUnrealFps/EcsactEntityMassSpawner.cpp index 27cb496..f435b34 100644 --- a/unreal-cpp-net-fps/Source/EcsactUnrealFps/EcsactEntityMassSpawner.cpp +++ b/unreal-cpp-net-fps/Source/EcsactUnrealFps/EcsactEntityMassSpawner.cpp @@ -10,6 +10,7 @@ #include "MassSpawnerSubsystem.h" #include "MassEntitySubsystem.h" #include "MassEntityTemplateRegistry.h" +#include "MassEntityManager.h" #include "MassCommonFragments.h" #include "EcsactUnreal/EcsactExecution.h" #include "EcsactUnreal/EcsactRunner.h" @@ -46,7 +47,7 @@ auto UEcsactEntityMassSpawner::CreateMassEntities(int count) -> void { .y = static_cast(RandomPointY), .z = 0 }) - .AddComponent(example::fps::MassEntity{}) + .AddComponent(example::fps::Enemy{}) .AddComponent(example::fps::Velocity{}) .AddComponent(example::fps::Toggle{.streaming = StreamEntities}) .OnCreate(TDelegate::CreateLambda( // @@ -114,8 +115,8 @@ auto UEcsactEntityMassSpawner::EntityDestroyed_Implementation( // } auto UEcsactEntityMassSpawner::InitEnemy_Implementation( - int32 Entity, - FExampleFpsMassentity MEntityTag + int32 Entity, + FExampleFpsEnemy Enemy ) -> void { } @@ -131,155 +132,65 @@ auto UEcsactEntityMassSpawner::InitPosition_Implementation( auto entity_handles = MassEntities.FindChecked(static_cast(Entity)); - entity_manager.Defer().PushCommand( - [entity_handles](FMassEntityManager& entity_manager) { - for(auto entity_handle : entity_handles) { - // entity_manager.AddFragmentToEntity - } - } - ); + for(auto entity_handle : entity_handles) { + entity_manager.Defer().PushCommand( + entity_handle, + FEcsactPositionFragment() + ); + } } auto UEcsactEntityMassSpawner::UpdatePosition_Implementation( int32 Entity, FExampleFpsPosition Position ) -> void { - auto EcsactEntity = static_cast(Entity); - if(MassEntities.Contains(EcsactEntity)) { - auto EntityHandles = - *MassEntities.Find(static_cast(Entity)); - - UWorld* world = GetWorld(); - - auto MassEntity = world->GetSubsystem(); - FMassEntityManager& EntityManager = MassEntity->GetMutableEntityManager(); + checkSlow(MassEntities.Contains(static_cast(Entity))); - auto vec = FVector{Position.X, Position.Y, Position.Z}; + auto* world = GetWorld(); + auto& entity_manager = + world->GetSubsystem()->GetMutableEntityManager(); - for(auto EntityHandle : EntityHandles) { - if(EntityManager.IsProcessing()) { - EntityManager.Defer().PushCommand( - [EntityHandle, vec](auto& EntityManager) { - auto* PositionFragment = - EntityManager - .template GetFragmentDataPtr( - EntityHandle - ); - PositionFragment->SetPosition(vec); - } - ); - } else { - auto* PositionFragment = - EntityManager.GetFragmentDataPtr(EntityHandle - ); - PositionFragment->SetPosition(vec); - } - } - EntityManager.FlushCommands(); + const auto& entity_handles = + MassEntities.FindChecked(static_cast(Entity)); + for(auto entity_handle : entity_handles) { + entity_manager.GetFragmentDataPtr(entity_handle) + ->SetPosition(FVector{ + Position.X, + Position.Y, + Position.Z, + }); } } -auto UEcsactEntityMassSpawner::Spawn( - ecsact_entity_id Entity, - const FExampleFpsPosition& Position +auto UEcsactEntityMassSpawner::InitToggle_Implementation( // + int32 Entity, + FExampleFpsToggle Toggle ) -> void { - UWorld* world = GetWorld(); - - const FMassEntityTemplate& EntityTemplate = - MassEntityConfigAsset->GetOrCreateEntityTemplate(*world); - - auto MassSpawner = world->GetSubsystem(); - auto MassEntity = world->GetSubsystem(); - - FMassEntityManager& EntityManager = MassEntity->GetMutableEntityManager(); - auto& DefaultBuffer = EntityManager.Defer(); - - TArray NewEntityHandles; - - MassSpawner->SpawnEntities(EntityTemplate, 1, NewEntityHandles); - for(auto& EntityHandle : NewEntityHandles) { - check(EntityHandle.IsValid()); - check(EntityHandle.IsSet()); - auto vec = FVector{Position.X, Position.Y, Position.Z}; - - auto* TransformFragment = - EntityManager.GetFragmentDataPtr(EntityHandle); - - auto Transform = FTransform(vec); - TransformFragment->SetTransform(Transform); - - DefaultBuffer.PushCommand( - EntityHandle, - std::move(Transform) - ); - - UE_LOG( - LogTemp, - Warning, - TEXT("Entity Manager spawning entity at %f %f"), - Position.X, - Position.Y - ); - - EntityManager.AddFragmentToEntity( - EntityHandle, - FEcsactEntityFragment::StaticStruct(), - [Entity](void* fragment, const UScriptStruct& FragmentType) { - FEcsactEntityFragment* EntityFragment = - static_cast(fragment); - EntityFragment->SetId(static_cast(Entity)); - } - ); - - EntityManager.AddFragmentToEntity( - EntityHandle, - FEcsactPositionFragment::StaticStruct(), - [&vec](void* fragment, const UScriptStruct& FragmentType) { - auto* PositionFragment = - static_cast(fragment); - PositionFragment->SetPosition(vec); - } - ); - - EntityManager.AddFragmentToEntity( - EntityHandle, - FEcsactStreamFragment::StaticStruct(), - [this](void* fragment, const UScriptStruct& FragmentType) { - auto* StreamFragment = static_cast(fragment); - StreamFragment->SetStream(StreamEntities); - } - ); - } - - MassEntities.Add(Entity, NewEntityHandles); - EntityPools.Remove(Entity); + UpdateToggle_Implementation(Entity, Toggle); } auto UEcsactEntityMassSpawner::UpdateToggle_Implementation( // int32 Entity, FExampleFpsToggle Toggle ) -> void { + checkSlow(MassEntities.Contains(static_cast(Entity))); + if(!StreamEntities) { return; } auto* world = GetWorld(); - auto MassEntitySubsystem = world->GetSubsystem(); - - auto& EntityManager = MassEntitySubsystem->GetMutableEntityManager(); + auto& entity_manager = + world->GetSubsystem()->GetMutableEntityManager(); - auto EcsactEntity = static_cast(Entity); - auto EntityHandles = MassEntities.Find(EcsactEntity); - if(!EntityHandles) { - UE_LOG(LogTemp, Error, TEXT("Unknown Entity not found (Mass Spawner)")); - return; - } + auto entity_handles = + MassEntities.FindChecked(static_cast(Entity)); - for(auto Entity : *EntityHandles) { + for(auto Entity : entity_handles) { if(Toggle.Streaming) { - EntityManager.Defer().AddTag(Entity); + entity_manager.Defer().AddTag(Entity); } else { - EntityManager.Defer().RemoveTag(Entity); + entity_manager.Defer().RemoveTag(Entity); } } } diff --git a/unreal-cpp-net-fps/SystemImpls/generated/EcsactUnrealFps.ecsact.hh b/unreal-cpp-net-fps/SystemImpls/generated/EcsactUnrealFps.ecsact.hh index e1c0b20..3eb2b68 100644 --- a/unreal-cpp-net-fps/SystemImpls/generated/EcsactUnrealFps.ecsact.hh +++ b/unreal-cpp-net-fps/SystemImpls/generated/EcsactUnrealFps.ecsact.hh @@ -45,11 +45,11 @@ struct Position { float z; auto operator<=>(const example::fps::Position&) const = default; }; -struct MassEntity { +struct Enemy { static constexpr bool transient = false; static constexpr bool has_assoc_fields = false; static constexpr auto id = static_cast(6); - auto operator<=>(const example::fps::MassEntity&) const = default; + auto operator<=>(const example::fps::Enemy&) const = default; }; struct Velocity { static constexpr bool transient = false; From 1fb3e9cc7d83c6585928b29495daf439af71bc10 Mon Sep 17 00:00:00 2001 From: Ezekiel Warren Date: Thu, 12 Dec 2024 22:41:46 -0800 Subject: [PATCH 04/15] chore: add missing position fragment init --- .../Source/EcsactUnrealFps/EcsactEntityMassSpawner.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/unreal-cpp-net-fps/Source/EcsactUnrealFps/EcsactEntityMassSpawner.cpp b/unreal-cpp-net-fps/Source/EcsactUnrealFps/EcsactEntityMassSpawner.cpp index f435b34..37da115 100644 --- a/unreal-cpp-net-fps/Source/EcsactUnrealFps/EcsactEntityMassSpawner.cpp +++ b/unreal-cpp-net-fps/Source/EcsactUnrealFps/EcsactEntityMassSpawner.cpp @@ -135,7 +135,11 @@ auto UEcsactEntityMassSpawner::InitPosition_Implementation( for(auto entity_handle : entity_handles) { entity_manager.Defer().PushCommand( entity_handle, - FEcsactPositionFragment() + FEcsactPositionFragment{FVector{ + Position.X, + Position.Y, + Position.Z, + }} ); } } From 7103467a79d1992b6d9413e258f9c52bdcfce1e9 Mon Sep 17 00:00:00 2001 From: Ezekiel Warren Date: Fri, 13 Dec 2024 10:04:52 -0800 Subject: [PATCH 05/15] chore: add some error logs when entities are bad, yo --- .../EcsactEntityMassSpawner.cpp | 44 +++++++++++++++++-- .../EcsactUnrealFps/EcsactEntityMassSpawner.h | 2 + 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/unreal-cpp-net-fps/Source/EcsactUnrealFps/EcsactEntityMassSpawner.cpp b/unreal-cpp-net-fps/Source/EcsactUnrealFps/EcsactEntityMassSpawner.cpp index 37da115..a2e0c0a 100644 --- a/unreal-cpp-net-fps/Source/EcsactUnrealFps/EcsactEntityMassSpawner.cpp +++ b/unreal-cpp-net-fps/Source/EcsactUnrealFps/EcsactEntityMassSpawner.cpp @@ -63,11 +63,33 @@ auto UEcsactEntityMassSpawner::CreateMassEntities(int count) -> void { } } +auto UEcsactEntityMassSpawner::CheckMassEntities( + int32 Entity, + const TCHAR* EventName +) -> bool { + // TODO: We really shouldn't need to do this test, but in some cases create + // entity event doesn't happen + auto has_entity_handles = + MassEntities.Contains(static_cast(Entity)); + if(!has_entity_handles) { + UE_LOG( + LogTemp, + Error, + TEXT("EntityCreated event did not happen for entity %i - Ignoring %s"), + Entity, + EventName + ); + } + return has_entity_handles; +} + auto UEcsactEntityMassSpawner::EntityCreated_Implementation( // int32 Entity ) -> void { checkSlow(!MassEntities.Contains(static_cast(Entity))); + UE_LOG(LogTemp, Log, TEXT("Create Entity %i"), Entity); + auto* world = GetWorld(); const FMassEntityTemplate& entity_template = @@ -97,7 +119,9 @@ auto UEcsactEntityMassSpawner::EntityCreated_Implementation( // auto UEcsactEntityMassSpawner::EntityDestroyed_Implementation( // int32 Entity ) -> void { - checkSlow(MassEntities.Contains(static_cast(Entity))); + if(!CheckMassEntities(Entity, TEXT("EntityDestroyed"))) { + return; + } auto* world = GetWorld(); auto& entity_manager = @@ -118,13 +142,18 @@ auto UEcsactEntityMassSpawner::InitEnemy_Implementation( int32 Entity, FExampleFpsEnemy Enemy ) -> void { + if(!CheckMassEntities(Entity, TEXT("InitEnemy"))) { + return; + } } auto UEcsactEntityMassSpawner::InitPosition_Implementation( int32 Entity, FExampleFpsPosition Position ) -> void { - checkSlow(MassEntities.Contains(static_cast(Entity))); + if(!CheckMassEntities(Entity, TEXT("InitPosition"))) { + return; + } auto* world = GetWorld(); auto& entity_manager = @@ -148,7 +177,9 @@ auto UEcsactEntityMassSpawner::UpdatePosition_Implementation( int32 Entity, FExampleFpsPosition Position ) -> void { - checkSlow(MassEntities.Contains(static_cast(Entity))); + if(!CheckMassEntities(Entity, TEXT("UpdatePosition"))) { + return; + } auto* world = GetWorld(); auto& entity_manager = @@ -170,6 +201,9 @@ auto UEcsactEntityMassSpawner::InitToggle_Implementation( // int32 Entity, FExampleFpsToggle Toggle ) -> void { + if(!CheckMassEntities(Entity, TEXT("InitToggle"))) { + return; + } UpdateToggle_Implementation(Entity, Toggle); } @@ -177,7 +211,9 @@ auto UEcsactEntityMassSpawner::UpdateToggle_Implementation( // int32 Entity, FExampleFpsToggle Toggle ) -> void { - checkSlow(MassEntities.Contains(static_cast(Entity))); + if(!CheckMassEntities(Entity, TEXT("UpdateToggle"))) { + return; + } if(!StreamEntities) { return; diff --git a/unreal-cpp-net-fps/Source/EcsactUnrealFps/EcsactEntityMassSpawner.h b/unreal-cpp-net-fps/Source/EcsactUnrealFps/EcsactEntityMassSpawner.h index c3ea331..57d88cb 100644 --- a/unreal-cpp-net-fps/Source/EcsactUnrealFps/EcsactEntityMassSpawner.h +++ b/unreal-cpp-net-fps/Source/EcsactUnrealFps/EcsactEntityMassSpawner.h @@ -24,6 +24,8 @@ class UEcsactEntityMassSpawner : public UExampleFpsEcsactRunnerSubsystem { TMap> MassEntities; + auto CheckMassEntities(int32 Entity, const TCHAR* EventName) -> bool; + public: UFUNCTION(BlueprintCallable) void CreateMassEntities(int count); From 53f7fc2a895e51f81956d6892b3cbf3fc999e574 Mon Sep 17 00:00:00 2001 From: Ezekiel Warren Date: Fri, 13 Dec 2024 10:33:55 -0800 Subject: [PATCH 06/15] feat: enemy state tree processor and early register --- .../Processors/EnemyStateTreeProcessor.cpp | 42 +++++++++++++++++++ .../Processors/EnemyStateTreeProcessor.h | 22 ++++++++++ .../Processors/LerpPositionProcessor.cpp | 4 +- .../Processors/StreamPositionProcessor.cpp | 4 +- .../Processors/SyncPositionProcessor.cpp | 4 +- 5 files changed, 67 insertions(+), 9 deletions(-) create mode 100644 unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/EnemyStateTreeProcessor.cpp create mode 100644 unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/EnemyStateTreeProcessor.h diff --git a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/EnemyStateTreeProcessor.cpp b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/EnemyStateTreeProcessor.cpp new file mode 100644 index 0000000..331be46 --- /dev/null +++ b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/EnemyStateTreeProcessor.cpp @@ -0,0 +1,42 @@ +#include "EnemyStateTreeProcessor.h" +#include "EcsactUnrealFps/Fragments/EcsactFragments.h" +#include "MassCommonFragments.h" +#include "MassExecutionContext.h" +#include "MassSignalSubsystem.h" +#include "MassRequirements.h" +#include "MassStateTreeFragments.h" +#include "MassStateTreeTypes.h" + +UEnemyStateTreeProcessor::UEnemyStateTreeProcessor() : EntityQuery(*this) { +} + +auto UEnemyStateTreeProcessor::ConfigureQueries() -> void { + using EMassFragmentAccess::ReadOnly; + using EMassFragmentAccess::ReadWrite; + using EMassFragmentPresence::All; + using EMassFragmentPresence::None; + + EntityQuery.AddSubsystemRequirement(ReadWrite); + EntityQuery // + .AddRequirement(ReadWrite, All) + .AddTagRequirement(None); +} + +auto UEnemyStateTreeProcessor::Execute( + FMassEntityManager& EntityManager, + FMassExecutionContext& Context +) -> void { + EntityQuery.ForEachEntityChunk( + EntityManager, + Context, + [](FMassExecutionContext& Context) { + auto& mass_signal = + Context.GetMutableSubsystemChecked(); + + mass_signal.SignalEntities( + UE::Mass::Signals::StateTreeActivate, + Context.GetEntities() + ); + } + ); +} diff --git a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/EnemyStateTreeProcessor.h b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/EnemyStateTreeProcessor.h new file mode 100644 index 0000000..298fd6c --- /dev/null +++ b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/EnemyStateTreeProcessor.h @@ -0,0 +1,22 @@ +#pragma once + +#include "CoreMinimal.h" +#include "MassProcessor.h" +#include "EnemyStateTreeProcessor.generated.h" + +UCLASS() + +class ECSACTUNREALFPS_API UEnemyStateTreeProcessor : public UMassProcessor { + GENERATED_BODY() // nolint + +protected: + UEnemyStateTreeProcessor(); + + auto ConfigureQueries() -> void override; + auto Execute( + FMassEntityManager& EntityManager, + FMassExecutionContext& Context + ) -> void override; + + FMassEntityQuery EntityQuery; +}; diff --git a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/LerpPositionProcessor.cpp b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/LerpPositionProcessor.cpp index 8d85b48..6375b78 100644 --- a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/LerpPositionProcessor.cpp +++ b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/LerpPositionProcessor.cpp @@ -6,7 +6,7 @@ #include "MassRequirements.h" #include "Math/UnrealMathUtility.h" -ULerpPositionProcessor::ULerpPositionProcessor() { +ULerpPositionProcessor::ULerpPositionProcessor() : EntityQuery(*this) { ProcessingPhase = EMassProcessingPhase::PostPhysics; } @@ -19,8 +19,6 @@ auto ULerpPositionProcessor::ConfigureQueries() -> void { .AddRequirement(ReadWrite, All) .AddRequirement(ReadOnly, All) .AddConstSharedRequirement(All); - - RegisterQuery(EntityQuery); } auto ULerpPositionProcessor::Execute( diff --git a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/StreamPositionProcessor.cpp b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/StreamPositionProcessor.cpp index 51fb72e..f627b2e 100644 --- a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/StreamPositionProcessor.cpp +++ b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/StreamPositionProcessor.cpp @@ -7,7 +7,7 @@ #include "MassExecutionContext.h" #include "MassRequirements.h" -UStreamPositionProcessor::UStreamPositionProcessor() { +UStreamPositionProcessor::UStreamPositionProcessor() : EntityQuery(*this) { ProcessingPhase = EMassProcessingPhase::PostPhysics; } @@ -20,8 +20,6 @@ auto UStreamPositionProcessor::ConfigureQueries() -> void { .AddRequirement(ReadOnly, All) .AddRequirement(ReadOnly, All) .AddTagRequirement(All); - - RegisterQuery(EntityQuery); } auto UStreamPositionProcessor::Execute( diff --git a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/SyncPositionProcessor.cpp b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/SyncPositionProcessor.cpp index d674c69..a236d80 100644 --- a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/SyncPositionProcessor.cpp +++ b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/SyncPositionProcessor.cpp @@ -8,7 +8,7 @@ #include "MassExecutionContext.h" #include "MassRequirements.h" -USyncPositionProcessor::USyncPositionProcessor() { +USyncPositionProcessor::USyncPositionProcessor() : EntityQuery(*this) { ProcessingPhase = EMassProcessingPhase::PostPhysics; } @@ -22,8 +22,6 @@ auto USyncPositionProcessor::ConfigureQueries() -> void { .AddRequirement(ReadOnly, All) .AddRequirement(ReadWrite, All) .AddTagRequirement(None); - - RegisterQuery(EntityQuery); } auto USyncPositionProcessor::Execute( From 197e5370bd9b620dfc37e6f839a87029b7af82a7 Mon Sep 17 00:00:00 2001 From: Ezekiel Warren Date: Fri, 13 Dec 2024 10:36:13 -0800 Subject: [PATCH 07/15] fix: bad display name for lerp trait --- .../Source/EcsactUnrealFps/Traits/LerpPositionTrait.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Traits/LerpPositionTrait.h b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Traits/LerpPositionTrait.h index 12a0e15..51160df 100644 --- a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Traits/LerpPositionTrait.h +++ b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Traits/LerpPositionTrait.h @@ -4,17 +4,17 @@ #include "EcsactUnrealFps/Fragments/LerpPositionParameters.h" #include "LerpPositionTrait.generated.h" -UCLASS(meta = (DisplayName = "Avoidance")) +UCLASS(meta = (DisplayName = "Lerp Position")) + class ECSACTUNREALFPS_API ULerpPositionTrait : public UMassEntityTraitBase { GENERATED_BODY() protected: - auto BuildTemplate( // FMassEntityTemplateBuildContext& BuildContext, - const UWorld& World + const UWorld& World ) const -> void override; - UPROPERTY(EditAnywhere, Category="") + UPROPERTY(EditAnywhere, Category = "") FLerpPositionParameters PositionParameters; }; From e12a27c5b332aa0e31080403db2e2989f0dc96b1 Mon Sep 17 00:00:00 2001 From: Ezekiel Warren Date: Fri, 13 Dec 2024 12:07:31 -0800 Subject: [PATCH 08/15] fix: defer more stuff I guess + remove state stuff for debug --- .../EcsactEntityMassSpawner.cpp | 36 +++++++++---------- .../Processors/LerpPositionProcessor.cpp | 4 +-- .../EcsactUnrealFps/Tasks/EcsactIdle.cpp | 20 +++++------ .../EcsactUnrealFps/Tasks/FollowPlayer.cpp | 19 +++++----- 4 files changed, 38 insertions(+), 41 deletions(-) diff --git a/unreal-cpp-net-fps/Source/EcsactUnrealFps/EcsactEntityMassSpawner.cpp b/unreal-cpp-net-fps/Source/EcsactUnrealFps/EcsactEntityMassSpawner.cpp index a2e0c0a..569b87d 100644 --- a/unreal-cpp-net-fps/Source/EcsactUnrealFps/EcsactEntityMassSpawner.cpp +++ b/unreal-cpp-net-fps/Source/EcsactUnrealFps/EcsactEntityMassSpawner.cpp @@ -88,11 +88,9 @@ auto UEcsactEntityMassSpawner::EntityCreated_Implementation( // ) -> void { checkSlow(!MassEntities.Contains(static_cast(Entity))); - UE_LOG(LogTemp, Log, TEXT("Create Entity %i"), Entity); - auto* world = GetWorld(); - const FMassEntityTemplate& entity_template = + const auto& entity_template = MassEntityConfigAsset->GetOrCreateEntityTemplate(*world); auto new_entity_handles = TArray{}; @@ -102,14 +100,9 @@ auto UEcsactEntityMassSpawner::EntityCreated_Implementation( // mass_spawner->SpawnEntities(entity_template, 1, new_entity_handles); for(auto entity_handle : new_entity_handles) { - entity_manager.AddFragmentToEntity( + entity_manager.Defer().PushCommand( entity_handle, - FEcsactEntityFragment::StaticStruct(), - [Entity](void* fragment, const UScriptStruct&) { - static_cast(fragment)->SetId( - static_cast(Entity) - ); - } + FEcsactEntityFragment{static_cast(Entity)} ); } @@ -188,12 +181,17 @@ auto UEcsactEntityMassSpawner::UpdatePosition_Implementation( const auto& entity_handles = MassEntities.FindChecked(static_cast(Entity)); for(auto entity_handle : entity_handles) { - entity_manager.GetFragmentDataPtr(entity_handle) - ->SetPosition(FVector{ - Position.X, - Position.Y, - Position.Z, - }); + entity_manager.Defer().PushCommand( + [entity_handle, Position](FMassEntityManager& entity_manager) { + entity_manager + .GetFragmentDataPtr(entity_handle) + ->SetPosition(FVector{ + Position.X, + Position.Y, + Position.Z, + }); + } + ); } } @@ -226,11 +224,11 @@ auto UEcsactEntityMassSpawner::UpdateToggle_Implementation( // auto entity_handles = MassEntities.FindChecked(static_cast(Entity)); - for(auto Entity : entity_handles) { + for(auto entity_handle : entity_handles) { if(Toggle.Streaming) { - entity_manager.Defer().AddTag(Entity); + entity_manager.Defer().AddTag(entity_handle); } else { - entity_manager.Defer().RemoveTag(Entity); + entity_manager.Defer().RemoveTag(entity_handle); } } } diff --git a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/LerpPositionProcessor.cpp b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/LerpPositionProcessor.cpp index 6375b78..1aba96c 100644 --- a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/LerpPositionProcessor.cpp +++ b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/LerpPositionProcessor.cpp @@ -7,7 +7,7 @@ #include "Math/UnrealMathUtility.h" ULerpPositionProcessor::ULerpPositionProcessor() : EntityQuery(*this) { - ProcessingPhase = EMassProcessingPhase::PostPhysics; + ProcessingPhase = EMassProcessingPhase::PrePhysics; } auto ULerpPositionProcessor::ConfigureQueries() -> void { @@ -49,7 +49,7 @@ auto ULerpPositionProcessor::Execute( Context.GetDeltaTimeSeconds() * lerp_pos_params.Speed ); - transform.SetLocation(new_location); + // transform.SetLocation(new_location); } } ); diff --git a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Tasks/EcsactIdle.cpp b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Tasks/EcsactIdle.cpp index ed6be3d..808b7a9 100644 --- a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Tasks/EcsactIdle.cpp +++ b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Tasks/EcsactIdle.cpp @@ -10,16 +10,16 @@ EStateTreeRunStatus FEcsactIdle::EnterState( ) const { UE_LOG(LogTemp, Log, TEXT("Entering Idle State")); - const auto& MassContext = - static_cast(Context); - - auto& MassSignalSubsystem = - Context.GetExternalData(MassSignalSubsystemHandle); - - MassSignalSubsystem.SignalEntity( - UE::Mass::Signals::StateTreeActivate, - MassContext.GetEntity() - ); + // const auto& MassContext = + // static_cast(Context); + + // auto& MassSignalSubsystem = + // Context.GetExternalData(MassSignalSubsystemHandle); + // + // MassSignalSubsystem.SignalEntity( + // UE::Mass::Signals::StateTreeActivate, + // MassContext.GetEntity() + // ); EStateTreeRunStatus Status = EStateTreeRunStatus::Running; diff --git a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Tasks/FollowPlayer.cpp b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Tasks/FollowPlayer.cpp index 193b645..500be72 100644 --- a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Tasks/FollowPlayer.cpp +++ b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Tasks/FollowPlayer.cpp @@ -16,16 +16,15 @@ EStateTreeRunStatus FFollowPlayer::EnterState( ) const { UE_LOG(LogTemp, Log, TEXT("Entering Follow Player State")); - const auto& MassContext = - static_cast(Context); - - auto& MassSignalSubsystem = - Context.GetExternalData(MassSignalSubsystemHandle); - - MassSignalSubsystem.SignalEntity( - UE::Mass::Signals::StateTreeActivate, - MassContext.GetEntity() - ); + // auto& MassContext = static_cast(Context); + // + // auto& MassSignalSubsystem = + // Context.GetExternalData(MassSignalSubsystemHandle); + // + // MassSignalSubsystem.SignalEntity( + // UE::Mass::Signals::StateTreeActivate, + // MassContext.GetEntity() + // ); auto& MoveTarget = Context.GetExternalData(MoveTargetHandle); MoveTarget.CreateNewAction(EMassMovementAction::Move, *Context.GetWorld()); From 44bba0b7985616612b5d66f7b9385988a3e6cb8e Mon Sep 17 00:00:00 2001 From: Ezekiel Warren Date: Fri, 13 Dec 2024 13:09:21 -0800 Subject: [PATCH 09/15] feat: its kind of working --- .../EntityMass/EcasctMassStateTree.uasset | Bin 50576 -> 31026 bytes .../EntityMass/EcsactMassConfig.uasset | Bin 6325 -> 6754 bytes unreal-cpp-net-fps/Plugins/Ecsact | 2 +- .../EcsactEntityMassSpawner.cpp | 4 +-- .../Fragments/EcsactFragments.h | 19 --------------- .../Processors/EnemyStateTreeProcessor.cpp | 2 +- .../Processors/LerpPositionProcessor.cpp | 5 +++- .../EcsactUnrealFps/Tasks/EcsactIdle.cpp | 16 ------------ .../Source/EcsactUnrealFps/Tasks/EcsactIdle.h | 6 ++--- .../EcsactUnrealFps/Tasks/EcsactTracker.cpp | 15 +----------- .../EcsactUnrealFps/Tasks/EcsactTracker.h | 5 ++-- .../EcsactUnrealFps/Tasks/FollowPlayer.cpp | 23 +++++++----------- .../EcsactUnrealFps/Tasks/FollowPlayer.h | 4 +-- 13 files changed, 23 insertions(+), 78 deletions(-) diff --git a/unreal-cpp-net-fps/Content/EntityMass/EcasctMassStateTree.uasset b/unreal-cpp-net-fps/Content/EntityMass/EcasctMassStateTree.uasset index 54576b33c8750cfe006963a5bbdef4fef6983770..f20dc369ffb36aac01f486d7c4848668fde904b4 100644 GIT binary patch literal 31026 zcmeG_2Yi&p@|#c$y$A}15_%7WW(Y;n2`zzyqEC@a?jt$4+=aVKAjks(C`b_$MNkpx z2u~@36hT2j6s0#s0sH#{59I+81%&_3ezV{0*HRBeAL}pK@0*#Oot>GTncchK&PmN* z|LxYTTSH3_Qr4f4E3}7JivEnN-t+z?ao-*5GP&=}kk@a{160WU-!*LzTQ|Xat#VGC zla*y4VJH-Q!i5`nbN^(gO z@F4%DKU-l2+OF3mgpMa2+J?3bZy(b!s$En_OsCLxq0y0TV>*OHwQCpAv2BOYHVW~5 z^l3>gLaN~4cj__}IYWE+`Gx)*$ez1p_eaP3ENIfSy1mBtrw6z%TzcsG$sU^;FU&1p zcFET;H}C;LN|BQEzeXqP_*bQGEm{nS?$DyYWS1;%sa1#I_6$$Rp!UD{8RO|7)}9>I zKL#FZ2N7~}Gy^HWsBvzAmX1(}f3^Z9)#zjE#1xk;!_zv_>5>M-Cl8lWJmj~yR^6%# zI8bbc-IASTNp+Ll0iUJU5s->*qpdcN)78gm&9qD8`PI&kY8X)=zSI44YKzk+dR&<) zo&g@4-A&r|e8N+Kjtx)o*s`P;7oFWro_+N3%jE#*a#^zD99C&GY3$naWJL~2a5*z1 zmnWOVt^M8x#z~HnKxCK2;kJ2fP6t^#>&-=F01@r5B-kDC3#!krbXk^=-8S zv=fq)FI}Z<5@o>wlJl}b76Sj8F+ub)ao%~ofDu=Dz znUT%>fx4w*MVmRG%g9U@RhlOujqY2r{ml?3%oJtuSWw!lzn9PAphRb;D@7ugS|rYc zl}Aa#ESYvsoWt$0I8r1SM2EC?Yr-n%FpmXwb}F{rvtWFM_mOXIw5~(}(J5|f z(ICn@WrXA+pI!Ux83txso$n2D^GN^pusAsaum$YsvEmSzj)r5&<<+>X-c2KyoB_!- zNYL?;UVWMcauhRnjKxMbtKb#4stn)%kO2vJzw9?_|Cx{tMu~Q0rYjEK@5F-c5UZmZ zS4ajRKSTmK_MlY)M@>A}JYTY~Fl9}oW?wnPPgOOhPg{2^Lm%1Wd%yk$R;N)d{BdCX@L+tOg2|ic7{Yo zS0B+$y%IG6osVS_)ecKrS|cFuL9upcvc;|%)Zm5k+k^8EB`i!Hq?nrRg{FZYf35aB zt$cl*4vC~b+W5n~B}z=ADZ#3eC}`lG;rR@L+D9!=w;jhSKzQ(~zJI0zESl*iRibz9 zq~(nfE-Xymi=^S;_-E8!po|X-8{lwD9x|`TsSav3iL|JpW^g)UEOvV`jVI)CnHF}n zuj**R!kB8#tZH&x?JLax6!Hiu&5~tvy2y})mD+SxNpDO@cZ(}q9?mmaoFm?4rJK|J z$?&df<;cDco_4D0gZtDAS8&wyE@x&2IXFFjuPF(mCRaLKd%3nsNN?;;aM5MZ0+y_Q zZhUrwe27pxr}l2iQU?ep(?S@_HXW^-zu3bj(_Lk1Vhax&*siK$TaXyT8XNP}lzWyCC+ z$;EBfz8$SVMwytwAotci8TEK60B72)Br@Sz8c2fmOaJpfKMIGpxG3V;vino^Xr+(W zhrz6IQAy6eG^zBlWMn|KOseEG4TAFuAeaIa%*eho%0RIQ7_ZG0bcKw-NGN>dL_T!U zystrPJmisBTgo=595?_1DN1L8o3ONCuoGv?el@`Is&cD>|MdCCv*iAN046Ov7 z%ef>;vfNO)Fsvt(3r#jQSuq^!8Appve&ktpq+?HrIlV1z59~)Mn7p+Z zJ>8^{tFNA7@&XC$Krjl8_Qw)-wF7RwZH^I=HQWuMyZgrx4|rj+`&R3+J{$}IbS&rA zKj1eXe5EffX-KEqp6zdcB(eF$e?-&P^U@y}zZ3o7#8{tC#Eq`-hI8 zfr4~Ce`E%%L*Bv(PCS#A3U;pty((e9%(%MsVMpO3-g7*76ogVV&<=lg8Bz+MUmr~tt9Y~*; zk|w2F$gTILj{-3hrPOp5_UcRzKLh()pMlq>zda9js6nlAF!w?u##BE44D7US6D(D( z_GeVT)P9cNF#C{2WqR zTmAtDH3h9(>e71ECPU!TQ9$7@@5-aK?~7AOz?r}XAfH@$ay{&O9Rp!|kP3m9#+VYJ zOOxQ5B^RqKFYl!!Te_s2ibpN`V*>0!Wj6MJo;0P)j3$sW)JV_<5>$S_Qy_C|a5Yde zqlAb`UjO#Z?)tI9er)jSx#e(#P?$roak?xndKKpOUK@-byRw1GN4y!9d~*J^i$N+D z^j1U;LwJM0n3JvTj}eu&yga<7^(^exOJz7O5e6Hw{P%`e|5?sL2)pQ` zWyH{BbH0(?9Pr$#qz!rP2BXWQH&+tflDQL7) zySms7nvBBS`_r;t;vhK4rO)mT!>2$XXK(Ut1bKL7d6-eDgLZdXahA=X)v=qDy0x`2 zLn*q$<+@4SLtV~7bn$A!P1a@P9)W!&z~nhodycORix&`3tBgKA>==YMSh;efA~)WR ztHm^c<#}kerO!eDXNh=1jO#n#G=LTaAsrPpZjK+?hJ)L+RluiPhV}$y45U{95?S0P zWF?qrAU&AT!_)FwC9RFeP_0cbZCqNR_2Md@e7k(Pw z4}7VQu*6)rk;;=wwWitOpEOkk4sZELZq&5mMit{0vDoWfp>wcy z9Q$kZgND=i{51Ar$LZkXqEiK+%cXQb7Le|z0?=Kh{JzVB?hN9@&S)JX-O&Q_J61qC z`Ux6q`OOPQFla_}-^wWRJ6-RW_?#cz$pX@SRY1D03qS|2tgaHF@il2 zQcIW*)+4S7Nm!@lCF1z{u4{Dg1rkAKZPZzQ86wOl@_=V?{=DX^hrIp}<^%ivh&;f4 zt6@^wCOt0qM!&_i<~836v_Bs)6^R;FI7hIq*i?M7d+X0Y_d!zr&%rO+d^sVMxX^Sm`4K1*q3KZmBt*s-q4t`g2-$6h z@*^`5B`$3>!VGnJ$eb6~<#Yp{OHFug1Nf_m?0`MkraL}sg@7YFZ_F!V@Xgf7ge*c5 zagjxH81Ihci=Ae`yJ!mZI%8YpDXtatK*(oi7{8CrFvMks@!O9WQSW^bmapF9k#N2N zlimeo(gz74?n8sCKUL#}ffiAj>pus{bVxz>usjd!r#}?!tc&R z3x6fG-DT0j&xg^%Pgx}Q*1JAh_?bov|6)ap1l)yxa4k*#u@^;!We`#w zF{TOYo(KI29yAcGQY;TjL?%PA7k$}x9^{v=2TjKL#c+^`FmOVMRlLQ}lXd_*XZ=dA z9xl_Li}dFNC98%cI!Dbxgf3zQBcdGk;{2--2J?%jI(GgtJu`SKs%)R}^cGDc4R|g! zk+b;ckO<`L#NNQm7a0gtv^efn91pO(FPdpnP%__{)@ugenYM&dPu*G5hUaHm|JzzL za??KLPe?@+MZ0UsR78kD32w2VX*Z>qwOE3EM5ZGxu^)pybTF8Zf3V;5S_yk93c)+n z{MmQGK{?p#B1;uS_|E;jrMK_g&+F26*8QSUJAZOyX@X*iVgFwU(|TA%NIOp&3|M6{ zpbR99QiwFG&fhc{tR%eV`7qd;AA@#CAg-~vsjdBO;Q3GFDQ?GDlf~(Fdk-Z1yf{qa zaPW)1VA9Zl$)gC@+N&`9-Yj}l`gbYN{3vAw334 zNFip!d+9@cNokfoVyW%|*p~l=ri?K9RvERaOBwf@A$WkRxP=6$AlQmHM7X z#fgFSZG>r~@sa8j@j4>SG`+-R@MC2ug4X6_I}%L69y%q?=O9pbPUGph5fX^l62GmvgoEN6l~?1eU5?`oCztb64DdTl=R+w3D!Ots<}E*ZtR%M5pWKi>Xx4EI6gwleF- zFX{gNdt1i6(ynZ5zJ`OtvFVOW`BMxRp5KnUy;sb|GF*{E<(jmKS$l4l={axmuGoY{ z7xw3CI4Ds~4aYB$@9d6?&d+eZ43$34aSx;Hg$|XiwrqG}=js*RH}=krdcWM|s@!l5 zkfB*wx(4xnv@e0r&?Q0X38mae@J5?hCM}Ucx@(Coz{Rv2y0^!?@BHLmNa6m$`(pWt&qIo-pZF<%V59RB@0SZ-Krb^3Nb5{u%dpZE0Rd?N z5{Ln5t;wJZ0Ds?LK%NIZ?RjAyGB6W)J>s=7G!@_g%t3;Q*h9x|8)hK@)&}h-Y?jpSZ5Q%P|cC;nJUr9T!8EEJB-I7G$Y)$?b|T?p4fNV`Jdb{eB-2}vzf*HjfmIA{cZq&R5bz##9*9f=4smSm#(9u zq+Z)fK9sPb&WXh~0|rIAMAc`CLJPO-UxQx^Z}wW$_HUnGa;w?yioO%hTSao!+i~G$ zs(%adTGfBW0fka{u7cf#paUuex~d@^@(o78Xwu*WAo9EMy6ffSJ8K?^MPONjEHY}{E?o0W;BVq)F>foucdmp3bzeD{ zWx(+;!oUN?R)fBhUc~&xUt1lB_?Z@5FyEd zH}XMjOb(8mh&^habB`&u$|v9qqQ(Z9h@?wpgOP9%_TsuWm@sJoa3>-UU=JNLsP}xr zbynyVYHuiJ;~e4&`youLblnOQk&u_|y853v041^@A#_T)-U5M!$RI?TIr}oigN&lH zE8MFifmSBvU!Hl;Udhh$u{uc@D-H4Jh<=Nuhng?2A{0W~9CB z-GzE6tnpHKq?&{b#4TfX5@k0N@R#vLyO~JAsqWoOP(|o=cob?UkC3#;z*fErZ+zPo z=4Jx^awGu0J_KQGmVt10(FKII%)LVw5JjydZbH!p5w}>C#6Yyky-ETV0UCwK$D3{! z-j$Vv_Tz-VTP1O^D9LCNtwjRhpJ=&#|JVb+O>BwoGKRYi?ZK1&djHsix2(N)l~2tl zhJprQ&peVZPmtQaFbOk)P7zT2zaGp2QE3xvXr&r2bV;ebF}*{XG2c@IE7y%yslCtG mN&oN1FJE0YA-MCdLo_ImTA&-DWt|S~S90$M&DtOg=>H%23aT3b literal 50576 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 Kv*!s0@c#p(@0o1? literal 6325 zcmb_h4RBP|6~55Wq*z*&La`!AXyixo10f*^w36M;ZitW{vLS78T6k=3k{37oHt)S8 zM6u8x6e_VrGQ-e_(_w10?NAu(jO{o|>1bz)+8>Lp+BOaiQiVdX{0hSMeD~eEdD(2p zI&w1m?!D)H_ndprIrrzi9Qw{(Ph5({VrxIa*u?RSoud*&Rra&#Gye4rZ{@xtZ3nkL z_jJVy@K3ewKlYxt@7zsqE!g9EE|5Qmc-tm3Hj~PNO0At2_#?J%_f%_^Rp1XG!}mQDqN)im_~}`U)2o78Usx`b$bHioF$O6-A2zWhL`V7L*1`{DGoE zfd+UY)h2v~u{)?lo8Kx#td~mscGJ)5I{eVru5IuhJTU8bxzC6G(zfi|+s+)0Rrc!b-B;<^JrRdVQXgst=YN}VQrHmK6iIOf>EtI!cBH2Ep4q^7PQUIkiE**!xk&N=j%3kk9kZ}>!~x1 zC^zjMR<(anpG4esKFzX&ty+`OZLyOBFFy>X*D%6y|G#fz8eEv@xj`l!hhvJI2<|8$lQlkrD7+vAg2 z`M2HW@K7LZXm*@?<*9+INST1CwX{6^+M`B<^!maSNY=lYZG>b+ZAC)d2Oj=~2eDmY zbkl5x+bMqF?7g>;3@&okp!DZ!e?9Ov*2_rW)oCWJSeu&``}O1BT%9XHL1bB|np>@E z%#FI$sfG3XMfO2Z!L1M*yjU;BSxs{_buC=m#Z6Oh;uh;Tb>KzHcrv-3CK1$IB6_nP zLRPSQGkfR2anQofW_IjFTtA`r?yXj_4nmZ zV5Dk$@<&>9^7}*Oo2w8X#fBx34T95IKf^E(m_YC%kcnIR-w|=l_ zD~=B#u`I)+kt?Aka*VAF-*y610K~}|$F#??xXo7nuy_0nDJ6$%>%x3kE;~8~woU6- z15jacBzCDJ#J@f_=Cr=;ev{tf}N$C!XjDEl%=~Uk&X8W7f1njhH#5hz?~+XF9qC5 z!Yxe!cY<)`DdIdS;=C#1DpJ6`N8`IY1>C!Y`%;RyFJA{&DP<^d3j`&LuN!xl0~-@3 zVPoP{u8n0E*C^0fxO@jTCaxew-24=A3sS@trihboB4f@2FGQeZeyMUq?7x&ROJx0e zxKo5HN)dNY3b_A}K7SJ2LdnAg1@u3{l{(d7^Egj@$rdku$a(k$``|lLfH#=YNcj+Q zQWe*gh(9VB=Mpewl8;;epJcrQ=jJE`d&=hZ?7brUUvSw&9I^J%AM@KG1M9(EV-!-h zAikcx?@L`*66`Ide##z>2+$FnL8DNr_Aq|X#7ney^ak4dAilWe zkO@I>v6p^{^Sg2b?R}VF4`-vYr}kxWzZ4@H`2bcCsfv$X@l-iWRxY|Qm>Wc13L5QJxHNZ7cemcodejkRKPt&i4Zk}D-x!7a3RT^C1Zt{h6wvvpgd$#zHb@gB@ zlk_o$Yb7YnDZ~V3yXtBS#)#y1Iw9?+X9ha`o1%fcs-upvW7NiCQGviaog!f*7TdTH z9Y9HiO4Bi%U6XWXEX%7pt5RSm${y~CskMuVAqf>dhRPh-a(g99@=)a9 zfl0t6&;TH2oK8Qyi%p;( void { auto PushAction = example::fps::Push{ .player_id = PlayerId, .radius = 500, - .tick_count = 200, - .force = 2, + .tick_count = 20, + .force = 50, }; runner->PushAction(PushAction); } diff --git a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Fragments/EcsactFragments.h b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Fragments/EcsactFragments.h index 2d450f3..cafa0b0 100644 --- a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Fragments/EcsactFragments.h +++ b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Fragments/EcsactFragments.h @@ -46,25 +46,6 @@ struct ECSACTUNREALFPS_API FEcsactStreamingFragment USTRUCT() -struct ECSACTUNREALFPS_API FEcsactStreamFragment : public FMassFragment { - GENERATED_BODY() // nolint - - FEcsactStreamFragment() = default; - - bool ShouldStream() const { - return should_stream; - } - - void SetStream(const bool ShouldStream) { - should_stream = ShouldStream; - } - -private: - bool should_stream; -}; - -USTRUCT() - struct ECSACTUNREALFPS_API FEcsactPositionFragment : public FMassFragment { GENERATED_BODY() // nolint diff --git a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/EnemyStateTreeProcessor.cpp b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/EnemyStateTreeProcessor.cpp index 331be46..5f745f1 100644 --- a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/EnemyStateTreeProcessor.cpp +++ b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/EnemyStateTreeProcessor.cpp @@ -19,7 +19,7 @@ auto UEnemyStateTreeProcessor::ConfigureQueries() -> void { EntityQuery.AddSubsystemRequirement(ReadWrite); EntityQuery // .AddRequirement(ReadWrite, All) - .AddTagRequirement(None); + .AddTagRequirement(All); } auto UEnemyStateTreeProcessor::Execute( diff --git a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/LerpPositionProcessor.cpp b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/LerpPositionProcessor.cpp index 1aba96c..a120058 100644 --- a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/LerpPositionProcessor.cpp +++ b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/LerpPositionProcessor.cpp @@ -1,4 +1,5 @@ #include "LerpPositionProcessor.h" +#include "EcsactUnrealFps/Fragments/EcsactFragments.h" #include "EcsactUnrealFps/Fragments/LerpPositionFragment.h" #include "EcsactUnrealFps/Fragments/LerpPositionParameters.h" #include "MassCommonFragments.h" @@ -14,10 +15,12 @@ auto ULerpPositionProcessor::ConfigureQueries() -> void { using EMassFragmentAccess::ReadOnly; using EMassFragmentAccess::ReadWrite; using EMassFragmentPresence::All; + using EMassFragmentPresence::None; EntityQuery // .AddRequirement(ReadWrite, All) .AddRequirement(ReadOnly, All) + .AddTagRequirement(None) .AddConstSharedRequirement(All); } @@ -49,7 +52,7 @@ auto ULerpPositionProcessor::Execute( Context.GetDeltaTimeSeconds() * lerp_pos_params.Speed ); - // transform.SetLocation(new_location); + transform.SetLocation(new_location); } } ); diff --git a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Tasks/EcsactIdle.cpp b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Tasks/EcsactIdle.cpp index 808b7a9..c7620f3 100644 --- a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Tasks/EcsactIdle.cpp +++ b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Tasks/EcsactIdle.cpp @@ -39,23 +39,7 @@ void FEcsactIdle::ExitState( UE_LOG(LogTemp, Log, TEXT("Exiting Idle State")); } -// EStateTreeRunStatus FEcsactIdle::Tick( -// FStateTreeExecutionContext& Context, -// const float DeltaTime -// ) const { -// const auto& StreamingFragment = -// Context.GetExternalData(StreamFragmentHandle); -// -// // Assign this to a global tree parameter every single tick -// if(StreamingFragment.ShouldStream()) { -// return EStateTreeRunStatus::Succeeded; -// } -// -// return EStateTreeRunStatus::Running; -// } - bool FEcsactIdle::Link(FStateTreeLinker& Linker) { - Linker.LinkExternalData(StreamFragmentHandle); Linker.LinkExternalData(MassSignalSubsystemHandle); Linker.LinkExternalData(MoveTargetHandle); return FMassStateTreeTaskBase::Link(Linker); diff --git a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Tasks/EcsactIdle.h b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Tasks/EcsactIdle.h index c696038..579df0f 100644 --- a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Tasks/EcsactIdle.h +++ b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Tasks/EcsactIdle.h @@ -11,12 +11,11 @@ #include "EcsactIdle.generated.h" -struct FEcsactStreamFragment; struct FMassMoveTargetFragment; USTRUCT() -struct ECSACTUNREALFPS_API FEcsactIdleInstanceData{ +struct ECSACTUNREALFPS_API FEcsactIdleInstanceData { GENERATED_BODY() // nolint }; @@ -49,7 +48,6 @@ struct ECSACTUNREALFPS_API FEcsactIdle : public FMassStateTreeTaskBase { // ) const override; private: - TStateTreeExternalDataHandle StreamFragmentHandle; - TStateTreeExternalDataHandle MassSignalSubsystemHandle; + TStateTreeExternalDataHandle MassSignalSubsystemHandle; TStateTreeExternalDataHandle MoveTargetHandle; }; diff --git a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Tasks/EcsactTracker.cpp b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Tasks/EcsactTracker.cpp index fea7442..c5243c8 100644 --- a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Tasks/EcsactTracker.cpp +++ b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Tasks/EcsactTracker.cpp @@ -10,8 +10,7 @@ FEcsactTracker::FEcsactTracker() { } bool FEcsactTracker::Link(FStateTreeLinker& Linker) { - Linker.LinkExternalData(StreamFragmentHandle); - return FMassStateTreeTaskBase::Link(Linker); + return Super::Link(Linker); } EStateTreeRunStatus FEcsactTracker::EnterState( @@ -49,17 +48,5 @@ EStateTreeRunStatus FEcsactTracker::Tick( // UE_LOG(LogTemp, Warning, TEXT("No players to follow")); } - const auto& StreamingFragment = Context.GetExternalData(StreamFragmentHandle); - - if(InstanceData.bIsStreaming != StreamingFragment.ShouldStream()) { - UE_LOG( - LogTemp, - Log, - TEXT("Setting Is Stream to: %i"), - StreamingFragment.ShouldStream() - ); - } - InstanceData.bIsStreaming = StreamingFragment.ShouldStream(); - return EStateTreeRunStatus::Running; } diff --git a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Tasks/EcsactTracker.h b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Tasks/EcsactTracker.h index d78c01d..70911a0 100644 --- a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Tasks/EcsactTracker.h +++ b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Tasks/EcsactTracker.h @@ -3,6 +3,8 @@ #include "CoreMinimal.h" #include "EcsactUnrealFps/Fragments/EcsactFragments.h" +#include "MassSignalSubsystem.h" +#include "MassStateTreeTypes.h" #include "MassStateTreeTypes.h" #include "StateTreeEvaluatorBase.h" #include "StateTreeExecutionContext.h" @@ -42,7 +44,4 @@ struct ECSACTUNREALFPS_API FEcsactTracker : public FMassStateTreeTaskBase { FStateTreeExecutionContext& Context, const float DeltaTime ) const override; - -private: - TStateTreeExternalDataHandle StreamFragmentHandle; }; diff --git a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Tasks/FollowPlayer.cpp b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Tasks/FollowPlayer.cpp index 500be72..1ec9a6f 100644 --- a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Tasks/FollowPlayer.cpp +++ b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Tasks/FollowPlayer.cpp @@ -16,15 +16,15 @@ EStateTreeRunStatus FFollowPlayer::EnterState( ) const { UE_LOG(LogTemp, Log, TEXT("Entering Follow Player State")); - // auto& MassContext = static_cast(Context); - // - // auto& MassSignalSubsystem = - // Context.GetExternalData(MassSignalSubsystemHandle); - // - // MassSignalSubsystem.SignalEntity( - // UE::Mass::Signals::StateTreeActivate, - // MassContext.GetEntity() - // ); + auto& MassContext = static_cast(Context); + + auto& MassSignalSubsystem = + Context.GetExternalData(MassSignalSubsystemHandle); + + MassSignalSubsystem.SignalEntity( + UE::Mass::Signals::StateTreeActivate, + MassContext.GetEntity() + ); auto& MoveTarget = Context.GetExternalData(MoveTargetHandle); MoveTarget.CreateNewAction(EMassMovementAction::Move, *Context.GetWorld()); @@ -53,10 +53,6 @@ EStateTreeRunStatus FFollowPlayer::Tick( // FStateTreeExecutionContext& Context, const float DeltaTime ) const { - if(!Context.GetExternalData(StreamFragmentHandle).ShouldStream()) { - return EStateTreeRunStatus::Running; - } - MoveToPlayerPosition(Context); return EStateTreeRunStatus::Running; @@ -81,7 +77,6 @@ void FFollowPlayer::MoveToPlayerPosition( // } bool FFollowPlayer::Link(FStateTreeLinker& Linker) { - Linker.LinkExternalData(StreamFragmentHandle); Linker.LinkExternalData(MoveTargetHandle); Linker.LinkExternalData(TransformHandle); Linker.LinkExternalData(MassSignalSubsystemHandle); diff --git a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Tasks/FollowPlayer.h b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Tasks/FollowPlayer.h index 51153b1..6e95a0f 100644 --- a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Tasks/FollowPlayer.h +++ b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Tasks/FollowPlayer.h @@ -14,7 +14,6 @@ struct FTransformFragment; struct FMassMoveTargetFragment; -struct FEcsactStreamFragment; USTRUCT() @@ -56,8 +55,7 @@ struct ECSACTUNREALFPS_API FFollowPlayer : public FMassStateTreeTaskBase { private: TStateTreeExternalDataHandle MoveTargetHandle; TStateTreeExternalDataHandle TransformHandle; - TStateTreeExternalDataHandle MassSignalSubsystemHandle; - TStateTreeExternalDataHandle StreamFragmentHandle; + TStateTreeExternalDataHandle MassSignalSubsystemHandle; void OnWaitComplete(); }; From c72438bfffd7ec6bd867440ea8afd64689d2ac24 Mon Sep 17 00:00:00 2001 From: Ezekiel Warren Date: Fri, 13 Dec 2024 15:00:22 -0800 Subject: [PATCH 10/15] feat: add the follow player trait and processor --- .../EntityMass/EcsactMassConfig.uasset | Bin 6754 -> 6450 bytes .../EcsactEntityMassSpawner.cpp | 12 ++++ .../EcsactPlayerEntitySpawner.cpp | 27 +++++++ .../EcsactPlayerEntitySpawner.h | 5 ++ .../Fragments/FollowPlayerTargetFragment.cpp | 48 +++++++++++++ .../Fragments/FollowPlayerTargetFragment.h | 23 ++++++ .../Processors/FollowPlayerProcessor.cpp | 68 ++++++++++++++++++ .../Processors/FollowPlayerProcessor.h | 22 ++++++ .../Processors/SyncPositionProcessor.cpp | 7 +- .../Traits/FollowPlayerTrait.cpp | 22 ++++++ .../Traits/FollowPlayerTrait.h | 17 +++++ .../SystemImpls/EcsactSystemImpls.cpp | 2 +- 12 files changed, 250 insertions(+), 3 deletions(-) create mode 100644 unreal-cpp-net-fps/Source/EcsactUnrealFps/Fragments/FollowPlayerTargetFragment.cpp create mode 100644 unreal-cpp-net-fps/Source/EcsactUnrealFps/Fragments/FollowPlayerTargetFragment.h create mode 100644 unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/FollowPlayerProcessor.cpp create mode 100644 unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/FollowPlayerProcessor.h create mode 100644 unreal-cpp-net-fps/Source/EcsactUnrealFps/Traits/FollowPlayerTrait.cpp create mode 100644 unreal-cpp-net-fps/Source/EcsactUnrealFps/Traits/FollowPlayerTrait.h diff --git a/unreal-cpp-net-fps/Content/EntityMass/EcsactMassConfig.uasset b/unreal-cpp-net-fps/Content/EntityMass/EcsactMassConfig.uasset index 24ba4dbaf674e621556685df8bdffef6e051d38e..1afff68f68cdc081c5f7da1df639c7b34093f1a1 100644 GIT binary patch delta 1252 zcmZvbQAkr^6vw~2baR_?v%7omHYHuw)U{GVO;KyhwP_X=74%S{Wn>~q5=BS^z2rlZ zdI}$cNPFw0Xn_R9Bz%bIAt@;0(Vz7V3CRHt=lVgFi_$k<;Ol~}eUS{(HNN(OiAv|!1C zf$W=b$aolD8hhZFsS^aJw5FnFYL5Cxdb+!N&h71PKYyb4KyUl0GpyL^I~z-y!U7yM zk4l?(>1w|I!1^`NU@5R|;mUVZB56tjmAe0wdQ!~#sc+!06fN!Mpg6B+sp2f252eww zkgzV(vvA9rPKVRz&(LOB&d*@A#DY8+?U{JO%bxa%{RXk3&?cL!>G@Nm)AeW!S3t z77^6P%)*B{bh-w3gBac>)#4(IiXM3FsPrwY!0Qpv@#7qSuHlNk!9x`rQ;=znR5;a& zm+e8hmP>T+Uh!wAq^5uI3foZDn}9)MW;EoaLu(RDLG-cvTzqT|31vX~H6)s^A=@(` z*EM9EBTX5QF}+cx@8^o&C!7JltKlXwC^uu8j9!YGlRa|55%HmM62G6A2!+l&CG<%z z#*=n6?NuWW>-p-3cg`jQ3xZcF7yQsF1@b@PBsrMY`6=j^THMns)Q`BQB0d=UK0tqUDqBOFg2U(Vtng&IXv7NI!`?#(H=g!P;{^v1s@7$ZY<7-cs zDMm!bz^qKtGHi8dM+F)&=I-gp*ydI-7LAiJ4P)^Hao(?|WyVIaC;NzIADwHu)bq6J z-ow=iPg(ud-IXW0W*@sNy3f8Yw@b%t6qDlA^~~3=v|!3C3xx&91|>j`nt|z0uL$=%Qi34*=NYcb!r9Pj{U^FuZKQW zoO%N%vq(0}$PXmNGBSf?ql`=;sg#kgNXlen5{aFN%9m>rE~w)DnQ6Tu#G^_^zM!U5 zMm{64g^&^tso{H~E|Z8*1JlvT`0P~aVK7=$2P*`wD2hpz@f^;(7w|P9Io5`dbs;2= zM|-;wB^l(=dsZQas=8zV`4RyRF3`sE>T?xER ztPzdchzLD83p~>);E*QYBeNm)$tFA^#?~49vPn&$RKzh|t`~f4k^GV!vEE3fxa5lj zOT4<7fb3Chz7Ph(TH}&Vv)?y=sn^#YP6cEGTvon_>|8)*(t4v+EFv2S$Rafv zNq_OIcpgTlRT<=x#ush{yR6De;`sLh*Qy{_U+wP$u79+JQZ09-Qz_x&DKk{-?kLCb zCO;5CrN1W8&+lJ{yTc=(KM=Kac&RT9J6@cKuUmv{Q54d_E@qe~u!=}rPxPo58^<|? zjsmyd1ee8B?Z*X*Pw+;xq4x?yijV<0q8_@Wm^{zNL*Ix3jp_@hAZQdk0!@kLd#y1z zrj`!p4Yh_;?}VlEo>XbjXEcIAGGijE^iG%ujd8m=SYaW>&`D#eAf!OC(WLl*b;6*@ Of~JNix(Ik~a{LDZpKZJV diff --git a/unreal-cpp-net-fps/Source/EcsactUnrealFps/EcsactEntityMassSpawner.cpp b/unreal-cpp-net-fps/Source/EcsactUnrealFps/EcsactEntityMassSpawner.cpp index 13779c7..5b2504f 100644 --- a/unreal-cpp-net-fps/Source/EcsactUnrealFps/EcsactEntityMassSpawner.cpp +++ b/unreal-cpp-net-fps/Source/EcsactUnrealFps/EcsactEntityMassSpawner.cpp @@ -163,6 +163,18 @@ auto UEcsactEntityMassSpawner::InitPosition_Implementation( Position.Z, }} ); + + entity_manager.Defer().PushCommand( + [entity_handle, Position](FMassEntityManager& entity_manager) { + entity_manager.GetFragmentDataPtr(entity_handle) + ->GetMutableTransform() + .SetLocation(FVector{ + Position.X, + Position.Y, + Position.Z, + }); + } + ); } } diff --git a/unreal-cpp-net-fps/Source/EcsactUnrealFps/EcsactPlayerEntitySpawner.cpp b/unreal-cpp-net-fps/Source/EcsactUnrealFps/EcsactPlayerEntitySpawner.cpp index 9ce35e7..7598268 100644 --- a/unreal-cpp-net-fps/Source/EcsactUnrealFps/EcsactPlayerEntitySpawner.cpp +++ b/unreal-cpp-net-fps/Source/EcsactUnrealFps/EcsactPlayerEntitySpawner.cpp @@ -1,6 +1,7 @@ #include "EcsactPlayerEntitySpawner.h" #include "EcsactUnrealFps/EcsactUnrealFpsCharacter.h" #include "EcsactUnreal/EcsactExecution.h" +#include "EcsactUnrealFps/Fragments/FollowPlayerTargetFragment.h" #include "EcsactUnrealFpsGameMode.h" #include "EcsactUnreal/EcsactAsyncRunner.h" #include "EcsactUnreal/EcsactSyncRunner.h" @@ -8,6 +9,8 @@ #include "GameFramework/CharacterMovementComponent.h" #include "EcsactUnrealFpsCharacter.h" #include "Kismet/GameplayStatics.h" +#include "MassEntitySubsystem.h" +#include "MassSpawnerSubsystem.h" #include "ecsact/runtime/dynamic.h" auto UEcsactPlayerEntitySpawner::CreateInitialEntities( // @@ -202,6 +205,13 @@ auto UEcsactPlayerEntitySpawner::RemovePlayerController( // PendingControllers.Remove(Controller); } +auto UEcsactPlayerEntitySpawner::InitPosition_Implementation( // + int32 Entity, + FExampleFpsPosition Position +) -> void { + UpdatePosition_Implementation(Entity, Position); +} + auto UEcsactPlayerEntitySpawner::UpdatePosition_Implementation( // int32 Entity, FExampleFpsPosition Position @@ -220,6 +230,23 @@ auto UEcsactPlayerEntitySpawner::UpdatePosition_Implementation( // auto desired_location = FVector{Position.X, Position.Y, Position.Z}; player->SetActorLocation(desired_location); } + + auto* world = GetWorld(); + auto& entity_manager = + world->GetSubsystem()->GetMutableEntityManager(); + + entity_manager.ForEachSharedFragment( + [&](FFollowPlayerTargetFragment& f) { + f.SetPlayerPosition( + Entity, + FVector{ + Position.X, + Position.Y, + Position.Z, + } + ); + } + ); } auto UEcsactPlayerEntitySpawner::UpdateMovedirection_Implementation( // diff --git a/unreal-cpp-net-fps/Source/EcsactUnrealFps/EcsactPlayerEntitySpawner.h b/unreal-cpp-net-fps/Source/EcsactUnrealFps/EcsactPlayerEntitySpawner.h index 2c23ede..0eb3f2e 100644 --- a/unreal-cpp-net-fps/Source/EcsactUnrealFps/EcsactPlayerEntitySpawner.h +++ b/unreal-cpp-net-fps/Source/EcsactUnrealFps/EcsactPlayerEntitySpawner.h @@ -57,6 +57,11 @@ class UEcsactPlayerEntitySpawner : public UExampleFpsEcsactRunnerSubsystem { FExampleFpsPlayer Player ) -> void override; + auto InitPosition_Implementation( // + int32 Entity, + FExampleFpsPosition Position + ) -> void override; + auto UpdatePosition_Implementation( // int32 Entity, FExampleFpsPosition Position diff --git a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Fragments/FollowPlayerTargetFragment.cpp b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Fragments/FollowPlayerTargetFragment.cpp new file mode 100644 index 0000000..73554c5 --- /dev/null +++ b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Fragments/FollowPlayerTargetFragment.cpp @@ -0,0 +1,48 @@ +#include "FollowPlayerTargetFragment.h" + +auto FFollowPlayerTargetFragment::SetPlayerPosition( + int32 Entity, + FVector Position +) -> void { + auto i = PlayerEntities.IndexOfByKey(Entity); + if(i == INDEX_NONE) { + PlayerEntities.Add(Entity); + PlayerPositions.Add(Position); + } else { + PlayerPositions[i] = Position; + } +} + +auto FFollowPlayerTargetFragment::GetPlayerPosition(int32 Entity +) const -> FVector { + auto i = PlayerEntities.IndexOfByKey(Entity); + if(i == INDEX_NONE) { + UE_LOG(LogTemp, Error, TEXT("Attempted to get non player position")); + return FVector{}; + } + + return PlayerPositions[i]; +} + +auto FFollowPlayerTargetFragment::GetClosestPlayerPosition( // + FVector Source +) const -> FVector { + if(PlayerPositions.IsEmpty()) { + UE_LOG( + LogTemp, + Error, + TEXT("GetClosestPlayerPosition() called with no player positions!") + ); + return Source; + } + + auto closest_index = 0; + auto closest_dist = FVector::Dist(Source, PlayerPositions[0]); + for(auto i = 1; PlayerPositions.Num() > i; ++i) { + if(FVector::Dist(Source, PlayerPositions[i]) < closest_dist) { + closest_index = i; + } + } + + return PlayerPositions[closest_index]; +} diff --git a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Fragments/FollowPlayerTargetFragment.h b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Fragments/FollowPlayerTargetFragment.h new file mode 100644 index 0000000..83fdab7 --- /dev/null +++ b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Fragments/FollowPlayerTargetFragment.h @@ -0,0 +1,23 @@ +#pragma once + +#include "MassEntityTypes.h" +#include "FollowPlayerTargetFragment.generated.h" + +USTRUCT() + +struct ECSACTUNREALFPS_API FFollowPlayerTargetFragment + : public FMassSharedFragment { + GENERATED_BODY() + + auto SetPlayerPosition(int32 Entity, FVector Position) -> void; + auto GetPlayerPosition(int32 Entity) const -> FVector; + + auto GetClosestPlayerPosition(FVector Source) const -> FVector; + +private: + UPROPERTY() + TArray PlayerEntities; + + UPROPERTY() + TArray PlayerPositions; +}; diff --git a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/FollowPlayerProcessor.cpp b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/FollowPlayerProcessor.cpp new file mode 100644 index 0000000..4908f96 --- /dev/null +++ b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/FollowPlayerProcessor.cpp @@ -0,0 +1,68 @@ +#include "FollowPlayerProcessor.h" +#include "EcsactUnrealFps/Fragments/EcsactFragments.h" +#include "EcsactUnrealFps/EcsactUnrealFps__ecsact__ue.h" +#include "EcsactUnreal/EcsactExecution.h" +#include "EcsactUnreal/EcsactRunner.h" +#include "EcsactUnrealFps/Fragments/FollowPlayerTargetFragment.h" +#include "EcsactUnrealFps/Fragments/LerpPositionFragment.h" +#include "MassCommonFragments.h" +#include "MassExecutionContext.h" +#include "MassNavigationFragments.h" +#include "MassNavigationTypes.h" +#include "MassRequirements.h" + +UFollowPlayerProcessor::UFollowPlayerProcessor() : EntityQuery(*this) { + ProcessingPhase = EMassProcessingPhase::PostPhysics; +} + +auto UFollowPlayerProcessor::ConfigureQueries() -> void { + using EMassFragmentAccess::ReadOnly; + using EMassFragmentAccess::ReadWrite; + using EMassFragmentPresence::All; + using EMassFragmentPresence::None; + + EntityQuery // + .AddRequirement(ReadOnly, All) + .AddRequirement(ReadWrite, All) + .AddSharedRequirement(ReadOnly, All) + .AddTagRequirement(All); +} + +auto UFollowPlayerProcessor::Execute( + FMassEntityManager& EntityManager, + FMassExecutionContext& Context +) -> void { + EntityQuery.ForEachEntityChunk( + EntityManager, + Context, + [](FMassExecutionContext& Context) { + const auto num_entities = Context.GetNumEntities(); + + auto transform_fragments = Context.GetFragmentView(); + auto move_target_fragments = + Context.GetMutableFragmentView(); + + const auto& follow_player_target = + Context.GetSharedFragment(); + + for(auto i = 0; num_entities > i; ++i) { + auto& move_target = move_target_fragments[i]; + const auto& transform = transform_fragments[i].GetTransform(); + auto entity_loc = transform.GetLocation(); + const auto closest_player_pos = + follow_player_target.GetClosestPlayerPosition(entity_loc); + + move_target.CreateNewAction( + EMassMovementAction::Move, + *Context.GetWorld() + ); + move_target.IntentAtGoal = EMassMovementAction::Move; + move_target.SlackRadius = 50.f; + move_target.Center = closest_player_pos; + move_target.Forward = (move_target.Center - entity_loc).GetSafeNormal(); + move_target.DistanceToGoal = + FVector::Dist(move_target.Center, entity_loc); + } + } + ); +} diff --git a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/FollowPlayerProcessor.h b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/FollowPlayerProcessor.h new file mode 100644 index 0000000..bcf4ecc --- /dev/null +++ b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/FollowPlayerProcessor.h @@ -0,0 +1,22 @@ +#pragma once + +#include "CoreMinimal.h" +#include "MassProcessor.h" +#include "FollowPlayerProcessor.generated.h" + +UCLASS() + +class ECSACTUNREALFPS_API UFollowPlayerProcessor : public UMassProcessor { + GENERATED_BODY() // nolint + +protected: + UFollowPlayerProcessor(); + + auto ConfigureQueries() -> void override; + auto Execute( + FMassEntityManager& EntityManager, + FMassExecutionContext& Context + ) -> void override; + + FMassEntityQuery EntityQuery; +}; diff --git a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/SyncPositionProcessor.cpp b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/SyncPositionProcessor.cpp index a236d80..739fdf7 100644 --- a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/SyncPositionProcessor.cpp +++ b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/SyncPositionProcessor.cpp @@ -41,8 +41,11 @@ auto USyncPositionProcessor::Execute( for(auto i = 0; num_entities > i; ++i) { auto entity_pos = pos_fragments[i].GetPosition(); auto& lerp_pos = lerp_pos_fragments[i]; - lerp_pos.DesiredPosition = - FVector{entity_pos.X, entity_pos.Y, entity_pos.Z}; + lerp_pos.DesiredPosition = FVector{ + entity_pos.X, + entity_pos.Y, + entity_pos.Z, + }; } } ); diff --git a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Traits/FollowPlayerTrait.cpp b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Traits/FollowPlayerTrait.cpp new file mode 100644 index 0000000..2a73d4b --- /dev/null +++ b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Traits/FollowPlayerTrait.cpp @@ -0,0 +1,22 @@ +#include "FollowPlayerTrait.h" +#include "EcsactUnrealFps/Fragments/FollowPlayerTargetFragment.h" +#include "MassCommonFragments.h" +#include "MassEntityUtils.h" +#include "MassEntityTemplateRegistry.h" +#include "MassNavigationFragments.h" + +auto UFollowPlayerTrait::BuildTemplate( // + FMassEntityTemplateBuildContext& BuildContext, + const UWorld& World +) const -> void { + auto& entity_manager = UE::Mass::Utils::GetEntityManagerChecked(World); + BuildContext.RequireFragment(); + BuildContext.RequireFragment(); + BuildContext.AddSharedFragment( + entity_manager.GetOrCreateSharedFragmentByHash( + UE::StructUtils::GetStructCrc32( + FConstStructView::Make(FFollowPlayerTargetFragment{}) + ) + ) + ); +} diff --git a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Traits/FollowPlayerTrait.h b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Traits/FollowPlayerTrait.h new file mode 100644 index 0000000..b641fef --- /dev/null +++ b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Traits/FollowPlayerTrait.h @@ -0,0 +1,17 @@ +#pragma once + +#include "MassEntityTraitBase.h" +#include "EcsactUnrealFps/Fragments/LerpPositionParameters.h" +#include "FollowPlayerTrait.generated.h" + +UCLASS(meta = (DisplayName = "Follow Player")) + +class ECSACTUNREALFPS_API UFollowPlayerTrait : public UMassEntityTraitBase { + GENERATED_BODY() + +protected: + auto BuildTemplate( // + FMassEntityTemplateBuildContext& BuildContext, + const UWorld& World + ) const -> void override; +}; diff --git a/unreal-cpp-net-fps/SystemImpls/EcsactSystemImpls.cpp b/unreal-cpp-net-fps/SystemImpls/EcsactSystemImpls.cpp index 385f4c3..2db7ddd 100644 --- a/unreal-cpp-net-fps/SystemImpls/EcsactSystemImpls.cpp +++ b/unreal-cpp-net-fps/SystemImpls/EcsactSystemImpls.cpp @@ -127,7 +127,7 @@ auto example::fps::ApplyDrag::impl(context& ctx) -> void { velocity.y = velocity.y * 0.9f; velocity.z = velocity.z * 0.9f; - if(velocity.x <= 2 && pushing.tick_count <= 0) { + if(std::abs(velocity.x) <= 2 && pushing.tick_count <= 0) { toggle.streaming = true; ctx.add(); ctx.update(toggle); From 27fd0e51a6b22b69a3fec98e900f78d416ba6bc6 Mon Sep 17 00:00:00 2001 From: Ezekiel Warren Date: Fri, 13 Dec 2024 15:03:47 -0800 Subject: [PATCH 11/15] chore: remove now unused tasks --- .../EntityMass/EcasctMassStateTree.uasset | Bin 31026 -> 0 bytes unreal-cpp-net-fps/Content/Maps/MainMenu.umap | Bin 84535 -> 84535 bytes .../EcsactUnrealFps/Tasks/EcsactIdle.cpp | 46 ---------- .../Source/EcsactUnrealFps/Tasks/EcsactIdle.h | 53 ----------- .../EcsactUnrealFps/Tasks/EcsactTracker.cpp | 52 ----------- .../EcsactUnrealFps/Tasks/EcsactTracker.h | 47 ---------- .../EcsactUnrealFps/Tasks/FollowPlayer.cpp | 84 ------------------ .../EcsactUnrealFps/Tasks/FollowPlayer.h | 61 ------------- 8 files changed, 343 deletions(-) delete mode 100644 unreal-cpp-net-fps/Content/EntityMass/EcasctMassStateTree.uasset delete mode 100644 unreal-cpp-net-fps/Source/EcsactUnrealFps/Tasks/EcsactIdle.cpp delete mode 100644 unreal-cpp-net-fps/Source/EcsactUnrealFps/Tasks/EcsactIdle.h delete mode 100644 unreal-cpp-net-fps/Source/EcsactUnrealFps/Tasks/EcsactTracker.cpp delete mode 100644 unreal-cpp-net-fps/Source/EcsactUnrealFps/Tasks/EcsactTracker.h delete mode 100644 unreal-cpp-net-fps/Source/EcsactUnrealFps/Tasks/FollowPlayer.cpp delete mode 100644 unreal-cpp-net-fps/Source/EcsactUnrealFps/Tasks/FollowPlayer.h diff --git a/unreal-cpp-net-fps/Content/EntityMass/EcasctMassStateTree.uasset b/unreal-cpp-net-fps/Content/EntityMass/EcasctMassStateTree.uasset deleted file mode 100644 index f20dc369ffb36aac01f486d7c4848668fde904b4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 31026 zcmeG_2Yi&p@|#c$y$A}15_%7WW(Y;n2`zzyqEC@a?jt$4+=aVKAjks(C`b_$MNkpx z2u~@36hT2j6s0#s0sH#{59I+81%&_3ezV{0*HRBeAL}pK@0*#Oot>GTncchK&PmN* z|LxYTTSH3_Qr4f4E3}7JivEnN-t+z?ao-*5GP&=}kk@a{160WU-!*LzTQ|Xat#VGC zla*y4VJH-Q!i5`nbN^(gO z@F4%DKU-l2+OF3mgpMa2+J?3bZy(b!s$En_OsCLxq0y0TV>*OHwQCpAv2BOYHVW~5 z^l3>gLaN~4cj__}IYWE+`Gx)*$ez1p_eaP3ENIfSy1mBtrw6z%TzcsG$sU^;FU&1p zcFET;H}C;LN|BQEzeXqP_*bQGEm{nS?$DyYWS1;%sa1#I_6$$Rp!UD{8RO|7)}9>I zKL#FZ2N7~}Gy^HWsBvzAmX1(}f3^Z9)#zjE#1xk;!_zv_>5>M-Cl8lWJmj~yR^6%# zI8bbc-IASTNp+Ll0iUJU5s->*qpdcN)78gm&9qD8`PI&kY8X)=zSI44YKzk+dR&<) zo&g@4-A&r|e8N+Kjtx)o*s`P;7oFWro_+N3%jE#*a#^zD99C&GY3$naWJL~2a5*z1 zmnWOVt^M8x#z~HnKxCK2;kJ2fP6t^#>&-=F01@r5B-kDC3#!krbXk^=-8S zv=fq)FI}Z<5@o>wlJl}b76Sj8F+ub)ao%~ofDu=Dz znUT%>fx4w*MVmRG%g9U@RhlOujqY2r{ml?3%oJtuSWw!lzn9PAphRb;D@7ugS|rYc zl}Aa#ESYvsoWt$0I8r1SM2EC?Yr-n%FpmXwb}F{rvtWFM_mOXIw5~(}(J5|f z(ICn@WrXA+pI!Ux83txso$n2D^GN^pusAsaum$YsvEmSzj)r5&<<+>X-c2KyoB_!- zNYL?;UVWMcauhRnjKxMbtKb#4stn)%kO2vJzw9?_|Cx{tMu~Q0rYjEK@5F-c5UZmZ zS4ajRKSTmK_MlY)M@>A}JYTY~Fl9}oW?wnPPgOOhPg{2^Lm%1Wd%yk$R;N)d{BdCX@L+tOg2|ic7{Yo zS0B+$y%IG6osVS_)ecKrS|cFuL9upcvc;|%)Zm5k+k^8EB`i!Hq?nrRg{FZYf35aB zt$cl*4vC~b+W5n~B}z=ADZ#3eC}`lG;rR@L+D9!=w;jhSKzQ(~zJI0zESl*iRibz9 zq~(nfE-Xymi=^S;_-E8!po|X-8{lwD9x|`TsSav3iL|JpW^g)UEOvV`jVI)CnHF}n zuj**R!kB8#tZH&x?JLax6!Hiu&5~tvy2y})mD+SxNpDO@cZ(}q9?mmaoFm?4rJK|J z$?&df<;cDco_4D0gZtDAS8&wyE@x&2IXFFjuPF(mCRaLKd%3nsNN?;;aM5MZ0+y_Q zZhUrwe27pxr}l2iQU?ep(?S@_HXW^-zu3bj(_Lk1Vhax&*siK$TaXyT8XNP}lzWyCC+ z$;EBfz8$SVMwytwAotci8TEK60B72)Br@Sz8c2fmOaJpfKMIGpxG3V;vino^Xr+(W zhrz6IQAy6eG^zBlWMn|KOseEG4TAFuAeaIa%*eho%0RIQ7_ZG0bcKw-NGN>dL_T!U zystrPJmisBTgo=595?_1DN1L8o3ONCuoGv?el@`Is&cD>|MdCCv*iAN046Ov7 z%ef>;vfNO)Fsvt(3r#jQSuq^!8Appve&ktpq+?HrIlV1z59~)Mn7p+Z zJ>8^{tFNA7@&XC$Krjl8_Qw)-wF7RwZH^I=HQWuMyZgrx4|rj+`&R3+J{$}IbS&rA zKj1eXe5EffX-KEqp6zdcB(eF$e?-&P^U@y}zZ3o7#8{tC#Eq`-hI8 zfr4~Ce`E%%L*Bv(PCS#A3U;pty((e9%(%MsVMpO3-g7*76ogVV&<=lg8Bz+MUmr~tt9Y~*; zk|w2F$gTILj{-3hrPOp5_UcRzKLh()pMlq>zda9js6nlAF!w?u##BE44D7US6D(D( z_GeVT)P9cNF#C{2WqR zTmAtDH3h9(>e71ECPU!TQ9$7@@5-aK?~7AOz?r}XAfH@$ay{&O9Rp!|kP3m9#+VYJ zOOxQ5B^RqKFYl!!Te_s2ibpN`V*>0!Wj6MJo;0P)j3$sW)JV_<5>$S_Qy_C|a5Yde zqlAb`UjO#Z?)tI9er)jSx#e(#P?$roak?xndKKpOUK@-byRw1GN4y!9d~*J^i$N+D z^j1U;LwJM0n3JvTj}eu&yga<7^(^exOJz7O5e6Hw{P%`e|5?sL2)pQ` zWyH{BbH0(?9Pr$#qz!rP2BXWQH&+tflDQL7) zySms7nvBBS`_r;t;vhK4rO)mT!>2$XXK(Ut1bKL7d6-eDgLZdXahA=X)v=qDy0x`2 zLn*q$<+@4SLtV~7bn$A!P1a@P9)W!&z~nhodycORix&`3tBgKA>==YMSh;efA~)WR ztHm^c<#}kerO!eDXNh=1jO#n#G=LTaAsrPpZjK+?hJ)L+RluiPhV}$y45U{95?S0P zWF?qrAU&AT!_)FwC9RFeP_0cbZCqNR_2Md@e7k(Pw z4}7VQu*6)rk;;=wwWitOpEOkk4sZELZq&5mMit{0vDoWfp>wcy z9Q$kZgND=i{51Ar$LZkXqEiK+%cXQb7Le|z0?=Kh{JzVB?hN9@&S)JX-O&Q_J61qC z`Ux6q`OOPQFla_}-^wWRJ6-RW_?#cz$pX@SRY1D03qS|2tgaHF@il2 zQcIW*)+4S7Nm!@lCF1z{u4{Dg1rkAKZPZzQ86wOl@_=V?{=DX^hrIp}<^%ivh&;f4 zt6@^wCOt0qM!&_i<~836v_Bs)6^R;FI7hIq*i?M7d+X0Y_d!zr&%rO+d^sVMxX^Sm`4K1*q3KZmBt*s-q4t`g2-$6h z@*^`5B`$3>!VGnJ$eb6~<#Yp{OHFug1Nf_m?0`MkraL}sg@7YFZ_F!V@Xgf7ge*c5 zagjxH81Ihci=Ae`yJ!mZI%8YpDXtatK*(oi7{8CrFvMks@!O9WQSW^bmapF9k#N2N zlimeo(gz74?n8sCKUL#}ffiAj>pus{bVxz>usjd!r#}?!tc&R z3x6fG-DT0j&xg^%Pgx}Q*1JAh_?bov|6)ap1l)yxa4k*#u@^;!We`#w zF{TOYo(KI29yAcGQY;TjL?%PA7k$}x9^{v=2TjKL#c+^`FmOVMRlLQ}lXd_*XZ=dA z9xl_Li}dFNC98%cI!Dbxgf3zQBcdGk;{2--2J?%jI(GgtJu`SKs%)R}^cGDc4R|g! zk+b;ckO<`L#NNQm7a0gtv^efn91pO(FPdpnP%__{)@ugenYM&dPu*G5hUaHm|JzzL za??KLPe?@+MZ0UsR78kD32w2VX*Z>qwOE3EM5ZGxu^)pybTF8Zf3V;5S_yk93c)+n z{MmQGK{?p#B1;uS_|E;jrMK_g&+F26*8QSUJAZOyX@X*iVgFwU(|TA%NIOp&3|M6{ zpbR99QiwFG&fhc{tR%eV`7qd;AA@#CAg-~vsjdBO;Q3GFDQ?GDlf~(Fdk-Z1yf{qa zaPW)1VA9Zl$)gC@+N&`9-Yj}l`gbYN{3vAw334 zNFip!d+9@cNokfoVyW%|*p~l=ri?K9RvERaOBwf@A$WkRxP=6$AlQmHM7X z#fgFSZG>r~@sa8j@j4>SG`+-R@MC2ug4X6_I}%L69y%q?=O9pbPUGph5fX^l62GmvgoEN6l~?1eU5?`oCztb64DdTl=R+w3D!Ots<}E*ZtR%M5pWKi>Xx4EI6gwleF- zFX{gNdt1i6(ynZ5zJ`OtvFVOW`BMxRp5KnUy;sb|GF*{E<(jmKS$l4l={axmuGoY{ z7xw3CI4Ds~4aYB$@9d6?&d+eZ43$34aSx;Hg$|XiwrqG}=js*RH}=krdcWM|s@!l5 zkfB*wx(4xnv@e0r&?Q0X38mae@J5?hCM}Ucx@(Coz{Rv2y0^!?@BHLmNa6m$`(pWt&qIo-pZF<%V59RB@0SZ-Krb^3Nb5{u%dpZE0Rd?N z5{Ln5t;wJZ0Ds?LK%NIZ?RjAyGB6W)J>s=7G!@_g%t3;Q*h9x|8)hK@)&}h-Y?jpSZ5Q%P|cC;nJUr9T!8EEJB-I7G$Y)$?b|T?p4fNV`Jdb{eB-2}vzf*HjfmIA{cZq&R5bz##9*9f=4smSm#(9u zq+Z)fK9sPb&WXh~0|rIAMAc`CLJPO-UxQx^Z}wW$_HUnGa;w?yioO%hTSao!+i~G$ zs(%adTGfBW0fka{u7cf#paUuex~d@^@(o78Xwu*WAo9EMy6ffSJ8K?^MPONjEHY}{E?o0W;BVq)F>foucdmp3bzeD{ zWx(+;!oUN?R)fBhUc~&xUt1lB_?Z@5FyEd zH}XMjOb(8mh&^habB`&u$|v9qqQ(Z9h@?wpgOP9%_TsuWm@sJoa3>-UU=JNLsP}xr zbynyVYHuiJ;~e4&`youLblnOQk&u_|y853v041^@A#_T)-U5M!$RI?TIr}oigN&lH zE8MFifmSBvU!Hl;Udhh$u{uc@D-H4Jh<=Nuhng?2A{0W~9CB z-GzE6tnpHKq?&{b#4TfX5@k0N@R#vLyO~JAsqWoOP(|o=cob?UkC3#;z*fErZ+zPo z=4Jx^awGu0J_KQGmVt10(FKII%)LVw5JjydZbH!p5w}>C#6Yyky-ETV0UCwK$D3{! z-j$Vv_Tz-VTP1O^D9LCNtwjRhpJ=&#|JVb+O>BwoGKRYi?ZK1&djHsix2(N)l~2tl zhJprQ&peVZPmtQaFbOk)P7zT2zaGp2QE3xvXr&r2bV;ebF}*{XG2c@IE7y%yslCtG mN&oN1FJE0YA-MCdLo_ImTA&-DWt|S~S90$M&DtOg=>H%23aT3b diff --git a/unreal-cpp-net-fps/Content/Maps/MainMenu.umap b/unreal-cpp-net-fps/Content/Maps/MainMenu.umap index 24b0ceb78cf8aa8b5dd6813be2ea16ebecae0d6c..0108f3049adf0c0e7c0bb76cffe7f50a95fe2814 100644 GIT binary patch delta 198 zcmV;%06G7+lm)kx1+cXN5E~a_$Wfe3OQLguP_pSY470@n*F+04H!UzQE;BSPF*mcP zMxvh}LqbGDK|w}EG%z?bK|?}BGBGtmHaIyqL^CisHZVjmlWFGlATuyVG)6{6Lo`J< zI5)HZe6cLNrD>H#s;&Hbyx?LqRY@ zH$_H4GD1YNrRZ;582|t*BHYG^zyJWDhXHh^K)}UqGyw3AKq{B99RU-!Kt%!f1d3fg AvH$=8 delta 198 zcmV;%06G7+lm)kx1+cXN5GVzBB4_~`M3$!s{SGcz%dKq{B99RU-!Kt%!f1iX(Context); - - // auto& MassSignalSubsystem = - // Context.GetExternalData(MassSignalSubsystemHandle); - // - // MassSignalSubsystem.SignalEntity( - // UE::Mass::Signals::StateTreeActivate, - // MassContext.GetEntity() - // ); - - EStateTreeRunStatus Status = EStateTreeRunStatus::Running; - - auto& MoveTarget = Context.GetExternalData(MoveTargetHandle); - - if(MoveTarget.GetCurrentAction() == EMassMovementAction::Move) { - UE_LOG(LogTemp, Error, TEXT("MASS MOVEMENT IS STILL MOVING")); - } - - return Status; -} - -void FEcsactIdle::ExitState( - FStateTreeExecutionContext& Context, - const FStateTreeTransitionResult& Transition -) const { - UE_LOG(LogTemp, Log, TEXT("Exiting Idle State")); -} - -bool FEcsactIdle::Link(FStateTreeLinker& Linker) { - Linker.LinkExternalData(MassSignalSubsystemHandle); - Linker.LinkExternalData(MoveTargetHandle); - return FMassStateTreeTaskBase::Link(Linker); -} diff --git a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Tasks/EcsactIdle.h b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Tasks/EcsactIdle.h deleted file mode 100644 index 579df0f..0000000 --- a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Tasks/EcsactIdle.h +++ /dev/null @@ -1,53 +0,0 @@ -#pragma once - -#include "CoreMinimal.h" -#include "EcsactUnrealFps/Fragments/EcsactFragments.h" -#include "MassStateTreeTypes.h" -#include "StateTreeEvaluatorBase.h" -#include "StateTreeExecutionContext.h" -#include "StateTreeExecutionTypes.h" -#include "StateTreeLinker.h" -#include "MassSignalSubsystem.h" - -#include "EcsactIdle.generated.h" - -struct FMassMoveTargetFragment; - -USTRUCT() - -struct ECSACTUNREALFPS_API FEcsactIdleInstanceData { - GENERATED_BODY() // nolint -}; - -USTRUCT() - -struct ECSACTUNREALFPS_API FEcsactIdle : public FMassStateTreeTaskBase { - GENERATED_BODY() // nolint - // - using FInstanceDataType = FEcsactIdleInstanceData; - - virtual const UStruct* GetInstanceDataType() const override { - return FEcsactIdleInstanceData::StaticStruct(); - } - - virtual bool Link(FStateTreeLinker& Linker) override; - - virtual EStateTreeRunStatus EnterState( - FStateTreeExecutionContext& Context, - const FStateTreeTransitionResult& Transition - ) const override; - - virtual void ExitState( - FStateTreeExecutionContext& Context, - const FStateTreeTransitionResult& Transition - ) const override; - - // virtual EStateTreeRunStatus Tick( - // FStateTreeExecutionContext& Context, - // const float DeltaTime - // ) const override; - -private: - TStateTreeExternalDataHandle MassSignalSubsystemHandle; - TStateTreeExternalDataHandle MoveTargetHandle; -}; diff --git a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Tasks/EcsactTracker.cpp b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Tasks/EcsactTracker.cpp deleted file mode 100644 index c5243c8..0000000 --- a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Tasks/EcsactTracker.cpp +++ /dev/null @@ -1,52 +0,0 @@ -#include "EcsactTracker.h" - -#include "StateTreeLinker.h" -#include "EcsactUnreal/EcsactExecution.h" -#include "EcsactUnrealFps/EcsactPlayerEntitySpawner.h" -#include "EcsactUnreal/EcsactRunner.h" -#include "EcsactUnrealFps/EcsactUnrealFpsCharacter.h" - -FEcsactTracker::FEcsactTracker() { -} - -bool FEcsactTracker::Link(FStateTreeLinker& Linker) { - return Super::Link(Linker); -} - -EStateTreeRunStatus FEcsactTracker::EnterState( - FStateTreeExecutionContext& Context, - const FStateTreeTransitionResult& Transition -) const { - FInstanceDataType& InstanceData = Context.GetInstanceData(*this); - InstanceData.bIsStreaming = true; - - return EStateTreeRunStatus::Running; -} - -EStateTreeRunStatus FEcsactTracker::Tick( // - FStateTreeExecutionContext& Context, - const float DeltaTime -) const { - FInstanceDataType& InstanceData = Context.GetInstanceData(*this); - - auto runner = EcsactUnrealExecution::Runner(Context.GetWorld()).Get(); - check(runner); - auto PlayerSpawner = runner->GetSubsystem(); - - // TODO: We currently only get the first player to follow instead of - // accounting for X amount - if(PlayerSpawner->PlayerEntities.Num() > 0) { - auto ActorWeakPtr = PlayerSpawner->PlayerEntities.begin().Value(); - if(ActorWeakPtr.IsValid()) { - auto* pawn = ActorWeakPtr.Get(); - - InstanceData.PlayerPosition = pawn->GetActorLocation(); - } else { - UE_LOG(LogTemp, Warning, TEXT("Player Actor is invalid")); - } - } else { - UE_LOG(LogTemp, Warning, TEXT("No players to follow")); - } - - return EStateTreeRunStatus::Running; -} diff --git a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Tasks/EcsactTracker.h b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Tasks/EcsactTracker.h deleted file mode 100644 index 70911a0..0000000 --- a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Tasks/EcsactTracker.h +++ /dev/null @@ -1,47 +0,0 @@ - -#pragma once - -#include "CoreMinimal.h" -#include "EcsactUnrealFps/Fragments/EcsactFragments.h" -#include "MassSignalSubsystem.h" -#include "MassStateTreeTypes.h" -#include "MassStateTreeTypes.h" -#include "StateTreeEvaluatorBase.h" -#include "StateTreeExecutionContext.h" -#include "StateTreeExecutionTypes.h" - -#include "EcsactTracker.generated.h" - -USTRUCT() - -struct ECSACTUNREALFPS_API FEcsactTrackerInstanceData { - GENERATED_BODY() // nolint - UPROPERTY(EditAnywhere, Category = Output) bool bIsStreaming = false; - UPROPERTY(VisibleAnywhere, Category = Output) FVector PlayerPosition; -}; - -USTRUCT() - -struct ECSACTUNREALFPS_API FEcsactTracker : public FMassStateTreeTaskBase { - GENERATED_BODY() // nolint - - FEcsactTracker(); - - using FInstanceDataType = FEcsactTrackerInstanceData; - - virtual const UStruct* GetInstanceDataType() const override { - return FEcsactTrackerInstanceData::StaticStruct(); - } - - virtual bool Link(FStateTreeLinker& Linker) override; - - virtual EStateTreeRunStatus EnterState( - FStateTreeExecutionContext& Context, - const FStateTreeTransitionResult& Transition - ) const override; - - virtual EStateTreeRunStatus Tick( - FStateTreeExecutionContext& Context, - const float DeltaTime - ) const override; -}; diff --git a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Tasks/FollowPlayer.cpp b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Tasks/FollowPlayer.cpp deleted file mode 100644 index 1ec9a6f..0000000 --- a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Tasks/FollowPlayer.cpp +++ /dev/null @@ -1,84 +0,0 @@ - -// Fill out your copyright notice in the Description page of Project Settings. -#include "FollowPlayer.h" - -#include "MassCommonFragments.h" -#include "MassNavigationFragments.h" -#include "MassNavigationTypes.h" -#include "MassStateTreeTypes.h" -#include "MassStateTreeExecutionContext.h" -#include "StateTreeExecutionTypes.h" -#include "StateTreeLinker.h" - -EStateTreeRunStatus FFollowPlayer::EnterState( - FStateTreeExecutionContext& Context, - const FStateTreeTransitionResult& Transition -) const { - UE_LOG(LogTemp, Log, TEXT("Entering Follow Player State")); - - auto& MassContext = static_cast(Context); - - auto& MassSignalSubsystem = - Context.GetExternalData(MassSignalSubsystemHandle); - - MassSignalSubsystem.SignalEntity( - UE::Mass::Signals::StateTreeActivate, - MassContext.GetEntity() - ); - auto& MoveTarget = Context.GetExternalData(MoveTargetHandle); - - MoveTarget.CreateNewAction(EMassMovementAction::Move, *Context.GetWorld()); - MoveTarget.IntentAtGoal = EMassMovementAction::Move; - MoveTarget.SlackRadius = 50.f; - - EStateTreeRunStatus Status = EStateTreeRunStatus::Running; - MoveToPlayerPosition(Context); - - return Status; -} - -void FFollowPlayer::ExitState( - FStateTreeExecutionContext& Context, - const FStateTreeTransitionResult& Transition -) const { - UE_LOG(LogTemp, Log, TEXT("Exiting State")); - auto& MoveTarget = Context.GetExternalData(MoveTargetHandle); - - MoveTarget = {}; - MoveTarget.CreateNewAction(EMassMovementAction::Stand, *Context.GetWorld()); - FMassStateTreeTaskBase::ExitState(Context, Transition); -} - -EStateTreeRunStatus FFollowPlayer::Tick( // - FStateTreeExecutionContext& Context, - const float DeltaTime -) const { - MoveToPlayerPosition(Context); - - return EStateTreeRunStatus::Running; -} - -void FFollowPlayer::MoveToPlayerPosition( // - FStateTreeExecutionContext& Context -) const { - auto& MoveTarget = Context.GetExternalData(MoveTargetHandle); - FInstanceDataType& InstanceData = Context.GetInstanceData(*this); - const auto& TransformFragment = Context.GetExternalData(TransformHandle); - const auto& PlayerPosition = InstanceData.PlayerPosition; - - MoveTarget.Center = PlayerPosition; - MoveTarget.Forward = - (MoveTarget.Center - TransformFragment.GetTransform().GetLocation()) - .GetSafeNormal(); - MoveTarget.DistanceToGoal = FVector::Dist( - MoveTarget.Center, - TransformFragment.GetTransform().GetLocation() - ); -} - -bool FFollowPlayer::Link(FStateTreeLinker& Linker) { - Linker.LinkExternalData(MoveTargetHandle); - Linker.LinkExternalData(TransformHandle); - Linker.LinkExternalData(MassSignalSubsystemHandle); - return FMassStateTreeTaskBase::Link(Linker); -} diff --git a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Tasks/FollowPlayer.h b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Tasks/FollowPlayer.h deleted file mode 100644 index 6e95a0f..0000000 --- a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Tasks/FollowPlayer.h +++ /dev/null @@ -1,61 +0,0 @@ - -#pragma once - -#include "CoreMinimal.h" -#include "EcsactUnrealFps/Fragments/EcsactFragments.h" -#include "MassStateTreeTypes.h" -#include "StateTreeEvaluatorBase.h" -#include "StateTreeExecutionContext.h" -#include "StateTreeExecutionTypes.h" -#include "MassSignalSubsystem.h" -#include "MassNavigationFragments.h" - -#include "FollowPlayer.generated.h" - -struct FTransformFragment; -struct FMassMoveTargetFragment; - -USTRUCT() - -struct ECSACTUNREALFPS_API FFollowPlayerInstanceData { - GENERATED_BODY() // nolint - UPROPERTY(VisibleAnywhere, Category = Input) FVector PlayerPosition; -}; - -USTRUCT() - -struct ECSACTUNREALFPS_API FFollowPlayer : public FMassStateTreeTaskBase { - GENERATED_BODY() // nolint - - using FInstanceDataType = FFollowPlayerInstanceData; - - virtual const UStruct* GetInstanceDataType() const override { - return FFollowPlayerInstanceData::StaticStruct(); - } - - virtual bool Link(FStateTreeLinker& Linker) override; - - virtual EStateTreeRunStatus EnterState( - FStateTreeExecutionContext& Context, - const FStateTreeTransitionResult& Transition - ) const override; - - virtual void ExitState( - FStateTreeExecutionContext& Context, - const FStateTreeTransitionResult& Transition - ) const override; - - virtual EStateTreeRunStatus Tick( - FStateTreeExecutionContext& Context, - const float DeltaTime - ) const override; - - void MoveToPlayerPosition(FStateTreeExecutionContext& Context) const; - -private: - TStateTreeExternalDataHandle MoveTargetHandle; - TStateTreeExternalDataHandle TransformHandle; - TStateTreeExternalDataHandle MassSignalSubsystemHandle; - - void OnWaitComplete(); -}; From f347df70ffab4c23505898e42ec123db18ed9900 Mon Sep 17 00:00:00 2001 From: Ezekiel Warren Date: Fri, 13 Dec 2024 17:04:28 -0800 Subject: [PATCH 12/15] feat: only show enemy visualization for entities --- unreal-cpp-net-fps/Config/DefaultMass.ini | 40 ++++++++++------- .../Blueprints/BP_EcsactMassEntity.uasset | Bin 32004 -> 32017 bytes .../BP_EcsactEntityMassSpawner.uasset | Bin 25131 -> 25131 bytes .../EntityMass/EcsactMassConfig.uasset | Bin 6450 -> 6450 bytes unreal-cpp-net-fps/Content/Maps/MainMenu.umap | Bin 84535 -> 84535 bytes .../EcsactEntityMassSpawner.cpp | 29 ++++++++++++ .../EcsactUnrealFps/EcsactEntityMassSpawner.h | 5 +++ .../EcsactUnrealFps/EcsactUnrealFps.Build.cs | 3 +- .../Fragments/EcsactFragments.h | 13 +++++- .../Processors/EnemyStateTreeProcessor.cpp | 42 ------------------ .../Processors/EnemyStateTreeProcessor.h | 22 --------- .../Processors/EnemyVisualProcessor.cpp | 22 +++++++++ .../Processors/EnemyVisualProcessor.h | 28 ++++++++++++ 13 files changed, 122 insertions(+), 82 deletions(-) delete mode 100644 unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/EnemyStateTreeProcessor.cpp delete mode 100644 unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/EnemyStateTreeProcessor.h create mode 100644 unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/EnemyVisualProcessor.cpp create mode 100644 unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/EnemyVisualProcessor.h diff --git a/unreal-cpp-net-fps/Config/DefaultMass.ini b/unreal-cpp-net-fps/Config/DefaultMass.ini index 183f525..d6797fc 100644 --- a/unreal-cpp-net-fps/Config/DefaultMass.ini +++ b/unreal-cpp-net-fps/Config/DefaultMass.ini @@ -1,15 +1,25 @@ -[/Script/MassRepresentation.MassRepresentationProcessor] - -bAutoRegisterWithProcessingPhases=True - - - -[/Script/MassRepresentation.MassVisualizationLODProcessor] - -bAutoRegisterWithProcessingPhases=True - - - -[/Script/MassLOD.MassLODCollectorProcessor] - -bAutoRegisterWithProcessingPhases=True +[/Script/MassRepresentation.MassRepresentationProcessor] + +bAutoRegisterWithProcessingPhases=False + + + +[/Script/MassRepresentation.MassVisualizationLODProcessor] + +bAutoRegisterWithProcessingPhases=False + + + +[/Script/MassLOD.MassLODCollectorProcessor] + +bAutoRegisterWithProcessingPhases=True + +[/Script/MassRepresentation.MassVisualizationProcessor] +bAutoRegisterWithProcessingPhases=False + +[/Script/EcsactUnrealFps.EnemyVisualProcessor] +bAutoRegisterWithProcessingPhases=True + +[/Script/EcsactUnrealFps.EnemyRepresentationProcessor] +bAutoRegisterWithProcessingPhases=True + diff --git a/unreal-cpp-net-fps/Content/Blueprints/BP_EcsactMassEntity.uasset b/unreal-cpp-net-fps/Content/Blueprints/BP_EcsactMassEntity.uasset index d6a0bbdfa3e02936979bb242f4871b4d1142db32..8911a9457a97e855271dc5650bd774d3e9f739d2 100644 GIT binary patch delta 3943 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 pATTjOI5?- diff --git a/unreal-cpp-net-fps/Content/EntityMass/EcsactMassConfig.uasset b/unreal-cpp-net-fps/Content/EntityMass/EcsactMassConfig.uasset index 1afff68f68cdc081c5f7da1df639c7b34093f1a1..84bc96885a5a1c210da3cf59f5a3ed0139a63906 100644 GIT binary patch delta 40 ycmV+@0N4MrGO{v|(-3Bl_6pC{^g^=uTMo)eS46iSnU6g{&D6)FUBR}d`# diff --git a/unreal-cpp-net-fps/Content/Maps/MainMenu.umap b/unreal-cpp-net-fps/Content/Maps/MainMenu.umap index 0108f3049adf0c0e7c0bb76cffe7f50a95fe2814..7cfa8d7130f6b404b744efe16f6752b241eb93c7 100644 GIT binary patch delta 207 zcmV;=05Jczlm)kx1+cXN5HpZ?JF+bBNT`O2n{O9u1hd5f)kF<4E-^GMFfcAOF)lGP zv!_O)pCC9fK|w<^L^U)+MKUxoGe$EpI7UQ6K}0e|K}0tIsLNPElF)}qpIYTf( zMm9z;Lq<47Gqa`WZ(JD?pF6=mQ;5J}ZufE@0B*q6#cebI@Q*+$m$4lI6PM6J0V4rU Jw`4^D(*((CLL~qI delta 213 zcmdl!g?0NB)(u-31*FB3P6W>J@m^Hi7_jN3DbMDkj8|QF4fTx7bqx&kj7{_m%{MQ1 zTQpz6*~!J((b3J-#K6MX5y&wzG<7nwurzluHn21^a50#i`RuELv4NY3o13e%iL1GV zg_EJ7o27x7xsj8ptCOpdtFwXA=DO$Xj0&zsjuy^lhNdP?CT^DImKH8%ZkCSDjt0)= zu5OM-PA;35zQ~V}WMI%yx^t}K00YCqcE*xr4hN3rnlOCmchH)?QI=77`UNLOS@u8w NLBMx=iYwz~764oEMGetSubsystem()->GetMutableEntityManager(); + auto entity_handles = + MassEntities.FindChecked(static_cast(Entity)); + + for(auto entity_handle : entity_handles) { + entity_manager.Defer().AddTag(entity_handle); + } +} + +auto UEcsactEntityMassSpawner::RemoveEnemy_Implementation( + int32 Entity, + FExampleFpsEnemy Enemy +) -> void { + if(!CheckMassEntities(Entity, TEXT("RemoveEnemy"))) { + return; + } + + auto* world = GetWorld(); + auto& entity_manager = + world->GetSubsystem()->GetMutableEntityManager(); + auto entity_handles = + MassEntities.FindChecked(static_cast(Entity)); + + for(auto entity_handle : entity_handles) { + entity_manager.Defer().RemoveTag(entity_handle); + } } auto UEcsactEntityMassSpawner::InitPosition_Implementation( diff --git a/unreal-cpp-net-fps/Source/EcsactUnrealFps/EcsactEntityMassSpawner.h b/unreal-cpp-net-fps/Source/EcsactUnrealFps/EcsactEntityMassSpawner.h index 57d88cb..5431a39 100644 --- a/unreal-cpp-net-fps/Source/EcsactUnrealFps/EcsactEntityMassSpawner.h +++ b/unreal-cpp-net-fps/Source/EcsactUnrealFps/EcsactEntityMassSpawner.h @@ -41,6 +41,11 @@ class UEcsactEntityMassSpawner : public UExampleFpsEcsactRunnerSubsystem { FExampleFpsEnemy Enemy ) -> void override; + auto RemoveEnemy_Implementation( // + int32 Entity, + FExampleFpsEnemy Enemy + ) -> void override; + auto InitPosition_Implementation( // int32 Entity, FExampleFpsPosition Position diff --git a/unreal-cpp-net-fps/Source/EcsactUnrealFps/EcsactUnrealFps.Build.cs b/unreal-cpp-net-fps/Source/EcsactUnrealFps/EcsactUnrealFps.Build.cs index 8e83963..91e3cb9 100644 --- a/unreal-cpp-net-fps/Source/EcsactUnrealFps/EcsactUnrealFps.Build.cs +++ b/unreal-cpp-net-fps/Source/EcsactUnrealFps/EcsactUnrealFps.Build.cs @@ -26,7 +26,8 @@ public EcsactUnrealFps(ReadOnlyTargetRules Target) : base(Target) { "MassEntity", "MassCommon", "MassSignals", - "MassMovement" + "MassMovement", + "MassRepresentation", }); } } diff --git a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Fragments/EcsactFragments.h b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Fragments/EcsactFragments.h index cafa0b0..aa19c5f 100644 --- a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Fragments/EcsactFragments.h +++ b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Fragments/EcsactFragments.h @@ -71,11 +71,20 @@ struct ECSACTUNREALFPS_API FEcsactPositionFragment : public FMassFragment { FVector Position; }; -USTRUCT() - /** * Tag indiciating that this mass entity should be streamed. */ +USTRUCT() + struct ECSACTUNREALFPS_API FEcsactStreamTag : public FMassTag { GENERATED_BODY() }; + +/** + * Tag indiciating that this entity is an enemy + */ +USTRUCT() + +struct ECSACTUNREALFPS_API FExampleEnemyTag : public FMassTag { + GENERATED_BODY() // nolint +}; diff --git a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/EnemyStateTreeProcessor.cpp b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/EnemyStateTreeProcessor.cpp deleted file mode 100644 index 5f745f1..0000000 --- a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/EnemyStateTreeProcessor.cpp +++ /dev/null @@ -1,42 +0,0 @@ -#include "EnemyStateTreeProcessor.h" -#include "EcsactUnrealFps/Fragments/EcsactFragments.h" -#include "MassCommonFragments.h" -#include "MassExecutionContext.h" -#include "MassSignalSubsystem.h" -#include "MassRequirements.h" -#include "MassStateTreeFragments.h" -#include "MassStateTreeTypes.h" - -UEnemyStateTreeProcessor::UEnemyStateTreeProcessor() : EntityQuery(*this) { -} - -auto UEnemyStateTreeProcessor::ConfigureQueries() -> void { - using EMassFragmentAccess::ReadOnly; - using EMassFragmentAccess::ReadWrite; - using EMassFragmentPresence::All; - using EMassFragmentPresence::None; - - EntityQuery.AddSubsystemRequirement(ReadWrite); - EntityQuery // - .AddRequirement(ReadWrite, All) - .AddTagRequirement(All); -} - -auto UEnemyStateTreeProcessor::Execute( - FMassEntityManager& EntityManager, - FMassExecutionContext& Context -) -> void { - EntityQuery.ForEachEntityChunk( - EntityManager, - Context, - [](FMassExecutionContext& Context) { - auto& mass_signal = - Context.GetMutableSubsystemChecked(); - - mass_signal.SignalEntities( - UE::Mass::Signals::StateTreeActivate, - Context.GetEntities() - ); - } - ); -} diff --git a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/EnemyStateTreeProcessor.h b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/EnemyStateTreeProcessor.h deleted file mode 100644 index 298fd6c..0000000 --- a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/EnemyStateTreeProcessor.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -#include "CoreMinimal.h" -#include "MassProcessor.h" -#include "EnemyStateTreeProcessor.generated.h" - -UCLASS() - -class ECSACTUNREALFPS_API UEnemyStateTreeProcessor : public UMassProcessor { - GENERATED_BODY() // nolint - -protected: - UEnemyStateTreeProcessor(); - - auto ConfigureQueries() -> void override; - auto Execute( - FMassEntityManager& EntityManager, - FMassExecutionContext& Context - ) -> void override; - - FMassEntityQuery EntityQuery; -}; diff --git a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/EnemyVisualProcessor.cpp b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/EnemyVisualProcessor.cpp new file mode 100644 index 0000000..065cdcc --- /dev/null +++ b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/EnemyVisualProcessor.cpp @@ -0,0 +1,22 @@ +#include "EnemyVisualProcessor.h" +#include "EcsactUnrealFps/Fragments/EcsactFragments.h" + +UEnemyVisualProcessor::UEnemyVisualProcessor() { +} + +auto UEnemyVisualProcessor::ConfigureQueries() -> void { + using EMassFragmentPresence::All; + Super::ConfigureQueries(); + + CloseEntityQuery.AddTagRequirement(All); + FarEntityQuery.AddTagRequirement(All); + DebugEntityQuery.AddTagRequirement(All); + CloseEntityAdjustDistanceQuery.AddTagRequirement(All); +} + +auto UEnemyRepresentationProcessor::ConfigureQueries() -> void { + using EMassFragmentPresence::All; + Super::ConfigureQueries(); + + EntityQuery.AddTagRequirement(All); +} diff --git a/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/EnemyVisualProcessor.h b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/EnemyVisualProcessor.h new file mode 100644 index 0000000..a0ca9a5 --- /dev/null +++ b/unreal-cpp-net-fps/Source/EcsactUnrealFps/Processors/EnemyVisualProcessor.h @@ -0,0 +1,28 @@ +#pragma once + +#include "MassRepresentationProcessor.h" +#include "MassVisualizationLODProcessor.h" +#include "EnemyVisualProcessor.generated.h" + +UCLASS() + +class ECSACTUNREALFPS_API UEnemyVisualProcessor + : public UMassVisualizationLODProcessor { + GENERATED_BODY() // nolint + +public: + UEnemyVisualProcessor(); + +protected: + auto ConfigureQueries() -> void override; +}; + +UCLASS() + +class ECSACTUNREALFPS_API UEnemyRepresentationProcessor + : public UMassRepresentationProcessor { + GENERATED_BODY() // nolint + +protected: + auto ConfigureQueries() -> void override; +}; From a00919f936936bfd4fe877a51885bba7b4ccae04 Mon Sep 17 00:00:00 2001 From: Ezekiel Warren Date: Sat, 14 Dec 2024 09:57:42 -0800 Subject: [PATCH 13/15] feat: authorize with node on main menu instead of editor tools --- .../Content/Blueprints/ConnectMenu.uasset | Bin 35901 -> 36415 bytes unreal-cpp-net-fps/Content/Maps/MainMenu.umap | Bin 84535 -> 116642 bytes unreal-cpp-net-fps/Plugins/Ecsact | 2 +- unreal-cpp-net-fps/Plugins/EcsactNet | 2 +- 4 files changed, 2 insertions(+), 2 deletions(-) diff --git a/unreal-cpp-net-fps/Content/Blueprints/ConnectMenu.uasset b/unreal-cpp-net-fps/Content/Blueprints/ConnectMenu.uasset index f16f650299ef9f49c27a7480f0a704adad3e5724..3161bd802a94b0688a4f0668b656eae471150448 100644 GIT binary patch delta 6978 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 literal 84535 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 Date: Sat, 14 Dec 2024 10:02:29 -0800 Subject: [PATCH 14/15] feat: some error logs instead of asserts --- .../EcsactPlayerEntitySpawner.cpp | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/unreal-cpp-net-fps/Source/EcsactUnrealFps/EcsactPlayerEntitySpawner.cpp b/unreal-cpp-net-fps/Source/EcsactUnrealFps/EcsactPlayerEntitySpawner.cpp index 7598268..b4bb628 100644 --- a/unreal-cpp-net-fps/Source/EcsactUnrealFps/EcsactPlayerEntitySpawner.cpp +++ b/unreal-cpp-net-fps/Source/EcsactUnrealFps/EcsactPlayerEntitySpawner.cpp @@ -52,10 +52,35 @@ auto UEcsactPlayerEntitySpawner::SetLocalEcsactPlayerId( // int32 NewPlayerId ) -> void { auto world = WorldContext->GetWorld(); - check(world); + if(!world) { + UE_LOG( + LogTemp, + Error, + TEXT("SetLocalEcsactPlayerId called too early - world not ready") + ); + return; + } auto runner = EcsactUnrealExecution::Runner(world); - check(runner.IsValid()); + if(!world) { + UE_LOG( + LogTemp, + Error, + TEXT("SetLocalEcsactPlayerId called too early - runner not ready") + ); + return; + } + auto self = runner->GetSubsystem(); + if(!self) { + UE_LOG( + LogTemp, + Error, + TEXT("SetLocalEcsactPlayerId called too early - player entity spawner " + "not ready") + ); + return; + } + self->LocallyControllerPlayerId = NewPlayerId; UE_LOG( From ffbf2daf17fd429ef5e03b6346a88dd828f4d648 Mon Sep 17 00:00:00 2001 From: Ezekiel Warren Date: Sat, 14 Dec 2024 11:58:19 -0800 Subject: [PATCH 15/15] feat: mouse cursor nicities --- .../Content/Maps/EcsactMass.umap | Bin 32991 -> 38222 bytes unreal-cpp-net-fps/Content/Maps/MainMenu.umap | Bin 116642 -> 120723 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/unreal-cpp-net-fps/Content/Maps/EcsactMass.umap b/unreal-cpp-net-fps/Content/Maps/EcsactMass.umap index 5b5334003de015f927cc015eb7af0b27d13bcdf9..b951e1186642d5d4492a95080407a28f42a68bd8 100644 GIT binary patch literal 38222 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 ONqFj!NhuNl{Qm``zeoZA literal 32991 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 uRDUe^X}MnjJbB^Ro38nOb4SwTKfMgq*@{fOAeL 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 C-K~cJ delta 25378 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 A#Q*>R