Files
UnrealEngine/Engine/Plugins/Runtime/MassGameplay/Source/MassSpawner/Private/MassEntityZoneGraphSpawnPointsGenerator.cpp
2025-05-18 13:04:45 +08:00

154 lines
5.3 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "MassEntityZoneGraphSpawnPointsGenerator.h"
#include "ZoneGraphSubsystem.h"
#include "ZoneGraphQuery.h"
#include "MassSpawnerTypes.h"
#include "Engine/World.h"
#include "VisualLogger/VisualLogger.h"
#include "MassGameplaySettings.h"
#include "MassSpawnLocationProcessor.h"
#include "Engine/World.h"
#include "MassCommonUtils.h"
void UMassEntityZoneGraphSpawnPointsGenerator::Generate(UObject& QueryOwner, TConstArrayView<FMassSpawnedEntityType> EntityTypes, int32 Count, FFinishedGeneratingSpawnDataSignature& FinishedGeneratingSpawnPointsDelegate) const
{
if (Count <= 0)
{
FinishedGeneratingSpawnPointsDelegate.Execute(TArray<FMassEntitySpawnDataGeneratorResult>());
return;
}
const UZoneGraphSubsystem* ZoneGraph = UWorld::GetSubsystem<UZoneGraphSubsystem>(QueryOwner.GetWorld());
if (ZoneGraph == nullptr)
{
UE_VLOG_UELOG(&QueryOwner, LogMassSpawner, Error, TEXT("No zone graph subsystem found in world"));
return;
}
TArray<FVector> Locations;
const FRandomStream RandomStream(UE::Mass::Utils::OverrideRandomSeedForTesting(GetRandomSelectionSeed()));
const TConstArrayView<FRegisteredZoneGraphData> RegisteredZoneGraphs = ZoneGraph->GetRegisteredZoneGraphData();
if (RegisteredZoneGraphs.IsEmpty())
{
UE_VLOG_UELOG(&QueryOwner, LogMassSpawner, Error, TEXT("No zone graphs found"));
return;
}
for (const FRegisteredZoneGraphData& Registered : RegisteredZoneGraphs)
{
if (Registered.bInUse && Registered.ZoneGraphData)
{
GeneratePointsForZoneGraphData(*Registered.ZoneGraphData, Locations, RandomStream);
}
}
if (Locations.IsEmpty())
{
UE_VLOG_UELOG(&QueryOwner, LogMassSpawner, Error, TEXT("No locations found on zone graphs"));
return;
}
// Randomize them
for (int32 I = 0; I < Locations.Num(); ++I)
{
const int32 J = RandomStream.RandHelper(Locations.Num());
Locations.Swap(I, J);
}
// If we generated too many, shrink it.
if (Locations.Num() > Count)
{
Locations.SetNum(Count);
}
// Build array of entity types to spawn.
TArray<FMassEntitySpawnDataGeneratorResult> Results;
BuildResultsFromEntityTypes(Count, EntityTypes, Results);
const int32 LocationCount = Locations.Num();
int32 LocationIndex = 0;
// Distribute points amongst the entities to spawn.
for (FMassEntitySpawnDataGeneratorResult& Result : Results)
{
// @todo: Make separate processors and pass the ZoneGraph locations directly.
Result.SpawnDataProcessor = UMassSpawnLocationProcessor::StaticClass();
Result.SpawnData.InitializeAs<FMassTransformsSpawnData>();
FMassTransformsSpawnData& Transforms = Result.SpawnData.GetMutable<FMassTransformsSpawnData>();
Transforms.Transforms.Reserve(Result.NumEntities);
for (int i = 0; i < Result.NumEntities; i++)
{
FTransform& Transform = Transforms.Transforms.AddDefaulted_GetRef();
Transform.SetLocation(Locations[LocationIndex % LocationCount]);
LocationIndex++;
}
}
#if ENABLE_VISUAL_LOG
UE_VLOG(this, LogMassSpawner, Log, TEXT("Spawning at %d locations"), LocationIndex);
if (GetDefault<UMassGameplaySettings>()->bLogSpawnLocations)
{
FVisualLogger::Get().ExecuteOnLastEntryForObject(this, [LocationIndex, &Results](FVisualLogEntry& LogEntry)
{
FVisualLogShapeElement Element(TEXT(""), FColor::Orange, /*Thickness*/20, LogMassSpawner.GetCategoryName());
Element.Points.Reserve(LocationIndex);
for (const FMassEntitySpawnDataGeneratorResult& Result : Results)
{
const FMassTransformsSpawnData& Transforms = Result.SpawnData.Get<FMassTransformsSpawnData>();
for (int i = 0; i < Result.NumEntities; i++)
{
Element.Points.Add(Transforms.Transforms[i].GetLocation());
}
}
Element.Type = EVisualLoggerShapeElement::SinglePoint;
Element.Verbosity = ELogVerbosity::Display;
LogEntry.AddElement(Element);
});
}
#endif // ENABLE_VISUAL_LOG
FinishedGeneratingSpawnPointsDelegate.Execute(Results);
}
void UMassEntityZoneGraphSpawnPointsGenerator::GeneratePointsForZoneGraphData(const ::AZoneGraphData& ZoneGraphData, TArray<FVector>& Locations, const FRandomStream& RandomStream) const
{
// Avoid an infinite loop.
if (MinGap == 0.0f && MaxGap == 0.0f)
{
UE_VLOG_UELOG(this, LogMassSpawner, Error, TEXT("You cannot set both Min Gap and Max Gap to 0.0f"));
return;
}
const FZoneGraphStorage &ZoneGraphStorage = ZoneGraphData.GetStorage();
// Loop through all lanes
for (int32 LaneIndex = 0; LaneIndex < ZoneGraphStorage.Lanes.Num(); ++LaneIndex)
{
const FZoneLaneData& Lane = ZoneGraphStorage.Lanes[LaneIndex];
const float LaneHalfWidth = Lane.Width / 2.0f;
if (TagFilter.Pass(Lane.Tags))
{
float LaneLength = 0.0f;
UE::ZoneGraph::Query::GetLaneLength(ZoneGraphStorage, LaneIndex, LaneLength);
float Distance = static_cast<float>(RandomStream.FRandRange(MinGap, MaxGap)); // ..initially
while (Distance <= LaneLength)
{
// Add location at the center of this space.
FZoneGraphLaneLocation LaneLocation;
UE::ZoneGraph::Query::CalculateLocationAlongLane(ZoneGraphStorage, LaneIndex, Distance, LaneLocation);
const FVector Perp = LaneLocation.Direction ^ LaneLocation.Up;
Locations.Add(LaneLocation.Position + Perp * RandomStream.FRandRange(-LaneHalfWidth, LaneHalfWidth));
// Advance ahead past the space we just consumed, plus a random gap.
Distance += static_cast<float>(RandomStream.FRandRange(MinGap, MaxGap));
}
}
}
}