Files
UnrealEngine/Engine/Source/Developer/MassEntityTestSuite/Private/MassEntityTestFarmPlot.cpp
2025-05-18 13:04:45 +08:00

315 lines
13 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "MassEntityTestFarmPlot.h"
#include "Components/HierarchicalInstancedStaticMeshComponent.h"
#include "Engine/CollisionProfile.h"
#include "MassExecutor.h"
#include "MassExecutionContext.h"
#include "MassProcessingContext.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(MassEntityTestFarmPlot)
//@TODO: Can add a ReadyToHarvest tag Fragment on when things are ready to harvest, to stop them ticking and signal that we need to create an icon
//-----------------------------------------------------------------------------
// UFarmProcessorBase
//-----------------------------------------------------------------------------
UFarmProcessorBase::UFarmProcessorBase()
: EntityQuery(*this)
{
// not auto-registering to manually control execution
bAutoRegisterWithProcessingPhases = false;
}
//-----------------------------------------------------------------------------
// UFarmWaterProcessor
//-----------------------------------------------------------------------------
void UFarmWaterProcessor::ConfigureQueries(const TSharedRef<FMassEntityManager>& EntityManager)
{
EntityQuery.AddRequirement<FFarmWaterFragment>(EMassFragmentAccess::ReadWrite);
}
void UFarmWaterProcessor::Execute(FMassEntityManager& EntityManager, FMassExecutionContext& Context)
{
QUICK_SCOPE_CYCLE_COUNTER(UFarmWaterProcessor_Run);
EntityQuery.ForEachEntityChunk(Context, [this](FMassExecutionContext& Context) {
const float DeltaTimeSeconds = Context.GetDeltaTimeSeconds();
TArrayView<FFarmWaterFragment> WaterList = Context.GetMutableFragmentView<FFarmWaterFragment>();
for (FFarmWaterFragment& WaterFragment : WaterList)
{
WaterFragment.CurrentWater = FMath::Clamp(WaterFragment.CurrentWater + WaterFragment.DeltaWaterPerSecond * DeltaTimeSeconds, 0.0f, 1.0f);
}
});
}
//-----------------------------------------------------------------------------
// UFarmHarvestTimerSystem_Flowers
//-----------------------------------------------------------------------------
void UFarmHarvestTimerSystem_Flowers::ConfigureQueries(const TSharedRef<FMassEntityManager>& EntityManager)
{
EntityQuery.AddRequirement<FHarvestTimerFragment>(EMassFragmentAccess::ReadWrite);
EntityQuery.AddRequirement<FFarmWaterFragment>(EMassFragmentAccess::ReadOnly);
EntityQuery.AddRequirement<FFarmFlowerFragment>(EMassFragmentAccess::ReadWrite);
}
void UFarmHarvestTimerSystem_Flowers::Execute(FMassEntityManager& EntityManager, FMassExecutionContext& Context)
{
QUICK_SCOPE_CYCLE_COUNTER(UFarmHarvestTimerSystem_Flowers_Run);
EntityQuery.ForEachEntityChunk(Context, [this](FMassExecutionContext& Context)
{
const float WellWateredThreshold = 0.25f;
TArrayView<FHarvestTimerFragment> TimerList = Context.GetMutableFragmentView<FHarvestTimerFragment>();
TConstArrayView<FFarmWaterFragment> WaterList = Context.GetFragmentView<FFarmWaterFragment>();
TArrayView<FFarmFlowerFragment> FlowerList = Context.GetMutableFragmentView<FFarmFlowerFragment>();
for (FMassExecutionContext::FEntityIterator EntityIt = Context.CreateEntityIterator(); EntityIt; ++EntityIt)
{
if (TimerList[EntityIt].NumSecondsLeft > 0)
{
--TimerList[EntityIt].NumSecondsLeft;
if (WaterList[EntityIt].CurrentWater > WellWateredThreshold)
{
++FlowerList[EntityIt].NumBonusTicks;
}
}
}
});
}
//-----------------------------------------------------------------------------
// UFarmHarvestTimerSystem_Crops
//-----------------------------------------------------------------------------
void UFarmHarvestTimerSystem_Crops::ConfigureQueries(const TSharedRef<FMassEntityManager>& EntityManager)
{
EntityQuery.AddRequirement<FHarvestTimerFragment>(EMassFragmentAccess::ReadWrite);
EntityQuery.AddRequirement<FFarmWaterFragment>(EMassFragmentAccess::ReadOnly);
EntityQuery.AddRequirement<FFarmCropFragment>(EMassFragmentAccess::None, EMassFragmentPresence::All);
}
void UFarmHarvestTimerSystem_Crops::Execute(FMassEntityManager & EntityManager, FMassExecutionContext & Context)
{
QUICK_SCOPE_CYCLE_COUNTER(UFarmHarvestTimerSystem_Crops_Run);
EntityQuery.ForEachEntityChunk(Context, [this](FMassExecutionContext& Context)
{
const float WellWateredThreshold = 0.25f;
TArrayView<FHarvestTimerFragment> TimerList = Context.GetMutableFragmentView<FHarvestTimerFragment>();
TConstArrayView<FFarmWaterFragment> WaterList = Context.GetFragmentView<FFarmWaterFragment>();
for (FMassExecutionContext::FEntityIterator EntityIt = Context.CreateEntityIterator(); EntityIt; ++EntityIt)
{
const uint32 TimeToSubtract = (WaterList[EntityIt].CurrentWater > WellWateredThreshold) ? 2 : 1;
TimerList[EntityIt].NumSecondsLeft = (TimerList[EntityIt].NumSecondsLeft >= TimeToSubtract) ? (TimerList[EntityIt].NumSecondsLeft - TimeToSubtract) : 0;
}
});
}
//-----------------------------------------------------------------------------
// UFarmHarvestTimerExpired
//-----------------------------------------------------------------------------
void UFarmHarvestTimerExpired::ConfigureQueries(const TSharedRef<FMassEntityManager>& EntityManager)
{
EntityQuery.AddRequirement<FHarvestTimerFragment>(EMassFragmentAccess::ReadOnly);
EntityQuery.AddTagRequirement<FFarmJustBecameReadyToHarvestTag>(EMassFragmentPresence::None);
EntityQuery.AddTagRequirement<FFarmReadyToHarvestTag>(EMassFragmentPresence::None);
}
void UFarmHarvestTimerExpired::Execute(FMassEntityManager& EntityManager, FMassExecutionContext& Context)
{
QUICK_SCOPE_CYCLE_COUNTER(UFarmHarvestTimerExpired_Run);
EntityQuery.ForEachEntityChunk(Context, [this](FMassExecutionContext& Context)
{
TConstArrayView<FHarvestTimerFragment> TimerList = Context.GetFragmentView<FHarvestTimerFragment>();
for (FMassExecutionContext::FEntityIterator EntityIt = Context.CreateEntityIterator(); EntityIt; ++EntityIt)
{
if (TimerList[EntityIt].NumSecondsLeft == 0)
{
Context.Defer().AddTag<FFarmJustBecameReadyToHarvestTag>(Context.GetEntity(EntityIt));
}
}
});
}
//-----------------------------------------------------------------------------
// UFarmHarvestTimerSetIcon
//-----------------------------------------------------------------------------
void UFarmHarvestTimerSetIcon::ConfigureQueries(const TSharedRef<FMassEntityManager>& EntityManager)
{
EntityQuery.AddRequirement<FFarmGridCellData>(EMassFragmentAccess::ReadOnly);
EntityQuery.AddRequirement<FFarmVisualFragment>(EMassFragmentAccess::ReadWrite);
EntityQuery.AddTagRequirement<FFarmJustBecameReadyToHarvestTag>(EMassFragmentPresence::All);
}
void UFarmHarvestTimerSetIcon::Execute(FMassEntityManager& EntityManager, FMassExecutionContext& Context)
{
if (HarvestIconISMC == nullptr)
{
return;
}
QUICK_SCOPE_CYCLE_COUNTER(SET_ICON_SET_ICON_SET_ICON);
EntityQuery.ForEachEntityChunk(Context, [this](FMassExecutionContext& Context)
{
TConstArrayView<FFarmGridCellData> GridCoordList = Context.GetFragmentView<FFarmGridCellData>();
TArrayView<FFarmVisualFragment> VisualList = Context.GetMutableFragmentView<FFarmVisualFragment>();
for (FMassExecutionContext::FEntityIterator EntityIt = Context.CreateEntityIterator(); EntityIt; ++EntityIt)
{
const FFarmGridCellData& GridCells = GridCoordList[EntityIt];
const FVector IconPosition(GridCells.CellX*GridCellWidth, GridCells.CellY*GridCellHeight, HarvestIconHeight);
const FTransform IconTransform(FQuat::Identity, IconPosition, FVector(HarvestIconScale, HarvestIconScale, HarvestIconScale));
VisualList[EntityIt].HarvestIconIndex = HarvestIconISMC->AddInstance(IconTransform);
FMassEntityHandle ThisEntity = Context.GetEntity(EntityIt);
Context.Defer().RemoveTag<FFarmJustBecameReadyToHarvestTag>(ThisEntity);
Context.Defer().AddTag<FFarmReadyToHarvestTag>(ThisEntity);
}
});
}
//-----------------------------------------------------------------------------
// ALWFragmentTestFarmPlot
//-----------------------------------------------------------------------------
AMassEntityTestFarmPlot::AMassEntityTestFarmPlot()
: SharedEntityManager(MakeShareable(new FMassEntityManager(this)))
{
PrimaryActorTick.bCanEverTick = true;
PrimaryActorTick.bStartWithTickEnabled = true;
USceneComponent* SceneComponent = CreateDefaultSubobject<USceneComponent>(TEXT("SceneComp"));
RootComponent = SceneComponent;
HarvestIconISMC = CreateDefaultSubobject<UHierarchicalInstancedStaticMeshComponent>(TEXT("HarvestIconISMC"));
HarvestIconISMC->SetupAttachment(SceneComponent);
HarvestIconISMC->SetCollisionProfileName(UCollisionProfile::NoCollision_ProfileName);
}
void AMassEntityTestFarmPlot::AddItemToGrid(FMassEntityManager& InEntityManager, uint16 X, uint16 Y, FMassArchetypeHandle Archetype, uint16 VisualIndex)
{
FMassEntityHandle NewItem = InEntityManager.CreateEntity(Archetype);
PlantedSquares[X + Y * GridWidth] = NewItem;
InEntityManager.GetFragmentDataChecked<FFarmWaterFragment>(NewItem).DeltaWaterPerSecond = FMath::FRandRange(-0.01f, -0.001f);
InEntityManager.GetFragmentDataChecked<FHarvestTimerFragment>(NewItem).NumSecondsLeft = 5 + (FMath::Rand() % 100);
FFarmGridCellData GridCoords;
GridCoords.CellX = X;
GridCoords.CellY = Y;
InEntityManager.GetFragmentDataChecked<FFarmGridCellData>(NewItem) = GridCoords;
const FVector MeshPosition(X*GridCellWidth, Y*GridCellHeight, 0.0f);
const FRotator MeshRotation(0.0f, FMath::FRand()*360.0f, 0.0f);
const FVector MeshScale(1.0f, 1.0f, 1.0f); //@TODO: plumb in scale param?
const FTransform MeshTransform(MeshRotation, MeshPosition, MeshScale);
FFarmVisualFragment& VisualComp = InEntityManager.GetFragmentDataChecked<FFarmVisualFragment>(NewItem);
VisualComp.VisualType = VisualIndex;
VisualComp.InstanceIndex = VisualDataISMCs[VisualComp.VisualType]->AddInstance(MeshTransform);
}
void AMassEntityTestFarmPlot::BeginPlay()
{
Super::BeginPlay();
if (TestDataCropIndicies.Num() == 0
|| TestDataFlowerIndicies.Num() == 0
|| VisualDataTable.Num() == 0)
{
UE_LOG(LogMass, Error, TEXT("%s is misconfigured. Make sure TestDataCropIndicies, TestDataFlowerIndicies and VisualDataTable are not empty"), *GetName());
return;
}
FMassEntityManager& EntityManager = SharedEntityManager.Get();
EntityManager.Initialize();
FMassArchetypeHandle CropArchetype = EntityManager.CreateArchetype(TArray<const UScriptStruct*>{ FFarmWaterFragment::StaticStruct(), FFarmCropFragment::StaticStruct(), FHarvestTimerFragment::StaticStruct(), FFarmVisualFragment::StaticStruct(), FFarmGridCellData::StaticStruct() });
FMassArchetypeHandle FlowerArchetype = EntityManager.CreateArchetype(TArray<const UScriptStruct*>{ FFarmWaterFragment::StaticStruct(), FFarmFlowerFragment::StaticStruct(), FHarvestTimerFragment::StaticStruct(), FFarmVisualFragment::StaticStruct(), FFarmGridCellData::StaticStruct() });
PerFrameSystems.Add(NewObject<UFarmWaterProcessor>(this));
PerSecondSystems.Add(NewObject<UFarmHarvestTimerSystem_Flowers>(this));
PerSecondSystems.Add(NewObject<UFarmHarvestTimerSystem_Crops>(this));
PerSecondSystems.Add(NewObject<UFarmHarvestTimerExpired>(this));
UFarmHarvestTimerSetIcon* IconSetter = NewObject<UFarmHarvestTimerSetIcon>(this);
IconSetter->HarvestIconISMC = HarvestIconISMC;
IconSetter->GridCellWidth = GridCellWidth;
IconSetter->GridCellHeight = GridCellHeight;
IconSetter->HarvestIconHeight = 200.0f;
IconSetter->HarvestIconScale = HarvestIconScale;
PerSecondSystems.Add(IconSetter);
HarvestIconISMC->SetCullDistances(IconNearCullDistance, IconFarCullDistance);
// Create ISMCs for each mesh type
for (const FFarmVisualDataRow& VisualData : VisualDataTable)
{
UHierarchicalInstancedStaticMeshComponent* HISMC = NewObject<UHierarchicalInstancedStaticMeshComponent>(this);
HISMC->SetStaticMesh(VisualData.Mesh);
if (VisualData.MaterialOverride != nullptr)
{
HISMC->SetMaterial(0, VisualData.MaterialOverride);
}
HISMC->SetCullDistances(VisualNearCullDistance, VisualFarCullDistance);
HISMC->SetupAttachment(RootComponent);
HISMC->RegisterComponent();
VisualDataISMCs.Add(HISMC);
}
// Plant us a grid
const int32 NumGridCells = GridWidth * GridHeight;
PlantedSquares.AddDefaulted(NumGridCells);
for (uint16 Y = 0; Y < GridHeight; ++Y)
{
for (uint16 X = 0; X < GridWidth; ++X)
{
const bool bSpawnCrop = FMath::FRand() < 0.5f;
const uint16 VisualIndex = bSpawnCrop ? TestDataCropIndicies[FMath::RandRange(0, TestDataCropIndicies.Num() - 1)] : TestDataFlowerIndicies[FMath::RandRange(0, TestDataFlowerIndicies.Num() - 1)];
AddItemToGrid(EntityManager, X, Y, bSpawnCrop ? CropArchetype : FlowerArchetype, VisualIndex);
}
}
}
void AMassEntityTestFarmPlot::TickActor(float DeltaTime, enum ELevelTick TickType, FActorTickFunction& ThisTickFunction)
{
Super::TickActor(DeltaTime, TickType, ThisTickFunction);
if (PlantedSquares.Num() == 0)
{
// not configured properly, ignore.
return;
}
FMassEntityManager& EntityManager = SharedEntityManager.Get();
// Run every frame systems
{
FMassProcessingContext Context(EntityManager, DeltaTime);
UE::Mass::Executor::RunProcessorsView(PerFrameSystems, Context);
}
// Run per-second systems when it's time
NextSecondTimer -= DeltaTime;
while (NextSecondTimer < 0.0f)
{
FMassProcessingContext Context(EntityManager, 1.f);
UE::Mass::Executor::RunProcessorsView(PerSecondSystems, Context);
NextSecondTimer += 1.0f;
}
}