Add wildlife spawn manager

This commit is contained in:
2026-05-18 14:16:50 -07:00
parent f0d8e3ef05
commit 494fe6f2ef
5 changed files with 304 additions and 1 deletions
@@ -0,0 +1,151 @@
// Copyright Pacificao. All Rights Reserved.
#include "AgrarianWildlifeSpawnManager.h"
#include "AgrarianWildlifeBase.h"
#include "NavigationSystem.h"
#include "Net/UnrealNetwork.h"
AAgrarianWildlifeSpawnManager::AAgrarianWildlifeSpawnManager()
{
PrimaryActorTick.bCanEverTick = true;
bReplicates = true;
SetReplicatingMovement(false);
}
void AAgrarianWildlifeSpawnManager::BeginPlay()
{
Super::BeginPlay();
SpawnTimer = SpawnIntervalSeconds;
if (HasAuthority() && bSpawnOnBeginPlay)
{
SpawnInitialWildlife();
}
}
void AAgrarianWildlifeSpawnManager::Tick(float DeltaSeconds)
{
Super::Tick(DeltaSeconds);
if (!HasAuthority() || !WildlifeClass)
{
return;
}
RemoveInvalidSpawnedWildlife();
if (GetActiveWildlifeCount() >= MaxActiveWildlife)
{
return;
}
SpawnTimer -= DeltaSeconds;
if (SpawnTimer <= 0.0f)
{
SpawnTimer = SpawnIntervalSeconds;
SpawnWildlife();
}
}
void AAgrarianWildlifeSpawnManager::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(AAgrarianWildlifeSpawnManager, SpawnedWildlife);
}
AAgrarianWildlifeBase* AAgrarianWildlifeSpawnManager::SpawnWildlife()
{
if (!HasAuthority() || !WildlifeClass || GetActiveWildlifeCount() >= MaxActiveWildlife)
{
return nullptr;
}
FVector SpawnLocation = GetActorLocation();
if (!ChooseSpawnLocation(SpawnLocation))
{
return nullptr;
}
FActorSpawnParameters SpawnParameters;
SpawnParameters.Owner = this;
SpawnParameters.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButAlwaysSpawn;
AAgrarianWildlifeBase* SpawnedActor = GetWorld()->SpawnActor<AAgrarianWildlifeBase>(
WildlifeClass,
SpawnLocation,
FRotator::ZeroRotator,
SpawnParameters);
if (SpawnedActor)
{
SpawnedWildlife.Add(SpawnedActor);
}
return SpawnedActor;
}
int32 AAgrarianWildlifeSpawnManager::GetActiveWildlifeCount() const
{
int32 ActiveCount = 0;
for (AAgrarianWildlifeBase* Wildlife : SpawnedWildlife)
{
if (IsValid(Wildlife))
{
++ActiveCount;
}
}
return ActiveCount;
}
void AAgrarianWildlifeSpawnManager::SpawnInitialWildlife()
{
const int32 TargetCount = FMath::Min(InitialSpawnCount, MaxActiveWildlife);
for (int32 SpawnIndex = GetActiveWildlifeCount(); SpawnIndex < TargetCount; ++SpawnIndex)
{
if (!SpawnWildlife())
{
break;
}
}
}
bool AAgrarianWildlifeSpawnManager::ChooseSpawnLocation(FVector& OutLocation) const
{
const FVector RandomOffset(
FMath::FRandRange(-SpawnRadius, SpawnRadius),
FMath::FRandRange(-SpawnRadius, SpawnRadius),
0.0f);
const FVector CandidateLocation = GetActorLocation() + RandomOffset;
if (!bProjectSpawnsToNavigation)
{
OutLocation = CandidateLocation;
return true;
}
const UWorld* World = GetWorld();
const UNavigationSystemV1* NavigationSystem = World ? FNavigationSystem::GetCurrent<UNavigationSystemV1>(World) : nullptr;
if (!NavigationSystem)
{
OutLocation = CandidateLocation;
return true;
}
FNavLocation ProjectedLocation;
if (!NavigationSystem->ProjectPointToNavigation(CandidateLocation, ProjectedLocation, NavigationProjectionExtent))
{
return false;
}
OutLocation = ProjectedLocation.Location;
return true;
}
void AAgrarianWildlifeSpawnManager::RemoveInvalidSpawnedWildlife()
{
SpawnedWildlife.RemoveAll([](const TObjectPtr<AAgrarianWildlifeBase>& Wildlife)
{
return !IsValid(Wildlife);
});
}
@@ -0,0 +1,62 @@
// Copyright Pacificao. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "AgrarianWildlifeSpawnManager.generated.h"
class AAgrarianWildlifeBase;
UCLASS(Blueprintable)
class AAgrarianWildlifeSpawnManager : public AActor
{
GENERATED_BODY()
public:
AAgrarianWildlifeSpawnManager();
virtual void BeginPlay() override;
virtual void Tick(float DeltaSeconds) override;
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Wildlife|Spawning")
TSubclassOf<AAgrarianWildlifeBase> WildlifeClass;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Wildlife|Spawning", meta = (ClampMin = "0"))
int32 InitialSpawnCount = 3;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Wildlife|Spawning", meta = (ClampMin = "0"))
int32 MaxActiveWildlife = 6;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Wildlife|Spawning", meta = (ClampMin = "0"))
float SpawnRadius = 3500.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Wildlife|Spawning", meta = (ClampMin = "0.1"))
float SpawnIntervalSeconds = 20.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Wildlife|Spawning")
bool bSpawnOnBeginPlay = true;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Wildlife|Spawning")
bool bProjectSpawnsToNavigation = true;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Wildlife|Spawning")
FVector NavigationProjectionExtent = FVector(650.0f, 650.0f, 900.0f);
UFUNCTION(BlueprintCallable, Category = "Agrarian|Wildlife|Spawning")
AAgrarianWildlifeBase* SpawnWildlife();
UFUNCTION(BlueprintCallable, Category = "Agrarian|Wildlife|Spawning")
int32 GetActiveWildlifeCount() const;
protected:
void SpawnInitialWildlife();
bool ChooseSpawnLocation(FVector& OutLocation) const;
void RemoveInvalidSpawnedWildlife();
UPROPERTY(Replicated)
TArray<TObjectPtr<AAgrarianWildlifeBase>> SpawnedWildlife;
float SpawnTimer = 0.0f;
};