Make VehicleBody3D detect CollisionShape3D from child scenes

Godot Version

4.2.1

Question

I am playing around with building a forklift truck that has swappable forks. I have noticed that the VehicleBody3D is only detecting CollisionShape3D that are direct children.

Is there a way to also make it detect the CollisionShape3D from child scenes? My idea is to build separate scenes per fork type and swap them during runtime.

Or what is the recommend best practice approach to get this done?

Max

You have to options:

@onready var shapes_container: Node2D = $ShapesContainer

func _ready() -> void:
	for child in shapes_container.get_children():
		if child is CollisionShape2D:
			child.reparent(self)
  • Manually setting up the shapes in the CollisionObject:
@onready var shapes_container: Node2D = $ShapesContainer

func _ready() -> void:
	for child in shapes_container.get_children():
		if child is CollisionShape2D:
			# Create a shape owner from the CollisionShape2D
			var shape_owner = create_shape_owner(child)
			# Save the shape owner in the metadata of the object
			# in case you want to modify the transform, one way collision, ...
			child.set_meta('__shape_owner', shape_owner) 
			# Add the shape
			shape_owner_add_shape(shape_owner, child.shape)
			# transform needs to be local to the CollisionObject
			shape_owner_set_transform(shape_owner, global_transform.affine_inverse() * child.global_transform)

Examples are for 2D but it should work for 3D too.

You can check the multiple shape_owner_* functions available here CollisionObject3D — Godot Engine (stable) documentation in English

@mrcdk thanks for the hint, the first approach works just fine for me :slight_smile:

Just as an extra note: I have added a meta tag for the collision shape so I can identify it easily later when swapping forks (since I will have to remove the Collision shape of the previous fork to replace it with the new one)

for child in fork.get_children():
		if child is CollisionShape3D:
			child.set_meta("forkCollisionShape3D", true)
			child.reparent(%Car/VehicleBody3D)

and then later when preparing the swap:

for child in %Car/VehicleBody3D.get_children():
		if child is CollisionShape3D && child.has_meta("forkCollisionShape3D"):
			child.queue_free()

Actually, I am not 100 % there yet …

I have a script attached to my fork scene that is handling the controller input to move the fork around, that part is working fine.

But when I move that scene around, it is not moving the collision shape with it since I reparented it which is obviously an issue now. What is the best way to fix this?

Or would it be easier to just delete and reparent the collision shape after every _physics_process cycle (which sounds like a not really good solution to me …)?

Maybe using a RemoteTransform3D pointing to the re-parented nodes.

But if you are moving the objects independently why do you want to re-parent the collision shapes to the VehicleBody3D?

If I don’t reparent it to the VehicleBody, it won’t be recognised as collision for the driving mechanics.

Ok, for now I am ignoring that CollisionShape issue because I just found out that the fork does not move properly anymore once the forklift takes a turn:

I know that the problem is that it is somehow not taking the new rotation into account but I don’t know how to fix it.

Here the code I am using to move the fork around:

func _physics_process(delta):
	
	var direction = Vector3.ZERO
	#print(direction)
	if Input.is_action_pressed("attachment_modifier"):
		rotation.x = (Input.get_action_strength("attachment_down") - Input.get_action_strength("attachment_up"))
	else:
		direction.x = Input.get_action_strength("attachment_left") - Input.get_action_strength("attachment_right") 
		direction.y = Input.get_action_strength("attachment_up") - Input.get_action_strength("attachment_down") 
		
	if direction != Vector3.ZERO:
		target_velocity.x = direction.x * horizontalSpeed
		target_velocity.y = direction.y * verticalSpeed
		
		print(global_position)
		
		if global_position.y < 0.1 && target_velocity.y < 0:
			target_velocity.y = 0	
				
		velocity = target_velocity