Hello,
so I have followed until what is now 329 (the lecture where Sam fixes the double spawning) and have updated my code with his. Everything is working fine, except the AI don’t start spawning until I reach tile 6, which is consistently where they begin to spawn. Below is my Tile.h and Tile.cpp
Tile.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Tile.generated.h"
USTRUCT(BlueprintType)
struct FSpawnPosition
{
GENERATED_USTRUCT_BODY()
FVector Location;
float XRotation = 0.f;
float YRotation = 0.f;
float ZRotation = 0.f;
float Scale = 1;
};
USTRUCT(BlueprintType)
struct FSpawnParameters
{
GENERATED_USTRUCT_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SpawnParams")
int32 MinSpawn = 1;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SpawnParams")
int32 MaxSpawn = 1;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SpawnParams")
float Radius = 300.f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SpawnParams")
float MinScale = 1.f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SpawnParams")
float MaxScale = 1.f;
};
#define Spawn ECC_GameTraceChannel2
class UActorPool;
UCLASS()
class TESTINGGROUNDS_API ATile : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
ATile();
UFUNCTION(BlueprintCallable, Category = "Pool")
void SetPool(UActorPool * PoolToSet);
UFUNCTION(BlueprintCallable, Category = "Spawn")
void PlaceActors(TSubclassOf <AActor> ActorToSpawn, FSpawnParameters SpawnParameters, bool bIsAllRotation = false);
UFUNCTION(BlueprintCallable, Category = "Spawn")
void PlaceAI(TSubclassOf <APawn> AIToSpawn, FSpawnParameters SpawnParameters, bool bIsAllRotation = false);
// Called every frame
virtual void Tick(float DeltaTime) override;
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
UPROPERTY(EditDefaultsOnly, Category = "Spawning")
FVector MinExtent;
UPROPERTY(EditDefaultsOnly, Category = "Spawning")
FVector MaxExtent;
UPROPERTY(EditDefaultsOnly, Category = "Navigation")
FVector NavigationBoundsOffset;
private:
bool FindEmptyLocation(FVector &OutLocation, float Radius);
template <class T>
void RandomlyPlaceActors(TSubclassOf <T> ActorToSpawn, FSpawnParameters SpawnParameters, bool bIsAllRotation = false);
void PlaceActor(TSubclassOf <AActor> ActorToSpawn, FSpawnPosition& SpawnPosition);
void PlaceActor(TSubclassOf<APawn> AIToSpawn, FSpawnPosition& SpawnPosition);
bool CanSpawnAtLocation(FVector Location, float Radius);
void PositionNavMeshBoundsVolume();
UActorPool * Pool;
AActor * NavMeshBoundsVolume;
TArray<AActor*> SpawnedActors;
FSpawnPosition SpawnPosition;
};
TIle.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "Tile.h"
#include "Engine/World.h"
#include "Public/WorldCollision.h"
#include "DrawDebugHelpers.h"
#include "EngineUtils.h"
#include "ActorPool.h"
#include "Engine/World.h"
#include "Runtime/Engine/Classes/AI/Navigation/NavigationSystem.h"
#include "GameFramework/Pawn.h"
// Sets default values
ATile::ATile()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
MinExtent = FVector(0, -2000, 0);
MaxExtent = FVector(4000, 2000, 0);
NavigationBoundsOffset = FVector(2000, 0, 0);
}
void ATile::SetPool(UActorPool * PoolToSet)
{
//UE_LOG(LogTemp, Warning, TEXT("[%s] Setting Pool: %s"), *(this->GetName()), *(PoolToSet->GetName()));
Pool = PoolToSet;
PositionNavMeshBoundsVolume();
}
template<class T>
void ATile::RandomlyPlaceActors(TSubclassOf<T> ActorToSpawn, FSpawnParameters SpawnParameters, bool bIsAllRotation)
{
int32 NumberToSpawn = FMath::RandRange(SpawnParameters.MinSpawn, SpawnParameters.MaxSpawn);
for (size_t i = 0; i < NumberToSpawn; i++)
{
//FSpawnPosition SpawnPosition;
SpawnPosition.Scale = FMath::RandRange(SpawnParameters.MinScale, SpawnParameters.MaxScale);
bool bHasFound = FindEmptyLocation(SpawnPosition.Location, (SpawnParameters.Radius * SpawnPosition.Scale));
if (bHasFound)
{
if (bIsAllRotation)
{
SpawnPosition.XRotation = FMath::RandRange(-180.f, 180.f);
SpawnPosition.YRotation = FMath::RandRange(-180.f, 180.f);
SpawnPosition.ZRotation = FMath::RandRange(-180.f, 180.f);
}
else if (!bIsAllRotation)
{
SpawnPosition.XRotation = FMath::RandRange(-180.f, 180.f);
SpawnPosition.YRotation = 0.f;
SpawnPosition.ZRotation = 0.f;
}
PlaceActor(ActorToSpawn, SpawnPosition);
}
}
}
void ATile::PlaceActors(TSubclassOf <AActor> ActorToSpawn, FSpawnParameters SpawnParameters, bool bIsAllRotation)
{
RandomlyPlaceActors(ActorToSpawn, SpawnParameters, bIsAllRotation);
}
void ATile::PlaceAI(TSubclassOf<APawn> AIToSpawn, FSpawnParameters SpawnParameters, bool bIsAllRotation)
{
RandomlyPlaceActors(AIToSpawn, SpawnParameters, bIsAllRotation);
}
bool ATile::FindEmptyLocation(FVector &OutLocation, float Radius)
{
FBox Bounds(MinExtent, MaxExtent);
const int32 MAX_ATTEMPTS = 100;
for (size_t i = 0; i < MAX_ATTEMPTS; i++)
{
FVector CandidatePoint = FMath::RandPointInBox(Bounds);
if (CanSpawnAtLocation(CandidatePoint, Radius))
{
OutLocation = CandidatePoint;
return true;
}
}
return false;
}
void ATile::PlaceActor(TSubclassOf<AActor> ActorToSpawn, FSpawnPosition& SpawnPosition)
{
AActor * SpawnedActor = GetWorld()->SpawnActor<AActor>(ActorToSpawn);
if (SpawnedActor != nullptr)
{
SpawnedActor->SetActorRelativeLocation(SpawnPosition.Location);
SpawnedActor->AttachToActor(this, FAttachmentTransformRules(EAttachmentRule::KeepRelative, false));
SpawnedActor->SetActorRotation(FRotator(SpawnPosition.YRotation, SpawnPosition.XRotation, SpawnPosition.ZRotation));
SpawnedActor->SetActorScale3D(FVector(SpawnPosition.Scale));
SpawnedActors.Add(SpawnedActor);
}
else
{
UE_LOG(LogTemp, Warning, TEXT("No Actor to Spawn"));
}
}
void ATile::PlaceActor(TSubclassOf<APawn> AIToSpawn, FSpawnPosition& SpawnPosition)
{
APawn * SpawnedAI = GetWorld()->SpawnActor<APawn>(AIToSpawn);
if (SpawnedAI != nullptr)
{
SpawnedAI->SetActorRelativeLocation(SpawnPosition.Location);
SpawnedAI->AttachToActor(this, FAttachmentTransformRules(EAttachmentRule::KeepRelative, false));
SpawnedAI->SetActorRotation(FRotator(0, SpawnPosition.XRotation, 0));
SpawnedAI->SpawnDefaultController();
SpawnedAI->Tags.Add(FName("Enemy"));
}
else
{
UE_LOG(LogTemp, Warning, TEXT("No Pawn to Spawn"));
}
}
// Called when the game starts or when spawned
void ATile::BeginPlay()
{
Super::BeginPlay();
}
void ATile::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
Super::EndPlay(EndPlayReason);
Pool->ReturnActor(NavMeshBoundsVolume);
}
// Called every frame
void ATile::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
bool ATile::CanSpawnAtLocation(FVector Location, float Radius)
{
FHitResult HitResult;
FVector GlobalLocation = ActorToWorld().TransformPosition(Location);
bool bHasHit = GetWorld()->SweepSingleByChannel(HitResult, GlobalLocation, GlobalLocation, FQuat::Identity, ECollisionChannel::ECC_GameTraceChannel2, FCollisionShape::MakeSphere(Radius));
return !bHasHit;
}
void ATile::PositionNavMeshBoundsVolume()
{
NavMeshBoundsVolume = Pool->CheckOut();
if (NavMeshBoundsVolume != nullptr)
{
UE_LOG(LogTemp, Warning, TEXT("[%s]: Checked out {%s}"), *GetName(), *NavMeshBoundsVolume->GetName());
NavMeshBoundsVolume->SetActorLocation(GetActorLocation() + NavigationBoundsOffset);
GetWorld()->GetNavigationSystem()->Build();
}
else
{
UE_LOG(LogTemp, Error, TEXT("[%s]: No Actors in Pool"), *GetName());
}
}
On the first 5 tiles, I receive the warning message, “No Pawn to Spawn”. Any help would be appreciated in getting them to spawn on the first tile.