diff --git a/Plugins/RTSFormations/Content/BP_FormationUnit.uasset b/Plugins/RTSFormations/Content/BP_FormationUnit.uasset new file mode 100644 index 00000000..b342a99a --- /dev/null +++ b/Plugins/RTSFormations/Content/BP_FormationUnit.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dd24092b2f239e96f004695cc7346e0c03209cf930df5c4a065c0b3a01d1a98c +size 43629 diff --git a/Plugins/RTSFormations/Content/BP_Pad.uasset b/Plugins/RTSFormations/Content/BP_Pad.uasset new file mode 100644 index 00000000..8dce72e9 --- /dev/null +++ b/Plugins/RTSFormations/Content/BP_Pad.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2166daab8221723086dec734f7c0201987e67a0bc3b39b28dc01b77bad722de5 +size 71052 diff --git a/Plugins/RTSFormations/Content/BP_RTSGameMode.uasset b/Plugins/RTSFormations/Content/BP_RTSGameMode.uasset new file mode 100644 index 00000000..7214f221 --- /dev/null +++ b/Plugins/RTSFormations/Content/BP_RTSGameMode.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:449fe40fdb052f4ff882939de401fa425428019e0670343e4330c23974bd126d +size 20713 diff --git a/Plugins/RTSFormations/Content/BP_RTSPlayer.uasset b/Plugins/RTSFormations/Content/BP_RTSPlayer.uasset new file mode 100644 index 00000000..0cd35cda --- /dev/null +++ b/Plugins/RTSFormations/Content/BP_RTSPlayer.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eb1c999c27a7c7fdaeae6c130a17cd81d7f84c2afc85fb780d86d5f369d9d358 +size 161881 diff --git a/Plugins/RTSFormations/Content/BP_SpawnEntity.uasset b/Plugins/RTSFormations/Content/BP_SpawnEntity.uasset new file mode 100644 index 00000000..6b6023c8 --- /dev/null +++ b/Plugins/RTSFormations/Content/BP_SpawnEntity.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b97bec3386038271aba2a295c6e7237016d705696ebd66fc02e0f4c816e39472 +size 44559 diff --git a/Plugins/RTSFormations/Content/BP_SpawnUnit.uasset b/Plugins/RTSFormations/Content/BP_SpawnUnit.uasset new file mode 100644 index 00000000..098e7611 --- /dev/null +++ b/Plugins/RTSFormations/Content/BP_SpawnUnit.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a615fe94c2b9b2752e8a1f98c9de991d365310bef11a6b14c40f7293b336e461 +size 46708 diff --git a/Plugins/RTSFormations/Content/DA_AgentFormation.uasset b/Plugins/RTSFormations/Content/DA_AgentFormation.uasset new file mode 100644 index 00000000..1026ea67 --- /dev/null +++ b/Plugins/RTSFormations/Content/DA_AgentFormation.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f2b30fac8712cfdb870abe52c4ab4220edc10c236bd7aa0d78fc6d5b14546772 +size 5852 diff --git a/Plugins/RTSFormations/Content/DA_Circle.uasset b/Plugins/RTSFormations/Content/DA_Circle.uasset new file mode 100644 index 00000000..0e2dfd19 --- /dev/null +++ b/Plugins/RTSFormations/Content/DA_Circle.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d3493bd947c27702c45138ad61ec2f70fc803e16880ad1ae256b13c919fbb9c0 +size 1416 diff --git a/Plugins/RTSFormations/Content/DA_Hollow.uasset b/Plugins/RTSFormations/Content/DA_Hollow.uasset new file mode 100644 index 00000000..d16427e5 --- /dev/null +++ b/Plugins/RTSFormations/Content/DA_Hollow.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1b88599e52b11f395e5fba5aac7fa98c5ba964d104aab752b50b96decc8a6bb6 +size 1289 diff --git a/Plugins/RTSFormations/Content/DA_Rectangle.uasset b/Plugins/RTSFormations/Content/DA_Rectangle.uasset new file mode 100644 index 00000000..c97c88fd --- /dev/null +++ b/Plugins/RTSFormations/Content/DA_Rectangle.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2705763c96d6b4dbfc7207ae09be3b6f635e95a956050d3c0de1981dd3cf69ee +size 1238 diff --git a/Plugins/RTSFormations/Content/DA_Spear.uasset b/Plugins/RTSFormations/Content/DA_Spear.uasset new file mode 100644 index 00000000..afd3f59b --- /dev/null +++ b/Plugins/RTSFormations/Content/DA_Spear.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fa7c7baa2af92d662046635ff5aa2292c1a1684cab7ee3e103577030a7c9d30a +size 1369 diff --git a/Plugins/RTSFormations/Content/RTSFormationsExample.umap b/Plugins/RTSFormations/Content/RTSFormationsExample.umap new file mode 100644 index 00000000..8b94ebd1 --- /dev/null +++ b/Plugins/RTSFormations/Content/RTSFormationsExample.umap @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5947043901a3264925b8ed1925b03346f3187e77be415eb46019a2b3ca5dfddc +size 49798 diff --git a/Plugins/RTSFormations/RTSFormations.uplugin b/Plugins/RTSFormations/RTSFormations.uplugin new file mode 100644 index 00000000..36b7eb8b --- /dev/null +++ b/Plugins/RTSFormations/RTSFormations.uplugin @@ -0,0 +1,46 @@ +{ + "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 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/FormationPresets.cpp b/Plugins/RTSFormations/Source/RTSFormations/Private/FormationPresets.cpp new file mode 100644 index 00000000..377aceb6 --- /dev/null +++ b/Plugins/RTSFormations/Source/RTSFormations/Private/FormationPresets.cpp @@ -0,0 +1,5 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "FormationPresets.h" + diff --git a/Plugins/RTSFormations/Source/RTSFormations/Private/GameplayDebuggerCategory_RTSAgents.cpp b/Plugins/RTSFormations/Source/RTSFormations/Private/GameplayDebuggerCategory_RTSAgents.cpp new file mode 100644 index 00000000..32b9e8da --- /dev/null +++ b/Plugins/RTSFormations/Source/RTSFormations/Private/GameplayDebuggerCategory_RTSAgents.cpp @@ -0,0 +1,62 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "GameplayDebuggerCategory_RTSAgents.h" + +#include "DrawDebugHelpers.h" +#include "MassEntitySubsystem.h" +#include "MassNavigationFragments.h" +#include "Engine/World.h" + +#if WITH_GAMEPLAY_DEBUGGER + +#include "GameFramework/PlayerController.h" + +FGameplayDebuggerCategory_RTSAgents::FGameplayDebuggerCategory_RTSAgents() +{ + bShowOnlyWithDebugActor = false; + SetDataPackReplication(&DataPack); +} + +void FGameplayDebuggerCategory_RTSAgents::CollectData(APlayerController* OwnerPC, AActor* DebugActor) +{ + if (OwnerPC) + { + //DataPack.ActorName = OwnerPC->GetPawn()->GetName(); + URTSFormationSubsystem* FormationSubsystem = UWorld::GetSubsystem(OwnerPC->GetWorld()); + UMassEntitySubsystem* EntitySubsystem = UWorld::GetSubsystem(OwnerPC->GetWorld()); + DataPack.NumUnits = FormationSubsystem->Units.Num(); + + DataPack.Positions.Empty(); + DataPack.Positions.Reserve(FormationSubsystem->Units[0].Entities.Num()); + + for(auto& Entity : FormationSubsystem->Units[0].Entities) + { + const FVector& Pos = EntitySubsystem->GetEntityManager().GetFragmentDataChecked(Entity).Center; + DataPack.Positions.Add(Pos); + } + } +} + +void FGameplayDebuggerCategory_RTSAgents::DrawData(APlayerController* OwnerPC, FGameplayDebuggerCanvasContext& CanvasContext) +{ + CanvasContext.Printf(TEXT("{yellow}Units: {white}%d"), DataPack.NumUnits); + CanvasContext.Printf(TEXT("{yellow}Entities in Unit 0: {white}%d"), DataPack.Positions.Num()); + for(FVector& Pos : DataPack.Positions) + { + DrawDebugCanvasWireSphere(CanvasContext.Canvas.Get(), Pos, FColor::Yellow, 20.f, 5); + } +} + +TSharedRef FGameplayDebuggerCategory_RTSAgents::MakeInstance() +{ + return MakeShareable(new FGameplayDebuggerCategory_RTSAgents()); +} + +void FGameplayDebuggerCategory_RTSAgents::FRepData::Serialize(FArchive& Ar) +{ + Ar << Positions; + Ar << NumUnits; +} + +#endif // WITH_GAMEPLAY_DEBUGGER diff --git a/Plugins/RTSFormations/Source/RTSFormations/Private/LaunchEntityProcessor.cpp b/Plugins/RTSFormations/Source/RTSFormations/Private/LaunchEntityProcessor.cpp new file mode 100644 index 00000000..881ee8f7 --- /dev/null +++ b/Plugins/RTSFormations/Source/RTSFormations/Private/LaunchEntityProcessor.cpp @@ -0,0 +1,107 @@ +// Fill out your copyright notice in the Description page of Project Settings. +#include "LaunchEntityProcessor.h" + +#include "DrawDebugHelpers.h" +#include "MassCommonFragments.h" +#include "MassExecutionContext.h" +#include "MassMovementFragments.h" +#include "MassNavigationFragments.h" +#include "TimerManager.h" +#include "Engine/World.h" + +//----------------------------------------------------------------------// +// ULaunchEntityProcessor +//----------------------------------------------------------------------// + +void ULaunchEntityProcessor::ConfigureQueries() +{ + EntityQuery.AddRequirement(EMassFragmentAccess::ReadWrite); + EntityQuery.AddRequirement(EMassFragmentAccess::ReadWrite); + EntityQuery.AddRequirement(EMassFragmentAccess::ReadOnly); + EntityQuery.AddTagRequirement(EMassFragmentPresence::None); +} + +void ULaunchEntityProcessor::Initialize(UObject& Owner) +{ + Super::Initialize(Owner); + SignalSubsystem = UWorld::GetSubsystem(Owner.GetWorld()); + FormationSubsystem = UWorld::GetSubsystem(Owner.GetWorld()); + SubscribeToSignal(*SignalSubsystem, LaunchEntity); +} + +void ULaunchEntityProcessor::SignalEntities(FMassEntityManager& EntityManager, + FMassExecutionContext& Context, FMassSignalNameLookup& EntitySignals) +{ + EntityQuery.ParallelForEachEntityChunk(EntityManager, Context, [this, &EntityManager](FMassExecutionContext& Context) + { + TArrayView LaunchEntityFragments = Context.GetMutableFragmentView(); + TArrayView MoveTargetFragments = Context.GetMutableFragmentView(); + TConstArrayView TransformFragments = Context.GetFragmentView(); + + for (int32 EntityIndex = 0; EntityIndex < Context.GetNumEntities(); ++EntityIndex) + { + const FLaunchEntityFragment& LaunchEntityFragment = LaunchEntityFragments[EntityIndex]; + FMassMoveTargetFragment& MoveTargetFragment = MoveTargetFragments[EntityIndex]; + const FTransformFragment& TransformFragment = TransformFragments[EntityIndex]; + + MoveTargetFragment.CreateNewAction(EMassMovementAction::Move, *GetWorld()); + MoveTargetFragment.Center = TransformFragment.GetTransform().GetLocation()+(TransformFragment.GetTransform().GetTranslation()-LaunchEntityFragment.Origin).GetSafeNormal()*LaunchEntityFragment.Magnitude; + MoveTargetFragment.Center.Z = 0.f; + MoveTargetFragment.Forward = (TransformFragment.GetTransform().GetTranslation()-LaunchEntityFragment.Origin).GetSafeNormal(); + MoveTargetFragment.DistanceToGoal = (TransformFragment.GetTransform().GetTranslation()-LaunchEntityFragment.Origin).Length(); + //DrawDebugPoint(GetWorld(), MoveTargetFragment.Center+(FVector::UpVector*200.f), 40.f, FColor::Green, false, 10.f); + //UE_LOG(LogTemp, Error, TEXT("MoveTarget: "), *MoveTargetFragment.Center.ToString()); + Context.Defer().AddTag(Context.GetEntity(EntityIndex)); + } + }); +} + +//----------------------------------------------------------------------// +// UMoveForceProcessor +//----------------------------------------------------------------------// + +void UMoveForceProcessor::ConfigureQueries() +{ + EntityQuery.AddRequirement(EMassFragmentAccess::ReadOnly); + EntityQuery.AddRequirement(EMassFragmentAccess::ReadWrite); + EntityQuery.AddRequirement(EMassFragmentAccess::ReadWrite); + EntityQuery.AddRequirement(EMassFragmentAccess::ReadWrite); + EntityQuery.AddTagRequirement(EMassFragmentPresence::All); + +} + +void UMoveForceProcessor::Execute(FMassEntityManager& EntityManager, FMassExecutionContext& Context) +{ + EntityQuery.ParallelForEachEntityChunk(EntityManager, Context, [this, &EntityManager](FMassExecutionContext& Context) + { + TConstArrayView LaunchEntityFragments = Context.GetFragmentView(); + TArrayView MoveTargetFragments = Context.GetMutableFragmentView(); + TArrayView ForceFragments = Context.GetMutableFragmentView(); + TArrayView TransformFragments = Context.GetMutableFragmentView(); + + for (int32 EntityIndex = 0; EntityIndex < Context.GetNumEntities(); ++EntityIndex) + { + const FLaunchEntityFragment& LaunchEntityFragment = LaunchEntityFragments[EntityIndex]; + FMassMoveTargetFragment& MoveTargetFragment = MoveTargetFragments[EntityIndex]; + FMassForceFragment& ForceFragment = ForceFragments[EntityIndex]; + FTransformFragment& TransformFragment = TransformFragments[EntityIndex]; + + MoveTargetFragment.DistanceToGoal = (TransformFragment.GetTransform().GetTranslation()-MoveTargetFragment.Center).Length(); + + if(MoveTargetFragment.DistanceToGoal < 50.f) + { + if (MoveTargetFragment.GetCurrentAction() == EMassMovementAction::Move) + { + //Context.Defer().RemoveFragment(Context.GetEntity(EntityIndex)); + //Context.Defer().RemoveTag(Context.GetEntity(EntityIndex)); + Context.Defer().DestroyEntity(Context.GetEntity(EntityIndex)); + MoveTargetFragment.CreateNewAction(EMassMovementAction::Stand, *GetWorld()); + } + } + else + { + DrawDebugSphere(GetWorld(), TransformFragment.GetTransform().GetLocation(), 40.f, 5, FColor::Red); + } + } + }); +} diff --git a/Plugins/RTSFormations/Source/RTSFormations/Private/RTSAgentProcessors.cpp b/Plugins/RTSFormations/Source/RTSFormations/Private/RTSAgentProcessors.cpp new file mode 100644 index 00000000..dad4bd87 --- /dev/null +++ b/Plugins/RTSFormations/Source/RTSFormations/Private/RTSAgentProcessors.cpp @@ -0,0 +1,134 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "RTSAgentProcessors.h" +#include "MassCommonFragments.h" +#include "MassEntitySubsystem.h" +#include "MassExecutionContext.h" +#include "RTSAgentTraits.h" +#include "Engine/World.h" + +//----------------------------------------------------------------------// +// URTSUpdateHashPosition +//----------------------------------------------------------------------// +void URTSUpdateHashPosition::ConfigureQueries() +{ + EntityQuery.AddRequirement(EMassFragmentAccess::ReadOnly); + EntityQuery.AddRequirement(EMassFragmentAccess::ReadWrite); + EntityQuery.AddRequirement(EMassFragmentAccess::ReadOnly); + EntityQuery.AddTagRequirement(EMassFragmentPresence::All); + EntityQuery.AddSubsystemRequirement(EMassFragmentAccess::ReadWrite); + EntityQuery.RegisterWithProcessor(*this); +} + +void URTSUpdateHashPosition::Execute(FMassEntityManager& EntityManager, FMassExecutionContext& Context) +{ + EntityQuery.ForEachEntityChunk(EntityManager, Context, [this](FMassExecutionContext& Context) + { + TConstArrayView TransformFragments = Context.GetFragmentView(); + TArrayView FormationAgents = Context.GetMutableFragmentView(); + TConstArrayView RadiusFragments = Context.GetFragmentView(); + auto& AgentSubsystem = Context.GetMutableSubsystemChecked(); + + for (int32 EntityIndex = 0; EntityIndex < Context.GetNumEntities(); ++EntityIndex) + { + FRTSFormationAgent& RTSAgent = FormationAgents[EntityIndex]; + const FVector& Location = TransformFragments[EntityIndex].GetTransform().GetLocation(); + const float Radius = RadiusFragments[EntityIndex].Radius; + + const FBox NewBounds(Location - FVector(Radius, Radius, 0.f), Location + FVector(Radius, Radius, 0.f)); + RTSAgent.CellLoc = AgentSubsystem.AgentHashGrid.Move(Context.GetEntity(EntityIndex), RTSAgent.CellLoc, NewBounds); + } + }); +} + +void URTSUpdateHashPosition::Initialize(UObject& Owner) +{ + Super::Initialize(Owner); +} + +//----------------------------------------------------------------------// +// URTSInitializeHashPosition +//----------------------------------------------------------------------// +URTSInitializeHashPosition::URTSInitializeHashPosition() +{ + ObservedType = FRTSFormationAgent::StaticStruct(); + Operation = EMassObservedOperation::Add; +} + +void URTSInitializeHashPosition::ConfigureQueries() +{ + EntityQuery.AddRequirement(EMassFragmentAccess::ReadOnly); + EntityQuery.AddRequirement(EMassFragmentAccess::ReadOnly); + EntityQuery.AddRequirement(EMassFragmentAccess::ReadOnly); + EntityQuery.AddTagRequirement(EMassFragmentPresence::None); + EntityQuery.AddSubsystemRequirement(EMassFragmentAccess::ReadWrite); + EntityQuery.RegisterWithProcessor(*this); +} + +void URTSInitializeHashPosition::Execute(FMassEntityManager& EntityManager, FMassExecutionContext& Context) +{ + EntityQuery.ForEachEntityChunk(EntityManager, Context, [this](FMassExecutionContext& Context) + { + TConstArrayView TransformFragments = Context.GetFragmentView(); + TArrayView FormationAgents = Context.GetMutableFragmentView(); + TConstArrayView RadiusFragments = Context.GetFragmentView(); + auto& AgentSubsystem = Context.GetMutableSubsystemChecked(); + + for (int32 EntityIndex = 0; EntityIndex < Context.GetNumEntities(); ++EntityIndex) + { + FRTSFormationAgent& RTSAgent = FormationAgents[EntityIndex]; + const FVector& Location = TransformFragments[EntityIndex].GetTransform().GetLocation(); + const float Radius = RadiusFragments[EntityIndex].Radius; + + const FBox NewBounds(Location - FVector(Radius, Radius, 0.f), Location + FVector(Radius, Radius, 0.f)); + UE_LOG(LogTemp, Log, TEXT("Agents: %d"), AgentSubsystem.AgentHashGrid.GetItems().Num()); + RTSAgent.CellLoc = AgentSubsystem.AgentHashGrid.Add(Context.GetEntity(EntityIndex), NewBounds); + + Context.Defer().AddTag(Context.GetEntity(EntityIndex)); + } + }); +} + +void URTSInitializeHashPosition::Initialize(UObject& Owner) +{ + Super::Initialize(Owner); +} + +//----------------------------------------------------------------------// +// URTSRemoveHashPosition +//----------------------------------------------------------------------// + +URTSRemoveHashPosition::URTSRemoveHashPosition() +{ + ObservedType = FRTSFormationAgent::StaticStruct(); + Operation = EMassObservedOperation::Remove; +} + +void URTSRemoveHashPosition::ConfigureQueries() +{ + EntityQuery.AddRequirement(EMassFragmentAccess::ReadOnly); + EntityQuery.AddSubsystemRequirement(EMassFragmentAccess::ReadWrite); + EntityQuery.RegisterWithProcessor(*this); +} + +void URTSRemoveHashPosition::Execute(FMassEntityManager& EntityManager, FMassExecutionContext& Context) +{ + EntityQuery.ForEachEntityChunk(EntityManager, Context, [this](FMassExecutionContext& Context) + { + TConstArrayView FormationAgents = Context.GetFragmentView(); + auto& AgentSubsystem = Context.GetMutableSubsystemChecked(); + + for (int32 EntityIndex = 0; EntityIndex < Context.GetNumEntities(); ++EntityIndex) + { + const FRTSFormationAgent& RTSAgent = FormationAgents[EntityIndex]; + + AgentSubsystem.AgentHashGrid.Remove(Context.GetEntity(EntityIndex), RTSAgent.CellLoc); + } + }); +} + +void URTSRemoveHashPosition::Initialize(UObject& Owner) +{ + Super::Initialize(Owner); +} \ No newline at end of file diff --git a/Plugins/RTSFormations/Source/RTSFormations/Private/RTSAgentSubsystem.cpp b/Plugins/RTSFormations/Source/RTSFormations/Private/RTSAgentSubsystem.cpp new file mode 100644 index 00000000..78bc8428 --- /dev/null +++ b/Plugins/RTSFormations/Source/RTSFormations/Private/RTSAgentSubsystem.cpp @@ -0,0 +1,38 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "RTSAgentSubsystem.h" + +#include "LaunchEntityProcessor.h" +#include "MassCommandBuffer.h" +#include "MassEntitySubsystem.h" +#include "Engine/World.h" + +void URTSAgentSubsystem::LaunchEntities(const FVector& Location, float Radius) const +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("LaunchEntities")); + + UMassEntitySubsystem* EntitySubsystem = GetWorld()->GetSubsystem(); + + // Query items in radius + TArray Entities; + const FBox Bounds(Location - FVector(Radius, Radius, 0.f), Location + FVector(Radius, Radius, 0.f)); + AgentHashGrid.QuerySmall(Bounds,Entities); + + if (EntitySubsystem) + { + FLaunchEntityFragment LaunchEntityFragment; + LaunchEntityFragment.Origin = Location; + LaunchEntityFragment.Magnitude = 500.f; + + + for(const FMassEntityHandle& Entity : Entities) + { + EntitySubsystem->GetEntityManager().Defer().PushCommand(Entity, + LaunchEntityFragment); + } + // hacky fix since I couldnt get observer working for the life of me + if (Entities.Num()) + GetWorld()->GetSubsystem()->DelaySignalEntities(LaunchEntity, Entities,0.1f); + } +} diff --git a/Plugins/RTSFormations/Source/RTSFormations/Private/RTSAgentTraits.cpp b/Plugins/RTSFormations/Source/RTSFormations/Private/RTSAgentTraits.cpp new file mode 100644 index 00000000..03d1ca2a --- /dev/null +++ b/Plugins/RTSFormations/Source/RTSFormations/Private/RTSAgentTraits.cpp @@ -0,0 +1,23 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "RTSAgentTraits.h" + +#include "MassEntitySubsystem.h" +#include "MassEntityTemplateRegistry.h" +#include "MassNavigationFragments.h" +#include "MassObserverRegistry.h" +#include "Engine/World.h" + +void URTSFormationAgentTrait::BuildTemplate(FMassEntityTemplateBuildContext& BuildContext, const UWorld& World) const +{ + UMassEntitySubsystem* EntitySubsystem = UWorld::GetSubsystem(&World); + FMassEntityManager& EntityManager = UE::Mass::Utils::GetEntityManagerChecked(World); + check(EntitySubsystem); + + BuildContext.AddFragment(); + + uint32 MySharedFragmentHash = UE::StructUtils::GetStructCrc32(FConstStructView::Make(FormationSettings)); + auto MySharedFragment = EntityManager.GetOrCreateSharedFragmentByHash(MySharedFragmentHash); + BuildContext.AddSharedFragment(MySharedFragment); +} diff --git a/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationProcessors.cpp b/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationProcessors.cpp new file mode 100644 index 00000000..b99e0f5f --- /dev/null +++ b/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationProcessors.cpp @@ -0,0 +1,332 @@ +#include "RTSFormationProcessors.h" + +#include "LaunchEntityProcessor.h" +#include "MassCommonFragments.h" +#include "MassMovementFragments.h" +#include "MassNavigationFragments.h" +#include "MassNavigationTypes.h" +#include "MassSignalSubsystem.h" +#include "MassSimulationLOD.h" +#include "RTSAgentTraits.h" +#include "RTSFormationSubsystem.h" +#include "Engine/World.h" + +//----------------------------------------------------------------------// +// URTSFormationInitializer +//----------------------------------------------------------------------// +URTSFormationInitializer::URTSFormationInitializer() +{ + ObservedType = FRTSFormationAgent::StaticStruct(); + Operation = EMassObservedOperation::Add; +} + +void URTSFormationInitializer::ConfigureQueries() +{ + EntityQuery.AddRequirement(EMassFragmentAccess::ReadWrite); + EntityQuery.AddSubsystemRequirement(EMassFragmentAccess::ReadWrite); + EntityQuery.AddSubsystemRequirement(EMassFragmentAccess::ReadWrite); + EntityQuery.RegisterWithProcessor(*this); +} + +void URTSFormationInitializer::Initialize(UObject& Owner) +{ + Super::Initialize(Owner); +} + +void URTSFormationInitializer::Execute(FMassEntityManager& EntityManager, FMassExecutionContext& Context) +{ + // First query is to give all units an appropriate unit index. + EntityQuery.ForEachEntityChunk(EntityManager, Context, [this](FMassExecutionContext& Context) + { + TArrayView RTSFormationAgents = Context.GetMutableFragmentView(); + auto& SignalSubsystem = Context.GetMutableSubsystemChecked(); + auto& FormationSubsystem = Context.GetMutableSubsystemChecked(); + + // 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) + FormationSubsystem.SetUnitPosition(FormationSubsystem.Units[Unit].UnitPosition, Unit); + }); +} + +//----------------------------------------------------------------------// +// URTSFormationDestroyer +//----------------------------------------------------------------------// +URTSFormationDestroyer::URTSFormationDestroyer() +{ + ObservedType = FRTSFormationAgent::StaticStruct(); + Operation = EMassObservedOperation::Remove; +} + +void URTSFormationDestroyer::ConfigureQueries() +{ + EntityQuery.AddRequirement(EMassFragmentAccess::ReadOnly); + EntityQuery.AddSubsystemRequirement(EMassFragmentAccess::ReadWrite); + EntityQuery.AddSubsystemRequirement(EMassFragmentAccess::ReadWrite); + EntityQuery.RegisterWithProcessor(*this); +} + +void URTSFormationDestroyer::Initialize(UObject& Owner) +{ + Super::Initialize(Owner); +} + +void URTSFormationDestroyer::Execute(FMassEntityManager& EntityManager, FMassExecutionContext& Context) +{ + EntityQuery.ForEachEntityChunk(EntityManager, Context, [this, &EntityManager](FMassExecutionContext& Context) + { + auto& FormationSubsystem = Context.GetMutableSubsystemChecked(); + 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 FMassEntityHandle* ItemIndex = FormationSubsystem.Units[FormationAgent.UnitIndex].Entities.Find(Context.GetEntity(EntityIndex)); + if (ItemIndex) + { + // 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 + + FormationSubsystem.Units[FormationAgent.UnitIndex].Entities.Remove(*ItemIndex); + UnitSignals.AddUnique(FormationAgent.UnitIndex); + } + } + } + + // Signal affected units/entities + for(const int& Unit : UnitSignals) + { + if (FormationSubsystem.Units.IsValidIndex(Unit)) + { + //@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(); + FormationSubsystem.UpdateUnitPosition(FormationSubsystem.Units[Unit].UnitPosition, Unit); + } + } + }); +} + +//----------------------------------------------------------------------// +// URTSAgentMovement +//----------------------------------------------------------------------// +void URTSAgentMovement::ConfigureQueries() +{ + EntityQuery.AddRequirement(EMassFragmentAccess::None, EMassFragmentPresence::None); + EntityQuery.AddRequirement(EMassFragmentAccess::ReadOnly); + EntityQuery.AddRequirement(EMassFragmentAccess::ReadWrite); + EntityQuery.AddRequirement(EMassFragmentAccess::ReadOnly); + EntityQuery.AddConstSharedRequirement(EMassFragmentPresence::All); + EntityQuery.AddSharedRequirement(EMassFragmentAccess::ReadOnly); + + EntityQuery.AddChunkRequirement(EMassFragmentAccess::ReadOnly, EMassFragmentPresence::Optional); + EntityQuery.SetChunkFilter(&FMassSimulationVariableTickChunkFragment::ShouldTickChunkThisFrame); + + EntityQuery.AddSubsystemRequirement(EMassFragmentAccess::ReadWrite); + EntityQuery.RegisterWithProcessor(*this); +} + +void URTSAgentMovement::Execute(FMassEntityManager& EntityManager, FMassExecutionContext& Context) +{ + EntityQuery.ForEachEntityChunk(EntityManager, Context, [this](FMassExecutionContext& Context) + { + TArrayView MoveTargetFragments = Context.GetMutableFragmentView(); + TConstArrayView TransformFragments = Context.GetFragmentView(); + TConstArrayView RTSFormationAgents = Context.GetFragmentView(); + + const FRTSFormationSettings& FormationSettings = Context.GetSharedFragment(); + const FMassMovementParameters& MovementParameters = Context.GetConstSharedFragment(); + + auto& FormationSubsystem = Context.GetMutableSubsystemChecked(); + + for (int32 EntityIndex = 0; EntityIndex < Context.GetNumEntities(); ++EntityIndex) + { + FMassMoveTargetFragment& MoveTarget = MoveTargetFragments[EntityIndex]; + const FTransform& Transform = TransformFragments[EntityIndex].GetTransform(); + + const FRTSFormationAgent& RTSFormationAgent = RTSFormationAgents[EntityIndex]; + + const FUnitInfo& Unit = FormationSubsystem.Units[RTSFormationAgent.UnitIndex]; + + if(MoveTarget.GetCurrentAction() == EMassMovementAction::Stand) + MoveTarget.CreateNewAction(EMassMovementAction::Move, *GetWorld()); + + FVector Offset = RTSFormationAgent.Offset; + if (Unit.bBlendAngle) + { + Offset = Offset.RotateAngleAxis(Unit.OldRotation.Yaw, FVector(0.f,0.f,-1.f)); + Offset = Offset.RotateAngleAxis(Unit.InterpRotation.Yaw, FVector(0.f,0.f,1.f)); + } + MoveTarget.Center = Unit.InterpolatedDestination + Offset; + + // 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, begin walking + if (MoveTarget.DistanceToGoal <= MoveTarget.SlackRadius) + { + //MoveTarget.CreateNewAction(EMassMovementAction::Stand, *GetWorld()); + MoveTarget.DesiredSpeed = FMassInt16Real(MovementParameters.GenerateDesiredSpeed(FormationSettings.WalkMovement, Context.GetEntity(EntityIndex).Index)); + } + } + }); +} + +//----------------------------------------------------------------------// +// URTSFormationUpdate +//----------------------------------------------------------------------// +void URTSFormationUpdate::Initialize(UObject& Owner) +{ + Super::Initialize(Owner); + auto SignalSubsystem = UWorld::GetSubsystem(Owner.GetWorld()); + SubscribeToSignal(*SignalSubsystem, FormationUpdated); +} + +void URTSFormationUpdate::ConfigureQueries() +{ + EntityQuery.AddRequirement(EMassFragmentAccess::ReadOnly); + EntityQuery.AddRequirement(EMassFragmentAccess::ReadWrite); + EntityQuery.AddRequirement(EMassFragmentAccess::ReadOnly); + EntityQuery.AddConstSharedRequirement(EMassFragmentPresence::All); + EntityQuery.AddSharedRequirement(EMassFragmentAccess::ReadOnly); + EntityQuery.RegisterWithProcessor(*this); +} + +void URTSFormationUpdate::SignalEntities(FMassEntityManager& EntityManager, FMassExecutionContext& Context, + FMassSignalNameLookup& EntitySignals) +{ + // Query to calculate move target for entities based on unit index + EntityQuery.ForEachEntityChunk(EntityManager, Context, [this](FMassExecutionContext& Context) + { + TArrayView MoveTargetFragments = Context.GetMutableFragmentView(); + TConstArrayView TransformFragments = Context.GetFragmentView(); + + const FRTSFormationSettings& FormationSettings = Context.GetSharedFragment(); + const FMassMovementParameters& MovementParameters = Context.GetConstSharedFragment(); + + for (int32 EntityIndex = 0; EntityIndex < Context.GetNumEntities(); ++EntityIndex) + { + FMassMoveTargetFragment& MoveTarget = MoveTargetFragments[EntityIndex]; + const FTransform& Transform = TransformFragments[EntityIndex].GetTransform(); + + // Create movement action + MoveTarget.CreateNewAction(EMassMovementAction::Move, *GetWorld()); + MoveTarget.Forward = (Transform.GetLocation() - MoveTarget.Center).GetSafeNormal(); + MoveTarget.DistanceToGoal = (Transform.GetLocation() - MoveTarget.Center).Length(); + MoveTarget.SlackRadius = 10.f; + MoveTarget.IntentAtGoal = EMassMovementAction::Stand; + MoveTarget.DesiredSpeed = FMassInt16Real(MovementParameters.GenerateDesiredSpeed(FormationSettings.RunMovement, Context.GetEntity(EntityIndex).Index)); + } + }); +} + +//----------------------------------------------------------------------// +// URTSUpdateEntityIndex +//----------------------------------------------------------------------// +void URTSUpdateEntityIndex::Initialize(UObject& Owner) +{ + Super::Initialize(Owner); + auto SignalSubsystem = UWorld::GetSubsystem(Owner.GetWorld()); + SubscribeToSignal(*SignalSubsystem, UpdateIndex); +} + +void URTSUpdateEntityIndex::ConfigureQueries() +{ + EntityQuery.AddRequirement(EMassFragmentAccess::ReadOnly); + EntityQuery.AddSubsystemRequirement(EMassFragmentAccess::ReadWrite); + EntityQuery.RegisterWithProcessor(*this); +} + +void URTSUpdateEntityIndex::SignalEntities(FMassEntityManager& EntityManager, FMassExecutionContext& Context, + FMassSignalNameLookup& EntitySignals) +{ + // Update entity index so that they go to the closest possible position + // Entities are signaled in order of distance to destination, this allows the NewPosition array to be sorted once + // and cut down on iterations significantly + EntityQuery.ForEachEntityChunk(EntityManager, Context, [this](FMassExecutionContext& Context) + { + auto& FormationSubsystem = Context.GetMutableSubsystemChecked(); + TArrayView FormationAgents = Context.GetMutableFragmentView(); + + for (int32 EntityIndex = 0; EntityIndex < Context.GetNumEntities(); ++EntityIndex) + { + FRTSFormationAgent& FormationAgent = FormationAgents[EntityIndex]; + + // Get first index since it is sorted + TPair ClosestPos; + float ClosestDistance = -1; + int i=0; + + { + SCOPED_NAMED_EVENT(STAT_RTS_FindClosestPoint, FColor::Green); + for(const TPair& NewPos : FormationSubsystem.Units[FormationAgent.UnitIndex].NewPositions) + { + float Dist = FVector::DistSquared2D(NewPos.Value, FormationAgent.Offset); + if (ClosestDistance == -1 || Dist < ClosestDistance) + { + ClosestPos = NewPos; + ClosestDistance = Dist; + + // While its not perfect, this adds a hard cap to how many positions to check + //if (++i > FormationSubsystem->Units[FormationAgent.UnitIndex].FormationLength*2) + // break; + } + } + } + + // Basically scoot up entities if there is space in the front + int& Index = ClosestPos.Key; + + { + SCOPED_NAMED_EVENT(STAT_RTS_RemoveClaimedPosition, FColor::Green); + FormationAgent.EntityIndex = Index; + FormationAgent.Offset = ClosestPos.Value; + FormationSubsystem.Units[FormationAgent.UnitIndex].NewPositions.Remove(Index); + } + + // Call subsystem function to get entities to move + if (FormationSubsystem.Units[FormationAgent.UnitIndex].NewPositions.Num() == 0) + { + FormationSubsystem.MoveEntities(FormationAgent.UnitIndex); + } + } + }); +} + + diff --git a/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationSubsystem.cpp b/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationSubsystem.cpp new file mode 100644 index 00000000..16a3e5fb --- /dev/null +++ b/Plugins/RTSFormations/Source/RTSFormations/Private/RTSFormationSubsystem.cpp @@ -0,0 +1,275 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "RTSFormationSubsystem.h" + +#include "DrawDebugHelpers.h" +#include "MassAgentComponent.h" +#include "MassCommonFragments.h" +#include "MassEntitySubsystem.h" +#include "MassMovementFragments.h" +#include "MassNavigationFragments.h" +#include "MassSignalSubsystem.h" +#include "MassSpawnerSubsystem.h" +#include "RTSAgentTraits.h" +#include "RTSFormationProcessors.h" +#include "Engine/World.h" +#include "Kismet/GameplayStatics.h" +#include "Kismet/KismetMathLibrary.h" + +void URTSFormationSubsystem::DestroyEntity(UMassAgentComponent* Entity) +{ + UMassEntitySubsystem* EntitySubsystem = GetWorld()->GetSubsystem(); + check(EntitySubsystem); + + EntitySubsystem->GetEntityManager().Defer().DestroyEntity(Entity->GetEntityHandle()); +} + +void URTSFormationSubsystem::CalculateNewPositions(FUnitInfo& Unit, TMap& NewPositions) +{ + // Empty NewPositions Map to make room for new calculations + NewPositions.Empty(Unit.Entities.Num()); + + // Calculate entity positions for new destination + // This is the logic that can change formation types + const FVector CenterOffset = FVector((Unit.Entities.Num()/Unit.FormationLength/2) * Unit.BufferDistance, (Unit.FormationLength/2) * Unit.BufferDistance, 0.f); + int PlacedUnits = 0; + int PosIndex = 0; + while (PlacedUnits < Unit.Entities.Num()) + { + float w = PosIndex / Unit.FormationLength; + float l = PosIndex % Unit.FormationLength; + + // Hollow formation logic (2 layers) + if (Unit.bHollow && Unit.Formation == Rectangle) + { + int Switch = Unit.Entities.Num() - Unit.FormationLength*2; + if (w != 0 && w != 1 && !(PlacedUnits >= Switch) + && l != 0 && l != 1 && l != Unit.FormationLength-1 && l != Unit.FormationLength-2) + { + PosIndex++; + continue; + } + } + + // Circle formation + if (Unit.Formation == Circle) + { + int AmountPerRing = Unit.Entities.Num() / Unit.Rings; + float Angle = PosIndex * PI * 2 / AmountPerRing; + float Radius = Unit.FormationLength + (PosIndex / AmountPerRing * 1.5f); + w = FMath::Cos(Angle) * Radius; + l = FMath::Sin(Angle) * Radius; + } + + PlacedUnits++; + FVector Position = FVector(w,l,0.f); + Position *= Unit.BufferDistance; + if (Unit.Formation == Rectangle) + Position -= CenterOffset; + + Position = Position.RotateAngleAxis(Unit.InterpRotation.Yaw+180.f, FVector(0.f,0.f,1.f)); + + NewPositions.Add(PosIndex, Position); + //DrawDebugPoint(GetWorld(), Position+Unit.InterpolatedDestination, 20.f, FColor::Yellow, false, 10.f); + PosIndex++; + } +} + +void URTSFormationSubsystem::UpdateUnitPosition(const FVector& NewPosition, int UnitIndex) +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("UpdateUnitPosition")) + if (!ensure(Units.IsValidIndex(UnitIndex))) { return; } + + // Convenience variables + FUnitInfo& Unit = Units[UnitIndex]; + UMassEntitySubsystem* EntitySubsystem = UWorld::GetSubsystem(GetWorld()); + TMap& NewPositions = Unit.NewPositions; + + // Calculate new positions for entities and output to NewPositions + CalculateNewPositions(Unit, NewPositions); + + // Calculate far corner by finding the new position that is furthest from the unit destination + Unit.FarCorner = NewPosition; + NewPositions.ValueSort([&Unit, &NewPosition](const FVector& A, const FVector& B) + { + return FVector::DistSquared2D(A+Unit.InterpolatedDestination, NewPosition) > FVector::DistSquared2D(B+Unit.InterpolatedDestination, NewPosition); + }); + + if (NewPositions.Num()) + { + TArray NewArray; + NewArray.Reserve(NewPositions.Num()); + NewPositions.GenerateValueArray(NewArray); + Unit.FarCorner = NewArray[0]; + } + //DrawDebugPoint(GetWorld(), Unit.FarCorner+Unit.InterpolatedDestination, 30.f, FColor::Green, false, 5.f); + + // Sort entities by distance to the far corner location + Unit.Entities.Sort([&EntitySubsystem, &Unit](const FMassEntityHandle& A, const FMassEntityHandle& B) + { + //@todo Find if theres a way to move this logic to a processor, most of the cost is coming from retrieving the location + const FVector& LocA = EntitySubsystem->GetEntityManager().GetFragmentDataChecked(A).GetTransform().GetLocation(); + const FVector& LocB = EntitySubsystem->GetEntityManager().GetFragmentDataChecked(B).GetTransform().GetLocation(); + return FVector::DistSquared2D(LocA, Unit.FarCorner+Unit.InterpolatedDestination) > FVector::DistSquared2D(LocB, Unit.FarCorner+Unit.InterpolatedDestination); + }); + + // Sort new positions by distance to the far corner location + NewPositions.ValueSort([&Unit](const FVector& A, const FVector& B) + { + return FVector::DistSquared2D(A, Unit.FarCorner) > FVector::DistSquared2D(B, Unit.FarCorner); + }); + + // Signal entities to update their position index + if (Unit.Entities.Num()) + { + TArray Entities = Unit.Entities.Array(); + GetWorld()->GetSubsystem()->SignalEntities(UpdateIndex, Entities); + } +} + +void URTSFormationSubsystem::MoveEntities(int UnitIndex) +{ + SCOPED_NAMED_EVENT(STAT_RTS_MoveEntities, FColor::Green); + + FUnitInfo& Unit = Units[UnitIndex]; + UMassEntitySubsystem* EntitySubsystem = UWorld::GetSubsystem(GetWorld()); + + // Final sort to ensure that entities are signaled from front to back + Unit.Entities.Sort([&EntitySubsystem, &Unit](const FMassEntityHandle& A, const FMassEntityHandle& B) + { + // Find if theres a way to move this logic to a processor, most of the cost is coming from retrieving the location + const FVector& LocA = EntitySubsystem->GetEntityManager().GetFragmentDataChecked(A).Offset; + const FVector& LocB = EntitySubsystem->GetEntityManager().GetFragmentDataChecked(B).Offset; + return FVector::DistSquared2D(LocA, Unit.FarCorner) > FVector::DistSquared2D(LocB, Unit.FarCorner); + }); + + CurrentIndex = 0; + + // Signal entities to begin moving + TArray Entities = Unit.Entities.Array(); + for(int i=0;iGetSubsystem()->DelaySignalEntity(FormationUpdated, Entities[i], 0.1*(i/Unit.FormationLength)); + } +} + +void URTSFormationSubsystem::SetUnitPosition(const FVector& NewPosition, int UnitIndex) +{ + if (Units.IsEmpty()) { return; } + + DrawDebugDirectionalArrow(GetWorld(), NewPosition, NewPosition+((NewPosition-Units[UnitIndex].InterpolatedDestination).GetSafeNormal()*250.f), 150.f, FColor::Red, false, 5.f, 0, 25.f); + + FUnitInfo& Unit = Units[UnitIndex]; + + FVector OldDir = Unit.ForwardDir; + Unit.ForwardDir = (NewPosition-Units[UnitIndex].InterpolatedDestination).GetSafeNormal(); + + // Calculate turn direction and angle for entities in unit + Unit.TurnDirection = Unit.ForwardDir.Y > 0 ? 1.f : -1.f; + + Unit.OldRotation = Unit.Rotation; + Unit.Rotation = UKismetMathLibrary::MakeRotFromX(Unit.ForwardDir); + + Unit.bBlendAngle = OldDir.Dot(Unit.ForwardDir) > 0.4; + Unit.InterpRotation = Unit.bBlendAngle ? Unit.InterpRotation : Unit.Rotation; + + // Jank solution to stop entities from moving + UMassEntitySubsystem* EntitySubsystem = GetWorld()->GetSubsystem(); + for(const FMassEntityHandle& Entity : Unit.Entities) + { + if (EntitySubsystem->GetEntityManager().GetFragmentDataPtr(Entity)) + { + EntitySubsystem->GetEntityManager().GetFragmentDataPtr(Entity)->CreateNewAction(EMassMovementAction::Stand, *GetWorld()); + EntitySubsystem->GetEntityManager().GetFragmentDataPtr(Entity)->Value = FVector::Zero(); + } + } + + Unit.UnitPosition = NewPosition; + + UpdateUnitPosition(NewPosition, UnitIndex); +} + +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; + auto& EntityTemplate = EntityConfig->GetConfig().GetOrCreateEntityTemplate(*UGameplayStatics::GetPlayerPawn(this, 0)->GetWorld()); + + // 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; + auto CreationContext = EntitySubsystem->GetMutableEntityManager().BatchCreateEntities(EntityTemplate.GetArchetype(), EntityTemplate.GetSharedFragmentValues(), Count, SpawnedEntities); + + // Set the template default values for the entities + TConstArrayView FragmentInstances = EntityTemplate.GetInitialFragmentValues(); + EntitySubsystem->GetEntityManager().BatchSetEntityFragmentsValues(CreationContext->GetEntityCollection(), FragmentInstances); + + // Set unit index for entities + FRTSFormationAgent FormationAgent; + FormationAgent.UnitIndex = UnitIndex; + + TArray Fragments; + Fragments.Add(FInstancedStruct::Make(FormationAgent)); + EntitySubsystem->GetEntityManager().BatchSetEntityFragmentsValues(CreationContext->GetEntityCollection(), 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; +} + +void URTSFormationSubsystem::SetFormationPreset(int UnitIndex, UFormationPresets* FormationAsset) +{ + if (!ensure(FormationAsset && Units.IsValidIndex(UnitIndex))) { return; } + + FUnitInfo& Unit = Units[UnitIndex]; + Unit.FormationLength = FormationAsset->FormationLength; + Unit.BufferDistance = FormationAsset->BufferDistance; + Unit.Formation = FormationAsset->Formation; + Unit.Rings = FormationAsset->Rings; + Unit.bHollow = FormationAsset->bHollow; + + SetUnitPosition(Units[UnitIndex].UnitPosition, UnitIndex); +} + +void URTSFormationSubsystem::Tick(float DeltaTime) +{ + for(int i=0;i Formation = Rectangle; + + // The formation length of the unit + UPROPERTY(EditAnywhere, BlueprintReadWrite) + int FormationLength = 8; + + // Distance between units + UPROPERTY(EditAnywhere, BlueprintReadWrite) + float BufferDistance = 100.f; + + // Amount of rings for the circle formation + UPROPERTY(EditAnywhere, BlueprintReadWrite) + int Rings = 2; + + UPROPERTY(EditAnywhere, BlueprintReadWrite) + bool bHollow = false; +}; diff --git a/Plugins/RTSFormations/Source/RTSFormations/Public/GameplayDebuggerCategory_RTSAgents.h b/Plugins/RTSFormations/Source/RTSFormations/Public/GameplayDebuggerCategory_RTSAgents.h new file mode 100644 index 00000000..f27d2aa6 --- /dev/null +++ b/Plugins/RTSFormations/Source/RTSFormations/Public/GameplayDebuggerCategory_RTSAgents.h @@ -0,0 +1,37 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#if WITH_GAMEPLAY_DEBUGGER + +#include "CoreMinimal.h" +#include "GameplayDebuggerCategory.h" +#include "RTSFormationSubsystem.h" + +class APlayerController; +class AActor; + +class FGameplayDebuggerCategory_RTSAgents : public FGameplayDebuggerCategory +{ +public: + FGameplayDebuggerCategory_RTSAgents(); + virtual void CollectData(APlayerController* OwnerPC, AActor* DebugActor) override; + virtual void DrawData(APlayerController* OwnerPC, FGameplayDebuggerCanvasContext& CanvasContext) override; + + static TSharedRef MakeInstance(); + +protected: + struct FRepData + { + // Put all data you want to display here + TArray Positions; + + int NumUnits = 0; + + void Serialize(FArchive& Ar); + }; + + FRepData DataPack; +}; + +#endif // WITH_GAMEPLAY_DEBUGGER diff --git a/Plugins/RTSFormations/Source/RTSFormations/Public/LaunchEntityProcessor.h b/Plugins/RTSFormations/Source/RTSFormations/Public/LaunchEntityProcessor.h new file mode 100644 index 00000000..14aea34b --- /dev/null +++ b/Plugins/RTSFormations/Source/RTSFormations/Public/LaunchEntityProcessor.h @@ -0,0 +1,55 @@ + +#pragma once + +#include "MassEntityQuery.h" +#include "MassEntityTypes.h" +#include "MassSignalProcessorBase.h" +#include "MassSignalSubsystem.h" +#include "RTSFormationSubsystem.h" +#include "LaunchEntityProcessor.generated.h" + +const FName LaunchEntity = FName(TEXT("LaunchEntity")); + +// 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 ULaunchEntityProcessor : public UMassSignalProcessorBase +{ + GENERATED_BODY() + + virtual void ConfigureQueries() override; + virtual void Initialize(UObject& Owner) override; + virtual void SignalEntities(FMassEntityManager& EntityManager, FMassExecutionContext& Context, FMassSignalNameLookup& EntitySignals) override; + + 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 UMoveForceProcessor : public UMassProcessor +{ + GENERATED_BODY() + + virtual void ConfigureQueries() override; + virtual void Execute(FMassEntityManager& EntityManager, FMassExecutionContext& Context) override; + + FMassEntityQuery EntityQuery; +}; + +USTRUCT() +struct RTSFORMATIONS_API FLaunchEntityFragment : public FMassFragment +{ + GENERATED_BODY() + + UPROPERTY() + FVector Origin; + + UPROPERTY() + float Magnitude = 500.f; +}; + +USTRUCT() +struct RTSFORMATIONS_API FInitLaunchFragment : public FMassTag +{ + GENERATED_BODY() +}; diff --git a/Plugins/RTSFormations/Source/RTSFormations/Public/RTSAgentProcessors.h b/Plugins/RTSFormations/Source/RTSFormations/Public/RTSAgentProcessors.h new file mode 100644 index 00000000..3238dd3e --- /dev/null +++ b/Plugins/RTSFormations/Source/RTSFormations/Public/RTSAgentProcessors.h @@ -0,0 +1,56 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "MassObserverProcessor.h" +#include "MassProcessor.h" +#include "RTSAgentProcessors.generated.h" + +// Defines that an entity is managed by the RTSHashGrid +USTRUCT() +struct FRTSAgentHashTag : public FMassTag +{ + GENERATED_BODY(); +}; + +// Update hash grid position of entities +UCLASS() +class RTSFORMATIONS_API URTSUpdateHashPosition : public UMassProcessor +{ + GENERATED_BODY() + + virtual void ConfigureQueries() override; + virtual void Execute(FMassEntityManager& EntityManager, FMassExecutionContext& Context) override; + virtual void Initialize(UObject& Owner) override; + + FMassEntityQuery EntityQuery; +}; + +// Initialize the hash grid position of RTS agents +UCLASS() +class RTSFORMATIONS_API URTSInitializeHashPosition : public UMassObserverProcessor +{ + GENERATED_BODY() + + URTSInitializeHashPosition(); + virtual void ConfigureQueries() override; + virtual void Execute(FMassEntityManager& EntityManager, FMassExecutionContext& Context) override; + virtual void Initialize(UObject& Owner) override; + + FMassEntityQuery EntityQuery; +}; + +// Remove the entities from the hash grid when destroyed/no longer an RTSAgent +UCLASS() +class RTSFORMATIONS_API URTSRemoveHashPosition : public UMassObserverProcessor +{ + GENERATED_BODY() + + URTSRemoveHashPosition(); + virtual void ConfigureQueries() override; + virtual void Execute(FMassEntityManager& EntityManager, FMassExecutionContext& Context) override; + virtual void Initialize(UObject& Owner) override; + + FMassEntityQuery EntityQuery; +}; diff --git a/Plugins/RTSFormations/Source/RTSFormations/Public/RTSAgentSubsystem.h b/Plugins/RTSFormations/Source/RTSFormations/Public/RTSAgentSubsystem.h new file mode 100644 index 00000000..2d148a11 --- /dev/null +++ b/Plugins/RTSFormations/Source/RTSFormations/Public/RTSAgentSubsystem.h @@ -0,0 +1,36 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "Subsystems/WorldSubsystem.h" +#include "HierarchicalHashGrid2D.h" +#include "MassEntityTypes.h" +#include "RTSAgentSubsystem.generated.h" + + +typedef THierarchicalHashGrid2D<2, 4, FMassEntityHandle> RTSAgentHashGrid2D; + +/** + * + */ +UCLASS() +class RTSFORMATIONS_API URTSAgentSubsystem : public UWorldSubsystem +{ + GENERATED_BODY() + +public: + RTSAgentHashGrid2D AgentHashGrid = RTSAgentHashGrid2D(); + + UFUNCTION(BlueprintCallable, BlueprintPure = false) + void LaunchEntities(const FVector& Location, float Radius) const; +}; + +template<> +struct TMassExternalSubsystemTraits +{ + enum + { + GameThreadOnly = false + }; +}; diff --git a/Plugins/RTSFormations/Source/RTSFormations/Public/RTSAgentTraits.h b/Plugins/RTSFormations/Source/RTSFormations/Public/RTSAgentTraits.h new file mode 100644 index 00000000..1a9175a7 --- /dev/null +++ b/Plugins/RTSFormations/Source/RTSFormations/Public/RTSAgentTraits.h @@ -0,0 +1,53 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "MassEntityTraitBase.h" +#include "MassEntityTypes.h" +#include "MassMovementTypes.h" +#include "RTSAgentSubsystem.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; + + FVector Offset; + + RTSAgentHashGrid2D::FCellLocation CellLoc; +}; + +USTRUCT() +struct RTSFORMATIONS_API FRTSFormationSettings : public FMassSharedFragment +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, Category = "Formation") + FMassMovementStyleRef WalkMovement; + + UPROPERTY(EditAnywhere, Category = "Formation") + FMassMovementStyleRef RunMovement; +}; + +// Provides entity with FRTSFormationAgent fragment to enable formations +UCLASS() +class RTSFORMATIONS_API URTSFormationAgentTrait : public UMassEntityTraitBase +{ + GENERATED_BODY() + + virtual void BuildTemplate(FMassEntityTemplateBuildContext& BuildContext, const UWorld& World) const override; + + UPROPERTY(EditAnywhere) + FRTSFormationSettings FormationSettings; +}; diff --git a/Plugins/RTSFormations/Source/RTSFormations/Public/RTSFormationProcessors.h b/Plugins/RTSFormations/Source/RTSFormations/Public/RTSFormationProcessors.h new file mode 100644 index 00000000..e941d31a --- /dev/null +++ b/Plugins/RTSFormations/Source/RTSFormations/Public/RTSFormationProcessors.h @@ -0,0 +1,76 @@ +#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")); +const FName UpdateIndex = FName(TEXT("UpdateIndex")); + +// 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(FMassEntityManager& EntityManager, FMassExecutionContext& Context) override; + + FMassEntityQuery EntityQuery; +}; + +// 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(FMassEntityManager& EntityManager, FMassExecutionContext& Context) override; + + FMassEntityQuery EntityQuery; +}; + + +// 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(FMassEntityManager& EntityManager, 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(FMassEntityManager& EntityManager, FMassExecutionContext& Context, FMassSignalNameLookup& EntitySignals) override; +}; + +// Signal Processor that updates the agents index in the unit based on values in the FormationSubsystem Unit Array +UCLASS() +class RTSFORMATIONS_API URTSUpdateEntityIndex : public UMassSignalProcessorBase +{ + GENERATED_BODY() + + virtual void Initialize(UObject& Owner) override; + virtual void ConfigureQueries() override; + virtual void SignalEntities(FMassEntityManager& EntityManager, FMassExecutionContext& Context, FMassSignalNameLookup& EntitySignals) override; +}; diff --git a/Plugins/RTSFormations/Source/RTSFormations/Public/RTSFormationSubsystem.h b/Plugins/RTSFormations/Source/RTSFormations/Public/RTSFormationSubsystem.h new file mode 100644 index 00000000..3e86a9b2 --- /dev/null +++ b/Plugins/RTSFormations/Source/RTSFormations/Public/RTSFormationSubsystem.h @@ -0,0 +1,140 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "FormationPresets.h" +#include "MassSubsystemBase.h" +#include "Common/Misc/MSBPFunctionLibrary.h" +#include "RTSFormationSubsystem.generated.h" + +USTRUCT(BlueprintType) +struct FUnitInfo +{ + GENERATED_BODY() + +public: + // Entities in the unit + UPROPERTY() + TSet Entities; + + UPROPERTY() + TMap NewPositions; + + // The current unit position + UPROPERTY() + 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(BlueprintReadWrite) + int FormationLength = 8; + + UPROPERTY(BlueprintReadWrite) + float BufferDistance = 100.f; + + // The type of formation - WIP + UPROPERTY(BlueprintReadWrite) + TEnumAsByte Formation = Rectangle; + + // Amount of rings for the circle formation + UPROPERTY(EditAnywhere, BlueprintReadWrite) + int Rings = 2; + + UPROPERTY(EditAnywhere, BlueprintReadWrite) + bool bHollow = false; + + UPROPERTY() + FVector FarCorner; + + // Interpolated movement + UPROPERTY() + FVector InterpolatedDestination; + + UPROPERTY() + FRotator Rotation; + + UPROPERTY() + FRotator InterpRotation; + + UPROPERTY() + FRotator OldRotation; + + UPROPERTY() + float InterpolationSpeed = 5.f; + + UPROPERTY() + bool bBlendAngle = false; + + UPROPERTY() + FVector ForwardDir; + + 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 UMassTickableSubsystemBase +{ + GENERATED_BODY() + +public: + // Stores the num of units in the formation + UPROPERTY(BlueprintReadWrite) + TArray Units; + + // Destroy a specified entity + UFUNCTION(BlueprintCallable) + void DestroyEntity(UMassAgentComponent* Entity); + + // Set the position of a unit + UFUNCTION() + void UpdateUnitPosition(const FVector& NewPosition, int UnitIndex = 0); + + UFUNCTION(BlueprintCallable) + void SetUnitPosition(const FVector& NewPosition, int UnitIndex = 0); + + UFUNCTION() + void MoveEntities(int UnitIndex); + + // 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); + + UFUNCTION(BlueprintCallable) + void SetFormationPreset(int UnitIndex, UFormationPresets* FormationAsset); + + void CalculateNewPositions(FUnitInfo& Unit, TMap& NewPositions); + + virtual void Tick(float DeltaTime) override; + virtual bool IsTickable() const override; + virtual TStatId GetStatId() const override; + + virtual void OnWorldBeginPlay(UWorld& InWorld) override; + + UPROPERTY() + FTimerHandle MoveHandle; + + int CurrentIndex = 0; +}; + +template<> +struct TMassExternalSubsystemTraits +{ + enum + { + GameThreadOnly = false + }; +}; \ No newline at end of file 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..9e0a96d6 --- /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", "StructUtils", "MassSignals", "MassMovement", "MassCommunitySample" + // ... add other public dependencies that you statically link with here ... + } + ); + + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "CoreUObject", + "Engine", + "Slate", + "SlateCore", "MassNavigation", "MassActors", "MassLOD", "GameplayDebugger" + // ... add private dependencies that you statically link with here ... + } + ); + + + DynamicallyLoadedModuleNames.AddRange( + new string[] + { + // ... add any modules that your module loads dynamically here ... + } + ); + } +}