Probably not cutting a hole in the floor, I could see a World Boundry being used for the floor, let’s say on layer 1, then the hole can use an Area3D to disable any overlapping RigidBody’s mask 1. To catch objects on the sides an extra ring-shaped collision(s) can be added on layer 2.
As I see it this would make the objects fall regardless of their size. What about objects that get stuck because they don’t fit the whole yet? One would need to wiggle them out of the hole (using some sort of realistic collision) in order to free the hole up again.
I think in addition to the fact that the floor lets stuff fall through a gap one would need an actual ring-shaped physical body which synchronously to the floor gap grows in size and can push things around a bit.
As for the visuals, one can create a shader material for the ground which gives the illusion of a whole at the right spot through transparency and moves synchronously with the ring-shaped physics object through shader parameters.
I heard it was better to avoid CSG during production builds. Not sure about that tho.
Also, if using the CSG in the first place, why not just subtracting a CSG cylinder from a CSG surface? Instead he goes through this lengthy setup with stencil mask settings. Interesting, but I don’t understand why.
Until minute 4:30 it is about the visuals, only after it starts go be about collision – the important part for the right game feel. The trick that he mentions is kind of what we described earlier.
I only don’t get what is cyan doughnut was for. I have the feeling the entire setup should work without it, but I might have misunderstood the purpose.
Very similar approach with Area3D, but overall I think the idea is make it bit less jittery the objects on contact with hole, also object which doesn’t fall still affects other objects.
The original studio is “MoonActive” - I believe they used Unity in this case, but principles should be same in Godot.
I was trying to get a minimal setup working for hole mechanics myself: godot_the-hole on my Github.
I think I implemented it with much less, at least there is no non straight-forward setup. So far I think everything is quite simple.
The one exception is the hole scene itself – that is what makes you feel the physics when dropping stuff inside (these shapes are on layer 2, which is always on).
This can probably be done nicer and cleaner. For example I didn’t need a cyan doughnut. No CSG meshes.
These two signals (shown below) toggle if a fruit can fall into the hole or not; and if a fruit moves the right way it stays on top of the ground, while fruits always look for collisions on layer 2.
func _on_area_3d_body_entered(body: Node3D) -> void:
if body is MyFruit:
print('hi fruit')
body.sleeping = false
body.set_meta('over_hole', true)
body.set_collision_mask_value(1, false)
func _on_area_3d_body_exited(body: Node3D) -> void:
if body is MyFruit:
print('bye fruit')
body.set_collision_mask_value(1, true)
body.set_meta('over_hole', false)
That is because I didn’t focus on the fruit shapes yet, their colliders are just ball shapes and therefore hard to stack. But that shouldn’t be hard to add in.
My main focus was about to get the pushy hole mechanics going. Next I would probably make sure that the colliders are a better approximation to a circular shape than what I did before.
First would make sure here that the approximation of the circle is smooth enough to not cause any game feel disturbances. Second I would make the hole grow in size depending on some parameter.
Your video looks already much smoother regarding the whole hole game feel – except maybe that the fruits still stack on a collider outside the visible mesh, but that is trivial to fix.
Here is some code which arranges the collision shapes (called pillars) in a ring shape and adjusts to the size of the hole. Two things about it tho: I don’t know if it is a good idea to have so many overlapping collision shapes – probably not; and somehow the algorithm should also account for how many shapes are actually needed approximate the ring shape for bigger sizes. But I think both issues are probably best fixed together. So for now that’s a quick and dirty implementation.
Also the initial hole state should be updated in the ground shader and there is some bug with waking up the fruits from the sleep state. Sometimes they just don’t wake up, I didn’t investigate, but my first guess would be the hole’s top collider is too low on the y-axis.
Below the code (and update on my Github). Maybe that helps.
func _on_area_3d_2_body_exited(body: Node3D) -> void:
#body.owner.queue_free() # TODO: remove the fruits.
_grow_hole()
func _ready() -> void:
_grow_hole()
func _grow_hole() -> void:
print('+1UP')
var shape: CylinderShape3D = ring.shape
shape.radius += GROWTH_INCREMENT
var cylinder_mesh: CylinderMesh = mesh as CylinderMesh
cylinder_mesh.top_radius = shape.radius
cylinder_mesh.bottom_radius = shape.radius
grows_in_radius.emit(shape.radius)
_update_pilars()
func _update_pilars() -> void:
var cylinder: CylinderShape3D = ring.shape
create_collision_ring(static_body, cylinder, 30)
func create_collision_ring(
parent: Node3D,
cylinder_shape: CylinderShape3D,
shape_count: int,
) -> void:
for child: Node in parent.get_children():
child.queue_free()
var pilar_depth: float = 6.0
var pilar_dimension: float = 0.5
var radius: float = cylinder_shape.radius
for i in range(shape_count):
var angle: float = TAU * float(i) / float(shape_count)
var collision: CollisionShape3D = CollisionShape3D.new()
var shape: BoxShape3D = BoxShape3D.new()
shape.size = Vector3(pilar_dimension, pilar_depth, pilar_dimension)
collision.shape = shape
var pos_x: float = cos(angle) * radius
var pos_z: float = sin(angle) * radius
collision.position = Vector3(pos_x, -pilar_depth/2.0, pos_z)
collision.rotation.y = PI - angle
parent.add_child(collision)