Physically accurate Interactibles

Godot Version

4.2

Question

I dont know how to word the title, but to explain:

I have a player controller, and it has a Node3D which is always in front of the camera. I then have interactibles, which are of type RigidBody3D. I am trying to script some kind of holding of them. I know this would be easily done with just freezing the body and setting its position to be the position of the Node3D thats in front of the camera. BUT, i want it to be physically accurate.

Let me set an example;

if the Node3D is underground, i dont want the RigidBody3D to be also underground, but try to get to the position, while still taking into acount the collisions.

This also would mean that if the held RigidBody3D would be smashed into an object, the object would take the forces and for example break

I am asking here just for the part on getting the RigidBody3D to behave physically accurate, not how to make something break and etc…

Try to lerp the velocity when the player holds it to achieve this magnet-like effect.

so, i managed to do a semi accurate system, it is very janky, but is better than nothing, could be improved

Here is basically the code for moving the Interactible into its place while obeying the Colliders

var dir_to : Vector3 = interactible_pos.global_position - item_interaction.global_position

item_interaction.move_and_collide(dir_to * interactible_follow_speed * delta)
		
item_interaction.gravity_scale = 0

where:
item_interaction is the currently held interactible
interactible_pos is the position that it needs to go to

And i made it when the pos and the interactible are further apart, means the interactible got stuck somewhere, it applies a force in the direction to the position it was supposed to be and reseting gravity

if item_interaction.global_position.distance_to(interactible_pos.global_position) > 2:
		item_interaction.apply_impulse(dir_to)
		item_interaction.gravity_scale = 1

This aproach i made is very janky and has its own flaws, for example getting stuck on smaller colliders where it could just slide (As CharacterBody3D, can slide, but Rigidbody3D doesnt have that function, it only has move_and_collide).

This could be improved, i know that, i just dont have the knowledge on how to improve it.

I dont think this solves my problem tho, i am just sharing what is atleast 50% working.

Can you just connect them with a 6DOF joint and then adjust the properties of that joint via code to reel it in?

1 Like

@nutlike4693 would you mind telling me how exactly i could do this? i managed to make the interactible as node_b, but else than that it doesnt work, or better said, i dont know how it works xd

I will later today

I had something working like this in an earlier project, but I am having trouble now. Ran out of time trying to debug it.

I did whip up a little script that is a good start though.

Attach this script to the node that is doing the attacting.

In the inspector, set the item to be picked up.

I would also set the gravity scale of the item to be picked up to zero.

extends Node3D

@export var item_to_pickup: RigidBody3D
@export var attract_force := 10.0
@export var attraction_enabled := true

func _physics_process(delta):
	if item_to_pickup and item_to_pickup.is_inside_tree():
		if attraction_enabled:
			apply_attraction_force(delta)

func apply_attraction_force(delta):
	var direction = global_position - item_to_pickup.global_position
	var attract_force = direction.normalized() * attract_force
	item_to_pickup.apply_central_force(attract_force)

func set_attraction(enabled: bool):
	attraction_enabled = enabled

This could use some improvements for sure, but it is a simple start.

Issues to address:

  1. damping the force when it gets near so it doesnt oscillate

  2. applying additional force to cancel the “rotational” component to prevent it from “orbiting” the desired location.

OK. Made a few tweaks to make the behaviour better. Item now snaps into place if close enough. Oscillations are dampened at a distance and canceled up close. Lemme know if this works:

extends Node3D

# Exported variables for easy configuration in the Godot editor
@export var item_to_pickup: RigidBody3D  # The object to be attracted
@export var attract_force := 10.0  # Strength of the attraction force
@export var attraction_enabled := true  # Toggle for the attraction behavior
@export var disable_gravity := true  # Option to disable gravity on the attracted object
@export var damping_factor := 2  # Factor to dampen the object's movement
@export var pickup_radius := 0.05  # Radius within which the object snaps to the attractor
@export var dead_zone_radius := 0.001  # Radius within which the object matches velocity with the attractor

func _ready():
	# Initial setup when the node enters the scene tree
	if item_to_pickup:
		if disable_gravity:
			item_to_pickup.gravity_scale = 0  # Disable gravity if specified

func _physics_process(_delta):
	# Called every physics frame. 'delta' is the elapsed time since the previous frame.
	if item_to_pickup and item_to_pickup.is_inside_tree() and attraction_enabled:
		# Calculate distance between attractor and object
		var distance = global_position.distance_to(item_to_pickup.global_position)
		
		# Apply different behaviors based on the distance
		if distance <= dead_zone_radius:
			apply_velocity_matching()  # Match velocity when very close
		elif distance <= pickup_radius:
			apply_centering_impulse()  # Snap to center when within pickup radius
		else:
			apply_attraction_force()  # Apply attraction force when far

func apply_attraction_force():
	# Calculate and apply the attraction force with damping
	var direction = global_position - item_to_pickup.global_position
	direction = direction.normalized()
	var force = direction * attract_force
	var velocity = item_to_pickup.linear_velocity
	var damping_force = -velocity * damping_factor
	var total_force = force + damping_force
	item_to_pickup.apply_central_force(total_force)

func apply_centering_impulse():
	# Calculate and apply an impulse to center the object in one frame
	var displacement = global_position - item_to_pickup.global_position
	var required_velocity = displacement / get_physics_process_delta_time()
	var velocity_change = required_velocity - item_to_pickup.linear_velocity
	var impulse = velocity_change * item_to_pickup.mass
	item_to_pickup.apply_central_impulse(impulse)

func apply_velocity_matching():
	# Calculate and apply an impulse to match the object's velocity to the attractor
	var velocity_difference = -item_to_pickup.linear_velocity  # Assuming attractor is stationary
	var impulse = velocity_difference * item_to_pickup.mass
	item_to_pickup.apply_central_impulse(impulse)

func set_attraction(enabled: bool):
	# Toggle the attraction behavior
	attraction_enabled = enabled

func set_damping(value: float):
	# Set the damping factor and update the object's linear damping
	damping_factor = value

func set_pickup_radius(value: float):
	# Set the pickup radius
	pickup_radius = value

func set_dead_zone_radius(value: float):
	# Set the dead zone radius
	dead_zone_radius = value
1 Like

Hey, thank you very much, after a bit of tweaking to adapt this to my game it works and is what i was looking for :smiley:

1 Like

That’s great! Glad it worked for you.

Were any of the tweaks you made general enough to be worth sharing? Or were they all very specific to your game?

@nutlike4693
hey, sorry for late reply, i did need to modify it a little bit, but nothing major, just swapping some values with what i have, as i also have interactions with other stuff, such as vehicles and etc. but else than that the code works flawlesly, big thanks man!

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.