How to Set Up Godot Crowd Spawning for a Hypercasual Game

Godot crowd spawning needs two parts to work together: creating the right number of characters and giving each one a position. Zenva’s experience teaching coding and game development through a catalog of over 400 courses guides the step-by-step approach here, from the first center position to a circular crowd in your hypercasual game.

This tutorial assumes you know basic GDScript and have the project open with its game manager, mover target, and reusable mover scene.

Download Project Files

The project files and full project are available through the course included in Godot Mobile Game Development Mini-Degree. Follow that guided path for the full game, or keep reading to connect crowd positioning with spawning here.

Make a Complete Card Battler in Godot e1788415191322 - How to Set Up Godot Crowd Spawning for a Hypercasual Game
FREE GODOT COURSE
LEARN GODOT, UNITY, UNREAL & MORE
ACCESS FOR FREE
AVAILABLE FOR A LIMITED TIME ONLY

Calculate Positions for Godot Crowd Spawning

Your formation begins with one mover in the center, then places additional movers in rings around it. A helper function will return the offsets, letting you use the same positioning logic as the crowd grows.

Examples of mover groups arranged as 2, 7, and 19 positions, with larger groups using concentric rings.

Creating the Crowd Position Function

In game_manager.gd, create _get_crowd_positions with two parameters: n for the number of positions and radius for the mover radius used in spacing.

The return type is Array[Vector2]. Although your game is 3D, the helper only needs two coordinates for each offset around the mover target. You will map those coordinates to X and Z when placing the characters.

func _get_crowd_positions (n : int, radius : float) -> Array[Vector2]:
	var positions : Array[Vector2] = []
	
	return positions

Adding the Center Position

Start with the center of the formation. Adding Vector2.ZERO gives the first mover an offset at the origin:

The Godot scene viewport shows the mover target centered on the road, representing the first position at the origin.

func _get_crowd_positions (n : int, radius : float) -> Array[Vector2]:
	var positions : Array[Vector2] = []
	
	positions.append(Vector2.ZERO)
	
	return positions

After that position, add two variables to track the rest of the formation. There are n - 1 movers left to place, and the first outer ring is ring 1:

var remaining : int = n - 1
var ring : int = 1

Looping Through the Rings

Use a while loop to keep adding positions while movers remain:

while remaining > 0:
	# Ring calculations will go here

Each pass handles one ring. Inside the loop, calculate how far that ring sits from the center. Multiplying the mover radius by 2.0 uses the mover’s full width for spacing:

A diagram shows concentric mover rings and the ring radius formula: ring number multiplied by mover radius multiplied by 2.

var ring_radius : float = ring * radius * 2.0

Calculating Ring Capacity

Now calculate the ring’s circumference, or the distance around it:

var circ : float = 2.0 * PI * ring_radius

Divide that distance by the mover’s full width to calculate how many movers fit around the ring. Convert the result to an integer because you need whole positions:

var capacity : int = int(circ / (radius * 2.0))

A ring might have room for more movers than you still need. Use min to choose the smaller of remaining and capacity, so you do not generate extra positions:

var count : int = min(remaining, capacity)

Dividing the Ring Into Angles

With the ring’s count decided, loop once for each position. Think of the angle as a clock hand pointing toward the next mover:

A ring diagram shows six red mover positions spaced around a circle, illustrating how each angle points to one position.

for i in count:
	var angle : float = (2.0 * PI * i) / count - PI / 2.0

2.0 * PI represents a full circle. Multiplying by the index and dividing by count spreads the positions evenly; subtracting PI / 2.0 starts the first position at the top of the ring.

Creating the Vector Position

Inside that inner loop, convert the angle into a position. Use cos(angle) for X and sin(angle) for Y, multiplying both by the ring radius:

var pos : Vector2 = Vector2(cos(angle) * ring_radius, sin(angle) * ring_radius)
positions.append(pos)

Once the inner loop finishes, subtract the positions you just added from remaining. Increase ring to move outward on the next pass:

remaining -= count
ring += 1

Completed Function

Here is the helper with all those pieces together. It adds the center, fills the rings, and returns the calculated offsets:

func _get_crowd_positions (n : int, radius : float) -> Array[Vector2]:
	var positions : Array[Vector2] = []
	
	positions.append(Vector2.ZERO)
	
	var remaining : int = n - 1
	var ring : int = 1
	
	while remaining > 0:
		var ring_radius : float = ring * radius * 2.0
		var circ : float = 2.0 * PI * ring_radius
		var capacity : int = int(circ / (radius * 2.0))
		var count : int = min(remaining, capacity)
		
		for i in count:
			var angle : float = (2.0 * PI * i) / count - PI / 2.0
			var pos : Vector2 = Vector2(cos(angle) * ring_radius, sin(angle) * ring_radius)
			positions.append(pos)
		
		remaining -= count
		ring += 1
	
	return positions

You now have the positions. Next, connect them to actual mover instances so the formation appears in the level.

Spawn and Arrange the Mover Crowd

Preparing the Mover References

In game_manager.gd, add an exported scene reference and an array to track your mover instances:

@export var mover_scene : PackedScene
var movers : Array[Mover]

To use Mover as the array’s type, give the script in mover.gd its class name:

class_name Mover
extends Area3D

Back in the Inspector, assign mover.tscn to the GameManager’s Mover Scene field. That is the resource the script will instantiate.

The GameManager Inspector in Godot shows exported movement settings and the Mover Scene field assigned to mover.tscn.

Creating the Mover Count Function

Create _set_mover_count to receive the desired number of movers. While the array contains fewer than that number, instantiate a mover, add it beneath mover_target, and track it in the array:

func _set_mover_count (new_count : int):
	# Create new movers if we have less than new_count
	while len(movers) < new_count:
		var mover : Mover = mover_scene.instantiate()
		mover_target.add_child(mover)
		movers.append(mover)

Parenting each new instance to mover_target makes the spawned crowd travel with the target. The array keeps those instances available for later count and position changes.

Testing With One Mover

Before adding the rest of the count logic, check that spawning works. Call the function from _ready() with a count of 1:

func _ready ():
	_set_mover_count(1)

Run the scene and look for one mover at the target when the level starts.

The Godot game view shows one spawned mover centered on the road.

Removing Extra Movers

The same function also needs to reduce the crowd. After the spawning loop in _set_mover_count, add a second loop that removes movers while the array is longer than the requested count:

# Destroy movers if we have more than new_count
while len(movers) > new_count:
	var mover : Mover = movers.pop_back()
	mover.queue_free()

pop_back() removes the final array element and returns its mover reference. Calling queue_free() then deletes that mover node from the game.

Positioning the Spawned Movers

Once the count is correct, the final step in _set_mover_count is to arrange the instances. Call your positioning helper with new_count and a mover radius of 0.4, then assign the returned offsets:

var positions : Array[Vector2] = _get_crowd_positions(new_count, 0.4)

# Assign new position to each mover
for i in len(movers):
	var pos : Vector3 = Vector3(positions[i].x, 0, positions[i].y)
	movers[i].position = pos

Each offset’s X value becomes the 3D X position. The 3D Y position stays at 0, while the offset’s Y value becomes the 3D Z position. This places the ring formation on the road around the mover target.

Checking Larger Groups

With positioning in place, temporarily change the starting count to 10 or 50 and run the scene. The crowd should form rings around the center. If the last ring is not full, its remaining movers are still spread evenly around it.

The Godot game view shows a large group of movers arranged in a circular cluster on the road.

After testing, restore _ready() to start with one mover. The same count function can later be called by gates that increase, decrease, multiply, or divide the group size.

Bring Your Crowd Formation Together

Your crowd now has both a formation and a way to manage its members. You have connected the positioning math to the scene instances by learning to:

  • Calculate center and ring offsets with Vector2 positions.
  • Limit each ring to the number of movers still waiting for a position.
  • Instantiate or remove movers to match a requested count.
  • Map the offsets onto the 3D X/Z plane around the target.
  • Check the resulting formation with small and larger groups.

With this foundation in place, you are ready to build on the mover-count function as the full game’s crowd-changing gates are introduced.

Continue the guided project with Godot Mobile Game Development Mini-Degree. Take these crowd-spawning skills into the next stages of building your mobile game.

Did you come across any errors in this tutorial? Please let us know by completing this form and we’ll look into it!

FREE COURSES
Python Blog Image - How to Set Up Godot Crowd Spawning for a Hypercasual Game

FINAL DAYS: Unlock coding courses in Unity, Godot, Unreal, Python and more.