How to handle entity pooling and node assignment

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By danielmeridew


I am trying to create a bullet-hell type game similar to Vampire Survivors.

Due to the sheer number of entities, I am thinking about creating a singleton that pre-instances all my items, mobs and projectiles.

These entities all have movement/collision logic as you would expect.

I’m trying to determine if this is the best approach, and I have questions like:

  • should I instance them as a child of the pool_manager and then move them when required?
    • does this negate requirements for enabling/disabling collision/movement logic?
  • should they be directly instanced into their game node instead?
  • is this even the best approach?

I realise this question is fairly broad, I was hoping on some advice/guidance on a way to manage large number of entities efficiently as possible.

See work in progress code below if you are interested.

extends Node

const MOB_POOL_SIZE = 1000
const ITEM_POOL_SIZE = 500

const MobScene = preload("res://mobs/base_mob.tscn")
const ItemScene = preload("res://items/item.tscn")

var projectile_scenes = {
	"gauss_round": preload("res://equipment/projectiles/gauss_round.tscn")

var projectile_pools = {
	"gauss_round": []

var mob_pool = []
var item_pool = []
var projectile_pool = []

var mob_parent = null
var item_parent = null
var projectile_parent = null

func initialize_pools():
	_init_pool(MobScene, MOB_POOL_SIZE, mob_pool, mob_parent)
	_init_pool(ItemScene, ITEM_POOL_SIZE, item_pool, item_parent)
	for projectile_type in projectile_scenes.keys():
		_init_pool(projectile_scenes[projectile_type], PROJECTILE_POOL_SIZE, projectile_pools[projectile_type], projectile_parent)

func set_mob_parent(parent):
	mob_parent = parent

func set_item_parent(parent):
	item_parent = parent

func set_projectile_parent(parent):
	projectile_parent = parent

func _init_pool(scene, pool_size, pool, category_parent):
	for i in range(pool_size):
		var obj = scene.instantiate()
		obj.set_meta("original_collision_layer", obj.get("original_collision_layer"))
		if category_parent:

func acquire_mob():
	return _acquire_object_from_pool(mob_pool)

func release_mob(mob):
	_release_object_to_pool(mob, mob_pool, mob_parent)

func acquire_item():
	return _acquire_object_from_pool(item_pool)

func release_item(item):
	_release_object_to_pool(item, item_pool, item_parent)

func acquire_projectile(projectile_type):
	if projectile_type in projectile_pools:
		return _acquire_object_from_pool(projectile_pools[projectile_type])
	return null

func release_projectile(projectile, projectile_type):
	if projectile_type in projectile_pools:
		_release_object_to_pool(projectile, projectile_pools[projectile_type], projectile_parent)

func _acquire_object_from_pool(pool):
	for obj in pool:
		if not obj.visible:
			obj.collision_layer = obj.get("original_collision_layer")
			obj.sleeping = false
			return obj
	return null

func _release_object_to_pool(obj, pool, category_parent):
	if obj in pool:
		obj.sleeping = true
		if category_parent and obj.get_parent() == category_parent:
		obj.collision_layer = 1 << 19

Pool menager approach seems reasonable. The way I understand it, the only problem with large amount of entities is actually instantiating them. So You can menage their movement and collision however You please, but they all should be created beforehand and reused.

Inces | 2023-05-04 19:40