From db88f6168e2ab821c88fc892d6f9eaa8dd6370f1 Mon Sep 17 00:00:00 2001 From: "Stefan Zimecki (Improbable.io)" Date: Fri, 3 Jun 2022 00:45:09 +0100 Subject: [PATCH 01/44] Additional clarification of a processor --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e382e8ae..c178e91c 100644 --- a/README.md +++ b/README.md @@ -99,7 +99,7 @@ In Mass, some ECS terminology differs from the norm in order to not get confused Typical Unreal Engine game code is expressed as actor objects that inherit from parent classes to change their data and functionality based on what they ***are***. In an ECS, an entity is only composed of fragments that get manipulated by processors based on which ECS components they ***have***. -An entity is really just a small unique identifier that points to some fragments. A Processor defines a query that filters only for entities that have specific fragments. For example, a basic "movement" Processor could query for entities that have a transform and velocity component to add the velocity to their current transform position. +An entity is really just a small unique identifier that points to some fragments. A Processor defines a query that filters only for entities that have specific fragments, and then performs an operation on those fragments. For example, a basic "movement" Processor could query for entities that have a transform and velocity component to add the velocity to their current transform position. Fragments are stored in memory as tightly packed arrays of other identical fragment arrangements called archetypes. Because of this, the aforementioned movement processor can be incredibly high performance because it does a simple operation on a small amount of data all at once. New functionality can easily be added by creating new fragments and processors. From c4f3a944b8a4e10631a66e4dc65f079d54d68899 Mon Sep 17 00:00:00 2001 From: PreyK Date: Sun, 3 Jul 2022 19:25:17 +0200 Subject: [PATCH 02/44] Mass Per Instance Material Data Demo & Sample Added a sample & demo on how to handle per instance ISM data. --- .../ISMPerInstanceData/EntityMaterial.uasset | 3 ++ .../EntityMaterial_Inst.uasset | 3 ++ .../RandomPerInstanceDataEntity.uasset | 3 ++ .../MassSample/Maps/ISMPerInstanceData.umap | 3 ++ .../MassSample/Common/Fragments/MSFragments.h | 6 +++ .../ISMPerInstanceDataInitializer.cpp | 45 +++++++++++++++++++ .../ISMPerInstanceDataInitializer.h | 24 ++++++++++ .../ISMPerInstanceDataProcessor.cpp | 29 ++++++++++++ .../Processors/ISMPerInstanceDataProcessor.h | 24 ++++++++++ .../Common/Traits/ISMPerInstanceDataTrait.cpp | 9 ++++ .../Common/Traits/ISMPerInstanceDataTrait.h | 21 +++++++++ 11 files changed, 170 insertions(+) create mode 100644 Content/MassSample/ISMPerInstanceData/EntityMaterial.uasset create mode 100644 Content/MassSample/ISMPerInstanceData/EntityMaterial_Inst.uasset create mode 100644 Content/MassSample/ISMPerInstanceData/RandomPerInstanceDataEntity.uasset create mode 100644 Content/MassSample/Maps/ISMPerInstanceData.umap create mode 100644 Source/MassSample/Common/Processors/ISMPerInstanceDataInitializer.cpp create mode 100644 Source/MassSample/Common/Processors/ISMPerInstanceDataInitializer.h create mode 100644 Source/MassSample/Common/Processors/ISMPerInstanceDataProcessor.cpp create mode 100644 Source/MassSample/Common/Processors/ISMPerInstanceDataProcessor.h create mode 100644 Source/MassSample/Common/Traits/ISMPerInstanceDataTrait.cpp create mode 100644 Source/MassSample/Common/Traits/ISMPerInstanceDataTrait.h diff --git a/Content/MassSample/ISMPerInstanceData/EntityMaterial.uasset b/Content/MassSample/ISMPerInstanceData/EntityMaterial.uasset new file mode 100644 index 00000000..a2de3390 --- /dev/null +++ b/Content/MassSample/ISMPerInstanceData/EntityMaterial.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4f1d6646f4f1f13d14aa25f9d80e74d89adea191656667fe741e6ff39e102c96 +size 14791 diff --git a/Content/MassSample/ISMPerInstanceData/EntityMaterial_Inst.uasset b/Content/MassSample/ISMPerInstanceData/EntityMaterial_Inst.uasset new file mode 100644 index 00000000..1a305eea --- /dev/null +++ b/Content/MassSample/ISMPerInstanceData/EntityMaterial_Inst.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9cb2aa6eb21a76c9edcbc9abde8eb00573d04fe4e3b3afa9ef9bad6d114d6b93 +size 7551 diff --git a/Content/MassSample/ISMPerInstanceData/RandomPerInstanceDataEntity.uasset b/Content/MassSample/ISMPerInstanceData/RandomPerInstanceDataEntity.uasset new file mode 100644 index 00000000..c59887d6 --- /dev/null +++ b/Content/MassSample/ISMPerInstanceData/RandomPerInstanceDataEntity.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0945719b856430d0de0f7af81c421bc5760b2422f695589fe7f16d9373cb1709 +size 4650 diff --git a/Content/MassSample/Maps/ISMPerInstanceData.umap b/Content/MassSample/Maps/ISMPerInstanceData.umap new file mode 100644 index 00000000..2394b90a --- /dev/null +++ b/Content/MassSample/Maps/ISMPerInstanceData.umap @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8bb2389d734b3f39d6d147c808fc66b5ecbb5c3c97a3be5f743681494a569a44 +size 36727 diff --git a/Source/MassSample/Common/Fragments/MSFragments.h b/Source/MassSample/Common/Fragments/MSFragments.h index ecace196..41fbe139 100644 --- a/Source/MassSample/Common/Fragments/MSFragments.h +++ b/Source/MassSample/Common/Fragments/MSFragments.h @@ -19,6 +19,12 @@ struct MASSSAMPLE_API FSampleColorFragment : public FMassFragment FColor Color = FColor::Red; }; +USTRUCT() +struct FISMPerInstanceDataFragment : public FMassFragment +{ + GENERATED_BODY() + float data = 0; +}; USTRUCT() struct MASSSAMPLE_API FInterpLocationFragment : public FMassFragment diff --git a/Source/MassSample/Common/Processors/ISMPerInstanceDataInitializer.cpp b/Source/MassSample/Common/Processors/ISMPerInstanceDataInitializer.cpp new file mode 100644 index 00000000..7951b686 --- /dev/null +++ b/Source/MassSample/Common/Processors/ISMPerInstanceDataInitializer.cpp @@ -0,0 +1,45 @@ +#include "ISMPerInstanceDataInitializer.h" + +#include "MassCommonFragments.h" +#include "MassRepresentationFragments.h" +#include "MassRepresentationSubsystem.h" +#include "Common/Fragments/MSFragments.h" + +UISMPerInstanceDataInitializer::UISMPerInstanceDataInitializer() +{ + bAutoRegisterWithProcessingPhases = true; + ObservedType = FISMPerInstanceDataFragment::StaticStruct(); + Operation = EMassObservedOperation::Add; +} + +void UISMPerInstanceDataInitializer::ConfigureQueries() +{ + EntityQuery.AddRequirement(EMassFragmentAccess::ReadWrite); + EntityQuery.AddRequirement(EMassFragmentAccess::ReadWrite); + EntityQuery.AddRequirement(EMassFragmentAccess::ReadOnly); + EntityQuery.AddSharedRequirement(EMassFragmentAccess::ReadWrite); +} + +void UISMPerInstanceDataInitializer::Execute(UMassEntitySubsystem& EntitySubsystem, FMassExecutionContext& Context) +{ + EntityQuery.ForEachEntityChunk(EntitySubsystem, Context, ([this](FMassExecutionContext& Context) + { + UMassRepresentationSubsystem* RepresentationSubsystem = Context.GetMutableSharedFragment().RepresentationSubsystem; + check(RepresentationSubsystem); + const FMassInstancedStaticMeshInfoArrayView ISMInfos = RepresentationSubsystem->GetMutableInstancedStaticMeshInfos(); + const int32 NumEntities = Context.GetNumEntities(); + + const TArrayView RepresentationList = Context.GetMutableFragmentView(); + const TConstArrayView RepresentationLODList = Context.GetFragmentView(); + const TArrayView RenderDatas = Context.GetMutableFragmentView(); + + for (int32 EntityIdx = 0; EntityIdx < NumEntities; EntityIdx++) + { + const FMassRepresentationFragment& Representation = RepresentationList[EntityIdx]; + const FMassRepresentationLODFragment& RepresentationLOD = RepresentationLODList[EntityIdx]; + FMassInstancedStaticMeshInfo& ISMInfo = ISMInfos[Representation.StaticMeshDescIndex]; + const FISMPerInstanceDataFragment& RenderData = RenderDatas[EntityIdx]; + ISMInfo.AddBatchedCustomData(RenderData.data, RepresentationLOD.LODSignificance); + } + })); +} diff --git a/Source/MassSample/Common/Processors/ISMPerInstanceDataInitializer.h b/Source/MassSample/Common/Processors/ISMPerInstanceDataInitializer.h new file mode 100644 index 00000000..7b5eeb5e --- /dev/null +++ b/Source/MassSample/Common/Processors/ISMPerInstanceDataInitializer.h @@ -0,0 +1,24 @@ +#pragma once +#include "CoreMinimal.h" +#include "MassObserverProcessor.h" +#include "MassProcessor.h" + +#include "ISMPerInstanceDataInitializer.generated.h" + +/** + * + */ +UCLASS() +class MASSSAMPLE_API UISMPerInstanceDataInitializer : public UMassObserverProcessor +{ + GENERATED_BODY() + +public: + UISMPerInstanceDataInitializer(); + +protected: + virtual void ConfigureQueries() override; + virtual void Execute(UMassEntitySubsystem& EntitySubsystem, FMassExecutionContext& Context) override; +private: + FMassEntityQuery EntityQuery; +}; \ No newline at end of file diff --git a/Source/MassSample/Common/Processors/ISMPerInstanceDataProcessor.cpp b/Source/MassSample/Common/Processors/ISMPerInstanceDataProcessor.cpp new file mode 100644 index 00000000..8bae0fab --- /dev/null +++ b/Source/MassSample/Common/Processors/ISMPerInstanceDataProcessor.cpp @@ -0,0 +1,29 @@ +#include "ISMPerInstanceDataProcessor.h" +#include "MassRepresentationFragments.h" +#include "MassRepresentationTypes.h" +#include "Common/Fragments/MSFragments.h" + +UISMPerInstanceDataProcessor::UISMPerInstanceDataProcessor() +{ + bAutoRegisterWithProcessingPhases = true; + ExecutionFlags = (int32)EProcessorExecutionFlags::All; + ExecutionOrder.ExecuteBefore.Add(UE::Mass::ProcessorGroupNames::Representation); +} +void UISMPerInstanceDataProcessor::ConfigureQueries() +{ + EntityQuery.AddRequirement(EMassFragmentAccess::ReadWrite); +} +void UISMPerInstanceDataProcessor::Execute(UMassEntitySubsystem& EntitySubsystem, FMassExecutionContext& Context) +{ + EntityQuery.ForEachEntityChunk(EntitySubsystem, Context, ([this](FMassExecutionContext& Context) + { + const TArrayView RenderDatas = Context.GetMutableFragmentView(); + const int32 NumEntities = Context.GetNumEntities(); + for (int32 EntityIdx = 0; EntityIdx < NumEntities; EntityIdx++) + { + FISMPerInstanceDataFragment& RenderData = RenderDatas[EntityIdx]; + //put the data you want to pass to the ISM here:) + RenderData.data = FMath::RandRange(0.f, 1.f); + } + })); +} \ No newline at end of file diff --git a/Source/MassSample/Common/Processors/ISMPerInstanceDataProcessor.h b/Source/MassSample/Common/Processors/ISMPerInstanceDataProcessor.h new file mode 100644 index 00000000..08826141 --- /dev/null +++ b/Source/MassSample/Common/Processors/ISMPerInstanceDataProcessor.h @@ -0,0 +1,24 @@ +#pragma once + +#include "CoreMinimal.h" +#include "MassProcessor.h" +#include "ISMPerInstanceDataProcessor.generated.h" + +/** + * + */ +UCLASS() +class UISMPerInstanceDataProcessor : public UMassProcessor +{ + GENERATED_BODY() + +public: + UISMPerInstanceDataProcessor(); + +protected: + virtual void ConfigureQueries() override; + virtual void Execute(UMassEntitySubsystem& EntitySubsystem, FMassExecutionContext& Context) override; + void UpdateCustomData(FMassExecutionContext& Context); +private: + FMassEntityQuery EntityQuery; +}; diff --git a/Source/MassSample/Common/Traits/ISMPerInstanceDataTrait.cpp b/Source/MassSample/Common/Traits/ISMPerInstanceDataTrait.cpp new file mode 100644 index 00000000..0daffd1f --- /dev/null +++ b/Source/MassSample/Common/Traits/ISMPerInstanceDataTrait.cpp @@ -0,0 +1,9 @@ +#include "ISMPerInstanceDataTrait.h" + +#include "MassEntityTemplateRegistry.h" +#include "Common/Fragments/MSFragments.h" + +void UISMPerInstanceDataTrait::BuildTemplate(FMassEntityTemplateBuildContext& BuildContext, UWorld& World) const +{ + BuildContext.AddFragment(); +} diff --git a/Source/MassSample/Common/Traits/ISMPerInstanceDataTrait.h b/Source/MassSample/Common/Traits/ISMPerInstanceDataTrait.h new file mode 100644 index 00000000..fb8277d5 --- /dev/null +++ b/Source/MassSample/Common/Traits/ISMPerInstanceDataTrait.h @@ -0,0 +1,21 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "MassEntityTraitBase.h" +#include "ISMPerInstanceDataTrait.generated.h" + +/** + * + */ + +UCLASS() +class MASSSAMPLE_API UISMPerInstanceDataTrait : public UMassEntityTraitBase +{ + GENERATED_BODY() + +protected: + virtual void BuildTemplate(FMassEntityTemplateBuildContext& BuildContext, UWorld& World) const override; + +}; From 2ecf0873dbf6136557d14a2eac1066f97a4e7831 Mon Sep 17 00:00:00 2001 From: Megafunk Date: Tue, 5 Jul 2022 00:20:45 -0600 Subject: [PATCH 03/44] More specific blueprint log --- .../Experimental/BPIntegration/BP_MassHitObserver.uasset | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Content/MassSample/Experimental/BPIntegration/BP_MassHitObserver.uasset b/Content/MassSample/Experimental/BPIntegration/BP_MassHitObserver.uasset index a61a13c9..36559480 100644 --- a/Content/MassSample/Experimental/BPIntegration/BP_MassHitObserver.uasset +++ b/Content/MassSample/Experimental/BPIntegration/BP_MassHitObserver.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:20d5bbf6339ca9ea2e1a301533a535c902c5051b50c4ea47afae637cacface8f -size 26123 +oid sha256:1f6f6b836b843ed69310a353951f73f06e8f9905185bb4fd6111d68b8c5d6f6f +size 22856 From cfe72375a4fcdbef3b4cdf0a19a1f42fdf164ce1 Mon Sep 17 00:00:00 2001 From: Ji-Rath Date: Thu, 7 Jul 2022 22:26:55 -0500 Subject: [PATCH 04/44] Add RTSFormation Plugin Add basic square formation with x amount of units --- .../Content/DA_AgentFormation.uasset | 3 + .../Content/RTSFormationsExample.umap | 3 + Plugins/RTSFormations/RTSFormations.uplugin | 24 ++++ Plugins/RTSFormations/Resources/Icon128.png | 3 + .../RTSFormations/Private/RTSAgentTraits.cpp | 109 ++++++++++++++++++ .../RTSFormations/Private/RTSFormations.cpp | 20 ++++ .../RTSFormations/Public/RTSAgentTraits.h | 54 +++++++++ .../RTSFormations/Public/RTSFormations.h | 15 +++ .../RTSFormations/RTSFormations.Build.cs | 53 +++++++++ 9 files changed, 284 insertions(+) create mode 100644 Plugins/RTSFormations/Content/DA_AgentFormation.uasset create mode 100644 Plugins/RTSFormations/Content/RTSFormationsExample.umap create mode 100644 Plugins/RTSFormations/RTSFormations.uplugin create mode 100644 Plugins/RTSFormations/Resources/Icon128.png create mode 100644 Plugins/RTSFormations/Source/RTSFormations/Private/RTSAgentTraits.cpp create mode 100644 Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormations.cpp create mode 100644 Plugins/RTSFormations/Source/RTSFormations/Public/RTSAgentTraits.h create mode 100644 Plugins/RTSFormations/Source/RTSFormations/Public/RTSFormations.h create mode 100644 Plugins/RTSFormations/Source/RTSFormations/RTSFormations.Build.cs diff --git a/Plugins/RTSFormations/Content/DA_AgentFormation.uasset b/Plugins/RTSFormations/Content/DA_AgentFormation.uasset new file mode 100644 index 00000000..88f2ba7e --- /dev/null +++ b/Plugins/RTSFormations/Content/DA_AgentFormation.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:25b9bef364eb89ab5f1afc55d48473f632ed62f6dfd396214d24489c128fae28 +size 3897 diff --git a/Plugins/RTSFormations/Content/RTSFormationsExample.umap b/Plugins/RTSFormations/Content/RTSFormationsExample.umap new file mode 100644 index 00000000..34c7f6e6 --- /dev/null +++ b/Plugins/RTSFormations/Content/RTSFormationsExample.umap @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:13e8cbdf14a10979f5ffbfae0622ac95f1b54bc0c81546434399e5a405ca4bfd +size 36392 diff --git a/Plugins/RTSFormations/RTSFormations.uplugin b/Plugins/RTSFormations/RTSFormations.uplugin new file mode 100644 index 00000000..7766683d --- /dev/null +++ b/Plugins/RTSFormations/RTSFormations.uplugin @@ -0,0 +1,24 @@ +{ + "FileVersion": 3, + "Version": 1, + "VersionName": "1.0", + "FriendlyName": "RTSFormations", + "Description": "", + "Category": "Other", + "CreatedBy": "JiRath", + "CreatedByURL": "", + "DocsURL": "", + "MarketplaceURL": "", + "SupportURL": "", + "CanContainContent": true, + "IsBetaVersion": true, + "IsExperimentalVersion": false, + "Installed": false, + "Modules": [ + { + "Name": "RTSFormations", + "Type": "Runtime", + "LoadingPhase": "Default" + } + ] +} \ No newline at end of file diff --git a/Plugins/RTSFormations/Resources/Icon128.png b/Plugins/RTSFormations/Resources/Icon128.png new file mode 100644 index 00000000..26245f6a --- /dev/null +++ b/Plugins/RTSFormations/Resources/Icon128.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f7239efaeefbd82de33ebe18518e50de075ea4188a468a9e4991396433d2275f +size 12699 diff --git a/Plugins/RTSFormations/Source/RTSFormations/Private/RTSAgentTraits.cpp b/Plugins/RTSFormations/Source/RTSFormations/Private/RTSAgentTraits.cpp new file mode 100644 index 00000000..4c4e4168 --- /dev/null +++ b/Plugins/RTSFormations/Source/RTSFormations/Private/RTSAgentTraits.cpp @@ -0,0 +1,109 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "RTSAgentTraits.h" + +#include "MassCommonFragments.h" +#include "MassEntityTemplateRegistry.h" +#include "MassNavigationFragments.h" + +void URTSFormationAgentTrait::BuildTemplate(FMassEntityTemplateBuildContext& BuildContext, UWorld& World) const +{ + BuildContext.AddFragment(); +} + +URTSFormationInitializer::URTSFormationInitializer() +{ + ObservedType = FRTSFormationAgent::StaticStruct(); + Operation = EMassObservedOperation::Add; +} + +void URTSFormationInitializer::ConfigureQueries() +{ + EntityQuery.AddRequirement(EMassFragmentAccess::ReadWrite); + + MoveEntityQuery.AddRequirement(EMassFragmentAccess::ReadOnly); + MoveEntityQuery.AddRequirement(EMassFragmentAccess::ReadWrite); + MoveEntityQuery.AddRequirement(EMassFragmentAccess::ReadOnly); +} + +void URTSFormationInitializer::Initialize(UObject& Owner) +{ + Super::Initialize(Owner); +} + +void URTSFormationInitializer::Execute(UMassEntitySubsystem& EntitySubsystem, FMassExecutionContext& Context) +{ + UE_LOG(LogTemp, Error, TEXT("RAN EXECUTE")); + // First query is to give all units an appropriate unit index. + // @todo The unit index is given 'randomly' regardless of distance to closest formation position. + int UnitIndex = 0; + EntityQuery.ForEachEntityChunk(EntitySubsystem, Context, [this, &UnitIndex](FMassExecutionContext& Context) + { + TArrayView RTSFormationAgents = Context.GetMutableFragmentView(); + for (int32 EntityIndex = 0; EntityIndex < Context.GetNumEntities(); ++EntityIndex) + { + FRTSFormationAgent& RTSFormationAgent = RTSFormationAgents[EntityIndex]; + RTSFormationAgent.UnitIndex = UnitIndex; + UnitIndex++; + } + }); + + MoveEntityQuery.ParallelForEachEntityChunk(EntitySubsystem, Context, [this, &UnitIndex](FMassExecutionContext& Context) + { + TConstArrayView RTSFormationAgents = Context.GetFragmentView(); + TArrayView MoveTargetFragments = Context.GetMutableFragmentView(); + TConstArrayView TransformFragments = Context.GetFragmentView(); + for (int32 EntityIndex = 0; EntityIndex < Context.GetNumEntities(); ++EntityIndex) + { + const FRTSFormationAgent& RTSFormationAgent = RTSFormationAgents[EntityIndex]; + FMassMoveTargetFragment& MoveTarget = MoveTargetFragments[EntityIndex]; + const FTransform& Transform = TransformFragments[EntityIndex].GetTransform(); + + const float UnitFloat = UnitIndex; + const int SideLength = FMath::CeilToInt(FMath::Sqrt(UnitFloat)); + const int BufferDistance = 200.f; + for(int w = 0;w(EMassFragmentAccess::ReadWrite); + EntityQuery.AddRequirement(EMassFragmentAccess::ReadOnly); +} + +void URTSAgentMovement::Execute(UMassEntitySubsystem& EntitySubsystem, FMassExecutionContext& Context) +{ + EntityQuery.ParallelForEachEntityChunk(EntitySubsystem, Context, [this](FMassExecutionContext& Context) + { + TArrayView MoveTargetFragments = Context.GetMutableFragmentView(); + TConstArrayView TransformFragments = Context.GetFragmentView(); + for (int32 EntityIndex = 0; EntityIndex < Context.GetNumEntities(); ++EntityIndex) + { + FMassMoveTargetFragment& MoveTarget = MoveTargetFragments[EntityIndex]; + const FTransform& Transform = TransformFragments[EntityIndex].GetTransform(); + + MoveTarget.DistanceToGoal = (MoveTarget.Center - Transform.GetLocation()).Length(); + MoveTarget.Forward = (MoveTarget.Center - Transform.GetLocation()).GetSafeNormal(); + } + }); +} diff --git a/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormations.cpp b/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormations.cpp new file mode 100644 index 00000000..65ff621e --- /dev/null +++ b/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormations.cpp @@ -0,0 +1,20 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "RTSFormations.h" + +#define LOCTEXT_NAMESPACE "FRTSFormationsModule" + +void FRTSFormationsModule::StartupModule() +{ + // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module +} + +void FRTSFormationsModule::ShutdownModule() +{ + // This function may be called during shutdown to clean up your module. For modules that support dynamic reloading, + // we call this function before unloading the module. +} + +#undef LOCTEXT_NAMESPACE + +IMPLEMENT_MODULE(FRTSFormationsModule, RTSFormations) \ No newline at end of file diff --git a/Plugins/RTSFormations/Source/RTSFormations/Public/RTSAgentTraits.h b/Plugins/RTSFormations/Source/RTSFormations/Public/RTSAgentTraits.h new file mode 100644 index 00000000..fb843815 --- /dev/null +++ b/Plugins/RTSFormations/Source/RTSFormations/Public/RTSAgentTraits.h @@ -0,0 +1,54 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "MassEntityTraitBase.h" +#include "MassEntityTypes.h" +#include "MassObserverProcessor.h" +#include "RTSAgentTraits.generated.h" + +// Store basic info about the unit +USTRUCT() +struct RTSFORMATIONS_API FRTSFormationAgent : public FMassFragment +{ + GENERATED_BODY() + + // The index of the unit in the formation + int UnitIndex = 0; +}; + +UCLASS() +class RTSFORMATIONS_API URTSFormationAgentTrait : public UMassEntityTraitBase +{ + GENERATED_BODY() + + virtual void BuildTemplate(FMassEntityTemplateBuildContext& BuildContext, UWorld& World) const override; +}; + +// Ensure that units are in formation +UCLASS() +class RTSFORMATIONS_API URTSFormationInitializer : public UMassObserverProcessor +{ + GENERATED_BODY() + + URTSFormationInitializer(); + virtual void ConfigureQueries() override; + virtual void Initialize(UObject& Owner) override; + virtual void Execute(UMassEntitySubsystem& EntitySubsystem, FMassExecutionContext& Context) override; + + FMassEntityQuery EntityQuery; + + FMassEntityQuery MoveEntityQuery; +}; + +UCLASS() +class RTSFORMATIONS_API URTSAgentMovement : public UMassProcessor +{ + GENERATED_BODY() + + virtual void ConfigureQueries() override; + virtual void Execute(UMassEntitySubsystem& EntitySubsystem, FMassExecutionContext& Context) override; + + FMassEntityQuery EntityQuery; +}; diff --git a/Plugins/RTSFormations/Source/RTSFormations/Public/RTSFormations.h b/Plugins/RTSFormations/Source/RTSFormations/Public/RTSFormations.h new file mode 100644 index 00000000..afdb4e36 --- /dev/null +++ b/Plugins/RTSFormations/Source/RTSFormations/Public/RTSFormations.h @@ -0,0 +1,15 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Modules/ModuleManager.h" + +class FRTSFormationsModule : public IModuleInterface +{ +public: + + /** IModuleInterface implementation */ + virtual void StartupModule() override; + virtual void ShutdownModule() override; +}; diff --git a/Plugins/RTSFormations/Source/RTSFormations/RTSFormations.Build.cs b/Plugins/RTSFormations/Source/RTSFormations/RTSFormations.Build.cs new file mode 100644 index 00000000..ff018dd0 --- /dev/null +++ b/Plugins/RTSFormations/Source/RTSFormations/RTSFormations.Build.cs @@ -0,0 +1,53 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; + +public class RTSFormations : ModuleRules +{ + public RTSFormations(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; + + PublicIncludePaths.AddRange( + new string[] { + // ... add public include paths required here ... + } + ); + + + PrivateIncludePaths.AddRange( + new string[] { + // ... add other private include paths required here ... + } + ); + + + PublicDependencyModuleNames.AddRange( + new string[] + { + "Core", "MassSpawner", "MassEntity", "MassCommon" + // ... add other public dependencies that you statically link with here ... + } + ); + + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "CoreUObject", + "Engine", + "Slate", + "SlateCore", "MassNavigation", + // ... add private dependencies that you statically link with here ... + } + ); + + + DynamicallyLoadedModuleNames.AddRange( + new string[] + { + // ... add any modules that your module loads dynamically here ... + } + ); + } +} From 4f78e837aeb445b7b006f9ae96a674f50e781a0d Mon Sep 17 00:00:00 2001 From: Ji-Rath Date: Fri, 8 Jul 2022 16:12:47 -0500 Subject: [PATCH 05/44] Add Stand action when reaching formation destination. (it seems like avoidance stops working while standing) --- .../Source/RTSFormations/Private/RTSAgentTraits.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Plugins/RTSFormations/Source/RTSFormations/Private/RTSAgentTraits.cpp b/Plugins/RTSFormations/Source/RTSFormations/Private/RTSAgentTraits.cpp index 4c4e4168..694aa8d9 100644 --- a/Plugins/RTSFormations/Source/RTSFormations/Private/RTSAgentTraits.cpp +++ b/Plugins/RTSFormations/Source/RTSFormations/Private/RTSAgentTraits.cpp @@ -76,8 +76,7 @@ void URTSFormationInitializer::Execute(UMassEntitySubsystem& EntitySubsystem, FM MoveTarget.Center = FVector(w*BufferDistance,l*BufferDistance,0.f); MoveTarget.Forward = (Transform.GetLocation() - MoveTarget.Center).GetSafeNormal(); MoveTarget.DistanceToGoal = (Transform.GetLocation() - MoveTarget.Center).Length(); - //MoveTarget.DesiredSpeed = FMassInt16Real(200.f); - UE_LOG(LogTemp, Error, TEXT("MOVE TARGET: %s"), *(MoveTarget.Center.ToString())); + MoveTarget.SlackRadius = 50.f; } } } @@ -104,6 +103,11 @@ void URTSAgentMovement::Execute(UMassEntitySubsystem& EntitySubsystem, FMassExec MoveTarget.DistanceToGoal = (MoveTarget.Center - Transform.GetLocation()).Length(); MoveTarget.Forward = (MoveTarget.Center - Transform.GetLocation()).GetSafeNormal(); + + if (MoveTarget.DistanceToGoal <= MoveTarget.SlackRadius) + { + MoveTarget.CreateNewAction(EMassMovementAction::Stand, *GetWorld()); + } } }); } From 135bbd19be7a234a0e666a67a8379d49c8a9d758 Mon Sep 17 00:00:00 2001 From: Ji-Rath Date: Fri, 8 Jul 2022 17:48:55 -0500 Subject: [PATCH 06/44] Begin implementing ratio-based formation shapes Add shared fragment to modify formation sizes Add some additional comments --- .../RTSFormations/Private/RTSAgentTraits.cpp | 61 ++++++++++++------- .../RTSFormations/Public/RTSAgentTraits.h | 22 ++++++- 2 files changed, 60 insertions(+), 23 deletions(-) diff --git a/Plugins/RTSFormations/Source/RTSFormations/Private/RTSAgentTraits.cpp b/Plugins/RTSFormations/Source/RTSFormations/Private/RTSAgentTraits.cpp index 694aa8d9..25acc7ee 100644 --- a/Plugins/RTSFormations/Source/RTSFormations/Private/RTSAgentTraits.cpp +++ b/Plugins/RTSFormations/Source/RTSFormations/Private/RTSAgentTraits.cpp @@ -9,7 +9,15 @@ void URTSFormationAgentTrait::BuildTemplate(FMassEntityTemplateBuildContext& BuildContext, UWorld& World) const { + UMassEntitySubsystem* EntitySubsystem = UWorld::GetSubsystem(&World); + check(EntitySubsystem); + BuildContext.AddFragment(); + + FRTSFormationSettings MyFragment; + uint32 MySharedFragmentHash = UE::StructUtils::GetStructCrc32(FConstStructView::Make(MyFragment)); + FSharedStruct MySharedFragment = EntitySubsystem->GetOrCreateSharedFragment(MySharedFragmentHash, MyFragment); + BuildContext.AddSharedFragment(MySharedFragment); } URTSFormationInitializer::URTSFormationInitializer() @@ -25,6 +33,7 @@ void URTSFormationInitializer::ConfigureQueries() MoveEntityQuery.AddRequirement(EMassFragmentAccess::ReadOnly); MoveEntityQuery.AddRequirement(EMassFragmentAccess::ReadWrite); MoveEntityQuery.AddRequirement(EMassFragmentAccess::ReadOnly); + MoveEntityQuery.AddSharedRequirement(EMassFragmentAccess::ReadOnly); } void URTSFormationInitializer::Initialize(UObject& Owner) @@ -34,11 +43,12 @@ void URTSFormationInitializer::Initialize(UObject& Owner) void URTSFormationInitializer::Execute(UMassEntitySubsystem& EntitySubsystem, FMassExecutionContext& Context) { - UE_LOG(LogTemp, Error, TEXT("RAN EXECUTE")); // First query is to give all units an appropriate unit index. // @todo The unit index is given 'randomly' regardless of distance to closest formation position. + // Ideas: When a unit is already assigned an index, we shouldnt have to recalculate it unless a unit was destroyed and the index destroyed is less than + // the units index - pretty much like an array, would it be inefficient though? int UnitIndex = 0; - EntityQuery.ForEachEntityChunk(EntitySubsystem, Context, [this, &UnitIndex](FMassExecutionContext& Context) + EntityQuery.ParallelForEachEntityChunk(EntitySubsystem, Context, [this, &UnitIndex](FMassExecutionContext& Context) { TArrayView RTSFormationAgents = Context.GetMutableFragmentView(); for (int32 EntityIndex = 0; EntityIndex < Context.GetNumEntities(); ++EntityIndex) @@ -49,37 +59,43 @@ void URTSFormationInitializer::Execute(UMassEntitySubsystem& EntitySubsystem, FM } }); + // Square formations have equal sides, so sqrt num units to get side length + //const float UnitFloat = UnitIndex; + //const int SideLength = FMath::CeilToInt(FMath::Sqrt(UnitFloat)); + + //const int BufferDistance = 200.f; // Distance between each unit + + // Query to calculate move target for entities based on unit index MoveEntityQuery.ParallelForEachEntityChunk(EntitySubsystem, Context, [this, &UnitIndex](FMassExecutionContext& Context) { TConstArrayView RTSFormationAgents = Context.GetFragmentView(); TArrayView MoveTargetFragments = Context.GetMutableFragmentView(); TConstArrayView TransformFragments = Context.GetFragmentView(); + const FRTSFormationSettings& RTSFormationSettings = Context.GetSharedFragment(); + + const int Whole = RTSFormationSettings.UnitRatioX + RTSFormationSettings.UnitRatioY; + const int SideLengthX = RTSFormationSettings.UnitRatioX / Whole * UnitIndex; + const int SideLengthY = RTSFormationSettings.UnitRatioY / Whole * UnitIndex; + for (int32 EntityIndex = 0; EntityIndex < Context.GetNumEntities(); ++EntityIndex) { const FRTSFormationAgent& RTSFormationAgent = RTSFormationAgents[EntityIndex]; FMassMoveTargetFragment& MoveTarget = MoveTargetFragments[EntityIndex]; const FTransform& Transform = TransformFragments[EntityIndex].GetTransform(); - const float UnitFloat = UnitIndex; - const int SideLength = FMath::CeilToInt(FMath::Sqrt(UnitFloat)); - const int BufferDistance = 200.f; - for(int w = 0;w Date: Fri, 8 Jul 2022 17:49:10 -0500 Subject: [PATCH 07/44] Add additional dependency to build.cs --- .../RTSFormations/Source/RTSFormations/RTSFormations.Build.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Plugins/RTSFormations/Source/RTSFormations/RTSFormations.Build.cs b/Plugins/RTSFormations/Source/RTSFormations/RTSFormations.Build.cs index ff018dd0..f5013a42 100644 --- a/Plugins/RTSFormations/Source/RTSFormations/RTSFormations.Build.cs +++ b/Plugins/RTSFormations/Source/RTSFormations/RTSFormations.Build.cs @@ -25,7 +25,7 @@ public RTSFormations(ReadOnlyTargetRules Target) : base(Target) PublicDependencyModuleNames.AddRange( new string[] { - "Core", "MassSpawner", "MassEntity", "MassCommon" + "Core", "MassSpawner", "MassEntity", "MassCommon", "StructUtils" // ... add other public dependencies that you statically link with here ... } ); From b70596fc75b4c66774535198ff7b40eefe6a81bf Mon Sep 17 00:00:00 2001 From: Ji-Rath Date: Sun, 10 Jul 2022 15:34:12 -0500 Subject: [PATCH 08/44] Add RTSFormationProcessors for processor handling Refactor RTSAgentTraits so that traits/fragments are separate from processors Streamline logic so readability is more of a focus over performance --- .../RTSFormations/Private/RTSAgentTraits.cpp | 116 +----------- .../Private/RTSFormationProcessors.cpp | 166 ++++++++++++++++++ .../Private/RTSFormationProcessors.h | 58 ++++++ .../RTSFormations/Public/RTSAgentTraits.h | 42 +---- 4 files changed, 236 insertions(+), 146 deletions(-) create mode 100644 Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationProcessors.cpp create mode 100644 Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationProcessors.h diff --git a/Plugins/RTSFormations/Source/RTSFormations/Private/RTSAgentTraits.cpp b/Plugins/RTSFormations/Source/RTSFormations/Private/RTSAgentTraits.cpp index 25acc7ee..abaf4e6e 100644 --- a/Plugins/RTSFormations/Source/RTSFormations/Private/RTSAgentTraits.cpp +++ b/Plugins/RTSFormations/Source/RTSFormations/Private/RTSAgentTraits.cpp @@ -3,9 +3,9 @@ #include "RTSAgentTraits.h" -#include "MassCommonFragments.h" #include "MassEntityTemplateRegistry.h" #include "MassNavigationFragments.h" +#include "MassObserverRegistry.h" void URTSFormationAgentTrait::BuildTemplate(FMassEntityTemplateBuildContext& BuildContext, UWorld& World) const { @@ -14,117 +14,7 @@ void URTSFormationAgentTrait::BuildTemplate(FMassEntityTemplateBuildContext& Bui BuildContext.AddFragment(); - FRTSFormationSettings MyFragment; - uint32 MySharedFragmentHash = UE::StructUtils::GetStructCrc32(FConstStructView::Make(MyFragment)); - FSharedStruct MySharedFragment = EntitySubsystem->GetOrCreateSharedFragment(MySharedFragmentHash, MyFragment); + uint32 MySharedFragmentHash = UE::StructUtils::GetStructCrc32(FConstStructView::Make(FormationSettings)); + FSharedStruct MySharedFragment = EntitySubsystem->GetOrCreateSharedFragment(MySharedFragmentHash, FormationSettings); BuildContext.AddSharedFragment(MySharedFragment); } - -URTSFormationInitializer::URTSFormationInitializer() -{ - ObservedType = FRTSFormationAgent::StaticStruct(); - Operation = EMassObservedOperation::Add; -} - -void URTSFormationInitializer::ConfigureQueries() -{ - EntityQuery.AddRequirement(EMassFragmentAccess::ReadWrite); - - MoveEntityQuery.AddRequirement(EMassFragmentAccess::ReadOnly); - MoveEntityQuery.AddRequirement(EMassFragmentAccess::ReadWrite); - MoveEntityQuery.AddRequirement(EMassFragmentAccess::ReadOnly); - MoveEntityQuery.AddSharedRequirement(EMassFragmentAccess::ReadOnly); -} - -void URTSFormationInitializer::Initialize(UObject& Owner) -{ - Super::Initialize(Owner); -} - -void URTSFormationInitializer::Execute(UMassEntitySubsystem& EntitySubsystem, FMassExecutionContext& Context) -{ - // First query is to give all units an appropriate unit index. - // @todo The unit index is given 'randomly' regardless of distance to closest formation position. - // Ideas: When a unit is already assigned an index, we shouldnt have to recalculate it unless a unit was destroyed and the index destroyed is less than - // the units index - pretty much like an array, would it be inefficient though? - int UnitIndex = 0; - EntityQuery.ParallelForEachEntityChunk(EntitySubsystem, Context, [this, &UnitIndex](FMassExecutionContext& Context) - { - TArrayView RTSFormationAgents = Context.GetMutableFragmentView(); - for (int32 EntityIndex = 0; EntityIndex < Context.GetNumEntities(); ++EntityIndex) - { - FRTSFormationAgent& RTSFormationAgent = RTSFormationAgents[EntityIndex]; - RTSFormationAgent.UnitIndex = UnitIndex; - UnitIndex++; - } - }); - - // Square formations have equal sides, so sqrt num units to get side length - //const float UnitFloat = UnitIndex; - //const int SideLength = FMath::CeilToInt(FMath::Sqrt(UnitFloat)); - - //const int BufferDistance = 200.f; // Distance between each unit - - // Query to calculate move target for entities based on unit index - MoveEntityQuery.ParallelForEachEntityChunk(EntitySubsystem, Context, [this, &UnitIndex](FMassExecutionContext& Context) - { - TConstArrayView RTSFormationAgents = Context.GetFragmentView(); - TArrayView MoveTargetFragments = Context.GetMutableFragmentView(); - TConstArrayView TransformFragments = Context.GetFragmentView(); - const FRTSFormationSettings& RTSFormationSettings = Context.GetSharedFragment(); - - const int Whole = RTSFormationSettings.UnitRatioX + RTSFormationSettings.UnitRatioY; - const int SideLengthX = RTSFormationSettings.UnitRatioX / Whole * UnitIndex; - const int SideLengthY = RTSFormationSettings.UnitRatioY / Whole * UnitIndex; - - for (int32 EntityIndex = 0; EntityIndex < Context.GetNumEntities(); ++EntityIndex) - { - const FRTSFormationAgent& RTSFormationAgent = RTSFormationAgents[EntityIndex]; - FMassMoveTargetFragment& MoveTarget = MoveTargetFragments[EntityIndex]; - const FTransform& Transform = TransformFragments[EntityIndex].GetTransform(); - - // Convert UnitIndex to X/Y coords - int w = RTSFormationAgent.UnitIndex / SideLengthX; - int l = RTSFormationAgent.UnitIndex % SideLengthY; - - // We want the formation to be 'centered' so we need to create an offset - FVector CenterOffset((SideLengthX/2) * RTSFormationSettings.BufferDistance, (SideLengthY/2) * RTSFormationSettings.BufferDistance, 0.f); - - // Create movement action - MoveTarget.CreateNewAction(EMassMovementAction::Move, *GetWorld()); - MoveTarget.Center = FVector(w*RTSFormationSettings.BufferDistance-CenterOffset.X,l*RTSFormationSettings.BufferDistance-CenterOffset.Y,0.f); - MoveTarget.Forward = (Transform.GetLocation() - MoveTarget.Center).GetSafeNormal(); - MoveTarget.DistanceToGoal = (Transform.GetLocation() - MoveTarget.Center).Length(); - MoveTarget.SlackRadius = 25.f; - } - }); -} - -void URTSAgentMovement::ConfigureQueries() -{ - EntityQuery.AddRequirement(EMassFragmentAccess::ReadWrite); - EntityQuery.AddRequirement(EMassFragmentAccess::ReadOnly); -} - -void URTSAgentMovement::Execute(UMassEntitySubsystem& EntitySubsystem, FMassExecutionContext& Context) -{ - EntityQuery.ParallelForEachEntityChunk(EntitySubsystem, Context, [this](FMassExecutionContext& Context) - { - TArrayView MoveTargetFragments = Context.GetMutableFragmentView(); - TConstArrayView TransformFragments = Context.GetFragmentView(); - for (int32 EntityIndex = 0; EntityIndex < Context.GetNumEntities(); ++EntityIndex) - { - FMassMoveTargetFragment& MoveTarget = MoveTargetFragments[EntityIndex]; - const FTransform& Transform = TransformFragments[EntityIndex].GetTransform(); - - MoveTarget.DistanceToGoal = (MoveTarget.Center - Transform.GetLocation()).Length(); - MoveTarget.Forward = (MoveTarget.Center - Transform.GetLocation()).GetSafeNormal(); - - // Once we are close enough to our goal, create stand action - if (MoveTarget.DistanceToGoal <= MoveTarget.SlackRadius) - { - MoveTarget.CreateNewAction(EMassMovementAction::Stand, *GetWorld()); - } - } - }); -} diff --git a/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationProcessors.cpp b/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationProcessors.cpp new file mode 100644 index 00000000..a8982684 --- /dev/null +++ b/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationProcessors.cpp @@ -0,0 +1,166 @@ +#include "RTSFormationProcessors.h" + +#include "MassCommonFragments.h" +#include "MassMovementFragments.h" +#include "MassNavigationFragments.h" +#include "MassNavigationTypes.h" +#include "MassObserverRegistry.h" +#include "MassSignalSubsystem.h" +#include "RTSAgentTraits.h" +#include "RTSFormationSubsystem.h" + +URTSFormationInitializer::URTSFormationInitializer() +{ + ObservedType = FRTSFormationAgent::StaticStruct(); + Operation = EMassObservedOperation::Add; +} + +void URTSFormationInitializer::ConfigureQueries() +{ + EntityQuery.AddRequirement(EMassFragmentAccess::ReadWrite); + EntityQuery.AddSharedRequirement(EMassFragmentAccess::ReadWrite); + + DestroyQuery.AddRequirement(EMassFragmentAccess::None, EMassFragmentPresence::None); + DestroyQuery.AddSharedRequirement(EMassFragmentAccess::ReadWrite); +} + +void URTSFormationInitializer::Initialize(UObject& Owner) +{ + Super::Initialize(Owner); + + SignalSubsystem = UWorld::GetSubsystem(Owner.GetWorld()); + FormationSubsystem = UWorld::GetSubsystem(Owner.GetWorld()); +} + +void URTSFormationInitializer::Execute(UMassEntitySubsystem& EntitySubsystem, FMassExecutionContext& Context) +{ + // First query is to give all units an appropriate unit index. + // @todo The unit index is given 'randomly' regardless of distance to closest formation position. + // Ideas: When a unit is already assigned an index, we shouldnt have to recalculate it unless a unit was destroyed and the index destroyed is less than + // the units index - pretty much like an array, would it be inefficient though? + // Another issue is that the center offset changes when enough units are spawned, causing the positions of the other units to be off + + // Since I really want this example to be straightforward, Im going to streamline adding/remove entities so that the formation gets recalculated + int UnitIndex = 0; + EntityQuery.ParallelForEachEntityChunk(EntitySubsystem, Context, [this, &UnitIndex](FMassExecutionContext& Context) + { + TArrayView RTSFormationAgents = Context.GetMutableFragmentView(); + FRTSFormationSettings& RTSFormationSettings = Context.GetMutableSharedFragment(); + for (int32 EntityIndex = 0; EntityIndex < Context.GetNumEntities(); ++EntityIndex) + { + FRTSFormationAgent& RTSFormationAgent = RTSFormationAgents[EntityIndex]; + RTSFormationAgent.UnitIndex = UnitIndex; + FormationSubsystem->Units.Emplace(Context.GetEntity(EntityIndex)); + SignalSubsystem->SignalEntities(FormationUpdated, FormationSubsystem->Units); + } + }); + + DestroyQuery.ParallelForEachEntityChunk(EntitySubsystem, Context, [this, &UnitIndex](FMassExecutionContext& Context) + { + FRTSFormationSettings& RTSFormationSettings = Context.GetMutableSharedFragment(); + for (int32 EntityIndex = 0; EntityIndex < Context.GetNumEntities(); ++EntityIndex) + { + FormationSubsystem->Units.Remove(Context.GetEntity(EntityIndex)); + SignalSubsystem->SignalEntities(FormationUpdated, FormationSubsystem->Units); + } + }); +} + +void URTSFormationInitializer::Register() +{ + Super::Register(); + + // Register removal of entity + UMassObserverRegistry::GetMutable().RegisterObserver(*ObservedType, EMassObservedOperation::Remove, GetClass()); +} + +void URTSAgentMovement::ConfigureQueries() +{ + EntityQuery.AddRequirement(EMassFragmentAccess::ReadWrite); + EntityQuery.AddRequirement(EMassFragmentAccess::ReadOnly); + EntityQuery.AddSharedRequirement(EMassFragmentAccess::ReadWrite); + EntityQuery.AddRequirement(EMassFragmentAccess::ReadWrite); +} + +void URTSAgentMovement::Execute(UMassEntitySubsystem& EntitySubsystem, FMassExecutionContext& Context) +{ + EntityQuery.ParallelForEachEntityChunk(EntitySubsystem, Context, [this](FMassExecutionContext& Context) + { + TArrayView MoveTargetFragments = Context.GetMutableFragmentView(); + TConstArrayView TransformFragments = Context.GetFragmentView(); + FRTSFormationSettings& FormationSettings = Context.GetMutableSharedFragment(); + TArrayView VelocityFragments = Context.GetMutableFragmentView(); + + for (int32 EntityIndex = 0; EntityIndex < Context.GetNumEntities(); ++EntityIndex) + { + FMassMoveTargetFragment& MoveTarget = MoveTargetFragments[EntityIndex]; + const FTransform& Transform = TransformFragments[EntityIndex].GetTransform(); + FVector& Velocity = VelocityFragments[EntityIndex].Value; + + // Update move target values + MoveTarget.DistanceToGoal = (MoveTarget.Center - Transform.GetLocation()).Length(); + MoveTarget.Forward = (MoveTarget.Center - Transform.GetLocation()).GetSafeNormal(); + + // Once we are close enough to our goal, create stand action + if (MoveTarget.DistanceToGoal <= MoveTarget.SlackRadius) + { + MoveTarget.CreateNewAction(EMassMovementAction::Stand, *GetWorld()); + Velocity = FVector::Zero(); + } + } + }); +} + +void URTSFormationUpdate::Initialize(UObject& Owner) +{ + Super::Initialize(Owner); + SubscribeToSignal(FormationUpdated); + + FormationSubsystem = UWorld::GetSubsystem(Owner.GetWorld()); +} + +void URTSFormationUpdate::ConfigureQueries() +{ + EntityQuery.AddRequirement(EMassFragmentAccess::ReadOnly); + EntityQuery.AddRequirement(EMassFragmentAccess::ReadWrite); + EntityQuery.AddRequirement(EMassFragmentAccess::ReadOnly); + EntityQuery.AddSharedRequirement(EMassFragmentAccess::ReadOnly); +} + +void URTSFormationUpdate::SignalEntities(UMassEntitySubsystem& EntitySubsystem, FMassExecutionContext& Context, + FMassSignalNameLookup& EntitySignals) +{ + // Query to calculate move target for entities based on unit index + EntityQuery.ParallelForEachEntityChunk(EntitySubsystem, Context, [this](FMassExecutionContext& Context) + { + TConstArrayView RTSFormationAgents = Context.GetFragmentView(); + TArrayView MoveTargetFragments = Context.GetMutableFragmentView(); + TConstArrayView TransformFragments = Context.GetFragmentView(); + const FRTSFormationSettings& RTSFormationSettings = Context.GetSharedFragment(); + + for (int32 EntityIndex = 0; EntityIndex < Context.GetNumEntities(); ++EntityIndex) + { + const FRTSFormationAgent& RTSFormationAgent = RTSFormationAgents[EntityIndex]; + FMassMoveTargetFragment& MoveTarget = MoveTargetFragments[EntityIndex]; + const FTransform& Transform = TransformFragments[EntityIndex].GetTransform(); + + // Can be optimized by caching? + const int UnitIndex = FormationSubsystem->Units.Find(Context.GetEntity(EntityIndex)); + + // Convert UnitIndex to X/Y coords + const int w = UnitIndex / RTSFormationSettings.FormationLength; + const int l = UnitIndex % RTSFormationSettings.FormationLength; + + // We want the formation to be 'centered' so we need to create an offset + const FVector CenterOffset((RTSFormationSettings.FormationLength/2) * RTSFormationSettings.BufferDistance, (FormationSubsystem->Units.Num()/RTSFormationSettings.FormationLength/2) * RTSFormationSettings.BufferDistance, 0.f); + + // Create movement action + MoveTarget.CreateNewAction(EMassMovementAction::Move, *GetWorld()); + MoveTarget.Center = FVector(w*RTSFormationSettings.BufferDistance-CenterOffset.Y,l*RTSFormationSettings.BufferDistance-CenterOffset.X,0.f); + MoveTarget.Forward = (Transform.GetLocation() - MoveTarget.Center).GetSafeNormal(); + MoveTarget.DistanceToGoal = (Transform.GetLocation() - MoveTarget.Center).Length(); + MoveTarget.SlackRadius = 10.f; + MoveTarget.IntentAtGoal = EMassMovementAction::Stand; + } + }); +} diff --git a/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationProcessors.h b/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationProcessors.h new file mode 100644 index 00000000..6e508255 --- /dev/null +++ b/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationProcessors.h @@ -0,0 +1,58 @@ +#pragma once +#include "MassEntityQuery.h" +#include "MassObserverProcessor.h" +#include "MassProcessor.h" +#include "MassSignalProcessorBase.h" +#include "RTSFormationProcessors.generated.h" + +class URTSFormationSubsystem; +const FName FormationUpdated = FName(TEXT("FormationUpdated")); + +// Observer that runs when a unit is spawned. Calculates square formation position based on unit count +UCLASS() +class RTSFORMATIONS_API URTSFormationInitializer : public UMassObserverProcessor +{ + GENERATED_BODY() + + URTSFormationInitializer(); + virtual void ConfigureQueries() override; + virtual void Initialize(UObject& Owner) override; + virtual void Execute(UMassEntitySubsystem& EntitySubsystem, FMassExecutionContext& Context) override; + virtual void Register() override; + + FMassEntityQuery EntityQuery; + + FMassEntityQuery MoveEntityQuery; + + FMassEntityQuery DestroyQuery; + + TObjectPtr SignalSubsystem; + + TObjectPtr FormationSubsystem; +}; + +// Simple movement processor to get agents from a to b +UCLASS() +class RTSFORMATIONS_API URTSAgentMovement : public UMassProcessor +{ + GENERATED_BODY() + + virtual void ConfigureQueries() override; + virtual void Execute(UMassEntitySubsystem& EntitySubsystem, FMassExecutionContext& Context) override; + + FMassEntityQuery EntityQuery; + + FMassEntityQuery FormationQuery; +}; + +UCLASS() +class RTSFORMATIONS_API URTSFormationUpdate : public UMassSignalProcessorBase +{ + GENERATED_BODY() + + virtual void Initialize(UObject& Owner) override; + virtual void ConfigureQueries() override; + virtual void SignalEntities(UMassEntitySubsystem& EntitySubsystem, FMassExecutionContext& Context, FMassSignalNameLookup& EntitySignals) override; + + TObjectPtr FormationSubsystem; +}; diff --git a/Plugins/RTSFormations/Source/RTSFormations/Public/RTSAgentTraits.h b/Plugins/RTSFormations/Source/RTSFormations/Public/RTSAgentTraits.h index 4e5ae897..babc22c7 100644 --- a/Plugins/RTSFormations/Source/RTSFormations/Public/RTSAgentTraits.h +++ b/Plugins/RTSFormations/Source/RTSFormations/Public/RTSAgentTraits.h @@ -5,9 +5,10 @@ #include "CoreMinimal.h" #include "MassEntityTraitBase.h" #include "MassEntityTypes.h" -#include "MassObserverProcessor.h" #include "RTSAgentTraits.generated.h" +class URTSFormationSubsystem; + // Store basic info about the unit USTRUCT() struct RTSFORMATIONS_API FRTSFormationAgent : public FMassFragment @@ -27,13 +28,9 @@ struct RTSFORMATIONS_API FRTSFormationSettings : public FMassSharedFragment UPROPERTY(EditAnywhere, Category = "Formation") float BufferDistance = 100.f; - // Width ratio for formation - UPROPERTY(EditAnywhere, Category = "Formation") - int UnitRatioX = 1; - - // Length ratio for formation + // Unit width of formation UPROPERTY(EditAnywhere, Category = "Formation") - int UnitRatioY = 1; + int FormationLength = 1; }; // Provides entity with FRTSFormationAgent fragment to enable formations @@ -43,32 +40,11 @@ class RTSFORMATIONS_API URTSFormationAgentTrait : public UMassEntityTraitBase GENERATED_BODY() virtual void BuildTemplate(FMassEntityTemplateBuildContext& BuildContext, UWorld& World) const override; -}; - -// Observer that runs when a unit is spawned. Calculates square formation position based on unit count -UCLASS() -class RTSFORMATIONS_API URTSFormationInitializer : public UMassObserverProcessor -{ - GENERATED_BODY() - URTSFormationInitializer(); - virtual void ConfigureQueries() override; - virtual void Initialize(UObject& Owner) override; - virtual void Execute(UMassEntitySubsystem& EntitySubsystem, FMassExecutionContext& Context) override; - - FMassEntityQuery EntityQuery; - - FMassEntityQuery MoveEntityQuery; + UPROPERTY(EditAnywhere) + FRTSFormationSettings FormationSettings; }; -// Simple movement processor to get agents from a to b -UCLASS() -class RTSFORMATIONS_API URTSAgentMovement : public UMassProcessor -{ - GENERATED_BODY() - - virtual void ConfigureQueries() override; - virtual void Execute(UMassEntitySubsystem& EntitySubsystem, FMassExecutionContext& Context) override; - - FMassEntityQuery EntityQuery; -}; +//@todo create destroyer observer processor to handle updating units +//@todo create another processor to handle when units need to be updated (using bUpdateUnitPosition in SharedFragment) +//@todo I definitely went overboard in terms of complexity, this example should really just be straightforward to read and have performance second i think From 6129cb5550428d0dcbbf8f24c5b6debe7da2416d Mon Sep 17 00:00:00 2001 From: Ji-Rath Date: Sun, 10 Jul 2022 15:34:27 -0500 Subject: [PATCH 09/44] Add plugin dependencies --- Plugins/RTSFormations/RTSFormations.uplugin | 22 +++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/Plugins/RTSFormations/RTSFormations.uplugin b/Plugins/RTSFormations/RTSFormations.uplugin index 7766683d..c58598ee 100644 --- a/Plugins/RTSFormations/RTSFormations.uplugin +++ b/Plugins/RTSFormations/RTSFormations.uplugin @@ -20,5 +20,27 @@ "Type": "Runtime", "LoadingPhase": "Default" } + ], + "Plugins": [ + { + "Name": "MassGameplay", + "Enabled": true + }, + { + "Name": "MassEntity", + "Enabled": true + }, + { + "Name": "MassGameplay", + "Enabled": true + }, + { + "Name": "MassAI", + "Enabled": true + }, + { + "Name": "StructUtils", + "Enabled": true + } ] } \ No newline at end of file From 208140345af06a9f1019c26ff9a5d36dd68659f0 Mon Sep 17 00:00:00 2001 From: Ji-Rath Date: Sun, 10 Jul 2022 15:34:44 -0500 Subject: [PATCH 10/44] Add RTSFormationSubsystem for handling entity destruction and unit array --- .../Private/RTSFormationSubsystem.cpp | 18 +++++++++++++ .../Private/RTSFormationSubsystem.h | 26 +++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationSubsystem.cpp create mode 100644 Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationSubsystem.h diff --git a/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationSubsystem.cpp b/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationSubsystem.cpp new file mode 100644 index 00000000..d47d6ac9 --- /dev/null +++ b/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationSubsystem.cpp @@ -0,0 +1,18 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "RTSFormationSubsystem.h" + +#include "MassAgentComponent.h" +#include "MassEntitySubsystem.h" +#include "MassSignalSubsystem.h" +#include "RTSFormationProcessors.h" + +void URTSFormationSubsystem::DestroyEntity(UMassAgentComponent* Entity) +{ + UMassEntitySubsystem* EntitySubsystem = GetWorld()->GetSubsystem(); + EntitySubsystem->Defer().DestroyEntity(Entity->GetEntityHandle()); + UE_LOG(LogTemp, Error, TEXT("Entity: %d"), Entity->GetEntityHandle().Index) + Units.Remove(Entity->GetEntityHandle()); + GetWorld()->GetSubsystem()->SignalEntities(FormationUpdated, Units); +} diff --git a/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationSubsystem.h b/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationSubsystem.h new file mode 100644 index 00000000..3e31c643 --- /dev/null +++ b/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationSubsystem.h @@ -0,0 +1,26 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "Common/Misc/MSBPFunctionLibrary.h" +#include "RTSFormationSubsystem.generated.h" + +class UMassAgentComponent; +struct FMassEntityHandle; +/** + * + */ +UCLASS() +class RTSFORMATIONS_API URTSFormationSubsystem : public UWorldSubsystem +{ + GENERATED_BODY() + +public: + // Stores the num of units in the formation + UPROPERTY() + TArray Units; + + UFUNCTION(BlueprintCallable) + void DestroyEntity(UMassAgentComponent* Entity); +}; From 126a4dd39ac9f7fb3c36a268c9daa80ee102f9c6 Mon Sep 17 00:00:00 2001 From: Ji-Rath Date: Sun, 10 Jul 2022 15:35:01 -0500 Subject: [PATCH 11/44] Add mass dependencies for C++ --- .../RTSFormations/Source/RTSFormations/RTSFormations.Build.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Plugins/RTSFormations/Source/RTSFormations/RTSFormations.Build.cs b/Plugins/RTSFormations/Source/RTSFormations/RTSFormations.Build.cs index f5013a42..7792cefb 100644 --- a/Plugins/RTSFormations/Source/RTSFormations/RTSFormations.Build.cs +++ b/Plugins/RTSFormations/Source/RTSFormations/RTSFormations.Build.cs @@ -25,7 +25,7 @@ public RTSFormations(ReadOnlyTargetRules Target) : base(Target) PublicDependencyModuleNames.AddRange( new string[] { - "Core", "MassSpawner", "MassEntity", "MassCommon", "StructUtils" + "Core", "MassSpawner", "MassEntity", "MassCommon", "StructUtils", "MassSignals", "MassMovement" // ... add other public dependencies that you statically link with here ... } ); @@ -37,7 +37,7 @@ public RTSFormations(ReadOnlyTargetRules Target) : base(Target) "CoreUObject", "Engine", "Slate", - "SlateCore", "MassNavigation", + "SlateCore", "MassNavigation", "MassSample", "MassActors", // ... add private dependencies that you statically link with here ... } ); From 76228326bec2b4d77938e4819f91d734eb411c92 Mon Sep 17 00:00:00 2001 From: Ji-Rath Date: Sun, 10 Jul 2022 16:03:52 -0500 Subject: [PATCH 12/44] Remove debug message from DestroyEntity --- .../Source/RTSFormations/Private/RTSFormationSubsystem.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationSubsystem.cpp b/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationSubsystem.cpp index d47d6ac9..f9a7cca5 100644 --- a/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationSubsystem.cpp +++ b/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationSubsystem.cpp @@ -12,7 +12,8 @@ void URTSFormationSubsystem::DestroyEntity(UMassAgentComponent* Entity) { UMassEntitySubsystem* EntitySubsystem = GetWorld()->GetSubsystem(); EntitySubsystem->Defer().DestroyEntity(Entity->GetEntityHandle()); - UE_LOG(LogTemp, Error, TEXT("Entity: %d"), Entity->GetEntityHandle().Index) + + // My current observer implementation doesnt handle entity destruction properly, so the logic is performed here for the time Units.Remove(Entity->GetEntityHandle()); GetWorld()->GetSubsystem()->SignalEntities(FormationUpdated, Units); } From 73e6ae385c264cf20ebb2b67374974ceafef3d7f Mon Sep 17 00:00:00 2001 From: Ji-Rath Date: Sun, 10 Jul 2022 16:09:49 -0500 Subject: [PATCH 13/44] Update Agent data asset Update RTSFormationExample map --- Plugins/RTSFormations/Content/DA_AgentFormation.uasset | 4 ++-- Plugins/RTSFormations/Content/RTSFormationsExample.umap | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Plugins/RTSFormations/Content/DA_AgentFormation.uasset b/Plugins/RTSFormations/Content/DA_AgentFormation.uasset index 88f2ba7e..094ba18f 100644 --- a/Plugins/RTSFormations/Content/DA_AgentFormation.uasset +++ b/Plugins/RTSFormations/Content/DA_AgentFormation.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:25b9bef364eb89ab5f1afc55d48473f632ed62f6dfd396214d24489c128fae28 -size 3897 +oid sha256:e09aeb6d360f2f876a8db14199a67c0c2c326abc3639ca6dacdb7d867c729095 +size 5886 diff --git a/Plugins/RTSFormations/Content/RTSFormationsExample.umap b/Plugins/RTSFormations/Content/RTSFormationsExample.umap index 34c7f6e6..8a4a0a85 100644 --- a/Plugins/RTSFormations/Content/RTSFormationsExample.umap +++ b/Plugins/RTSFormations/Content/RTSFormationsExample.umap @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:13e8cbdf14a10979f5ffbfae0622ac95f1b54bc0c81546434399e5a405ca4bfd -size 36392 +oid sha256:027894da6515631e5c3356ec6cab86bf95505056734fdedf77b3107af446718a +size 39665 From 3c5ecfe14f0a2f0b171eda8ba98bfca8ae64badd Mon Sep 17 00:00:00 2001 From: Ji-Rath Date: Mon, 11 Jul 2022 08:37:18 -0500 Subject: [PATCH 14/44] Add BP_FormationUnit Add simple actor visualization for entity destruction --- Plugins/RTSFormations/Content/BP_FormationUnit.uasset | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 Plugins/RTSFormations/Content/BP_FormationUnit.uasset diff --git a/Plugins/RTSFormations/Content/BP_FormationUnit.uasset b/Plugins/RTSFormations/Content/BP_FormationUnit.uasset new file mode 100644 index 00000000..6ede93cd --- /dev/null +++ b/Plugins/RTSFormations/Content/BP_FormationUnit.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d8e28e4279179d36e997962034ee97186a3067994d4f6cf7bc235bfed152e552 +size 44055 From 2cd193a07e7dded3e8f49b9d8fe593d074806b43 Mon Sep 17 00:00:00 2001 From: Ji-Rath Date: Mon, 11 Jul 2022 08:38:01 -0500 Subject: [PATCH 15/44] Update DataAsset and example map Experimental changes for resolving Visualization --- Plugins/RTSFormations/Content/DA_AgentFormation.uasset | 2 +- Plugins/RTSFormations/Content/RTSFormationsExample.umap | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Plugins/RTSFormations/Content/DA_AgentFormation.uasset b/Plugins/RTSFormations/Content/DA_AgentFormation.uasset index 094ba18f..913b7360 100644 --- a/Plugins/RTSFormations/Content/DA_AgentFormation.uasset +++ b/Plugins/RTSFormations/Content/DA_AgentFormation.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e09aeb6d360f2f876a8db14199a67c0c2c326abc3639ca6dacdb7d867c729095 +oid sha256:6a01f8695e47cca3db6c4a23430ce3cce42ad66ec2113601e2a8ad50011fdef7 size 5886 diff --git a/Plugins/RTSFormations/Content/RTSFormationsExample.umap b/Plugins/RTSFormations/Content/RTSFormationsExample.umap index 8a4a0a85..d12f26ee 100644 --- a/Plugins/RTSFormations/Content/RTSFormationsExample.umap +++ b/Plugins/RTSFormations/Content/RTSFormationsExample.umap @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:027894da6515631e5c3356ec6cab86bf95505056734fdedf77b3107af446718a +oid sha256:350edda04a05f03767a9adac411990f6bb3abb9b6bef30b899f4d173d7ef8b6f size 39665 From 9d26b367dfa3832661af525f12f32ccb1f82b01f Mon Sep 17 00:00:00 2001 From: Ji-Rath Date: Mon, 11 Jul 2022 08:53:26 -0500 Subject: [PATCH 16/44] Update RTSFormationSubsystem.cpp Add simple check for entitysubsystem --- .../Source/RTSFormations/Private/RTSFormationSubsystem.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationSubsystem.cpp b/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationSubsystem.cpp index f9a7cca5..c0a12fa5 100644 --- a/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationSubsystem.cpp +++ b/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationSubsystem.cpp @@ -11,6 +11,8 @@ void URTSFormationSubsystem::DestroyEntity(UMassAgentComponent* Entity) { UMassEntitySubsystem* EntitySubsystem = GetWorld()->GetSubsystem(); + check(EntitySubsystem); + EntitySubsystem->Defer().DestroyEntity(Entity->GetEntityHandle()); // My current observer implementation doesnt handle entity destruction properly, so the logic is performed here for the time From 3727dbb6b205c2d2fb7c3decbd9670dac06a031a Mon Sep 17 00:00:00 2001 From: Ji-Rath Date: Mon, 11 Jul 2022 09:02:19 -0500 Subject: [PATCH 17/44] Create BP_UpdateFormation.uasset Allows player to create entities to add to the unit --- Plugins/RTSFormations/Content/BP_UpdateFormation.uasset | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 Plugins/RTSFormations/Content/BP_UpdateFormation.uasset diff --git a/Plugins/RTSFormations/Content/BP_UpdateFormation.uasset b/Plugins/RTSFormations/Content/BP_UpdateFormation.uasset new file mode 100644 index 00000000..2bc145ec --- /dev/null +++ b/Plugins/RTSFormations/Content/BP_UpdateFormation.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9b55ed843972a8419fdf466f9c2989060fc596519f452de1c9a0cbac2322b01e +size 80106 From a7bf8d3a2d9ce4e29dc71965f4267412d43a0751 Mon Sep 17 00:00:00 2001 From: Ji-Rath Date: Mon, 11 Jul 2022 14:51:43 -0500 Subject: [PATCH 18/44] Cache unit index in fragment Optimize adding and removing entities from unit (unit index of swapped unit still needs to be resolved) RTSFormationUpdate still takes ~22.2ms with 5000 units. My guess right now is division/remainder calculations taking up processing time. Add RTSFormationDestroyer for handling entity destructtion (still needs testing) --- .../Private/RTSFormationProcessors.cpp | 72 ++++++++++++------- .../Private/RTSFormationProcessors.h | 19 +++-- 2 files changed, 60 insertions(+), 31 deletions(-) diff --git a/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationProcessors.cpp b/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationProcessors.cpp index a8982684..ca3dfa58 100644 --- a/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationProcessors.cpp +++ b/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationProcessors.cpp @@ -4,7 +4,6 @@ #include "MassMovementFragments.h" #include "MassNavigationFragments.h" #include "MassNavigationTypes.h" -#include "MassObserverRegistry.h" #include "MassSignalSubsystem.h" #include "RTSAgentTraits.h" #include "RTSFormationSubsystem.h" @@ -18,10 +17,6 @@ URTSFormationInitializer::URTSFormationInitializer() void URTSFormationInitializer::ConfigureQueries() { EntityQuery.AddRequirement(EMassFragmentAccess::ReadWrite); - EntityQuery.AddSharedRequirement(EMassFragmentAccess::ReadWrite); - - DestroyQuery.AddRequirement(EMassFragmentAccess::None, EMassFragmentPresence::None); - DestroyQuery.AddSharedRequirement(EMassFragmentAccess::ReadWrite); } void URTSFormationInitializer::Initialize(UObject& Owner) @@ -41,39 +36,65 @@ void URTSFormationInitializer::Execute(UMassEntitySubsystem& EntitySubsystem, FM // Another issue is that the center offset changes when enough units are spawned, causing the positions of the other units to be off // Since I really want this example to be straightforward, Im going to streamline adding/remove entities so that the formation gets recalculated - int UnitIndex = 0; - EntityQuery.ParallelForEachEntityChunk(EntitySubsystem, Context, [this, &UnitIndex](FMassExecutionContext& Context) + EntityQuery.ParallelForEachEntityChunk(EntitySubsystem, Context, [this](FMassExecutionContext& Context) { TArrayView RTSFormationAgents = Context.GetMutableFragmentView(); - FRTSFormationSettings& RTSFormationSettings = Context.GetMutableSharedFragment(); + + // Reserve units in advance to prevent resizing array every time + FormationSubsystem->Units.Reserve(FormationSubsystem->Units.Num()+Context.GetNumEntities()); + for (int32 EntityIndex = 0; EntityIndex < Context.GetNumEntities(); ++EntityIndex) { FRTSFormationAgent& RTSFormationAgent = RTSFormationAgents[EntityIndex]; - RTSFormationAgent.UnitIndex = UnitIndex; + RTSFormationAgent.UnitIndex = FormationSubsystem->Units.Num(); FormationSubsystem->Units.Emplace(Context.GetEntity(EntityIndex)); - SignalSubsystem->SignalEntities(FormationUpdated, FormationSubsystem->Units); } + + SignalSubsystem->SignalEntities(FormationUpdated, FormationSubsystem->Units); }); +} - DestroyQuery.ParallelForEachEntityChunk(EntitySubsystem, Context, [this, &UnitIndex](FMassExecutionContext& Context) +URTSFormationDestroyer::URTSFormationDestroyer() +{ + ObservedType = FRTSFormationAgent::StaticStruct(); + Operation = EMassObservedOperation::Remove; +} + +void URTSFormationDestroyer::ConfigureQueries() +{ + EntityQuery.AddSharedRequirement(EMassFragmentAccess::ReadOnly); +} + +void URTSFormationDestroyer::Initialize(UObject& Owner) +{ + SignalSubsystem = UWorld::GetSubsystem(Owner.GetWorld()); + FormationSubsystem = UWorld::GetSubsystem(Owner.GetWorld()); +} + +void URTSFormationDestroyer::Execute(UMassEntitySubsystem& EntitySubsystem, FMassExecutionContext& Context) +{ + EntityQuery.ParallelForEachEntityChunk(EntitySubsystem, Context, [this](FMassExecutionContext& Context) { - FRTSFormationSettings& RTSFormationSettings = Context.GetMutableSharedFragment(); + const FRTSFormationSettings& RTSFormationSettings = Context.GetSharedFragment(); for (int32 EntityIndex = 0; EntityIndex < Context.GetNumEntities(); ++EntityIndex) { - FormationSubsystem->Units.Remove(Context.GetEntity(EntityIndex)); - SignalSubsystem->SignalEntities(FormationUpdated, FormationSubsystem->Units); + // Remove entity from units array + // @todo using RemoveAtSwap will cause the last unit to swap with the destroyed unit, need to fix index + const int32 ItemIndex = FormationSubsystem->Units.IndexOfByKey(Context.GetEntity(EntityIndex)); + if (ItemIndex != INDEX_NONE) + { + // Since we are caching the index, we need to fix the entity index that replaces the destroyed one + FormationSubsystem->Units.RemoveAtSwap(ItemIndex, 1, false); + } } + // Shrink array and signal entities + FormationSubsystem->Units.Shrink(); + + if (!FormationSubsystem->Units.IsEmpty()) + SignalSubsystem->SignalEntities(FormationUpdated, FormationSubsystem->Units); }); } -void URTSFormationInitializer::Register() -{ - Super::Register(); - - // Register removal of entity - UMassObserverRegistry::GetMutable().RegisterObserver(*ObservedType, EMassObservedOperation::Remove, GetClass()); -} - void URTSAgentMovement::ConfigureQueries() { EntityQuery.AddRequirement(EMassFragmentAccess::ReadWrite); @@ -144,12 +165,9 @@ void URTSFormationUpdate::SignalEntities(UMassEntitySubsystem& EntitySubsystem, FMassMoveTargetFragment& MoveTarget = MoveTargetFragments[EntityIndex]; const FTransform& Transform = TransformFragments[EntityIndex].GetTransform(); - // Can be optimized by caching? - const int UnitIndex = FormationSubsystem->Units.Find(Context.GetEntity(EntityIndex)); - // Convert UnitIndex to X/Y coords - const int w = UnitIndex / RTSFormationSettings.FormationLength; - const int l = UnitIndex % RTSFormationSettings.FormationLength; + const int w = RTSFormationAgent.UnitIndex / RTSFormationSettings.FormationLength; + const int l = RTSFormationAgent.UnitIndex % RTSFormationSettings.FormationLength; // We want the formation to be 'centered' so we need to create an offset const FVector CenterOffset((RTSFormationSettings.FormationLength/2) * RTSFormationSettings.BufferDistance, (FormationSubsystem->Units.Num()/RTSFormationSettings.FormationLength/2) * RTSFormationSettings.BufferDistance, 0.f); diff --git a/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationProcessors.h b/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationProcessors.h index 6e508255..64ea1ee0 100644 --- a/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationProcessors.h +++ b/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationProcessors.h @@ -18,19 +18,30 @@ class RTSFORMATIONS_API URTSFormationInitializer : public UMassObserverProcessor virtual void ConfigureQueries() override; virtual void Initialize(UObject& Owner) override; virtual void Execute(UMassEntitySubsystem& EntitySubsystem, FMassExecutionContext& Context) override; - virtual void Register() override; FMassEntityQuery EntityQuery; - FMassEntityQuery MoveEntityQuery; + TObjectPtr SignalSubsystem; + TObjectPtr FormationSubsystem; +}; + +UCLASS() +class RTSFORMATIONS_API URTSFormationDestroyer : public UMassObserverProcessor +{ + GENERATED_BODY() - FMassEntityQuery DestroyQuery; + URTSFormationDestroyer(); + virtual void ConfigureQueries() override; + virtual void Initialize(UObject& Owner) override; + virtual void Execute(UMassEntitySubsystem& EntitySubsystem, FMassExecutionContext& Context) override; + + FMassEntityQuery EntityQuery; TObjectPtr SignalSubsystem; - TObjectPtr FormationSubsystem; }; + // Simple movement processor to get agents from a to b UCLASS() class RTSFORMATIONS_API URTSAgentMovement : public UMassProcessor From 6e4111d07b8843a9aef6cd91159e8194822eaf5b Mon Sep 17 00:00:00 2001 From: Ji-Rath Date: Mon, 11 Jul 2022 14:52:16 -0500 Subject: [PATCH 19/44] Comment out destruction handling in subsystem since it should be handled in the observer processor --- .../Source/RTSFormations/Private/RTSFormationSubsystem.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationSubsystem.cpp b/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationSubsystem.cpp index c0a12fa5..05a7c747 100644 --- a/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationSubsystem.cpp +++ b/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationSubsystem.cpp @@ -16,6 +16,6 @@ void URTSFormationSubsystem::DestroyEntity(UMassAgentComponent* Entity) EntitySubsystem->Defer().DestroyEntity(Entity->GetEntityHandle()); // My current observer implementation doesnt handle entity destruction properly, so the logic is performed here for the time - Units.Remove(Entity->GetEntityHandle()); - GetWorld()->GetSubsystem()->SignalEntities(FormationUpdated, Units); + //Units.Remove(Entity->GetEntityHandle()); + //GetWorld()->GetSubsystem()->SignalEntities(FormationUpdated, Units); } From 4de0e3c127c4d236e08983aade7fd4fa3fdff654 Mon Sep 17 00:00:00 2001 From: Ji-Rath Date: Mon, 11 Jul 2022 15:16:26 -0500 Subject: [PATCH 20/44] Clean up comments Fix destroyed entity not being properly filled in --- .../Private/RTSFormationProcessors.cpp | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationProcessors.cpp b/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationProcessors.cpp index ca3dfa58..ab1cbc5e 100644 --- a/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationProcessors.cpp +++ b/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationProcessors.cpp @@ -30,12 +30,6 @@ void URTSFormationInitializer::Initialize(UObject& Owner) void URTSFormationInitializer::Execute(UMassEntitySubsystem& EntitySubsystem, FMassExecutionContext& Context) { // First query is to give all units an appropriate unit index. - // @todo The unit index is given 'randomly' regardless of distance to closest formation position. - // Ideas: When a unit is already assigned an index, we shouldnt have to recalculate it unless a unit was destroyed and the index destroyed is less than - // the units index - pretty much like an array, would it be inefficient though? - // Another issue is that the center offset changes when enough units are spawned, causing the positions of the other units to be off - - // Since I really want this example to be straightforward, Im going to streamline adding/remove entities so that the formation gets recalculated EntityQuery.ParallelForEachEntityChunk(EntitySubsystem, Context, [this](FMassExecutionContext& Context) { TArrayView RTSFormationAgents = Context.GetMutableFragmentView(); @@ -62,7 +56,6 @@ URTSFormationDestroyer::URTSFormationDestroyer() void URTSFormationDestroyer::ConfigureQueries() { - EntityQuery.AddSharedRequirement(EMassFragmentAccess::ReadOnly); } void URTSFormationDestroyer::Initialize(UObject& Owner) @@ -73,17 +66,20 @@ void URTSFormationDestroyer::Initialize(UObject& Owner) void URTSFormationDestroyer::Execute(UMassEntitySubsystem& EntitySubsystem, FMassExecutionContext& Context) { - EntityQuery.ParallelForEachEntityChunk(EntitySubsystem, Context, [this](FMassExecutionContext& Context) + EntityQuery.ParallelForEachEntityChunk(EntitySubsystem, Context, [this, &EntitySubsystem](FMassExecutionContext& Context) { - const FRTSFormationSettings& RTSFormationSettings = Context.GetSharedFragment(); for (int32 EntityIndex = 0; EntityIndex < Context.GetNumEntities(); ++EntityIndex) { // Remove entity from units array - // @todo using RemoveAtSwap will cause the last unit to swap with the destroyed unit, need to fix index const int32 ItemIndex = FormationSubsystem->Units.IndexOfByKey(Context.GetEntity(EntityIndex)); if (ItemIndex != INDEX_NONE) { // Since we are caching the index, we need to fix the entity index that replaces the destroyed one + // Not sure if this is the 'correct' way to handle this, but it works for now + FRTSFormationAgent* FormationAgent = EntitySubsystem.GetFragmentDataPtr(FormationSubsystem->Units.Last()); + if (FormationAgent) + FormationAgent->UnitIndex = ItemIndex; + FormationSubsystem->Units.RemoveAtSwap(ItemIndex, 1, false); } } From 4921c5c150df6a429dd816a2a09a1ddf87b34661 Mon Sep 17 00:00:00 2001 From: Ji-Rath Date: Mon, 11 Jul 2022 15:17:50 -0500 Subject: [PATCH 21/44] Remove material from actor visualization --- Plugins/RTSFormations/Content/BP_FormationUnit.uasset | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Plugins/RTSFormations/Content/BP_FormationUnit.uasset b/Plugins/RTSFormations/Content/BP_FormationUnit.uasset index 6ede93cd..8a86873b 100644 --- a/Plugins/RTSFormations/Content/BP_FormationUnit.uasset +++ b/Plugins/RTSFormations/Content/BP_FormationUnit.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d8e28e4279179d36e997962034ee97186a3067994d4f6cf7bc235bfed152e552 -size 44055 +oid sha256:5de952812f878408ad7c833fcc325ea5366581b4eeaa148080e0799213fb0ccb +size 43128 From 7fad122ab0c332dfb07720e6605360bacc856545 Mon Sep 17 00:00:00 2001 From: Ji-Rath Date: Mon, 11 Jul 2022 15:18:02 -0500 Subject: [PATCH 22/44] Remove debug visualization trait --- Plugins/RTSFormations/Content/DA_AgentFormation.uasset | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Plugins/RTSFormations/Content/DA_AgentFormation.uasset b/Plugins/RTSFormations/Content/DA_AgentFormation.uasset index 913b7360..d7cc6603 100644 --- a/Plugins/RTSFormations/Content/DA_AgentFormation.uasset +++ b/Plugins/RTSFormations/Content/DA_AgentFormation.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6a01f8695e47cca3db6c4a23430ce3cce42ad66ec2113601e2a8ad50011fdef7 +oid sha256:9be66644672059b6dbc79941ade0a7a778e92e139718395e119407f8103e99c9 size 5886 From 846830da869aab572a4527c9d77c8e03c92c38c9 Mon Sep 17 00:00:00 2001 From: Ji-Rath Date: Mon, 11 Jul 2022 17:11:36 -0500 Subject: [PATCH 23/44] Add UnitPosition to RTSFormationSubsystem so that a unit can move to a location --- .../Source/RTSFormations/Private/RTSFormationSubsystem.cpp | 6 ++++++ .../Source/RTSFormations/Private/RTSFormationSubsystem.h | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationSubsystem.cpp b/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationSubsystem.cpp index 05a7c747..89e24cb2 100644 --- a/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationSubsystem.cpp +++ b/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationSubsystem.cpp @@ -19,3 +19,9 @@ void URTSFormationSubsystem::DestroyEntity(UMassAgentComponent* Entity) //Units.Remove(Entity->GetEntityHandle()); //GetWorld()->GetSubsystem()->SignalEntities(FormationUpdated, Units); } + +void URTSFormationSubsystem::SetUnitPosition(FVector NewPosition) +{ + UnitPosition = NewPosition; + GetWorld()->GetSubsystem()->SignalEntities(FormationUpdated, Units); +} \ No newline at end of file diff --git a/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationSubsystem.h b/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationSubsystem.h index 3e31c643..c4a81e2e 100644 --- a/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationSubsystem.h +++ b/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationSubsystem.h @@ -23,4 +23,10 @@ class RTSFORMATIONS_API URTSFormationSubsystem : public UWorldSubsystem UFUNCTION(BlueprintCallable) void DestroyEntity(UMassAgentComponent* Entity); + + UFUNCTION(BlueprintCallable) + void SetUnitPosition(FVector NewPosition); + + UPROPERTY() + FVector UnitPosition; }; From b5bd4feeeb234ad361e709e790e4bd2f8b62a3f2 Mon Sep 17 00:00:00 2001 From: Ji-Rath Date: Mon, 11 Jul 2022 17:12:06 -0500 Subject: [PATCH 24/44] Fix dependency since new mass sample files are contained in a plugin --- .../RTSFormations/Source/RTSFormations/RTSFormations.Build.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Plugins/RTSFormations/Source/RTSFormations/RTSFormations.Build.cs b/Plugins/RTSFormations/Source/RTSFormations/RTSFormations.Build.cs index 7792cefb..2a572eb0 100644 --- a/Plugins/RTSFormations/Source/RTSFormations/RTSFormations.Build.cs +++ b/Plugins/RTSFormations/Source/RTSFormations/RTSFormations.Build.cs @@ -25,7 +25,7 @@ public RTSFormations(ReadOnlyTargetRules Target) : base(Target) PublicDependencyModuleNames.AddRange( new string[] { - "Core", "MassSpawner", "MassEntity", "MassCommon", "StructUtils", "MassSignals", "MassMovement" + "Core", "MassSpawner", "MassEntity", "MassCommon", "StructUtils", "MassSignals", "MassMovement", "MassCommunitySample" // ... add other public dependencies that you statically link with here ... } ); @@ -37,7 +37,7 @@ public RTSFormations(ReadOnlyTargetRules Target) : base(Target) "CoreUObject", "Engine", "Slate", - "SlateCore", "MassNavigation", "MassSample", "MassActors", + "SlateCore", "MassNavigation", "MassActors" // ... add private dependencies that you statically link with here ... } ); From efc2e955f523d8e33253541f93cf10b9efbb286a Mon Sep 17 00:00:00 2001 From: Ji-Rath Date: Mon, 11 Jul 2022 17:12:30 -0500 Subject: [PATCH 25/44] Implement UnitPosition to URTSFormationUpdate --- .../Source/RTSFormations/Private/RTSFormationProcessors.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationProcessors.cpp b/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationProcessors.cpp index ab1cbc5e..f92bc3e5 100644 --- a/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationProcessors.cpp +++ b/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationProcessors.cpp @@ -167,10 +167,11 @@ void URTSFormationUpdate::SignalEntities(UMassEntitySubsystem& EntitySubsystem, // We want the formation to be 'centered' so we need to create an offset const FVector CenterOffset((RTSFormationSettings.FormationLength/2) * RTSFormationSettings.BufferDistance, (FormationSubsystem->Units.Num()/RTSFormationSettings.FormationLength/2) * RTSFormationSettings.BufferDistance, 0.f); + const FVector UnitPosition = FormationSubsystem->UnitPosition + CenterOffset; // Create movement action MoveTarget.CreateNewAction(EMassMovementAction::Move, *GetWorld()); - MoveTarget.Center = FVector(w*RTSFormationSettings.BufferDistance-CenterOffset.Y,l*RTSFormationSettings.BufferDistance-CenterOffset.X,0.f); + MoveTarget.Center = FVector(w*RTSFormationSettings.BufferDistance-UnitPosition.Y,l*RTSFormationSettings.BufferDistance-UnitPosition.X,0.f); MoveTarget.Forward = (Transform.GetLocation() - MoveTarget.Center).GetSafeNormal(); MoveTarget.DistanceToGoal = (Transform.GetLocation() - MoveTarget.Center).Length(); MoveTarget.SlackRadius = 10.f; From 2c5bc746235542805abffcac35be676ac5ef95a6 Mon Sep 17 00:00:00 2001 From: Ji-Rath Date: Mon, 11 Jul 2022 17:12:48 -0500 Subject: [PATCH 26/44] Fix invalid interface in BP_FormationUnit.uasset --- Plugins/RTSFormations/Content/BP_FormationUnit.uasset | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Plugins/RTSFormations/Content/BP_FormationUnit.uasset b/Plugins/RTSFormations/Content/BP_FormationUnit.uasset index 8a86873b..b342a99a 100644 --- a/Plugins/RTSFormations/Content/BP_FormationUnit.uasset +++ b/Plugins/RTSFormations/Content/BP_FormationUnit.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5de952812f878408ad7c833fcc325ea5366581b4eeaa148080e0799213fb0ccb -size 43128 +oid sha256:dd24092b2f239e96f004695cc7346e0c03209cf930df5c4a065c0b3a01d1a98c +size 43629 From 33b4533587200e1d2c88b5e2abd6e14721391b02 Mon Sep 17 00:00:00 2001 From: Ji-Rath Date: Mon, 11 Jul 2022 17:13:11 -0500 Subject: [PATCH 27/44] Add BP_FormationGameMode for automatic unit movement --- Plugins/RTSFormations/Content/BP_FormationGameMode.uasset | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 Plugins/RTSFormations/Content/BP_FormationGameMode.uasset diff --git a/Plugins/RTSFormations/Content/BP_FormationGameMode.uasset b/Plugins/RTSFormations/Content/BP_FormationGameMode.uasset new file mode 100644 index 00000000..a53de25d --- /dev/null +++ b/Plugins/RTSFormations/Content/BP_FormationGameMode.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:47517588dac308b5295c2ee431fc7625c5c3a4940a7c194ef0a3682b779f4d1b +size 49812 From 9423eafb80687b9df428a883d5f4c6c5dfdb5944 Mon Sep 17 00:00:00 2001 From: Ji-Rath Date: Mon, 11 Jul 2022 17:13:25 -0500 Subject: [PATCH 28/44] Update RTSFormationsExample.umap to use new game mode --- Plugins/RTSFormations/Content/RTSFormationsExample.umap | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Plugins/RTSFormations/Content/RTSFormationsExample.umap b/Plugins/RTSFormations/Content/RTSFormationsExample.umap index d12f26ee..a712a19b 100644 --- a/Plugins/RTSFormations/Content/RTSFormationsExample.umap +++ b/Plugins/RTSFormations/Content/RTSFormationsExample.umap @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:350edda04a05f03767a9adac411990f6bb3abb9b6bef30b899f4d173d7ef8b6f -size 39665 +oid sha256:e72d8e03e290b7f3ac9383bedb31d89d080855e7f3391ed4292efa7d6dc8bea3 +size 39877 From de2661a7a0f458f8ff8b583115ab10398e780b61 Mon Sep 17 00:00:00 2001 From: Ji-Rath Date: Wed, 13 Jul 2022 18:18:50 -0500 Subject: [PATCH 29/44] Optimized signaling entities during initialization so that they dont get repeated to the same entity Begin implementing rotations to units in formation, still jank --- .../Private/RTSFormationProcessors.cpp | 83 ++++++++++++------- 1 file changed, 55 insertions(+), 28 deletions(-) diff --git a/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationProcessors.cpp b/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationProcessors.cpp index f92bc3e5..de2f5a23 100644 --- a/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationProcessors.cpp +++ b/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationProcessors.cpp @@ -7,6 +7,7 @@ #include "MassSignalSubsystem.h" #include "RTSAgentTraits.h" #include "RTSFormationSubsystem.h" +#include "Kismet/KismetMathLibrary.h" URTSFormationInitializer::URTSFormationInitializer() { @@ -34,17 +35,30 @@ void URTSFormationInitializer::Execute(UMassEntitySubsystem& EntitySubsystem, FM { TArrayView RTSFormationAgents = Context.GetMutableFragmentView(); - // Reserve units in advance to prevent resizing array every time - FormationSubsystem->Units.Reserve(FormationSubsystem->Units.Num()+Context.GetNumEntities()); - + // Signal affected units/entities at the end + TArray UnitSignals; + UnitSignals.Reserve(FormationSubsystem->Units.Num()); + + // Since we can have multiple units, reserving is only done in the formation subsystem + // This is because it might be possible that a batch of spawned entities should go to different units for (int32 EntityIndex = 0; EntityIndex < Context.GetNumEntities(); ++EntityIndex) { FRTSFormationAgent& RTSFormationAgent = RTSFormationAgents[EntityIndex]; - RTSFormationAgent.UnitIndex = FormationSubsystem->Units.Num(); - FormationSubsystem->Units.Emplace(Context.GetEntity(EntityIndex)); + + // If for some reason the unit hasnt been created, we should create it now + // Unfortunately, with the nature of an array, this might cause a crash if the unit index is not next in line, need to handle this somehow + if (!FormationSubsystem->Units.IsValidIndex(RTSFormationAgent.UnitIndex)) + FormationSubsystem->Units.AddDefaulted(1); + + RTSFormationAgent.EntityIndex = FormationSubsystem->Units[RTSFormationAgent.UnitIndex].Entities.Num(); + FormationSubsystem->Units[RTSFormationAgent.UnitIndex].Entities.Emplace(Context.GetEntity(EntityIndex)); + + UnitSignals.AddUnique(RTSFormationAgent.UnitIndex); } - - SignalSubsystem->SignalEntities(FormationUpdated, FormationSubsystem->Units); + // Signal entities in the unit that their position is updated + // @todo only notify affected entities + for(const int& Unit : UnitSignals) + SignalSubsystem->SignalEntities(FormationUpdated, FormationSubsystem->Units[Unit].Entities); }); } @@ -56,6 +70,7 @@ URTSFormationDestroyer::URTSFormationDestroyer() void URTSFormationDestroyer::ConfigureQueries() { + EntityQuery.AddRequirement(EMassFragmentAccess::ReadOnly); } void URTSFormationDestroyer::Initialize(UObject& Owner) @@ -68,26 +83,31 @@ void URTSFormationDestroyer::Execute(UMassEntitySubsystem& EntitySubsystem, FMas { EntityQuery.ParallelForEachEntityChunk(EntitySubsystem, Context, [this, &EntitySubsystem](FMassExecutionContext& Context) { + TConstArrayView FormationAgents = Context.GetFragmentView(); for (int32 EntityIndex = 0; EntityIndex < Context.GetNumEntities(); ++EntityIndex) { + const FRTSFormationAgent& FormationAgent = FormationAgents[EntityIndex]; + // Remove entity from units array - const int32 ItemIndex = FormationSubsystem->Units.IndexOfByKey(Context.GetEntity(EntityIndex)); - if (ItemIndex != INDEX_NONE) + if (FormationSubsystem->Units.IsValidIndex(FormationAgent.UnitIndex)) { - // Since we are caching the index, we need to fix the entity index that replaces the destroyed one - // Not sure if this is the 'correct' way to handle this, but it works for now - FRTSFormationAgent* FormationAgent = EntitySubsystem.GetFragmentDataPtr(FormationSubsystem->Units.Last()); - if (FormationAgent) - FormationAgent->UnitIndex = ItemIndex; - - FormationSubsystem->Units.RemoveAtSwap(ItemIndex, 1, false); + const int32 ItemIndex = FormationSubsystem->Units[FormationAgent.UnitIndex].Entities.IndexOfByKey(Context.GetEntity(EntityIndex)); + if (ItemIndex != INDEX_NONE) + { + // Since we are caching the index, we need to fix the entity index that replaces the destroyed one + // Not sure if this is the 'correct' way to handle this, but it works for now + if (FRTSFormationAgent* ReplacementFormationAgent = EntitySubsystem.GetFragmentDataPtr(FormationSubsystem->Units[FormationAgent.UnitIndex].Entities.Last())) + ReplacementFormationAgent->EntityIndex = ItemIndex; + + FormationSubsystem->Units[FormationAgent.UnitIndex].Entities.RemoveAtSwap(ItemIndex, 1, true); + + if (!FormationSubsystem->Units[FormationAgent.UnitIndex].Entities.IsEmpty()) + SignalSubsystem->SignalEntities(FormationUpdated, FormationSubsystem->Units[FormationAgent.UnitIndex].Entities); + } } } // Shrink array and signal entities - FormationSubsystem->Units.Shrink(); - - if (!FormationSubsystem->Units.IsEmpty()) - SignalSubsystem->SignalEntities(FormationUpdated, FormationSubsystem->Units); + //FormationSubsystem->Units.Shrink(); }); } @@ -113,11 +133,11 @@ void URTSAgentMovement::Execute(UMassEntitySubsystem& EntitySubsystem, FMassExec FMassMoveTargetFragment& MoveTarget = MoveTargetFragments[EntityIndex]; const FTransform& Transform = TransformFragments[EntityIndex].GetTransform(); FVector& Velocity = VelocityFragments[EntityIndex].Value; - + // Update move target values MoveTarget.DistanceToGoal = (MoveTarget.Center - Transform.GetLocation()).Length(); MoveTarget.Forward = (MoveTarget.Center - Transform.GetLocation()).GetSafeNormal(); - + // Once we are close enough to our goal, create stand action if (MoveTarget.DistanceToGoal <= MoveTarget.SlackRadius) { @@ -157,21 +177,28 @@ void URTSFormationUpdate::SignalEntities(UMassEntitySubsystem& EntitySubsystem, for (int32 EntityIndex = 0; EntityIndex < Context.GetNumEntities(); ++EntityIndex) { + const FRTSFormationAgent& RTSFormationAgent = RTSFormationAgents[EntityIndex]; FMassMoveTargetFragment& MoveTarget = MoveTargetFragments[EntityIndex]; const FTransform& Transform = TransformFragments[EntityIndex].GetTransform(); // Convert UnitIndex to X/Y coords - const int w = RTSFormationAgent.UnitIndex / RTSFormationSettings.FormationLength; - const int l = RTSFormationAgent.UnitIndex % RTSFormationSettings.FormationLength; - + const int w = RTSFormationAgent.EntityIndex / RTSFormationSettings.FormationLength; + const int l = RTSFormationAgent.EntityIndex % RTSFormationSettings.FormationLength; + // We want the formation to be 'centered' so we need to create an offset - const FVector CenterOffset((RTSFormationSettings.FormationLength/2) * RTSFormationSettings.BufferDistance, (FormationSubsystem->Units.Num()/RTSFormationSettings.FormationLength/2) * RTSFormationSettings.BufferDistance, 0.f); - const FVector UnitPosition = FormationSubsystem->UnitPosition + CenterOffset; + const FVector CenterOffset = FVector((FormationSubsystem->Units[RTSFormationAgent.UnitIndex].Entities.Num()/RTSFormationSettings.FormationLength/2) * RTSFormationSettings.BufferDistance, (RTSFormationSettings.FormationLength/2) * RTSFormationSettings.BufferDistance, 0.f); + const FVector UnitPosition = FormationSubsystem->Units[RTSFormationAgent.UnitIndex].UnitPosition - CenterOffset; // Create movement action MoveTarget.CreateNewAction(EMassMovementAction::Move, *GetWorld()); - MoveTarget.Center = FVector(w*RTSFormationSettings.BufferDistance-UnitPosition.Y,l*RTSFormationSettings.BufferDistance-UnitPosition.X,0.f); + + FVector EntityPosition = FVector(w,l,0.f); + EntityPosition *= RTSFormationSettings.BufferDistance; + EntityPosition += UnitPosition; + FVector RotateValue = EntityPosition.RotateAngleAxis(FormationSubsystem->Units[RTSFormationAgent.UnitIndex].Angle, FVector(0.f,0.f,1.f)); + + MoveTarget.Center = RotateValue; MoveTarget.Forward = (Transform.GetLocation() - MoveTarget.Center).GetSafeNormal(); MoveTarget.DistanceToGoal = (Transform.GetLocation() - MoveTarget.Center).Length(); MoveTarget.SlackRadius = 10.f; From 4e7704acacb8e1ca2051e15826ebd26e79756b26 Mon Sep 17 00:00:00 2001 From: Ji-Rath Date: Wed, 13 Jul 2022 18:25:51 -0500 Subject: [PATCH 30/44] Implement multiple units Add Spawning units function --- .../Private/RTSFormationSubsystem.cpp | 65 +++++++++++++++++-- .../Private/RTSFormationSubsystem.h | 55 ++++++++++++++-- 2 files changed, 107 insertions(+), 13 deletions(-) diff --git a/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationSubsystem.cpp b/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationSubsystem.cpp index 89e24cb2..f74f6588 100644 --- a/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationSubsystem.cpp +++ b/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationSubsystem.cpp @@ -6,7 +6,10 @@ #include "MassAgentComponent.h" #include "MassEntitySubsystem.h" #include "MassSignalSubsystem.h" +#include "MassSpawnerSubsystem.h" +#include "RTSAgentTraits.h" #include "RTSFormationProcessors.h" +#include "Kismet/GameplayStatics.h" void URTSFormationSubsystem::DestroyEntity(UMassAgentComponent* Entity) { @@ -14,14 +17,62 @@ void URTSFormationSubsystem::DestroyEntity(UMassAgentComponent* Entity) check(EntitySubsystem); EntitySubsystem->Defer().DestroyEntity(Entity->GetEntityHandle()); +} + +void URTSFormationSubsystem::SetUnitPosition(const FVector& NewPosition, int UnitIndex) +{ + if (!ensure(Units.IsValidIndex(UnitIndex))) { return; } + + DrawDebugPoint(GetWorld(), Units[UnitIndex].UnitPosition, 20.f, FColor::Red, false, 10.f); + DrawDebugPoint(GetWorld(), NewPosition, 20.f, FColor::Green, false, 10.f); - // My current observer implementation doesnt handle entity destruction properly, so the logic is performed here for the time - //Units.Remove(Entity->GetEntityHandle()); - //GetWorld()->GetSubsystem()->SignalEntities(FormationUpdated, Units); + Units[UnitIndex].Angle = FMath::RadiansToDegrees(acosf(FVector::DotProduct(FVector::ForwardVector, (NewPosition - Units[UnitIndex].UnitPosition).GetSafeNormal()))); + //UE_LOG(LogTemp, Error, TEXT("Angle: %f"), Units[UnitIndex].Angle); + Units[UnitIndex].ForwardDirection = (NewPosition-Units[UnitIndex].UnitPosition).GetSafeNormal(); + Units[UnitIndex].UnitPosition = NewPosition; + if (Units[UnitIndex].Entities.Num() > 0) + GetWorld()->GetSubsystem()->SignalEntities(FormationUpdated, Units[UnitIndex].Entities); } -void URTSFormationSubsystem::SetUnitPosition(FVector NewPosition) +void URTSFormationSubsystem::SpawnEntitiesForUnit(int UnitIndex, const UMassEntityConfigAsset* EntityConfig, int Count) { - UnitPosition = NewPosition; - GetWorld()->GetSubsystem()->SignalEntities(FormationUpdated, Units); -} \ No newline at end of file + if (!ensure(Units.IsValidIndex(UnitIndex))) { return; } + + UMassEntitySubsystem* EntitySubsystem = GetWorld()->GetSubsystem(); + + // Reserve space for the new units, the space will be filled in a processor + // Give a random Unit position for the heck of it + Units[UnitIndex].Entities.Reserve(Units[UnitIndex].Entities.Num()+Count); + Units[UnitIndex].UnitPosition = FVector(FMath::RandRange(-1000.f,1000.f), FMath::RandRange(-1000.f,1000.f), 0.f); + + TArray Entities; + const FMassEntityTemplate* EntityTemplate = EntityConfig->GetConfig().GetOrCreateEntityTemplate(*UGameplayStatics::GetPlayerPawn(this, 0), *EntityConfig); + + // We are doing a little bit of work here since we are setting the unit index manually + // Otherwise, using SpawnEntities would be perfectly fine + // @todo find if there is a better way to modify templates in code + TArray SpawnedEntities; + TSharedRef CreationContext = EntitySubsystem->BatchCreateEntities(EntityTemplate->GetArchetype(), Count, SpawnedEntities); + + // Set the template default values for the entities + TConstArrayView FragmentInstances = EntityTemplate->GetInitialFragmentValues(); + EntitySubsystem->BatchSetEntityFragmentsValues(CreationContext->GetChunkCollection(), FragmentInstances); + + // Set unit index for entities + FRTSFormationAgent FormationAgent; + FormationAgent.UnitIndex = UnitIndex; + + TArray Fragments; + Fragments.Add(FConstStructView::Make(FormationAgent)); + EntitySubsystem->BatchSetEntityFragmentsValues(CreationContext->GetChunkCollection(), Fragments); + + //@todo Look into MassArchetypeSubChunks since it might be a key to iterating through units +} + +int URTSFormationSubsystem::SpawnNewUnit(const UMassEntityConfigAsset* EntityConfig, int Count) +{ + int UnitIndex = Units.Num(); + Units.AddDefaulted(1); + SpawnEntitiesForUnit(UnitIndex, EntityConfig, Count); + return UnitIndex; +} diff --git a/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationSubsystem.h b/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationSubsystem.h index c4a81e2e..d520a4ad 100644 --- a/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationSubsystem.h +++ b/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationSubsystem.h @@ -6,6 +6,46 @@ #include "Common/Misc/MSBPFunctionLibrary.h" #include "RTSFormationSubsystem.generated.h" +UENUM() +enum FormationType +{ + Rectangle, + Circle +}; + +USTRUCT(BlueprintType) +struct FUnitInfo +{ + GENERATED_BODY() + +public: + // Entities in the unit + UPROPERTY() + TArray Entities; + + UPROPERTY(BlueprintReadOnly) + FVector UnitPosition; + + UPROPERTY() + FVector ForwardDirection; + + UPROPERTY() + float FormationLength = 0; + + UPROPERTY() + TEnumAsByte Formation = Rectangle; + + UPROPERTY() + float Angle = 0; + + FUnitInfo() {}; + + FUnitInfo(const TArray& Entities) + { + this->Entities.Append(Entities); + } +}; + class UMassAgentComponent; struct FMassEntityHandle; /** @@ -18,15 +58,18 @@ class RTSFORMATIONS_API URTSFormationSubsystem : public UWorldSubsystem public: // Stores the num of units in the formation - UPROPERTY() - TArray Units; + UPROPERTY(BlueprintReadOnly) + TArray Units; UFUNCTION(BlueprintCallable) void DestroyEntity(UMassAgentComponent* Entity); UFUNCTION(BlueprintCallable) - void SetUnitPosition(FVector NewPosition); - - UPROPERTY() - FVector UnitPosition; + void SetUnitPosition(const FVector& NewPosition, int UnitIndex = 0); + + UFUNCTION(BlueprintCallable) + void SpawnEntitiesForUnit(int UnitIndex, const UMassEntityConfigAsset* EntityConfig, int Count); + + UFUNCTION(BlueprintCallable) + int SpawnNewUnit(const UMassEntityConfigAsset* EntityConfig, int Count); }; From 1b1e15a1b26ed1e9ecd2981829843e7722df4a24 Mon Sep 17 00:00:00 2001 From: Ji-Rath Date: Wed, 13 Jul 2022 18:26:59 -0500 Subject: [PATCH 31/44] Continue implementing multiple units --- .../Source/RTSFormations/Public/RTSAgentTraits.h | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Plugins/RTSFormations/Source/RTSFormations/Public/RTSAgentTraits.h b/Plugins/RTSFormations/Source/RTSFormations/Public/RTSAgentTraits.h index babc22c7..567065c9 100644 --- a/Plugins/RTSFormations/Source/RTSFormations/Public/RTSAgentTraits.h +++ b/Plugins/RTSFormations/Source/RTSFormations/Public/RTSAgentTraits.h @@ -15,7 +15,10 @@ struct RTSFORMATIONS_API FRTSFormationAgent : public FMassFragment { GENERATED_BODY() - // The index of the unit in the formation + // The index of the entity in the formation + int EntityIndex = 0; + + // The unit that this entity is a part of int UnitIndex = 0; }; @@ -44,7 +47,3 @@ class RTSFORMATIONS_API URTSFormationAgentTrait : public UMassEntityTraitBase UPROPERTY(EditAnywhere) FRTSFormationSettings FormationSettings; }; - -//@todo create destroyer observer processor to handle updating units -//@todo create another processor to handle when units need to be updated (using bUpdateUnitPosition in SharedFragment) -//@todo I definitely went overboard in terms of complexity, this example should really just be straightforward to read and have performance second i think From a900b99d4e7d012b00f455ff606548b98127d6c6 Mon Sep 17 00:00:00 2001 From: Ji-Rath Date: Thu, 14 Jul 2022 10:00:39 -0500 Subject: [PATCH 32/44] Finally implement proper unit rotation --- .../Private/RTSFormationProcessors.cpp | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationProcessors.cpp b/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationProcessors.cpp index de2f5a23..a7a4f261 100644 --- a/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationProcessors.cpp +++ b/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationProcessors.cpp @@ -100,7 +100,9 @@ void URTSFormationDestroyer::Execute(UMassEntitySubsystem& EntitySubsystem, FMas ReplacementFormationAgent->EntityIndex = ItemIndex; FormationSubsystem->Units[FormationAgent.UnitIndex].Entities.RemoveAtSwap(ItemIndex, 1, true); - + + // Really the only time we need to notify every entity in the unit is when the center point changes + // Every other time we just have to notify the entity thats replacing the destroyed one if (!FormationSubsystem->Units[FormationAgent.UnitIndex].Entities.IsEmpty()) SignalSubsystem->SignalEntities(FormationUpdated, FormationSubsystem->Units[FormationAgent.UnitIndex].Entities); } @@ -188,15 +190,20 @@ void URTSFormationUpdate::SignalEntities(UMassEntitySubsystem& EntitySubsystem, // We want the formation to be 'centered' so we need to create an offset const FVector CenterOffset = FVector((FormationSubsystem->Units[RTSFormationAgent.UnitIndex].Entities.Num()/RTSFormationSettings.FormationLength/2) * RTSFormationSettings.BufferDistance, (RTSFormationSettings.FormationLength/2) * RTSFormationSettings.BufferDistance, 0.f); - const FVector UnitPosition = FormationSubsystem->Units[RTSFormationAgent.UnitIndex].UnitPosition - CenterOffset; // Create movement action MoveTarget.CreateNewAction(EMassMovementAction::Move, *GetWorld()); + // Set entity position based on index in formation FVector EntityPosition = FVector(w,l,0.f); EntityPosition *= RTSFormationSettings.BufferDistance; - EntityPosition += UnitPosition; - FVector RotateValue = EntityPosition.RotateAngleAxis(FormationSubsystem->Units[RTSFormationAgent.UnitIndex].Angle, FVector(0.f,0.f,1.f)); + EntityPosition -= CenterOffset; + + // Rotate unit by calculated angle + FVector RotateValue = EntityPosition.RotateAngleAxis(FormationSubsystem->Units[RTSFormationAgent.UnitIndex].Angle, FVector(0.f,0.f,FormationSubsystem->Units[RTSFormationAgent.UnitIndex].TurnDirection)); + + // Finally add the units position to the entity position + RotateValue += FormationSubsystem->Units[RTSFormationAgent.UnitIndex].UnitPosition; MoveTarget.Center = RotateValue; MoveTarget.Forward = (Transform.GetLocation() - MoveTarget.Center).GetSafeNormal(); From 8354253bf0896f88f40fc946531f019306baebba Mon Sep 17 00:00:00 2001 From: Ji-Rath Date: Thu, 14 Jul 2022 10:01:17 -0500 Subject: [PATCH 33/44] Cleanup RTSFormationSubsystem with new rotation system --- .../Private/RTSFormationSubsystem.cpp | 17 +++++++++-------- .../Private/RTSFormationSubsystem.h | 2 +- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationSubsystem.cpp b/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationSubsystem.cpp index f74f6588..fb9f8f54 100644 --- a/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationSubsystem.cpp +++ b/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationSubsystem.cpp @@ -23,13 +23,16 @@ void URTSFormationSubsystem::SetUnitPosition(const FVector& NewPosition, int Uni { if (!ensure(Units.IsValidIndex(UnitIndex))) { return; } - DrawDebugPoint(GetWorld(), Units[UnitIndex].UnitPosition, 20.f, FColor::Red, false, 10.f); - DrawDebugPoint(GetWorld(), NewPosition, 20.f, FColor::Green, false, 10.f); + DrawDebugDirectionalArrow(GetWorld(), NewPosition, NewPosition+((NewPosition-Units[UnitIndex].UnitPosition).GetSafeNormal()*250.f), 150.f, FColor::Red, false, 10.f, 0, 25.f); + + // Calculate turn direction and angle for entities in unit + Units[UnitIndex].TurnDirection = (NewPosition-Units[UnitIndex].UnitPosition).GetSafeNormal().Y > 0 ? 1.f : -1.f; + Units[UnitIndex].Angle = FMath::RadiansToDegrees(acosf(FVector::DotProduct(FVector::ForwardVector, (NewPosition-Units[UnitIndex].UnitPosition).GetSafeNormal()))); + Units[UnitIndex].Angle += 180.f; // Temporary fix to resolve unit facing the wrong direction - Units[UnitIndex].Angle = FMath::RadiansToDegrees(acosf(FVector::DotProduct(FVector::ForwardVector, (NewPosition - Units[UnitIndex].UnitPosition).GetSafeNormal()))); - //UE_LOG(LogTemp, Error, TEXT("Angle: %f"), Units[UnitIndex].Angle); - Units[UnitIndex].ForwardDirection = (NewPosition-Units[UnitIndex].UnitPosition).GetSafeNormal(); Units[UnitIndex].UnitPosition = NewPosition; + + // Signal entities of a position update if (Units[UnitIndex].Entities.Num() > 0) GetWorld()->GetSubsystem()->SignalEntities(FormationUpdated, Units[UnitIndex].Entities); } @@ -43,7 +46,7 @@ void URTSFormationSubsystem::SpawnEntitiesForUnit(int UnitIndex, const UMassEnti // Reserve space for the new units, the space will be filled in a processor // Give a random Unit position for the heck of it Units[UnitIndex].Entities.Reserve(Units[UnitIndex].Entities.Num()+Count); - Units[UnitIndex].UnitPosition = FVector(FMath::RandRange(-1000.f,1000.f), FMath::RandRange(-1000.f,1000.f), 0.f); + Units[UnitIndex].UnitPosition = FVector(FMath::RandRange(-2000.f,2000.f), FMath::RandRange(-1000.f,1000.f), 0.f); TArray Entities; const FMassEntityTemplate* EntityTemplate = EntityConfig->GetConfig().GetOrCreateEntityTemplate(*UGameplayStatics::GetPlayerPawn(this, 0), *EntityConfig); @@ -65,8 +68,6 @@ void URTSFormationSubsystem::SpawnEntitiesForUnit(int UnitIndex, const UMassEnti TArray Fragments; Fragments.Add(FConstStructView::Make(FormationAgent)); EntitySubsystem->BatchSetEntityFragmentsValues(CreationContext->GetChunkCollection(), Fragments); - - //@todo Look into MassArchetypeSubChunks since it might be a key to iterating through units } int URTSFormationSubsystem::SpawnNewUnit(const UMassEntityConfigAsset* EntityConfig, int Count) diff --git a/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationSubsystem.h b/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationSubsystem.h index d520a4ad..bd6aac7c 100644 --- a/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationSubsystem.h +++ b/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationSubsystem.h @@ -27,7 +27,7 @@ struct FUnitInfo FVector UnitPosition; UPROPERTY() - FVector ForwardDirection; + float TurnDirection = 1.f; UPROPERTY() float FormationLength = 0; From 9dfd0f7e84f294edddc2823b87f40cc5ba3d87f2 Mon Sep 17 00:00:00 2001 From: Ji-Rath Date: Thu, 14 Jul 2022 10:01:43 -0500 Subject: [PATCH 34/44] Change game mode so it moves all current units in the world --- Plugins/RTSFormations/Content/BP_FormationGameMode.uasset | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Plugins/RTSFormations/Content/BP_FormationGameMode.uasset b/Plugins/RTSFormations/Content/BP_FormationGameMode.uasset index a53de25d..3da8cd4b 100644 --- a/Plugins/RTSFormations/Content/BP_FormationGameMode.uasset +++ b/Plugins/RTSFormations/Content/BP_FormationGameMode.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:47517588dac308b5295c2ee431fc7625c5c3a4940a7c194ef0a3682b779f4d1b -size 49812 +oid sha256:23930ce006e48f259eb128cec23236a56e977e9a9748277d15961c374cc495a8 +size 62555 From 6f09afb7353c06ca6c97059f78acdd9a68f57a44 Mon Sep 17 00:00:00 2001 From: Ji-Rath Date: Thu, 14 Jul 2022 10:02:16 -0500 Subject: [PATCH 35/44] Add BP_SpawnEntity and BP_SpawnUnit primarily for debugging --- Plugins/RTSFormations/Content/BP_SpawnEntity.uasset | 3 +++ Plugins/RTSFormations/Content/BP_SpawnUnit.uasset | 3 +++ 2 files changed, 6 insertions(+) create mode 100644 Plugins/RTSFormations/Content/BP_SpawnEntity.uasset create mode 100644 Plugins/RTSFormations/Content/BP_SpawnUnit.uasset diff --git a/Plugins/RTSFormations/Content/BP_SpawnEntity.uasset b/Plugins/RTSFormations/Content/BP_SpawnEntity.uasset new file mode 100644 index 00000000..ab22b363 --- /dev/null +++ b/Plugins/RTSFormations/Content/BP_SpawnEntity.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:41062c587661c36432e1b8f328e4b751229a8a507a3efc1ba728f2e13a391a8b +size 76296 diff --git a/Plugins/RTSFormations/Content/BP_SpawnUnit.uasset b/Plugins/RTSFormations/Content/BP_SpawnUnit.uasset new file mode 100644 index 00000000..90e01661 --- /dev/null +++ b/Plugins/RTSFormations/Content/BP_SpawnUnit.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f7841fc6958941d4efcc7ba24a20058153b3099da39505dae1a772269def8361 +size 54872 From e21b12b8799416d99ad7187ca82806141f35ad8a Mon Sep 17 00:00:00 2001 From: Ji-Rath Date: Thu, 14 Jul 2022 10:02:40 -0500 Subject: [PATCH 36/44] Rename BP_UpdateFormation.uasset --- Plugins/RTSFormations/Content/BP_UpdateFormation.uasset | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 Plugins/RTSFormations/Content/BP_UpdateFormation.uasset diff --git a/Plugins/RTSFormations/Content/BP_UpdateFormation.uasset b/Plugins/RTSFormations/Content/BP_UpdateFormation.uasset deleted file mode 100644 index 2bc145ec..00000000 --- a/Plugins/RTSFormations/Content/BP_UpdateFormation.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9b55ed843972a8419fdf466f9c2989060fc596519f452de1c9a0cbac2322b01e -size 80106 From 01f4c482574c5691c0ac2595db3c8d82360f13a2 Mon Sep 17 00:00:00 2001 From: Ji-Rath Date: Thu, 14 Jul 2022 10:20:21 -0500 Subject: [PATCH 37/44] Add additional comments Clean up RTSFormationProcessors --- .../Private/RTSFormationProcessors.cpp | 44 ++++++++++++++----- .../Private/RTSFormationProcessors.h | 5 ++- 2 files changed, 38 insertions(+), 11 deletions(-) diff --git a/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationProcessors.cpp b/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationProcessors.cpp index a7a4f261..5a98adda 100644 --- a/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationProcessors.cpp +++ b/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationProcessors.cpp @@ -7,8 +7,10 @@ #include "MassSignalSubsystem.h" #include "RTSAgentTraits.h" #include "RTSFormationSubsystem.h" -#include "Kismet/KismetMathLibrary.h" +//----------------------------------------------------------------------// +// URTSFormationInitializer +//----------------------------------------------------------------------// URTSFormationInitializer::URTSFormationInitializer() { ObservedType = FRTSFormationAgent::StaticStruct(); @@ -62,6 +64,9 @@ void URTSFormationInitializer::Execute(UMassEntitySubsystem& EntitySubsystem, FM }); } +//----------------------------------------------------------------------// +// URTSFormationDestroyer +//----------------------------------------------------------------------// URTSFormationDestroyer::URTSFormationDestroyer() { ObservedType = FRTSFormationAgent::StaticStruct(); @@ -84,6 +89,11 @@ void URTSFormationDestroyer::Execute(UMassEntitySubsystem& EntitySubsystem, FMas EntityQuery.ParallelForEachEntityChunk(EntitySubsystem, Context, [this, &EntitySubsystem](FMassExecutionContext& Context) { TConstArrayView FormationAgents = Context.GetFragmentView(); + + // Signal affected units/entities at the end + TArray UnitSignals; + UnitSignals.Reserve(FormationSubsystem->Units.Num()); + for (int32 EntityIndex = 0; EntityIndex < Context.GetNumEntities(); ++EntityIndex) { const FRTSFormationAgent& FormationAgent = FormationAgents[EntityIndex]; @@ -100,24 +110,36 @@ void URTSFormationDestroyer::Execute(UMassEntitySubsystem& EntitySubsystem, FMas ReplacementFormationAgent->EntityIndex = ItemIndex; FormationSubsystem->Units[FormationAgent.UnitIndex].Entities.RemoveAtSwap(ItemIndex, 1, true); - - // Really the only time we need to notify every entity in the unit is when the center point changes - // Every other time we just have to notify the entity thats replacing the destroyed one - if (!FormationSubsystem->Units[FormationAgent.UnitIndex].Entities.IsEmpty()) - SignalSubsystem->SignalEntities(FormationUpdated, FormationSubsystem->Units[FormationAgent.UnitIndex].Entities); + UnitSignals.Emplace(FormationAgent.UnitIndex); } } } - // Shrink array and signal entities - //FormationSubsystem->Units.Shrink(); + + // Signal affected units/entities + for(const int& Unit : UnitSignals) + { + //@todo add a consistent way to reference units since the index isn't reliable + if (FormationSubsystem->Units[Unit].Entities.Num() == 0) + { + FormationSubsystem->Units.RemoveAtSwap(Unit); + continue; + } + + // Really the only time we should notify every entity in the unit is when the center point changes + // Every other time we just have to notify the entity that is replacing the destroyed one + FormationSubsystem->Units[Unit].Entities.Shrink(); + SignalSubsystem->SignalEntities(FormationUpdated, FormationSubsystem->Units[Unit].Entities); + } }); } +//----------------------------------------------------------------------// +// URTSAgentMovement +//----------------------------------------------------------------------// void URTSAgentMovement::ConfigureQueries() { EntityQuery.AddRequirement(EMassFragmentAccess::ReadWrite); EntityQuery.AddRequirement(EMassFragmentAccess::ReadOnly); - EntityQuery.AddSharedRequirement(EMassFragmentAccess::ReadWrite); EntityQuery.AddRequirement(EMassFragmentAccess::ReadWrite); } @@ -127,7 +149,6 @@ void URTSAgentMovement::Execute(UMassEntitySubsystem& EntitySubsystem, FMassExec { TArrayView MoveTargetFragments = Context.GetMutableFragmentView(); TConstArrayView TransformFragments = Context.GetFragmentView(); - FRTSFormationSettings& FormationSettings = Context.GetMutableSharedFragment(); TArrayView VelocityFragments = Context.GetMutableFragmentView(); for (int32 EntityIndex = 0; EntityIndex < Context.GetNumEntities(); ++EntityIndex) @@ -150,6 +171,9 @@ void URTSAgentMovement::Execute(UMassEntitySubsystem& EntitySubsystem, FMassExec }); } +//----------------------------------------------------------------------// +// URTSFormationUpdate +//----------------------------------------------------------------------// void URTSFormationUpdate::Initialize(UObject& Owner) { Super::Initialize(Owner); diff --git a/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationProcessors.h b/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationProcessors.h index 64ea1ee0..23a6ae1d 100644 --- a/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationProcessors.h +++ b/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationProcessors.h @@ -8,7 +8,8 @@ class URTSFormationSubsystem; const FName FormationUpdated = FName(TEXT("FormationUpdated")); -// Observer that runs when a unit is spawned. Calculates square formation position based on unit count +// Observer that runs when a unit is spawned. Its main purpose is to add entities to a unit array +// in the subsystem and cache the index for future use in URTSFormationUpdate UCLASS() class RTSFORMATIONS_API URTSFormationInitializer : public UMassObserverProcessor { @@ -25,6 +26,7 @@ class RTSFORMATIONS_API URTSFormationInitializer : public UMassObserverProcessor TObjectPtr FormationSubsystem; }; +// Observer that runs when an entity is destroyed. Cleans up the unit array and tells the last unit to take their place UCLASS() class RTSFORMATIONS_API URTSFormationDestroyer : public UMassObserverProcessor { @@ -56,6 +58,7 @@ class RTSFORMATIONS_API URTSAgentMovement : public UMassProcessor FMassEntityQuery FormationQuery; }; +// Main bulk of formation logic. Calculates position of entities and sends it to the FMassMoveTargetFragment. UCLASS() class RTSFORMATIONS_API URTSFormationUpdate : public UMassSignalProcessorBase { From fefd767dc32002c3c6097366caf94add4d00421c Mon Sep 17 00:00:00 2001 From: Ji-Rath Date: Thu, 14 Jul 2022 10:20:46 -0500 Subject: [PATCH 38/44] Add comments to RTSFormationSubsystem Modify SpawnNewUnit() so that a position can be specified --- .../Private/RTSFormationSubsystem.cpp | 6 +++--- .../Private/RTSFormationSubsystem.h | 18 +++++++++++------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationSubsystem.cpp b/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationSubsystem.cpp index fb9f8f54..550612bd 100644 --- a/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationSubsystem.cpp +++ b/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationSubsystem.cpp @@ -44,9 +44,7 @@ void URTSFormationSubsystem::SpawnEntitiesForUnit(int UnitIndex, const UMassEnti UMassEntitySubsystem* EntitySubsystem = GetWorld()->GetSubsystem(); // Reserve space for the new units, the space will be filled in a processor - // Give a random Unit position for the heck of it Units[UnitIndex].Entities.Reserve(Units[UnitIndex].Entities.Num()+Count); - Units[UnitIndex].UnitPosition = FVector(FMath::RandRange(-2000.f,2000.f), FMath::RandRange(-1000.f,1000.f), 0.f); TArray Entities; const FMassEntityTemplate* EntityTemplate = EntityConfig->GetConfig().GetOrCreateEntityTemplate(*UGameplayStatics::GetPlayerPawn(this, 0), *EntityConfig); @@ -70,10 +68,12 @@ void URTSFormationSubsystem::SpawnEntitiesForUnit(int UnitIndex, const UMassEnti EntitySubsystem->BatchSetEntityFragmentsValues(CreationContext->GetChunkCollection(), Fragments); } -int URTSFormationSubsystem::SpawnNewUnit(const UMassEntityConfigAsset* EntityConfig, int Count) +int URTSFormationSubsystem::SpawnNewUnit(const UMassEntityConfigAsset* EntityConfig, int Count, const FVector& Position) { int UnitIndex = Units.Num(); Units.AddDefaulted(1); + Units[UnitIndex].UnitPosition = Position; + SpawnEntitiesForUnit(UnitIndex, EntityConfig, Count); return UnitIndex; } diff --git a/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationSubsystem.h b/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationSubsystem.h index bd6aac7c..955a59f6 100644 --- a/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationSubsystem.h +++ b/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationSubsystem.h @@ -23,33 +23,33 @@ struct FUnitInfo UPROPERTY() TArray Entities; + // The current unit position UPROPERTY(BlueprintReadOnly) FVector UnitPosition; + // The direction to turn the unit when rotating UPROPERTY() float TurnDirection = 1.f; + // The entity length of the 'front' of the unit UPROPERTY() float FormationLength = 0; + // The type of formation - WIP UPROPERTY() TEnumAsByte Formation = Rectangle; + // The angle of the unit UPROPERTY() float Angle = 0; FUnitInfo() {}; - - FUnitInfo(const TArray& Entities) - { - this->Entities.Append(Entities); - } }; class UMassAgentComponent; struct FMassEntityHandle; /** - * + * Subsystem that handles the bulk of data shared among entities for the formation system. Enables simple unit creation and entity spawning */ UCLASS() class RTSFORMATIONS_API URTSFormationSubsystem : public UWorldSubsystem @@ -61,15 +61,19 @@ class RTSFORMATIONS_API URTSFormationSubsystem : public UWorldSubsystem UPROPERTY(BlueprintReadOnly) TArray Units; + // Destroy a specified entity UFUNCTION(BlueprintCallable) void DestroyEntity(UMassAgentComponent* Entity); + // Set the position of a unit UFUNCTION(BlueprintCallable) void SetUnitPosition(const FVector& NewPosition, int UnitIndex = 0); + // Spawn entities for a unit UFUNCTION(BlueprintCallable) void SpawnEntitiesForUnit(int UnitIndex, const UMassEntityConfigAsset* EntityConfig, int Count); + // Spawn a new unit UFUNCTION(BlueprintCallable) - int SpawnNewUnit(const UMassEntityConfigAsset* EntityConfig, int Count); + int SpawnNewUnit(const UMassEntityConfigAsset* EntityConfig, int Count, const FVector& Position); }; From 5ca9199547be84bd9c1f461d7cd1ed9e86c1a5d2 Mon Sep 17 00:00:00 2001 From: Ji-Rath Date: Thu, 14 Jul 2022 10:21:18 -0500 Subject: [PATCH 39/44] Re-add avoidance to DA_AgentFormation Update RTSFormationsExample.umap --- Plugins/RTSFormations/Content/DA_AgentFormation.uasset | 4 ++-- Plugins/RTSFormations/Content/RTSFormationsExample.umap | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Plugins/RTSFormations/Content/DA_AgentFormation.uasset b/Plugins/RTSFormations/Content/DA_AgentFormation.uasset index d7cc6603..737d7adb 100644 --- a/Plugins/RTSFormations/Content/DA_AgentFormation.uasset +++ b/Plugins/RTSFormations/Content/DA_AgentFormation.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9be66644672059b6dbc79941ade0a7a778e92e139718395e119407f8103e99c9 -size 5886 +oid sha256:e788f5713099b0897af582767f421b3088be9d32e333ae1adcfc1483051d3fcc +size 5834 diff --git a/Plugins/RTSFormations/Content/RTSFormationsExample.umap b/Plugins/RTSFormations/Content/RTSFormationsExample.umap index a712a19b..721b6a0f 100644 --- a/Plugins/RTSFormations/Content/RTSFormationsExample.umap +++ b/Plugins/RTSFormations/Content/RTSFormationsExample.umap @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e72d8e03e290b7f3ac9383bedb31d89d080855e7f3391ed4292efa7d6dc8bea3 -size 39877 +oid sha256:7ee708c1b5079bb312343918ad48d202a40f0cdad9f990e2b9ee509f2a3b1b7e +size 43618 From f593b8c27c50f8b5676b7b80aa6de6c87025ac74 Mon Sep 17 00:00:00 2001 From: Ji-Rath Date: Thu, 14 Jul 2022 10:37:15 -0500 Subject: [PATCH 40/44] Add MassCommunitySample as dependency --- Plugins/RTSFormations/RTSFormations.uplugin | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Plugins/RTSFormations/RTSFormations.uplugin b/Plugins/RTSFormations/RTSFormations.uplugin index c58598ee..36b7eb8b 100644 --- a/Plugins/RTSFormations/RTSFormations.uplugin +++ b/Plugins/RTSFormations/RTSFormations.uplugin @@ -31,15 +31,15 @@ "Enabled": true }, { - "Name": "MassGameplay", + "Name": "MassAI", "Enabled": true }, { - "Name": "MassAI", + "Name": "StructUtils", "Enabled": true }, { - "Name": "StructUtils", + "Name": "MassCommunitySample", "Enabled": true } ] From e81f29e921a4058d7cd089006554f3a634e1fd19 Mon Sep 17 00:00:00 2001 From: Ji-Rath Date: Thu, 14 Jul 2022 14:24:20 -0500 Subject: [PATCH 41/44] Move RTSFormationsPlugin to new branch Sorry this is a little messy, I just have a new idea for MassSample and should have created a new branch in my fork first --- .../Content/BP_FormationGameMode.uasset | 3 - .../Content/BP_FormationUnit.uasset | 3 - .../Content/BP_SpawnEntity.uasset | 3 - .../RTSFormations/Content/BP_SpawnUnit.uasset | 3 - .../Content/DA_AgentFormation.uasset | 3 - .../Content/RTSFormationsExample.umap | 3 - Plugins/RTSFormations/RTSFormations.uplugin | 46 ---- Plugins/RTSFormations/Resources/Icon128.png | 3 - .../RTSFormations/Private/RTSAgentTraits.cpp | 20 -- .../Private/RTSFormationProcessors.cpp | 239 ------------------ .../Private/RTSFormationProcessors.h | 72 ------ .../Private/RTSFormationSubsystem.cpp | 79 ------ .../Private/RTSFormationSubsystem.h | 79 ------ .../RTSFormations/Private/RTSFormations.cpp | 20 -- .../RTSFormations/Public/RTSAgentTraits.h | 49 ---- .../RTSFormations/Public/RTSFormations.h | 15 -- .../RTSFormations/RTSFormations.Build.cs | 53 ---- 17 files changed, 693 deletions(-) delete mode 100644 Plugins/RTSFormations/Content/BP_FormationGameMode.uasset delete mode 100644 Plugins/RTSFormations/Content/BP_FormationUnit.uasset delete mode 100644 Plugins/RTSFormations/Content/BP_SpawnEntity.uasset delete mode 100644 Plugins/RTSFormations/Content/BP_SpawnUnit.uasset delete mode 100644 Plugins/RTSFormations/Content/DA_AgentFormation.uasset delete mode 100644 Plugins/RTSFormations/Content/RTSFormationsExample.umap delete mode 100644 Plugins/RTSFormations/RTSFormations.uplugin delete mode 100644 Plugins/RTSFormations/Resources/Icon128.png delete mode 100644 Plugins/RTSFormations/Source/RTSFormations/Private/RTSAgentTraits.cpp delete mode 100644 Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationProcessors.cpp delete mode 100644 Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationProcessors.h delete mode 100644 Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationSubsystem.cpp delete mode 100644 Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationSubsystem.h delete mode 100644 Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormations.cpp delete mode 100644 Plugins/RTSFormations/Source/RTSFormations/Public/RTSAgentTraits.h delete mode 100644 Plugins/RTSFormations/Source/RTSFormations/Public/RTSFormations.h delete mode 100644 Plugins/RTSFormations/Source/RTSFormations/RTSFormations.Build.cs diff --git a/Plugins/RTSFormations/Content/BP_FormationGameMode.uasset b/Plugins/RTSFormations/Content/BP_FormationGameMode.uasset deleted file mode 100644 index 3da8cd4b..00000000 --- a/Plugins/RTSFormations/Content/BP_FormationGameMode.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:23930ce006e48f259eb128cec23236a56e977e9a9748277d15961c374cc495a8 -size 62555 diff --git a/Plugins/RTSFormations/Content/BP_FormationUnit.uasset b/Plugins/RTSFormations/Content/BP_FormationUnit.uasset deleted file mode 100644 index b342a99a..00000000 --- a/Plugins/RTSFormations/Content/BP_FormationUnit.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:dd24092b2f239e96f004695cc7346e0c03209cf930df5c4a065c0b3a01d1a98c -size 43629 diff --git a/Plugins/RTSFormations/Content/BP_SpawnEntity.uasset b/Plugins/RTSFormations/Content/BP_SpawnEntity.uasset deleted file mode 100644 index ab22b363..00000000 --- a/Plugins/RTSFormations/Content/BP_SpawnEntity.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:41062c587661c36432e1b8f328e4b751229a8a507a3efc1ba728f2e13a391a8b -size 76296 diff --git a/Plugins/RTSFormations/Content/BP_SpawnUnit.uasset b/Plugins/RTSFormations/Content/BP_SpawnUnit.uasset deleted file mode 100644 index 90e01661..00000000 --- a/Plugins/RTSFormations/Content/BP_SpawnUnit.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f7841fc6958941d4efcc7ba24a20058153b3099da39505dae1a772269def8361 -size 54872 diff --git a/Plugins/RTSFormations/Content/DA_AgentFormation.uasset b/Plugins/RTSFormations/Content/DA_AgentFormation.uasset deleted file mode 100644 index 737d7adb..00000000 --- a/Plugins/RTSFormations/Content/DA_AgentFormation.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e788f5713099b0897af582767f421b3088be9d32e333ae1adcfc1483051d3fcc -size 5834 diff --git a/Plugins/RTSFormations/Content/RTSFormationsExample.umap b/Plugins/RTSFormations/Content/RTSFormationsExample.umap deleted file mode 100644 index 721b6a0f..00000000 --- a/Plugins/RTSFormations/Content/RTSFormationsExample.umap +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7ee708c1b5079bb312343918ad48d202a40f0cdad9f990e2b9ee509f2a3b1b7e -size 43618 diff --git a/Plugins/RTSFormations/RTSFormations.uplugin b/Plugins/RTSFormations/RTSFormations.uplugin deleted file mode 100644 index 36b7eb8b..00000000 --- a/Plugins/RTSFormations/RTSFormations.uplugin +++ /dev/null @@ -1,46 +0,0 @@ -{ - "FileVersion": 3, - "Version": 1, - "VersionName": "1.0", - "FriendlyName": "RTSFormations", - "Description": "", - "Category": "Other", - "CreatedBy": "JiRath", - "CreatedByURL": "", - "DocsURL": "", - "MarketplaceURL": "", - "SupportURL": "", - "CanContainContent": true, - "IsBetaVersion": true, - "IsExperimentalVersion": false, - "Installed": false, - "Modules": [ - { - "Name": "RTSFormations", - "Type": "Runtime", - "LoadingPhase": "Default" - } - ], - "Plugins": [ - { - "Name": "MassGameplay", - "Enabled": true - }, - { - "Name": "MassEntity", - "Enabled": true - }, - { - "Name": "MassAI", - "Enabled": true - }, - { - "Name": "StructUtils", - "Enabled": true - }, - { - "Name": "MassCommunitySample", - "Enabled": true - } - ] -} \ No newline at end of file diff --git a/Plugins/RTSFormations/Resources/Icon128.png b/Plugins/RTSFormations/Resources/Icon128.png deleted file mode 100644 index 26245f6a..00000000 --- a/Plugins/RTSFormations/Resources/Icon128.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f7239efaeefbd82de33ebe18518e50de075ea4188a468a9e4991396433d2275f -size 12699 diff --git a/Plugins/RTSFormations/Source/RTSFormations/Private/RTSAgentTraits.cpp b/Plugins/RTSFormations/Source/RTSFormations/Private/RTSAgentTraits.cpp deleted file mode 100644 index abaf4e6e..00000000 --- a/Plugins/RTSFormations/Source/RTSFormations/Private/RTSAgentTraits.cpp +++ /dev/null @@ -1,20 +0,0 @@ -// Fill out your copyright notice in the Description page of Project Settings. - - -#include "RTSAgentTraits.h" - -#include "MassEntityTemplateRegistry.h" -#include "MassNavigationFragments.h" -#include "MassObserverRegistry.h" - -void URTSFormationAgentTrait::BuildTemplate(FMassEntityTemplateBuildContext& BuildContext, UWorld& World) const -{ - UMassEntitySubsystem* EntitySubsystem = UWorld::GetSubsystem(&World); - check(EntitySubsystem); - - BuildContext.AddFragment(); - - uint32 MySharedFragmentHash = UE::StructUtils::GetStructCrc32(FConstStructView::Make(FormationSettings)); - FSharedStruct MySharedFragment = EntitySubsystem->GetOrCreateSharedFragment(MySharedFragmentHash, FormationSettings); - BuildContext.AddSharedFragment(MySharedFragment); -} diff --git a/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationProcessors.cpp b/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationProcessors.cpp deleted file mode 100644 index 5a98adda..00000000 --- a/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationProcessors.cpp +++ /dev/null @@ -1,239 +0,0 @@ -#include "RTSFormationProcessors.h" - -#include "MassCommonFragments.h" -#include "MassMovementFragments.h" -#include "MassNavigationFragments.h" -#include "MassNavigationTypes.h" -#include "MassSignalSubsystem.h" -#include "RTSAgentTraits.h" -#include "RTSFormationSubsystem.h" - -//----------------------------------------------------------------------// -// URTSFormationInitializer -//----------------------------------------------------------------------// -URTSFormationInitializer::URTSFormationInitializer() -{ - ObservedType = FRTSFormationAgent::StaticStruct(); - Operation = EMassObservedOperation::Add; -} - -void URTSFormationInitializer::ConfigureQueries() -{ - EntityQuery.AddRequirement(EMassFragmentAccess::ReadWrite); -} - -void URTSFormationInitializer::Initialize(UObject& Owner) -{ - Super::Initialize(Owner); - - SignalSubsystem = UWorld::GetSubsystem(Owner.GetWorld()); - FormationSubsystem = UWorld::GetSubsystem(Owner.GetWorld()); -} - -void URTSFormationInitializer::Execute(UMassEntitySubsystem& EntitySubsystem, FMassExecutionContext& Context) -{ - // First query is to give all units an appropriate unit index. - EntityQuery.ParallelForEachEntityChunk(EntitySubsystem, Context, [this](FMassExecutionContext& Context) - { - TArrayView RTSFormationAgents = Context.GetMutableFragmentView(); - - // Signal affected units/entities at the end - TArray UnitSignals; - UnitSignals.Reserve(FormationSubsystem->Units.Num()); - - // Since we can have multiple units, reserving is only done in the formation subsystem - // This is because it might be possible that a batch of spawned entities should go to different units - for (int32 EntityIndex = 0; EntityIndex < Context.GetNumEntities(); ++EntityIndex) - { - FRTSFormationAgent& RTSFormationAgent = RTSFormationAgents[EntityIndex]; - - // If for some reason the unit hasnt been created, we should create it now - // Unfortunately, with the nature of an array, this might cause a crash if the unit index is not next in line, need to handle this somehow - if (!FormationSubsystem->Units.IsValidIndex(RTSFormationAgent.UnitIndex)) - FormationSubsystem->Units.AddDefaulted(1); - - RTSFormationAgent.EntityIndex = FormationSubsystem->Units[RTSFormationAgent.UnitIndex].Entities.Num(); - FormationSubsystem->Units[RTSFormationAgent.UnitIndex].Entities.Emplace(Context.GetEntity(EntityIndex)); - - UnitSignals.AddUnique(RTSFormationAgent.UnitIndex); - } - // Signal entities in the unit that their position is updated - // @todo only notify affected entities - for(const int& Unit : UnitSignals) - SignalSubsystem->SignalEntities(FormationUpdated, FormationSubsystem->Units[Unit].Entities); - }); -} - -//----------------------------------------------------------------------// -// URTSFormationDestroyer -//----------------------------------------------------------------------// -URTSFormationDestroyer::URTSFormationDestroyer() -{ - ObservedType = FRTSFormationAgent::StaticStruct(); - Operation = EMassObservedOperation::Remove; -} - -void URTSFormationDestroyer::ConfigureQueries() -{ - EntityQuery.AddRequirement(EMassFragmentAccess::ReadOnly); -} - -void URTSFormationDestroyer::Initialize(UObject& Owner) -{ - SignalSubsystem = UWorld::GetSubsystem(Owner.GetWorld()); - FormationSubsystem = UWorld::GetSubsystem(Owner.GetWorld()); -} - -void URTSFormationDestroyer::Execute(UMassEntitySubsystem& EntitySubsystem, FMassExecutionContext& Context) -{ - EntityQuery.ParallelForEachEntityChunk(EntitySubsystem, Context, [this, &EntitySubsystem](FMassExecutionContext& Context) - { - TConstArrayView FormationAgents = Context.GetFragmentView(); - - // Signal affected units/entities at the end - TArray UnitSignals; - UnitSignals.Reserve(FormationSubsystem->Units.Num()); - - for (int32 EntityIndex = 0; EntityIndex < Context.GetNumEntities(); ++EntityIndex) - { - const FRTSFormationAgent& FormationAgent = FormationAgents[EntityIndex]; - - // Remove entity from units array - if (FormationSubsystem->Units.IsValidIndex(FormationAgent.UnitIndex)) - { - const int32 ItemIndex = FormationSubsystem->Units[FormationAgent.UnitIndex].Entities.IndexOfByKey(Context.GetEntity(EntityIndex)); - if (ItemIndex != INDEX_NONE) - { - // Since we are caching the index, we need to fix the entity index that replaces the destroyed one - // Not sure if this is the 'correct' way to handle this, but it works for now - if (FRTSFormationAgent* ReplacementFormationAgent = EntitySubsystem.GetFragmentDataPtr(FormationSubsystem->Units[FormationAgent.UnitIndex].Entities.Last())) - ReplacementFormationAgent->EntityIndex = ItemIndex; - - FormationSubsystem->Units[FormationAgent.UnitIndex].Entities.RemoveAtSwap(ItemIndex, 1, true); - UnitSignals.Emplace(FormationAgent.UnitIndex); - } - } - } - - // Signal affected units/entities - for(const int& Unit : UnitSignals) - { - //@todo add a consistent way to reference units since the index isn't reliable - if (FormationSubsystem->Units[Unit].Entities.Num() == 0) - { - FormationSubsystem->Units.RemoveAtSwap(Unit); - continue; - } - - // Really the only time we should notify every entity in the unit is when the center point changes - // Every other time we just have to notify the entity that is replacing the destroyed one - FormationSubsystem->Units[Unit].Entities.Shrink(); - SignalSubsystem->SignalEntities(FormationUpdated, FormationSubsystem->Units[Unit].Entities); - } - }); -} - -//----------------------------------------------------------------------// -// URTSAgentMovement -//----------------------------------------------------------------------// -void URTSAgentMovement::ConfigureQueries() -{ - EntityQuery.AddRequirement(EMassFragmentAccess::ReadWrite); - EntityQuery.AddRequirement(EMassFragmentAccess::ReadOnly); - EntityQuery.AddRequirement(EMassFragmentAccess::ReadWrite); -} - -void URTSAgentMovement::Execute(UMassEntitySubsystem& EntitySubsystem, FMassExecutionContext& Context) -{ - EntityQuery.ParallelForEachEntityChunk(EntitySubsystem, Context, [this](FMassExecutionContext& Context) - { - TArrayView MoveTargetFragments = Context.GetMutableFragmentView(); - TConstArrayView TransformFragments = Context.GetFragmentView(); - TArrayView VelocityFragments = Context.GetMutableFragmentView(); - - for (int32 EntityIndex = 0; EntityIndex < Context.GetNumEntities(); ++EntityIndex) - { - FMassMoveTargetFragment& MoveTarget = MoveTargetFragments[EntityIndex]; - const FTransform& Transform = TransformFragments[EntityIndex].GetTransform(); - FVector& Velocity = VelocityFragments[EntityIndex].Value; - - // Update move target values - MoveTarget.DistanceToGoal = (MoveTarget.Center - Transform.GetLocation()).Length(); - MoveTarget.Forward = (MoveTarget.Center - Transform.GetLocation()).GetSafeNormal(); - - // Once we are close enough to our goal, create stand action - if (MoveTarget.DistanceToGoal <= MoveTarget.SlackRadius) - { - MoveTarget.CreateNewAction(EMassMovementAction::Stand, *GetWorld()); - Velocity = FVector::Zero(); - } - } - }); -} - -//----------------------------------------------------------------------// -// URTSFormationUpdate -//----------------------------------------------------------------------// -void URTSFormationUpdate::Initialize(UObject& Owner) -{ - Super::Initialize(Owner); - SubscribeToSignal(FormationUpdated); - - FormationSubsystem = UWorld::GetSubsystem(Owner.GetWorld()); -} - -void URTSFormationUpdate::ConfigureQueries() -{ - EntityQuery.AddRequirement(EMassFragmentAccess::ReadOnly); - EntityQuery.AddRequirement(EMassFragmentAccess::ReadWrite); - EntityQuery.AddRequirement(EMassFragmentAccess::ReadOnly); - EntityQuery.AddSharedRequirement(EMassFragmentAccess::ReadOnly); -} - -void URTSFormationUpdate::SignalEntities(UMassEntitySubsystem& EntitySubsystem, FMassExecutionContext& Context, - FMassSignalNameLookup& EntitySignals) -{ - // Query to calculate move target for entities based on unit index - EntityQuery.ParallelForEachEntityChunk(EntitySubsystem, Context, [this](FMassExecutionContext& Context) - { - TConstArrayView RTSFormationAgents = Context.GetFragmentView(); - TArrayView MoveTargetFragments = Context.GetMutableFragmentView(); - TConstArrayView TransformFragments = Context.GetFragmentView(); - const FRTSFormationSettings& RTSFormationSettings = Context.GetSharedFragment(); - - for (int32 EntityIndex = 0; EntityIndex < Context.GetNumEntities(); ++EntityIndex) - { - - const FRTSFormationAgent& RTSFormationAgent = RTSFormationAgents[EntityIndex]; - FMassMoveTargetFragment& MoveTarget = MoveTargetFragments[EntityIndex]; - const FTransform& Transform = TransformFragments[EntityIndex].GetTransform(); - - // Convert UnitIndex to X/Y coords - const int w = RTSFormationAgent.EntityIndex / RTSFormationSettings.FormationLength; - const int l = RTSFormationAgent.EntityIndex % RTSFormationSettings.FormationLength; - - // We want the formation to be 'centered' so we need to create an offset - const FVector CenterOffset = FVector((FormationSubsystem->Units[RTSFormationAgent.UnitIndex].Entities.Num()/RTSFormationSettings.FormationLength/2) * RTSFormationSettings.BufferDistance, (RTSFormationSettings.FormationLength/2) * RTSFormationSettings.BufferDistance, 0.f); - - // Create movement action - MoveTarget.CreateNewAction(EMassMovementAction::Move, *GetWorld()); - - // Set entity position based on index in formation - FVector EntityPosition = FVector(w,l,0.f); - EntityPosition *= RTSFormationSettings.BufferDistance; - EntityPosition -= CenterOffset; - - // Rotate unit by calculated angle - FVector RotateValue = EntityPosition.RotateAngleAxis(FormationSubsystem->Units[RTSFormationAgent.UnitIndex].Angle, FVector(0.f,0.f,FormationSubsystem->Units[RTSFormationAgent.UnitIndex].TurnDirection)); - - // Finally add the units position to the entity position - RotateValue += FormationSubsystem->Units[RTSFormationAgent.UnitIndex].UnitPosition; - - MoveTarget.Center = RotateValue; - MoveTarget.Forward = (Transform.GetLocation() - MoveTarget.Center).GetSafeNormal(); - MoveTarget.DistanceToGoal = (Transform.GetLocation() - MoveTarget.Center).Length(); - MoveTarget.SlackRadius = 10.f; - MoveTarget.IntentAtGoal = EMassMovementAction::Stand; - } - }); -} diff --git a/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationProcessors.h b/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationProcessors.h deleted file mode 100644 index 23a6ae1d..00000000 --- a/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationProcessors.h +++ /dev/null @@ -1,72 +0,0 @@ -#pragma once -#include "MassEntityQuery.h" -#include "MassObserverProcessor.h" -#include "MassProcessor.h" -#include "MassSignalProcessorBase.h" -#include "RTSFormationProcessors.generated.h" - -class URTSFormationSubsystem; -const FName FormationUpdated = FName(TEXT("FormationUpdated")); - -// Observer that runs when a unit is spawned. Its main purpose is to add entities to a unit array -// in the subsystem and cache the index for future use in URTSFormationUpdate -UCLASS() -class RTSFORMATIONS_API URTSFormationInitializer : public UMassObserverProcessor -{ - GENERATED_BODY() - - URTSFormationInitializer(); - virtual void ConfigureQueries() override; - virtual void Initialize(UObject& Owner) override; - virtual void Execute(UMassEntitySubsystem& EntitySubsystem, FMassExecutionContext& Context) override; - - FMassEntityQuery EntityQuery; - - TObjectPtr SignalSubsystem; - TObjectPtr FormationSubsystem; -}; - -// Observer that runs when an entity is destroyed. Cleans up the unit array and tells the last unit to take their place -UCLASS() -class RTSFORMATIONS_API URTSFormationDestroyer : public UMassObserverProcessor -{ - GENERATED_BODY() - - URTSFormationDestroyer(); - virtual void ConfigureQueries() override; - virtual void Initialize(UObject& Owner) override; - virtual void Execute(UMassEntitySubsystem& EntitySubsystem, FMassExecutionContext& Context) override; - - FMassEntityQuery EntityQuery; - - TObjectPtr SignalSubsystem; - TObjectPtr FormationSubsystem; -}; - - -// Simple movement processor to get agents from a to b -UCLASS() -class RTSFORMATIONS_API URTSAgentMovement : public UMassProcessor -{ - GENERATED_BODY() - - virtual void ConfigureQueries() override; - virtual void Execute(UMassEntitySubsystem& EntitySubsystem, FMassExecutionContext& Context) override; - - FMassEntityQuery EntityQuery; - - FMassEntityQuery FormationQuery; -}; - -// Main bulk of formation logic. Calculates position of entities and sends it to the FMassMoveTargetFragment. -UCLASS() -class RTSFORMATIONS_API URTSFormationUpdate : public UMassSignalProcessorBase -{ - GENERATED_BODY() - - virtual void Initialize(UObject& Owner) override; - virtual void ConfigureQueries() override; - virtual void SignalEntities(UMassEntitySubsystem& EntitySubsystem, FMassExecutionContext& Context, FMassSignalNameLookup& EntitySignals) override; - - TObjectPtr FormationSubsystem; -}; diff --git a/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationSubsystem.cpp b/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationSubsystem.cpp deleted file mode 100644 index 550612bd..00000000 --- a/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationSubsystem.cpp +++ /dev/null @@ -1,79 +0,0 @@ -// Fill out your copyright notice in the Description page of Project Settings. - - -#include "RTSFormationSubsystem.h" - -#include "MassAgentComponent.h" -#include "MassEntitySubsystem.h" -#include "MassSignalSubsystem.h" -#include "MassSpawnerSubsystem.h" -#include "RTSAgentTraits.h" -#include "RTSFormationProcessors.h" -#include "Kismet/GameplayStatics.h" - -void URTSFormationSubsystem::DestroyEntity(UMassAgentComponent* Entity) -{ - UMassEntitySubsystem* EntitySubsystem = GetWorld()->GetSubsystem(); - check(EntitySubsystem); - - EntitySubsystem->Defer().DestroyEntity(Entity->GetEntityHandle()); -} - -void URTSFormationSubsystem::SetUnitPosition(const FVector& NewPosition, int UnitIndex) -{ - if (!ensure(Units.IsValidIndex(UnitIndex))) { return; } - - DrawDebugDirectionalArrow(GetWorld(), NewPosition, NewPosition+((NewPosition-Units[UnitIndex].UnitPosition).GetSafeNormal()*250.f), 150.f, FColor::Red, false, 10.f, 0, 25.f); - - // Calculate turn direction and angle for entities in unit - Units[UnitIndex].TurnDirection = (NewPosition-Units[UnitIndex].UnitPosition).GetSafeNormal().Y > 0 ? 1.f : -1.f; - Units[UnitIndex].Angle = FMath::RadiansToDegrees(acosf(FVector::DotProduct(FVector::ForwardVector, (NewPosition-Units[UnitIndex].UnitPosition).GetSafeNormal()))); - Units[UnitIndex].Angle += 180.f; // Temporary fix to resolve unit facing the wrong direction - - Units[UnitIndex].UnitPosition = NewPosition; - - // Signal entities of a position update - if (Units[UnitIndex].Entities.Num() > 0) - GetWorld()->GetSubsystem()->SignalEntities(FormationUpdated, Units[UnitIndex].Entities); -} - -void URTSFormationSubsystem::SpawnEntitiesForUnit(int UnitIndex, const UMassEntityConfigAsset* EntityConfig, int Count) -{ - if (!ensure(Units.IsValidIndex(UnitIndex))) { return; } - - UMassEntitySubsystem* EntitySubsystem = GetWorld()->GetSubsystem(); - - // Reserve space for the new units, the space will be filled in a processor - Units[UnitIndex].Entities.Reserve(Units[UnitIndex].Entities.Num()+Count); - - TArray Entities; - const FMassEntityTemplate* EntityTemplate = EntityConfig->GetConfig().GetOrCreateEntityTemplate(*UGameplayStatics::GetPlayerPawn(this, 0), *EntityConfig); - - // We are doing a little bit of work here since we are setting the unit index manually - // Otherwise, using SpawnEntities would be perfectly fine - // @todo find if there is a better way to modify templates in code - TArray SpawnedEntities; - TSharedRef CreationContext = EntitySubsystem->BatchCreateEntities(EntityTemplate->GetArchetype(), Count, SpawnedEntities); - - // Set the template default values for the entities - TConstArrayView FragmentInstances = EntityTemplate->GetInitialFragmentValues(); - EntitySubsystem->BatchSetEntityFragmentsValues(CreationContext->GetChunkCollection(), FragmentInstances); - - // Set unit index for entities - FRTSFormationAgent FormationAgent; - FormationAgent.UnitIndex = UnitIndex; - - TArray Fragments; - Fragments.Add(FConstStructView::Make(FormationAgent)); - EntitySubsystem->BatchSetEntityFragmentsValues(CreationContext->GetChunkCollection(), Fragments); -} - -int URTSFormationSubsystem::SpawnNewUnit(const UMassEntityConfigAsset* EntityConfig, int Count, const FVector& Position) -{ - int UnitIndex = Units.Num(); - Units.AddDefaulted(1); - Units[UnitIndex].UnitPosition = Position; - - SpawnEntitiesForUnit(UnitIndex, EntityConfig, Count); - return UnitIndex; -} diff --git a/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationSubsystem.h b/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationSubsystem.h deleted file mode 100644 index 955a59f6..00000000 --- a/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationSubsystem.h +++ /dev/null @@ -1,79 +0,0 @@ -// Fill out your copyright notice in the Description page of Project Settings. - -#pragma once - -#include "CoreMinimal.h" -#include "Common/Misc/MSBPFunctionLibrary.h" -#include "RTSFormationSubsystem.generated.h" - -UENUM() -enum FormationType -{ - Rectangle, - Circle -}; - -USTRUCT(BlueprintType) -struct FUnitInfo -{ - GENERATED_BODY() - -public: - // Entities in the unit - UPROPERTY() - TArray Entities; - - // The current unit position - UPROPERTY(BlueprintReadOnly) - FVector UnitPosition; - - // The direction to turn the unit when rotating - UPROPERTY() - float TurnDirection = 1.f; - - // The entity length of the 'front' of the unit - UPROPERTY() - float FormationLength = 0; - - // The type of formation - WIP - UPROPERTY() - TEnumAsByte Formation = Rectangle; - - // The angle of the unit - UPROPERTY() - float Angle = 0; - - FUnitInfo() {}; -}; - -class UMassAgentComponent; -struct FMassEntityHandle; -/** - * Subsystem that handles the bulk of data shared among entities for the formation system. Enables simple unit creation and entity spawning - */ -UCLASS() -class RTSFORMATIONS_API URTSFormationSubsystem : public UWorldSubsystem -{ - GENERATED_BODY() - -public: - // Stores the num of units in the formation - UPROPERTY(BlueprintReadOnly) - TArray Units; - - // Destroy a specified entity - UFUNCTION(BlueprintCallable) - void DestroyEntity(UMassAgentComponent* Entity); - - // Set the position of a unit - UFUNCTION(BlueprintCallable) - void SetUnitPosition(const FVector& NewPosition, int UnitIndex = 0); - - // Spawn entities for a unit - UFUNCTION(BlueprintCallable) - void SpawnEntitiesForUnit(int UnitIndex, const UMassEntityConfigAsset* EntityConfig, int Count); - - // Spawn a new unit - UFUNCTION(BlueprintCallable) - int SpawnNewUnit(const UMassEntityConfigAsset* EntityConfig, int Count, const FVector& Position); -}; diff --git a/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormations.cpp b/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormations.cpp deleted file mode 100644 index 65ff621e..00000000 --- a/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormations.cpp +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -#include "RTSFormations.h" - -#define LOCTEXT_NAMESPACE "FRTSFormationsModule" - -void FRTSFormationsModule::StartupModule() -{ - // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module -} - -void FRTSFormationsModule::ShutdownModule() -{ - // This function may be called during shutdown to clean up your module. For modules that support dynamic reloading, - // we call this function before unloading the module. -} - -#undef LOCTEXT_NAMESPACE - -IMPLEMENT_MODULE(FRTSFormationsModule, RTSFormations) \ No newline at end of file diff --git a/Plugins/RTSFormations/Source/RTSFormations/Public/RTSAgentTraits.h b/Plugins/RTSFormations/Source/RTSFormations/Public/RTSAgentTraits.h deleted file mode 100644 index 567065c9..00000000 --- a/Plugins/RTSFormations/Source/RTSFormations/Public/RTSAgentTraits.h +++ /dev/null @@ -1,49 +0,0 @@ -// Fill out your copyright notice in the Description page of Project Settings. - -#pragma once - -#include "CoreMinimal.h" -#include "MassEntityTraitBase.h" -#include "MassEntityTypes.h" -#include "RTSAgentTraits.generated.h" - -class URTSFormationSubsystem; - -// Store basic info about the unit -USTRUCT() -struct RTSFORMATIONS_API FRTSFormationAgent : public FMassFragment -{ - GENERATED_BODY() - - // The index of the entity in the formation - int EntityIndex = 0; - - // The unit that this entity is a part of - int UnitIndex = 0; -}; - -USTRUCT() -struct RTSFORMATIONS_API FRTSFormationSettings : public FMassSharedFragment -{ - GENERATED_BODY() - - // Distance between each unit - UPROPERTY(EditAnywhere, Category = "Formation") - float BufferDistance = 100.f; - - // Unit width of formation - UPROPERTY(EditAnywhere, Category = "Formation") - int FormationLength = 1; -}; - -// Provides entity with FRTSFormationAgent fragment to enable formations -UCLASS() -class RTSFORMATIONS_API URTSFormationAgentTrait : public UMassEntityTraitBase -{ - GENERATED_BODY() - - virtual void BuildTemplate(FMassEntityTemplateBuildContext& BuildContext, UWorld& World) const override; - - UPROPERTY(EditAnywhere) - FRTSFormationSettings FormationSettings; -}; diff --git a/Plugins/RTSFormations/Source/RTSFormations/Public/RTSFormations.h b/Plugins/RTSFormations/Source/RTSFormations/Public/RTSFormations.h deleted file mode 100644 index afdb4e36..00000000 --- a/Plugins/RTSFormations/Source/RTSFormations/Public/RTSFormations.h +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -#pragma once - -#include "CoreMinimal.h" -#include "Modules/ModuleManager.h" - -class FRTSFormationsModule : public IModuleInterface -{ -public: - - /** IModuleInterface implementation */ - virtual void StartupModule() override; - virtual void ShutdownModule() override; -}; diff --git a/Plugins/RTSFormations/Source/RTSFormations/RTSFormations.Build.cs b/Plugins/RTSFormations/Source/RTSFormations/RTSFormations.Build.cs deleted file mode 100644 index 2a572eb0..00000000 --- a/Plugins/RTSFormations/Source/RTSFormations/RTSFormations.Build.cs +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -using UnrealBuildTool; - -public class RTSFormations : ModuleRules -{ - public RTSFormations(ReadOnlyTargetRules Target) : base(Target) - { - PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; - - PublicIncludePaths.AddRange( - new string[] { - // ... add public include paths required here ... - } - ); - - - PrivateIncludePaths.AddRange( - new string[] { - // ... add other private include paths required here ... - } - ); - - - PublicDependencyModuleNames.AddRange( - new string[] - { - "Core", "MassSpawner", "MassEntity", "MassCommon", "StructUtils", "MassSignals", "MassMovement", "MassCommunitySample" - // ... add other public dependencies that you statically link with here ... - } - ); - - - PrivateDependencyModuleNames.AddRange( - new string[] - { - "CoreUObject", - "Engine", - "Slate", - "SlateCore", "MassNavigation", "MassActors" - // ... add private dependencies that you statically link with here ... - } - ); - - - DynamicallyLoadedModuleNames.AddRange( - new string[] - { - // ... add any modules that your module loads dynamically here ... - } - ); - } -} From 17a0ded49afc985f9fd132063606b33e96bfa375 Mon Sep 17 00:00:00 2001 From: Ji-Rath Date: Thu, 14 Jul 2022 22:59:42 -0500 Subject: [PATCH 42/44] Add LODExamplePlugin Add LODExampleProcessors --- .../Content/BP_ActorVisualization.uasset | 3 + .../LODExample/Content/DA_LODExample.uasset | 3 + .../LODExample/Content/EQ_LODExample.uasset | 3 + Plugins/LODExample/Content/LODExampleMap.umap | 3 + Plugins/LODExample/LODExample.uplugin | 24 +++++ Plugins/LODExample/Resources/Icon128.png | 3 + .../Source/LODExample/LODExample.Build.cs | 53 +++++++++++ .../Source/LODExample/Private/LODExample.cpp | 20 +++++ .../Private/LODExampleProcessors.cpp | 88 +++++++++++++++++++ .../Source/LODExample/Public/LODExample.h | 15 ++++ .../LODExample/Public/LODExampleProcessors.h | 38 ++++++++ 11 files changed, 253 insertions(+) create mode 100644 Plugins/LODExample/Content/BP_ActorVisualization.uasset create mode 100644 Plugins/LODExample/Content/DA_LODExample.uasset create mode 100644 Plugins/LODExample/Content/EQ_LODExample.uasset create mode 100644 Plugins/LODExample/Content/LODExampleMap.umap create mode 100644 Plugins/LODExample/LODExample.uplugin create mode 100644 Plugins/LODExample/Resources/Icon128.png create mode 100644 Plugins/LODExample/Source/LODExample/LODExample.Build.cs create mode 100644 Plugins/LODExample/Source/LODExample/Private/LODExample.cpp create mode 100644 Plugins/LODExample/Source/LODExample/Private/LODExampleProcessors.cpp create mode 100644 Plugins/LODExample/Source/LODExample/Public/LODExample.h create mode 100644 Plugins/LODExample/Source/LODExample/Public/LODExampleProcessors.h diff --git a/Plugins/LODExample/Content/BP_ActorVisualization.uasset b/Plugins/LODExample/Content/BP_ActorVisualization.uasset new file mode 100644 index 00000000..d9037b81 --- /dev/null +++ b/Plugins/LODExample/Content/BP_ActorVisualization.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7b42ca714085c710d17ede4c086aefcc7382139bc4796bccfa6967b032ac0aac +size 29395 diff --git a/Plugins/LODExample/Content/DA_LODExample.uasset b/Plugins/LODExample/Content/DA_LODExample.uasset new file mode 100644 index 00000000..da46bbe1 --- /dev/null +++ b/Plugins/LODExample/Content/DA_LODExample.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d293e99ea050713aaf1a6cbfca926c83956cfde3e7d5eb3170f3bcdb2119ae86 +size 5691 diff --git a/Plugins/LODExample/Content/EQ_LODExample.uasset b/Plugins/LODExample/Content/EQ_LODExample.uasset new file mode 100644 index 00000000..40cce595 --- /dev/null +++ b/Plugins/LODExample/Content/EQ_LODExample.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f5c849628ada1310c95a2e712cc6dbf6f646ab152341cc6a669513d020111082 +size 5607 diff --git a/Plugins/LODExample/Content/LODExampleMap.umap b/Plugins/LODExample/Content/LODExampleMap.umap new file mode 100644 index 00000000..161c03d9 --- /dev/null +++ b/Plugins/LODExample/Content/LODExampleMap.umap @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3edede52fc478c3ebd9a98c374fb4f9796a81015f3188eee754803944fb894db +size 36338 diff --git a/Plugins/LODExample/LODExample.uplugin b/Plugins/LODExample/LODExample.uplugin new file mode 100644 index 00000000..10f66381 --- /dev/null +++ b/Plugins/LODExample/LODExample.uplugin @@ -0,0 +1,24 @@ +{ + "FileVersion": 3, + "Version": 1, + "VersionName": "1.0", + "FriendlyName": "LODExample", + "Description": "Simple example showcasing LODs for Visualization and Processor logic", + "Category": "Other", + "CreatedBy": "JiRath", + "CreatedByURL": "https://github.com/Ji-Rath", + "DocsURL": "", + "MarketplaceURL": "", + "SupportURL": "", + "CanContainContent": true, + "IsBetaVersion": false, + "IsExperimentalVersion": false, + "Installed": false, + "Modules": [ + { + "Name": "LODExample", + "Type": "Runtime", + "LoadingPhase": "Default" + } + ] +} \ No newline at end of file diff --git a/Plugins/LODExample/Resources/Icon128.png b/Plugins/LODExample/Resources/Icon128.png new file mode 100644 index 00000000..26245f6a --- /dev/null +++ b/Plugins/LODExample/Resources/Icon128.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f7239efaeefbd82de33ebe18518e50de075ea4188a468a9e4991396433d2275f +size 12699 diff --git a/Plugins/LODExample/Source/LODExample/LODExample.Build.cs b/Plugins/LODExample/Source/LODExample/LODExample.Build.cs new file mode 100644 index 00000000..ccdc0d5e --- /dev/null +++ b/Plugins/LODExample/Source/LODExample/LODExample.Build.cs @@ -0,0 +1,53 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; + +public class LODExample : ModuleRules +{ + public LODExample(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; + + PublicIncludePaths.AddRange( + new string[] { + // ... add public include paths required here ... + } + ); + + + PrivateIncludePaths.AddRange( + new string[] { + // ... add other private include paths required here ... + } + ); + + + PublicDependencyModuleNames.AddRange( + new string[] + { + "Core", "MassLOD", "MassEntity", "MassCommon" + // ... add other public dependencies that you statically link with here ... + } + ); + + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "CoreUObject", + "Engine", + "Slate", + "SlateCore", + // ... add private dependencies that you statically link with here ... + } + ); + + + DynamicallyLoadedModuleNames.AddRange( + new string[] + { + // ... add any modules that your module loads dynamically here ... + } + ); + } +} diff --git a/Plugins/LODExample/Source/LODExample/Private/LODExample.cpp b/Plugins/LODExample/Source/LODExample/Private/LODExample.cpp new file mode 100644 index 00000000..185aa97c --- /dev/null +++ b/Plugins/LODExample/Source/LODExample/Private/LODExample.cpp @@ -0,0 +1,20 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "LODExample.h" + +#define LOCTEXT_NAMESPACE "FLODExampleModule" + +void FLODExampleModule::StartupModule() +{ + // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module +} + +void FLODExampleModule::ShutdownModule() +{ + // This function may be called during shutdown to clean up your module. For modules that support dynamic reloading, + // we call this function before unloading the module. +} + +#undef LOCTEXT_NAMESPACE + +IMPLEMENT_MODULE(FLODExampleModule, LODExample) \ No newline at end of file diff --git a/Plugins/LODExample/Source/LODExample/Private/LODExampleProcessors.cpp b/Plugins/LODExample/Source/LODExample/Private/LODExampleProcessors.cpp new file mode 100644 index 00000000..332c4029 --- /dev/null +++ b/Plugins/LODExample/Source/LODExample/Private/LODExampleProcessors.cpp @@ -0,0 +1,88 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "LODExampleProcessors.h" + +#include "MassCommonFragments.h" +#include "MassLODFragments.h" +#include "MassSimulationLOD.h" + +ULODCollectorExampleProcessor::ULODCollectorExampleProcessor() +{ + bAutoRegisterWithProcessingPhases = true; + ExecutionFlags = (int32)EProcessorExecutionFlags::All; + + ExecutionOrder.ExecuteInGroup = UE::Mass::ProcessorGroupNames::LODCollector; + ExecutionOrder.ExecuteAfter.Add(UE::Mass::ProcessorGroupNames::SyncWorldToMass); +} + +ULODExampleProcessor::ULODExampleProcessor() +{ + +} + +void ULODExampleProcessor::Initialize(UObject& Owner) +{ + Super::Initialize(Owner); +} + +void ULODExampleProcessor::ConfigureQueries() +{ + EntityQueryBase.AddRequirement(EMassFragmentAccess::ReadOnly); + + // Dont perform logic if outside view + // @note CulledByFrustumTag appears to only be added when the LOD tag is FMassOffLODTag + EntityQueryBase.AddTagRequirement(EMassFragmentPresence::None); + + //Chunk fragments to tick at specified intervals + EntityQueryBase.AddChunkRequirement(EMassFragmentAccess::ReadOnly, EMassFragmentPresence::Optional); + EntityQueryBase.SetChunkFilter(&FMassSimulationVariableTickChunkFragment::ShouldTickChunkThisFrame); + + // Add base queries to high/med/low entity queries + EntityQuery_High = EntityQueryBase; + EntityQuery_Medium = EntityQueryBase; + EntityQuery_Low = EntityQueryBase; + + EntityQuery_High.AddTagRequirement(EMassFragmentPresence::All); // Query for high LOD + EntityQuery_Medium.AddTagRequirement(EMassFragmentPresence::All); // Query for medium LOD + EntityQuery_Low.AddTagRequirement(EMassFragmentPresence::All); // Query for low + + //@note it seems like when FMassOffLODTag is set, it will fluctuate between Off and High LOD settings. + // I can only guess that it is a bug +} + +void ULODExampleProcessor::Execute(UMassEntitySubsystem& EntitySubsystem, FMassExecutionContext& Context) +{ + // High LOD logic + EntityQuery_High.ForEachEntityChunk(EntitySubsystem, Context, [this](const FMassExecutionContext& Context) + { + TConstArrayView Transforms = Context.GetFragmentView(); + for (int EntityIdx = 0; EntityIdx < Context.GetNumEntities(); EntityIdx++) + { + const FVector& Position = Transforms[EntityIdx].GetTransform().GetLocation(); + DrawDebugPoint(GetWorld(), Position+(FVector::UpVector*500.f), 20.f, FColor::Green, false, 0.25f); + } + }); + + // Med LOD logic + EntityQuery_Medium.ForEachEntityChunk(EntitySubsystem, Context, [this](const FMassExecutionContext& Context) + { + TConstArrayView Transforms = Context.GetFragmentView(); + for (int EntityIdx = 0; EntityIdx < Context.GetNumEntities(); EntityIdx++) + { + const FVector& Position = Transforms[EntityIdx].GetTransform().GetLocation(); + DrawDebugPoint(GetWorld(), Position+(FVector::UpVector*500.f), 20.f, FColor::Yellow, false, 0.25f); + } + }); + + // Low LOD logic + EntityQuery_Low.ForEachEntityChunk(EntitySubsystem, Context, [this](const FMassExecutionContext& Context) + { + TConstArrayView Transforms = Context.GetFragmentView(); + for (int EntityIdx = 0; EntityIdx < Context.GetNumEntities(); EntityIdx++) + { + const FVector& Position = Transforms[EntityIdx].GetTransform().GetLocation(); + DrawDebugPoint(GetWorld(), Position+(FVector::UpVector*500.f), 20.f, FColor::Red, false, 0.25f); + } + }); +} diff --git a/Plugins/LODExample/Source/LODExample/Public/LODExample.h b/Plugins/LODExample/Source/LODExample/Public/LODExample.h new file mode 100644 index 00000000..fc8d2052 --- /dev/null +++ b/Plugins/LODExample/Source/LODExample/Public/LODExample.h @@ -0,0 +1,15 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Modules/ModuleManager.h" + +class FLODExampleModule : public IModuleInterface +{ +public: + + /** IModuleInterface implementation */ + virtual void StartupModule() override; + virtual void ShutdownModule() override; +}; diff --git a/Plugins/LODExample/Source/LODExample/Public/LODExampleProcessors.h b/Plugins/LODExample/Source/LODExample/Public/LODExampleProcessors.h new file mode 100644 index 00000000..fb489c69 --- /dev/null +++ b/Plugins/LODExample/Source/LODExample/Public/LODExampleProcessors.h @@ -0,0 +1,38 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "MassLODCollectorProcessor.h" +#include "LODExampleProcessors.generated.h" + +//MassSample already includes MS + +/** + * Simple class to enable LODCollection for entities + * @see UMassCrowdLODCollectorProcessor + */ +UCLASS() +class LODEXAMPLE_API ULODCollectorExampleProcessor : public UMassLODCollectorProcessor +{ + GENERATED_BODY() + + ULODCollectorExampleProcessor(); +}; + +UCLASS() +class LODEXAMPLE_API ULODExampleProcessor : public UMassProcessor +{ + GENERATED_BODY() + + ULODExampleProcessor(); + + virtual void Initialize(UObject& Owner) override; + virtual void ConfigureQueries() override; + virtual void Execute(UMassEntitySubsystem& EntitySubsystem, FMassExecutionContext& Context) override; + + FMassEntityQuery EntityQueryBase; + FMassEntityQuery EntityQuery_High; + FMassEntityQuery EntityQuery_Medium; + FMassEntityQuery EntityQuery_Low; +}; From aea9c0e3b67a3204b38d40f76ee551f825d93dd0 Mon Sep 17 00:00:00 2001 From: Ji-Rath Date: Sat, 16 Jul 2022 09:03:29 -0500 Subject: [PATCH 43/44] Increase distance on spawned LOD entities --- Plugins/LODExample/Content/DA_LODExample.uasset | 4 ++-- Plugins/LODExample/Content/EQ_LODExample.uasset | 2 +- Plugins/LODExample/Content/LODExampleMap.umap | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Plugins/LODExample/Content/DA_LODExample.uasset b/Plugins/LODExample/Content/DA_LODExample.uasset index da46bbe1..4fe08f80 100644 --- a/Plugins/LODExample/Content/DA_LODExample.uasset +++ b/Plugins/LODExample/Content/DA_LODExample.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d293e99ea050713aaf1a6cbfca926c83956cfde3e7d5eb3170f3bcdb2119ae86 -size 5691 +oid sha256:7386535038fd1cca96f55d90b83d8592eef8432e1d636ec7b1f1d9553d1dc421 +size 5969 diff --git a/Plugins/LODExample/Content/EQ_LODExample.uasset b/Plugins/LODExample/Content/EQ_LODExample.uasset index 40cce595..c6e0630d 100644 --- a/Plugins/LODExample/Content/EQ_LODExample.uasset +++ b/Plugins/LODExample/Content/EQ_LODExample.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f5c849628ada1310c95a2e712cc6dbf6f646ab152341cc6a669513d020111082 +oid sha256:d8a9904e01b561c02034c282aa5127c955efc5d3e508f2a7ad9757e564ab204d size 5607 diff --git a/Plugins/LODExample/Content/LODExampleMap.umap b/Plugins/LODExample/Content/LODExampleMap.umap index 161c03d9..69cc9cb9 100644 --- a/Plugins/LODExample/Content/LODExampleMap.umap +++ b/Plugins/LODExample/Content/LODExampleMap.umap @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3edede52fc478c3ebd9a98c374fb4f9796a81015f3188eee754803944fb894db +oid sha256:0d073333ece012d9899b2c9253ff51becacdb035b061d8069868256dad8b36ec size 36338 From f8ef1b28d5013cbc798262a39d5024da908b1e94 Mon Sep 17 00:00:00 2001 From: Ji-Rath Date: Sat, 16 Jul 2022 09:07:36 -0500 Subject: [PATCH 44/44] Change execution order of example processor Switch ForEachEntityChunk to Parallel --- .../Private/LODExampleProcessors.cpp | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/Plugins/LODExample/Source/LODExample/Private/LODExampleProcessors.cpp b/Plugins/LODExample/Source/LODExample/Private/LODExampleProcessors.cpp index 332c4029..39688bc5 100644 --- a/Plugins/LODExample/Source/LODExample/Private/LODExampleProcessors.cpp +++ b/Plugins/LODExample/Source/LODExample/Private/LODExampleProcessors.cpp @@ -10,15 +10,12 @@ ULODCollectorExampleProcessor::ULODCollectorExampleProcessor() { bAutoRegisterWithProcessingPhases = true; - ExecutionFlags = (int32)EProcessorExecutionFlags::All; - - ExecutionOrder.ExecuteInGroup = UE::Mass::ProcessorGroupNames::LODCollector; - ExecutionOrder.ExecuteAfter.Add(UE::Mass::ProcessorGroupNames::SyncWorldToMass); } ULODExampleProcessor::ULODExampleProcessor() { - + ExecutionOrder.ExecuteInGroup = UE::Mass::ProcessorGroupNames::Tasks; + ExecutionOrder.ExecuteAfter.Add(UE::Mass::ProcessorGroupNames::LOD); } void ULODExampleProcessor::Initialize(UObject& Owner) @@ -31,7 +28,7 @@ void ULODExampleProcessor::ConfigureQueries() EntityQueryBase.AddRequirement(EMassFragmentAccess::ReadOnly); // Dont perform logic if outside view - // @note CulledByFrustumTag appears to only be added when the LOD tag is FMassOffLODTag + // @note CulledByFrustumTag appears to only be added when not affected by LOD Max Count EntityQueryBase.AddTagRequirement(EMassFragmentPresence::None); //Chunk fragments to tick at specified intervals @@ -46,15 +43,12 @@ void ULODExampleProcessor::ConfigureQueries() EntityQuery_High.AddTagRequirement(EMassFragmentPresence::All); // Query for high LOD EntityQuery_Medium.AddTagRequirement(EMassFragmentPresence::All); // Query for medium LOD EntityQuery_Low.AddTagRequirement(EMassFragmentPresence::All); // Query for low - - //@note it seems like when FMassOffLODTag is set, it will fluctuate between Off and High LOD settings. - // I can only guess that it is a bug } void ULODExampleProcessor::Execute(UMassEntitySubsystem& EntitySubsystem, FMassExecutionContext& Context) { // High LOD logic - EntityQuery_High.ForEachEntityChunk(EntitySubsystem, Context, [this](const FMassExecutionContext& Context) + EntityQuery_High.ParallelForEachEntityChunk(EntitySubsystem, Context, [this](const FMassExecutionContext& Context) { TConstArrayView Transforms = Context.GetFragmentView(); for (int EntityIdx = 0; EntityIdx < Context.GetNumEntities(); EntityIdx++) @@ -65,7 +59,7 @@ void ULODExampleProcessor::Execute(UMassEntitySubsystem& EntitySubsystem, FMassE }); // Med LOD logic - EntityQuery_Medium.ForEachEntityChunk(EntitySubsystem, Context, [this](const FMassExecutionContext& Context) + EntityQuery_Medium.ParallelForEachEntityChunk(EntitySubsystem, Context, [this](const FMassExecutionContext& Context) { TConstArrayView Transforms = Context.GetFragmentView(); for (int EntityIdx = 0; EntityIdx < Context.GetNumEntities(); EntityIdx++) @@ -76,7 +70,7 @@ void ULODExampleProcessor::Execute(UMassEntitySubsystem& EntitySubsystem, FMassE }); // Low LOD logic - EntityQuery_Low.ForEachEntityChunk(EntitySubsystem, Context, [this](const FMassExecutionContext& Context) + EntityQuery_Low.ParallelForEachEntityChunk(EntitySubsystem, Context, [this](const FMassExecutionContext& Context) { TConstArrayView Transforms = Context.GetFragmentView(); for (int EntityIdx = 0; EntityIdx < Context.GetNumEntities(); EntityIdx++)