How to handle Area2D instances with different collision shapes

Godot Version

4.5.1

Question

I have been programming in Godot for a while and there is an issue I’ve come upon many times without a solid answer. A lot of times when developing I will find myself creating a general Area2D node that I want to instance multiple times, but have a different CollisionShape2D in every instance. For example an enemy hitbox functions the same in every enemy but different enemies have larger / smaller hitboxes.

How am I supposed to implement this behavior cleanly? I want the Area2D node to abstract all the complex behavior and just assign a different CollisionShape2D to each instance, but as far as I’ve seen there is no clean way to expose the children. I know I can select editable children, but when instancing a lot of Area2D scenes those extra clicks become cumbersome as well as the other children that are not CollisionShape2D cluttering the scene tree.

Hi mojeime_2,

in order to scale the CollisionShape2D programmatically (in-code) you can initialize each instance of your scenes in a function (say set_data(…)) and then set up each instance by giving it its own CollisionShape2D and scaling it.

For example with two scenes (world and creature):

This is how you create instances with different values from inside your world:

extends Node2D

var creature_packed : PackedScene = preload("res://creature.tscn")
var arr_x    : Array = [0, 0, 1, 2]
var arr_y    : Array = [1, 2, 0, 1]
var arr_size : Array = [1.0, 0.6, 1.4, 2.0]

func _ready() -> void:
	for i in range(0, len(arr_x)):
		var x    : int = arr_x[i]
		var y    : int = arr_y[i]
		var size : float = arr_size[i]
		var creature_instance : MyCreature = creature_packed.instantiate()
		creature_instance.set_data(x, y, size)
		%objs.add_child(creature_instance)

And this is how each creature creates its own collision shape:

extends Node2D
class_name MyCreature

const grid_scale_factor : float = 20.0
const cshape_scale_factor : float = 20.0
const x_offset : float = 30.0
const y_offset : float = 30.0

# class-level variables only for future reference.
@export var mypos : Vector2 = Vector2(0, 0)
@export var myscale : float = 1.0

func set_data(x : int, y : int, size : float):
	print(str(x) + ", " + str(y) + ", " + str(size))
	# set class-level variables.
	mypos = Vector2(x, y)
	myscale = size
	# update creature's properties.
	position.x = x * grid_scale_factor + x_offset
	position.y = y * grid_scale_factor + y_offset

	# solution #1: scale the creature.
	#scale *= size # the collision shape scales with the entire scene.

	# solution #2: make a new collision shape and scale it.
	var rect : RectangleShape2D = RectangleShape2D.new()
	rect.size.x = size * cshape_scale_factor
	rect.size.y = size * cshape_scale_factor
	%cshape.shape = rect

The result is instances with differently sized collision shapes (see below).

Kind regards :four_leaf_clover:

1 Like

Why not just create the Area2D without a collision shape, and add the collision shape as a child of the Area2D in each scene it’s used.

1 Like

You mean one could make a group of areas who all have the same RectangleShape2D, instantiate that RectangleShape2D once, but share it among all members of the group? I think that should also be quite easy to implement. :thinking:

Do you think that would result in a notable performance boost or why bother with introducing groups?

As a downside, I could imagine that for scenarios where there is a lot of different RectangleShape2D sizes the number of groups would probably be equal to the amount of areas in those groups. In such cases the group management would be a bit tedious.