2D physics, objects able to clip through concave corners

Godot Version

Godot 4.5

Question

Hello all! This is my first Godot game, so I’m still learning how the engine works. I’m working on a top-down 2D game.

I’ve got a TileMapLayer node with a physics layer that looks like this for one of my sprites (representing a fence):

I also have a RigidBody2D node (representing leaves) with a circular CollisionShape2D. I’m calling apply_impulse in order to move these leaves around. I’m expecting the leaves to not be able to pass through the corner of the fence, but they are able to squeeze through. Here’s a screenshot showing this (unable to upload video because I’m a new forum user). Most of the leaves started within the fence boundary:

I’ve just realized that even if I paint the sprite’s physics layer such that it’s entirely filled in (i.e. not doing a concave polygon like in the first photo), the leaves are still able to clip through the corner.

Is there a way to fix this behavior?

It honestly looks like there is a gap in the corner, and RigidBodies just go through it.

Are leaves shapes tiny? Not seeing them on the screenshot.

I paint the sprite’s physics layer such that it’s entirely filled in (i.e. not doing a concave polygon like in the first photo), the leaves are still able to clip through the corner.

Can’t quite picture this, what do you mean? If there is no concave polygon, there is no corner, right?

I’m been assuming that the visible gap in the corner is just a quirk of the debug tool, because the actual collision shape of that tile shouldn’t have a gap in it. From my first screenshot, the shape of the physics layer is 1 solid polygon with no gap. I’m not sure though.

I’m also not sure why the leave’s collision box isn’t showing in the debugger, like it is for my player and the fence. But the leaves have a circular CollisionShape2D with a radius of 6:

I did just find some odd behavior that hopefully someone can help explain. Whenever I spawn leaves in at run-time, I use this function (that lives on my base_leaf scene that I describe below) to scale the leaf up or down a bit:

@onready var sprite: Sprite2D = $Sprite2D
@onready var collision_shape_2d: CollisionShape2D = $CollisionShape2D

func apply_uniform_scale(f: float) -> void:
	sprite.scale = Vector2(f, f);
	collision_shape_2d.shape.radius *= f;
	mass *= f;

This was working to visually scale the sprite and the mass, but I just noticed that commenting out the collision_shape_2d.shape.radius *= f; causes the corner clipping behavior to stop. I call the function like this: leaf.apply_uniform_scale(randf_range(0.6, 1));, so the scaling is only from 0.6 to 1.

I have a feeling its something to do with how I’ve structured my leaf scenes. I have a base_leaf scene with the following node hierarchy:

  • RigidBody2D
    • CollisionShape2D (circular shape, radius = 6px)
    • Sprite2D (no texture)

I never instantiate this one. I then have 6 leaf scenes that inherit from base_leaf. All I change on these 6 is the Sprite2D node so that I can have 6 leaf scenes with the same physics, but different sprites.

I found the problem.

I’m spawning 200 leaves, so that apply_uniform_scale function gets called 200 times. Even though this function is on the base_leaf scene, I assumed that each leaf would have its own collision_shape_2d.shape.radius, but by printing out this value, I see it’s trending toward 0 (because I’m only scaling leaves down in size):

6.0
5.56510210037231
4.49599838256836
4.13387012481689
3.34306025505066
2.69827842712402
2.32993006706238
2.31106328964233
1.89437830448151
1.42756378650665
1.36529529094696
1.14140701293945
...
0.00000000000000

Here’s the relevant parts of my base_leaf script:

extends RigidBody2D

@onready var sprite: Sprite2D = $Sprite2D
@onready var collision_shape_2d: CollisionShape2D = $CollisionShape2D

func _ready() -> void:
	add_to_group("leaves");

func apply_uniform_scale(f: float) -> void:
	print(collision_shape_2d.shape.radius)
	sprite.scale = Vector2(f, f);
	collision_shape_2d.shape.radius *= f;
	mass *= f;

My 6 other leaf scenes that inherit from base_leaf don’t have their own script. How does inheritance work in this case? How can I make sure that each inherited leaf gets it’s own collision_shape_2d.shape.radius value so that I’m not just changing the one on the base_leaf?

1 Like

Shape is a resource and resources are shared by default across instantiated scenes. You can enable “resource_local_to_scene” flag for the shape resource and the engine will duplicate it for each instantiated scene.

Alternatively you could uniformly scale the shape node instead.

1 Like

Gotcha, setting that resource_local_to_scene flag worked! Just to make sure I understand: by default, if a “parent” scene contains a Shape2D (or any other Resource node) then all instantiated scenes that inherit from that “parent” will share the same instance of the resource?

I don’t understand what you mean by this suggestion.

Resources are not nodes. They are different kinds of objects that hold some shared data (like mesh vertex data, or curve points). You typically assign them to node’s properties that need that data.

You noted that your scene structure is

RigidBody2D 
   CollisionShape2D
   Sprite2D

Those are nodes.
Your CollisionShape2D node has a CircleShape2D assigned to its shape property. CircleShape2D is a resource.

Resources are never duplicated on instantiation. All instantiated nodes will keep the reference to the same shared resource. Unless you enable that flag in which case, a fresh duplicate of that resource object will be created for each instantiated scene.

So to scale the shape, you can either change the radius in the resource object, as you’re currently doing (given that it has been duplicated via local to scene mechanism), or you can change the actual scale property on the CollisionShape2D node.

1 Like