How can I implement delay into my project?

I’m so lost in timers and delegates…
I can’t find a clear description on how to use timers, specially the GetWorldTimerManager().SetTimer(…) function and what the arguments inside it refer to or how they are related to each other.

I’m trying to accomplish a simple delay between spawning Actors.
In my BeginPlay() function, I have a loop from 0 to 8, inside the loop body it spawns a new actor that is stored in an array (i.e myActor[0] to myActor[8]).
I just want to add a delay after each actor is spawned so that not all the actors get spawned at the same time.

-Where do I implement the GetWorldTimerManager().SetTimer(…)

-What are the arguments inside SetTimer(…)

-Does SetTimer() provide delay? How?

-Do I need to create my own Delay function outside BeginPlay()? How does it relate to SetTimer()?

You’ll want to do something like:

GetWorldTimerManager().SetTimer(this, &AActorFunctionBelongsTo::SpawnActor, 1.0f);

This will call spawn actor after one second. You can also add a ‘true’ parameter after your ‘time to wait’ variable that will loop the call as well.

Therefore, you could create a recursive function for a simple approach:

int nNumSpawned = 0; // Probably needs to be in header or something
GetWorldTimerManager().SetTimer(this, &AActorFunctionBelongsTo::SpawnActor, 1.0f);

void SpawnActor()
{
    // Spawn Actor here
    nNumSpawned++;
    if (nNumSpawned < 8)
        GetWorldTimerManager().SetTimer(this, &AActorFunctionBelongsTo::SpawnActor, 1.0f);
}

with SpawnActor being a void with no parameters – void SpawnActor(); – Obviously, you can get a lot more in-depth with the timing methods, but this is a simplistic approach which should suit your needs hopefully?

Thank you for your answer!

If I understand correctly, SetTimer() will just call a function and delays execution of everything else for a specified period of time (for example 1.0 seconds), correct? Does this stop Tick() from executing as well?

I’m trying to spawn my actor and then have it immediately rotate using the Tick() function.

I also have few questions regarding the code provided above:

  • The first line of code you have in your comment

    GetWorldTimerManager().SetTimer(this, &AActorFunctionBelongsTo::SpawnActor, 1.0f);

Where do I put that line of code? Is it in the BeginPlay() function? Top of the function? Inside the loop body?

  • The overridden BeginPlay() and the SpawnActor functions I have, they both belong to the same class. How will SetTimer() look like…is it something like this:
    GetWorldTimerManager().SetTimer(this, this::SpawnActor, 1.0f);

  • In the last block of code in your comment, you have SetTimer() called twice…one before the SpawnActor() function and the other is inside it, what’s the difference and impact?

I apologize for not being able to test your code yet…will do when I’m in front of my personal PC tonight.

Thanks a lot!

When I put SetTimer() inside my SpawnActor() function like so:

void AMyActorClass::SpawnActor(int rows, int columns)

{

//Code for two nested for loops to Spawn Actor here

GetWorldTimerManager().SetTimer(this, &AMyActorClass::SpawnActor, 1, true);

}

I get the following error:

error C2664: ‘void FTimerManager::SetTimer(FTimerHandle &,float,bool,float)’ : cannot convert argument 2 from 'void (__cdecl AMyActorClass:: )(int,int)’ to 'void (__cdecl AMyActorClass:: )(void)'**

Looks like SetTimer() is not accepting a function with arguments. Do I need to create a delegate? how? or is there an existing overloaded SetTimer() version of function that accepts functions with arguments?

Still not sure whether I need to use SetTimer() in SpawnActot() or BeginPlay()

I changed the SpawnActor(int i, int j) function to SpawnActor() with no parameters then moved all the nested loops required for spawning inside the SpawnActor() so it’s not dependent on arguments any more.

I also added the below line inside SpawnActor() function:
GetWorldTimerManager().SetTimer(this, &AMyActorClass::SpawnActor, false, 1.0f);

Still no delay…all actors spawn at the same time.

Code looks like this now:

void AMyActorClass::SpawnActor()

 {
 
 //Code for two nested for loops to Spawn Actor here
 
 GetWorldTimerManager().SetTimer(this, &AMyActorClass::SpawnActor, false, 1.0f);

 }

void AMyActorClass::BeginPlay()
{
Super::BeginPlay();
SpawnActor();
}

I tried moving SetTimer() to BeginPlay() before calling SpawnActor() but still no delay.
The only thing I noticed is Actors keep spawning forever when I change the (false) argument in SetTimer() to (true)

The boolean tells the timer to loop by creating a new identical timer at the end of execution. It will call the function referenced and then reset itself to call again. You can set a looping timer in BeginPlay event and it will call the function until you unset the timer. You never need to explicitly call the function.

The example provided above spawns an actor and keeps a reference to the total number spawned. If that is fewer than 8 it will create a new timer to call SpawnActor again. You should only ever have to call spawn actor once in this example and it will stop on its own.

Remember, all the timer does is wait a specified time and then call the provided function.

Thank you , makes a lot of sense

Problem is I can’t seem to make it work with my code below…I have a dynamic 2D array. I have to loop through rows (i) and columns (j) and spawn the [i][j] element Actor each loop cycle

The problem I’m having is that I can’t seem to get delay/SetTimer() to work after each element gets spawned due to loops (most likely)…hopefully code below makes more sense

I modified the code and changed the naming of the class, functions, and variables to make it more readable

void ABoardGrid::SpawnCells()
{
	for (int32 i = 0; i < mNumBoardRows; i++)
	{
		for (int32 j = 0; j < mNumBoardColumns; j++)
		{
			if (SpawnedCell != NULL)
			{
				UWorld* const World = GetWorld();
				if (World)
				{
					FActorSpawnParameters SpawnParams;
					SpawnParams.Owner = this;
					SpawnParams.Instigator = Instigator;

					const float XOffset = j * CellSpacing;
					const float YOffset = i * CellSpacing;
					const FVector CellLocation = FVector(XOffset, YOffset, 0.f) + GetActorLocation();

					FRotator CellRotation;
					CellRotation.Yaw = 0.f;
					CellRotation.Pitch = 0.f;
					CellRotation.Roll = 180.f;
					
					MainBoardGrid.Rows[i].Columns[j] = World->SpawnActor<ABoardCell>(SpawnedCell, CellLocation, CellRotation, SpawnParams);
					MainBoardGrid.Rows[i].Columns[j]->OwningBoard = this;
					MainBoardGrid.Rows[i].Columns[j]->iCellValue = 0;


					//GetWorldTimerManager().SetTimer(this, &ABoardGrid::SpawnCells, false, 1.0f);
					

				}
			}

			else
				MainBoardGrid.Rows[i].Columns[j] = NULL;
		}

	}
	
	
}

void ABoardGrid::BeginPlay()
{
	Super::BeginPlay();	
	SpawnCells();
	
}

It seems like you are setting a new timer every time you spawn a cell, and that timer will after one second spawn the actors again.

Instead you need spawn cell to spawn one actor and then set a timer and when the timer executes SpawnCell it will create the next actor, working through the loop one step at a time.

Maybe something like this?

// in BoardGrid.h

int32 i = 0;
int32 j = 0;

// .cpp

void ABoardGrid::SpawnCells()
{
    if(j >= mNumBoardColumns)
    {
            j=0;
            i++
    }
    if( i >= mNumBoardRows)
    {
        return;
    }
    
    if (SpawnedCell != NULL)
    {
        UWorld* const World = GetWorld();
        if (World)
        {
            FActorSpawnParameters SpawnParams;
            SpawnParams.Owner = this;
            SpawnParams.Instigator = Instigator;

            const float XOffset = j * CellSpacing;
            const float YOffset = i * CellSpacing;
            const FVector CellLocation = 
                            FVector(XOffset, YOffset, 0.f) + GetActorLocation();

            FRotator CellRotation;
            CellRotation.Yaw = 0.f;
            CellRotation.Pitch = 0.f;
            CellRotation.Roll = 180.f;

            MainBoardGrid.Rows[i].Columns[j] =
                            World->SpawnActor<ABoardCell>(SpawnedCell,
                                                          CellLocation,
                                                          CellRotation,
                                                          SpawnParams);
            
            MainBoardGrid.Rows[i].Columns[j]->OwningBoard = this;
            MainBoardGrid.Rows[i].Columns[j]->iCellValue = 0;
            
            j++;
            GetWorldTimerManager().SetTimer(this,
                                            &ABoardGrid::SpawnCells,
                                            false,
                                            1.0f);
        }
    }
    else 
    {
        MainBoardGrid.Rows[i].Columns[j] = NULL;
        j++;
        SpawnCells();
    }
    
}

 void ABoardGrid::BeginPlay()
 {
     Super::BeginPlay();    
     SpawnCells();
     
 }

Aha thank you, much appreciated! That worked!

I’m running into the pitch rotation bug now, discussed here:

Every time you set an actor’s rotation it must recalculate them to an equivalent orientation in the domain of Euler angles. For any given orientation only only one set of angles exist. That means that Pitch will go up to 90 and then any value above that has an equivalent value in inside the Euler domain that is a combination of a new pitch, roll and yaw.

  1. (P: 89, Y:0, R: 0) is great… everything in the domain.

  2. (P: 91, Y:0, R: 0) is outside the domain

  3. (P: 89, Y:180, R: 180) is equivalent to #2 and inside the domain.

#3 is the value that the actor will use when you give it P:91, 0 , 0. Then if you try to get the pitch and add one you will end up locked around P:90 as yaw and roll swap back and forth from +/- 180 to 0. It is not a bug. This is a property of the equations used for rotations and has been the bane of game developers and 3D artists for a long time.

If you want to post another question, I would be glad to give a more specific answer to whatever issue you might be having, but I do recommend reading up on the maths. Geometry, Trigonometry and Linear Algebra important to understanding the how and why of game engines like UE4. Some of it may be beyond the scope of the UE4 answerhub, but I think we should try to clear up misconceptions about the implementation.

Makes sense…thanks for the information!