Moving a container with CharacterBody2d as walls while holding Rigidbody2d inside

Godot Version

Godot Engine v4.4.1.stable.mono.official.49a5bc7b6

Question

I’m trying create a cup with bubble tea pearls inside of it. I’m using StaticBody2D as walls for the cup and RigidBody2D for the pearls.

With the current setup, it works slightly well but if I move the cup too fast, the tunneling issue happens where the pearls fall outside of the cup.

Since forum does not allow new user to attach stuff so here is the link to the demo

Note: I found that increasing the size of the collision shapes help a bit but if I move much faster, the issue occurs again. I tried searching it up but couldn’t find the right keywords.

Current setup:
pearl.tscn

[gd_scene load_steps=5 format=3 uid="uid://bk45ipognydds"]

[ext_resource type="Script" uid="uid://dft8j6nqkphck" path="res://prototype/topping/pearl/pearl.gd" id="1_25gxw"]
[ext_resource type="Texture2D" uid="uid://j6gewbxp3f05" path="res://prototype/topping/pearl/assets/pearl.webp" id="1_pxne4"]

[sub_resource type="PhysicsMaterial" id="PhysicsMaterial_25gxw"]
friction = 0.7
bounce = 0.3

[sub_resource type="CircleShape2D" id="CircleShape2D_tdk6r"]
radius = 7.0

[node name="Pearl" type="RigidBody2D"]
collision_layer = 8
collision_mask = 12
physics_material_override = SubResource("PhysicsMaterial_25gxw")
can_sleep = false
continuous_cd = 2
script = ExtResource("1_25gxw")

[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
shape = SubResource("CircleShape2D_tdk6r")

[node name="CollisionPolygon2D" type="CollisionPolygon2D" parent="."]
visible = false
position = Vector2(-2.84217e-14, -1)
scale = Vector2(0.0622407, 0.0622407)
polygon = PackedVector2Array(23, -92.4, -8, -93.6, -8, -92.1, -30.1, -86.5, -33.5, -86.5, -55.5, -73.5, -57.8, -73.5, -79.8, -52.5, -81.2, -52.5, -94.1, -29.5, -95.6, -29.5, -101.5, -4.5, -103, -4.5, -103, 30.8, -94, 55.9, -94, 58.2, -75, 83.1, -75, 85.4, -52, 104.4, -52, 105.8, -30, 114.9, -30, 116.2, -9, 119.2, -9, 120.5, -6.8, 120.5, 26.2, 117.5, 30.5, 117.5, 57.5, 103.5, 59.7, 103.5, 81.7, 84.5, 83.1, 84.5, 99.1, 60.5, 100.5, 60.5, 108.5, 29.5, 110, 29.5, 110, 2.2, 107, -8.5, 107, -14, 94, -40, 94, -42.3, 85, -52.3, 85, -54.4, 67, -70.4, 67, -71.7, 39, -85.8, 39, -87.1, 23, -91.1)
disabled = true

[node name="Sprite2D" type="Sprite2D" parent="."]
position = Vector2(-2.84217e-14, -1)
scale = Vector2(0.0622407, 0.0622407)
texture = ExtResource("1_pxne4")

cup.tscn

[gd_scene load_steps=9 format=3 uid="uid://dy0majlmfkegw"]

[ext_resource type="Script" uid="uid://vl5vran0r25f" path="res://prototype/cup/cup.gd" id="1_ponjl"]
[ext_resource type="Texture2D" uid="uid://oqow1s2an4aa" path="res://icon.svg" id="2_cmrwc"]
[ext_resource type="PackedScene" uid="uid://bk45ipognydds" path="res://prototype/topping/pearl/pearl.tscn" id="3_qi7ja"]

[sub_resource type="RectangleShape2D" id="RectangleShape2D_msvy8"]
size = Vector2(80, 192)

[sub_resource type="RectangleShape2D" id="RectangleShape2D_kssxt"]
size = Vector2(16, 192)

[sub_resource type="RectangleShape2D" id="RectangleShape2D_qi7ja"]
size = Vector2(112, 11.5)

[sub_resource type="RectangleShape2D" id="RectangleShape2D_sqg1t"]
size = Vector2(16, 192)

[sub_resource type="RectangleShape2D" id="RectangleShape2D_u3gmq"]
size = Vector2(112, 16)

[node name="Cup" type="Area2D" groups=["cups"]]
collision_mask = 0
script = ExtResource("1_ponjl")

[node name="FillingTimer" type="Timer" parent="."]

[node name="Polygon2D" type="Polygon2D" parent="."]
color = Color(0.337255, 0.164706, 1, 0.905882)
polygon = PackedVector2Array(-40, -96, 40, -96, 40, 96, -40, 96)
uv = PackedVector2Array(-40, -96, 40, -96, 40, 96, -40, 96)

[node name="Label" type="Label" parent="."]
offset_left = -40.0
offset_top = -96.0
offset_right = 40.0
offset_bottom = 96.0
text = "Cup"
horizontal_alignment = 1
vertical_alignment = 1
metadata/_edit_use_anchors_ = true

[node name="Liquid" type="TextureProgressBar" parent="."]
offset_left = -40.0
offset_top = -96.0
offset_right = 40.0
offset_bottom = 96.0
fill_mode = 3
nine_patch_stretch = true
texture_progress = ExtResource("2_cmrwc")

[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
shape = SubResource("RectangleShape2D_msvy8")

[node name="Walls" type="CharacterBody2D" parent="."]
z_index = 1
collision_layer = 4
collision_mask = 8

[node name="Left" type="CollisionShape2D" parent="Walls" groups=["cup_walls"]]
position = Vector2(-48, 0)
shape = SubResource("RectangleShape2D_kssxt")
one_way_collision_margin = 10.0
debug_color = Color(0.852554, 0.303196, 0.551431, 0.42)

[node name="Bottom" type="CollisionShape2D" parent="Walls"]
position = Vector2(0, 101.75)
shape = SubResource("RectangleShape2D_qi7ja")
one_way_collision_margin = 10.0
debug_color = Color(0.852554, 0.303196, 0.551431, 0.42)

[node name="Right" type="CollisionShape2D" parent="Walls" groups=["cup_walls"]]
position = Vector2(48, 0)
shape = SubResource("RectangleShape2D_sqg1t")
one_way_collision_margin = 10.0
debug_color = Color(0.824591, 0.331672, 0.57583, 0.42)

[node name="Top" type="CollisionShape2D" parent="Walls"]
position = Vector2(-3.49691e-07, -104)
rotation = -3.14159
shape = SubResource("RectangleShape2D_u3gmq")
one_way_collision = true
debug_color = Color(0.852554, 0.303196, 0.551431, 0.42)

[node name="Pearl" parent="." instance=ExtResource("3_qi7ja")]
position = Vector2(16, 48)

[connection signal="input_event" from="." to="." method="_on_input_event"]
[connection signal="timeout" from="FillingTimer" to="Walls" method="fill_liquid"]

cup.gd

class_name Cup
extends Area2D

var dragging: bool = false
var offset: Vector2
var fill_percentage = 10
var in_area = false

@onready var liquid: TextureProgressBar = $Liquid
@export var drag_speed = 10


func _ready() -> void:
	pass

func _on_input_event(_viewport: Node, event: InputEvent, _shape_idx: int) -> void:
	if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT:
		if event.is_pressed():
			dragging = true
			offset = global_position - event.position
		else:
			stop_dragging()

func _physics_process(_delta: float) -> void:
	if dragging:
		var new_position = get_global_mouse_position() + offset
		position = new_position

func fill_liquid() -> void:
	if !in_area:
		print('Please place the cup in the area first!')
		return

	if liquid.value < 100:
		liquid.value += fill_percentage
	elif liquid.value <= 110:
		print('Please stop pouring!')
	else:
		print('Pleaseeeeeeeeeeeeee')

func stop_dragging() -> void:
	dragging = false

I probably would not use Staticbody2D as they are designed not to be moved. They can be moved via code, but as the docs say… When StaticBody2D is moved, it is teleported to its new position without affecting other physics bodies in its path. If this is not desired, use AnimatableBody2D instead.

I tried using AnimatableBody2D, it had the same effect- when I moved the cup too fast, the pearls tunnelled out

Sorry I just realised I was using CharacterBody2D not StaticBody2D → I kept changing around so I got confused