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.
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.
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).
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.
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.