
I had a problem where I noticed that after you are informed of a collision, your linear velocity has already been effected by the collision, not so handy !
Initially I just tracked the previous frames velocity for the player, and for deltas past a limit I applied damage, this worked just fine, till I realised that it doesn't account for the velocity of other bodies...
As I was dealing with a decent number of RigidBody3D derived props, I didn't want to add the previous velocity tracking to each one, and what might happen if I forgot to add one to an existing or even new rigid body derivative....?
Then it hit me, if I tracked nodes being added to the "world" then I could track velocity for all rigid body derived classes, in one place.
As it happened I already had an autoload script which is responsible to destroying old levels and adding the next level to its scene tree etc, this would be a great place to add rigid body tracking.
If you don't have an autoload script, you can always add a dedicated autoload script just for the velocity tracking.
First we need some utility functions, on registering a body we add the body to an array, and also a dictionary of velocities indexed on the bodies instance id, we also connect to tree_exited passing an additional body parameter, the body exited signal allows us to remove bodies from the array and dictionary.
func _register_body(body: RigidBody3D) -> void:
if rigid_bodies.has(body):
return
rigid_bodies.append(body)
previous_velocities[body.get_instance_id()] = body.linear_velocity
body.tree_exited.connect(_on_body_exited.bind(body))
a function to register a node that is added is needed too
func _on_node_added(node: Node) -> void:
if node is RigidBody3D:
_register_body(node)
there is also a function to register any bodies that might exist as we kick off things, this is likely not needed but I put it at the end of the autoloads _ready function
In the _physics_process we update our tracked bodies
func _physics_process(_delta: float) -> void:
for body in rigid_bodies:
if is_instance_valid(body):
previous_velocities[body.get_instance_id()] = body.linear_velocity
and finally for our singleton a function to get previous velocity for bodies
func get_previous_velocity(body: RigidBody3D) -> Vector3:
if !is_instance_valid(body):
return Vector3.ZERO
return previous_velocities.get(
body.get_instance_id(),
Vector3.ZERO
)
Currently the only body I'm tracking damage for is the player, so lets look at the players code
Here MainScene is my autoload script (where the code above lives)
var was_colliding = false
func _integrate_forces(state: PhysicsDirectBodyState3D) -> void:
var contact_count = state.get_contact_count()
var is_colliding = contact_count > 0
if is_colliding and !was_colliding:
var my_previous_velocity:Vector3 = MainScene.get_previous_velocity(self)
var collision_speed:float = 0
for i in contact_count:
var other_body = state.get_contact_collider_object(i)
var other_previous_velocity = Vector3.ZERO
if other_body is RigidBody3D:
other_previous_velocity = \
MainScene.get_previous_velocity(other_body)
var relative_velocity = \
my_previous_velocity - other_previous_velocity
var relative_magnitue:float = relative_velocity.length()
if relative_magnitue > collision_speed:
collision_speed = relative_magnitue
# scale and clamp collision speed for damage and damage sound
Note that if the other collision object isn't a rigid body (like scenery) we still calculate collision speed just with a velocity of zero (it is static!)
I knew attempting to look at contact impulses was at best an indication of the collision speed, but this method is much more robust and provides consistent results.
Obviously we are paying a price for this convenience, each player collision there are a bunch of dictionary lookups, but generally each contact seems to give me usually in the order of 4-8 contacts so not terrible, as its only on the players collision, as for updating the previous velocities well that's a dictionary edit each rigid body every frame, actually really not that bad...
I hope you found this useful.Enjoy!