Download

Timer Not Starting

I am trying to make an actor that spawns enemies into the map. The timer is initialized, however it does not seem to expire, and therefore nothing is spawned. Here is my Constructor:



// Sets default values
ASpawner::ASpawner()
{
 	// 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;

	for (int i = 0; i < 20; ++i)
	{
		// Initialize a wave and add it to top 
		Wave wave;
		memset(&wave, 0, sizeof(Wave));
		wave.sizeOfWave = i + (i * 5) + 1;
		WaveStack.push(wave);

		int waveNum = i + 1;
		UE_LOG(LogTimeShooter, Log, TEXT("Initialized wave #%d with %d enemies"), waveNum, wave.sizeOfWave);
	}

	bCanSpawn = 1;
}


Based on this, WaveStack should be initialized and bCanSpawn should be equal to 1. Next, I have BeginPlay():



// Called when the game starts or when spawned
void ASpawner::BeginPlay()
{
	Super::BeginPlay();
	CurrentWave = 0;
	UE_LOG(LogTimeShooter, Log, TEXT("CurrentWave = %d in ASpawner::BeginPlay()"), CurrentWave);
}


Therefore, CurrentWave should be 0. Next, I overwrite Tick():



// Called every frame
void ASpawner::Tick( float DeltaTime )
{
	Super::Tick( DeltaTime );

	GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, FString::Printf(TEXT("Current Wave: %d"), CurrentWave));

	int enemyCount = 0;
	TArray<ATimeShooterEnemyPawn*> EnemyPawnArray;

	for (FConstPawnIterator Iterator = GetWorld()->GetPawnIterator(); Iterator; ++Iterator)
	{
		// Get number of enemies remaining
		ATimeShooterEnemyPawn* ep = Cast<ATimeShooterEnemyPawn>(*Iterator);
		if (ep)
		{
			enemyCount++;
			UE_LOG(LogTimeShooter, Warning, TEXT("Incrementing enemyCount..."));
		}
	}
	UE_LOG(LogTimeShooter, Log, TEXT("There are %d enemies"), enemyCount);

	if (bCanSpawn)
	{
		// If no enemies remaining, initialize the next wave
		if ((enemyCount == 0) && (CurrentWave != 0))
		{
			UE_LOG(LogTimeShooter, Warning, TEXT("Enemy count = %d"), enemyCount);
			SpawnPawn();
		}

		else if (CurrentWave == 0)
		{
			SpawnPawn();
		}
	}
}


Here is my code for SpawnPawn():



void ASpawner::SpawnPawn()
{
	UWorld* World = GetWorld();
	if (World)
	{
		World->GetTimerManager().SetTimer(TimerHandle_SpawnEnemyTimerExpired, this, &ASpawner::SpawnEnemyTimerExpired, SpawnDelay);
		UE_LOG(LogTimeShooter, Warning, TEXT("Timer set."));
		bCanSpawn = 0;
	}
}


This code does seem to get called. Now, however, we come to the timer itself, and for some reason, it is never called.



void ASpawner::SpawnEnemyTimerExpired()
{
	UE_LOG(LogTimeShooter, Warning, TEXT("Timer expired."));
	if (WaveStack.top().sizeOfWave != 0)
	{
		UE_LOG(LogTimeShooter, Log, TEXT("Size of Current Wave: %d"), WaveStack.top().sizeOfWave);
		for (int i = 0; i < WaveStack.top().sizeOfWave; i++)
		{
			if (SpawnActor != NULL)
			{
				UE_LOG(LogTimeShooter, Warning, TEXT("Spawning Pawn..."));
				// Check for valid World
				UWorld* const World = GetWorld();
				if (World)
				{
					// Set spawn parameters
					FActorSpawnParameters SpawnParams;
					SpawnParams.Owner = this;
					SpawnParams.Instigator = Instigator;

					// Get random spawn location
					FVector RandomSpawnLocation = GetRandomLocation();

					// Get random rotation for spawned enemy
					FRotator SpawnRotation;
					SpawnRotation.Yaw = FMath::FRand() * 360.f;
					SpawnRotation.Pitch = FMath::FRand() * 360.f;
					SpawnRotation.Roll = 0.0f;

					ATimeShooterEnemyPawn* const SpawnedEnemy = World->SpawnActor<ATimeShooterEnemyPawn>(SpawnActor, RandomSpawnLocation, SpawnRotation, SpawnParams);
				}
			}
		}
		CurrentWave++;
		WaveStack.pop();
		bCanSpawn = 1;
	}

	else
	{
		GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, TEXT("All enemies destroyed and all waves completed."));
	}
}


Here is my log as of the last Build I performed:

It then goes on to repeat “There are 0 enemies” until I stop the test. Note that SpawnDelay is set to 10.0 in Blueprints.

Is there some flaw in my design or code that I’m missing? I would be thankful for any help, as I have been beating my head over this design for almost a week now and this is the most I’ve been able to come up with.

What if u replaced this:



World->GetTimerManager().SetTimer(TimerHandle_SpawnEnemyTimerExpired, this, &ASpawner::SpawnEnemyTimerExpired, SpawnDelay);


with this:



FTimerHandle TimerHandle;
World->GetTimerManager().SetTimer(TimerHandle, this, &ASpawner::SpawnEnemyTimerExpired, SpawnDelay);


I did that, and it seems as though it is spawning loop that’s wrong. This is the log I get from Visual Studio’s Debugger:

It seems as though it may be picking an invalid location to spawn the pawn, which I confirmed using breakpoints. Here is the code for the GetRandomLocation() function:




FVector ASpawner::GetRandomLocation()
{
	// Calculate spawn location
	FVector RandomLocation;
	float MinX, MinY, MinZ;
	float MaxX, MaxY, MaxZ;

	FVector Origin;
	FVector BoxExtent;

	// Get Origin and extent
	Origin = WhereToSpawn->Bounds.Origin;
	BoxExtent = WhereToSpawn->Bounds.BoxExtent;

	// Calculate minimum X, Y, and Z coords
	MinX = Origin.X - BoxExtent.X / 2.f;
	MinY = Origin.Y - BoxExtent.Y / 2.f;
	MinZ = Origin.Z - BoxExtent.Z / 2.f;

	// Calculate maximum X, Y, and Z coords
	MaxX = Origin.X + BoxExtent.X / 2.f;
	MaxY = Origin.Y + BoxExtent.Y / 2.f;
	MaxZ = Origin.Z + BoxExtent.Z / 2.f;

	// Get random location between maximum and minimum points
	RandomLocation.X = FMath::FRandRange(MinX, MaxX);
	RandomLocation.Y = FMath::FRandRange(MinY, MaxY);
	RandomLocation.Z = FMath::FRandRange(MinZ, MaxZ);

	return RandomLocation;

}


Based on the Symbols view in Visual Studio, the problem lies in WhereToSpawn, which is NULL. However, I am unsure how to fix this.

Further, it appears as though the spawn loop is trying to go straight to wave 20 (based on the message at time 2015.07.18-18.29.02:508, which says that the size of the current wave is 115; the first wave, based on the initialization code, should be 1).

Haven’t read the full code but just a couple of things regarding timers:

  • If you have a ‘time’ or ‘delay’ of 0.f, the timer will never actually work. The delay MUST be more than zero.
  • I wouldn’t use the name ‘TimerHandle’ for an actual TimerHandle, it might screw with things. Always try to give them a more unique name. I believe TimerHandle is a struct (hence the FTimerHandle name), and calling it the same thing might mess with it.

TheJamsh,

  • The delay is set as 10.0, although it’s not hardcoded but rather initialized in Blueprints. I don’t believe that matters, however.

  • I used the name TimerHandle_SpawnEnemyTimerExpired even after MaxL’s suggestion, although I changed it from a class variable to a local variable. That led to the log I posted previously, which seems to indicate two problems. The first is that only wave 20 is firing (likely an algorithmic problem). The second is that I have not initialized WhereToSpawn, leading to a First-chance unhandled exception.

Unfortunately, I’m not sure how to handle either problem, since I can’t actually get a stack trace of how the waves are being popped from WaveStack and I don’t know how I initialize WhereToSpawn (it is a UBoxComponent pointer).

Hey,

i just noticed, that the problem might be the delay behaviour of a TimerHandle. Means, it wont prevent the code after your SetTimer from being triggered.

I assume when you set the delay of your of when to do your magical timer stuff it means your processor has time to do other stuff meanwhile and that is simply the stuff coming after your SetTimer and that is the boolean you set to 0.

so what comes after? The tick event is called again an bCanSpawn is zero.
So you see your log called but bCanSpawn is zero.

I dont quite know why your timer isnt called after the delay you set up, but it might be because of the tick event running, and i would use events instead of Tick.
I would like to know what happens when you disable tick events inside SpawnPawn after setting the timer. (just for a test).

Btw.

of course it is, you use a push/pop stack for your waves. In your Ctor you push all waves on top of each other, means the last one pushed is your current one to work with and that is 20.

I assume your CurrentWave var is just an integer which you set to zero on BeginPlay() but your wave is at 20. Even if your delegate is triggered and i understood it right (might be wrong)
you would spawn waves from 20 to 0 (everytime you pop your stack, discards the wave lying on top the next wave below that one is valid now) on the other hand you increment CurrentWave.

Maybe im totally wrong but that was my suggestion :wink: maybe that helped

edit: i dont see where you instantiate your FTimerHandle can you confirm it to be not null during debugging? else call TimerHandle_SpawnEnemyTimerExpired = new FTimerHandle() before setting the timer

Not completely sure what you mean by this, though I think I understand the gist of it. The reason why the loop is continuing is that the program is trying to spawn enemies while bCanSpawn is set to zero?

I get the following log:

This is after adding the line PrimaryActorTick.bCanEverTick = false in SpawnPawn() after bCanSpawn = 0. Interestingly, this is my current memory setup:

Based on the log, it seems as though the timer is firing at this point (albeit opposite my intended order, but that’s due to my stupidity). However, it doesn’t know where to spawn it, likely because WhereToSpawn is NULL.

Well, that was an embarrassing mistake. I completely blanked on how to push and pop to/from a stack. I definitely need to rethink that part of the algorithm, because it basically means that the most difficult wave would be at the very beginning of the game (clearly a broken design).

The FTimerHandle is currently a local variable. When I tried to do what you said (TimerHandle_SpawnEnemyTimerExpired = new FTimerHandle()), Visual Studio said it cannot convert from FTimerHandle * to FTimerHandle. However, the debugger shows its current value to be 0xffffffff.

sorry my bad, new addresses memory on heap and so you´ll get back a pointer, so either you convert your TimerHandle_SpawnEnemyTimerExpired into TimerHandle_SpawnEnemyTimerExpired* (pointer) or just call

TimerHandle_SpawnEnemyTimerExpired = FTimerHandle()

Put that line inside SpawnPawn before you set the Timer.

almost, he is not trying to spawn something since bCanSpawn is zero, but your tick event is triggered all this time and inside you have your UELOG which prints that there are 0 enemies around.

Thank you for the help. That fixed it, along with making sure that I instantiate WhereToSpawn in the Spawner ctor. The only bug that remains is that all of the enemies in the same wave seem to cluster in the same region of the map, which I’m not too concerned about since the random spawning algorithm was more of a hack to quickly test my Spawner class rather than something that I plan to put into production.

However, I pretty much know why that’s the case, so I know how to fix it if I want to. I probably need to make sure to loop it by WaveStack.top().sizeOfWave times.

Therefore, this thread can be considered solved.