I watched https://www.youtube.com/watch?v=gnxnmw5ryhg and wanted to recreate the first part in Godot. So I did… kinda
:
It’s kinda scary when they are all towering on top of each other and climbing any obstacle in their path ngl ![]()
It’s using Jolt, physics interpolation, and I had to tweak a bunch of values of the physics engine: lowering the physics ticks to 30, running the physics server in a separate thread,… I’m also using the RenderingServer and PhysicsServer3D directly so no nodes for the entities.
On my machine it can keep >60fps with 6000 entities running in the editor (until they surround you and everything dies
). Each entity is also a capsule which is not the best performant shape. I tried with spheres and it runs faster but the result is less impactful.
When exported 6000 entities stay at ~90fps. 7000 entities stay at ~60fps but I think they reach some of Jolt limits and it’s not stable. I tweaked some of the limits values but I’m not sure how much they help.
Here’s the Entity script if someone want to test it:
entity.gd
class_name Entity extends RefCounted
static var mesh: Mesh = CapsuleMesh.new()
static var shape: Shape3D = CapsuleShape3D.new()
var position: Vector3: set=_set_position, get=_get_position
var speed: float = 8.0
var target: Vector3
var floor_wall_angle = deg_to_rad(85)
var direction_angle = deg_to_rad(160)
var _body_rid: RID
var _instance_rid: RID
var PS = PhysicsServer3D
var RS = RenderingServer
var _old_transform: Transform3D
var _transform_interp: Transform3D
var _transform: Transform3D
func _init(world: World3D) -> void:
speed = randf_range(5.0, 8.0)
_body_rid = PS.body_create()
PS.body_set_space(_body_rid, world.space)
PS.body_set_mode(_body_rid, PhysicsServer3D.BODY_MODE_RIGID_LINEAR)
PS.body_set_state(_body_rid, PhysicsServer3D.BODY_STATE_CAN_SLEEP, false)
PS.body_set_enable_continuous_collision_detection(_body_rid, false)
PS.body_set_max_contacts_reported(_body_rid, 4)
PS.body_set_collision_layer(_body_rid, 2)
PS.body_set_collision_mask(_body_rid, 3)
shape.radius = 0.5
PS.body_add_shape(_body_rid, shape.get_rid())
#var t = Transform3D()
#t.origin = Vector3(0, -0.5, 0)
#PS.body_set_shape_transform(_body_rid, 0, t)
PS.body_reset_mass_properties(_body_rid)
PS.body_set_param(_body_rid, PhysicsServer3D.BODY_PARAM_MASS, 1.0)
PS.body_set_param(_body_rid, PhysicsServer3D.BODY_PARAM_FRICTION, 0.0)
PS.body_set_param(_body_rid, PhysicsServer3D.BODY_PARAM_LINEAR_DAMP, 1.0)
PS.body_set_param(_body_rid, PhysicsServer3D.BODY_PARAM_LINEAR_DAMP_MODE, PhysicsServer3D.BODY_DAMP_MODE_REPLACE)
PS.body_set_state_sync_callback(_body_rid, _on_state_sync)
_instance_rid = RS.instance_create()
RS.instance_set_scenario(_instance_rid, world.scenario)
RS.instance_set_base(_instance_rid, mesh.get_rid())
RS.frame_pre_draw.connect(_interpolate_transforms)
func _notification(what: int) -> void:
if what == NOTIFICATION_PREDELETE:
if _body_rid.is_valid():
PS.free_rid(_body_rid)
if _instance_rid.is_valid():
RS.free_rid(_instance_rid)
if RS.frame_pre_draw.is_connected(_interpolate_transforms):
RS.frame_pre_draw.disconnect(_interpolate_transforms)
func update(delta: float) -> void:
var direction = position.direction_to(target)
direction.y = 0.0
var motion = direction.normalized() * speed
var state = PS.body_get_direct_state(_body_rid)
motion.y = state.linear_velocity.y
state.linear_velocity = motion
var collisions = state.get_contact_count()
for i in collisions:
var normal = state.get_contact_local_normal(i)
var wall_dot = normal.dot(Vector3.UP)
if acos(wall_dot) >= floor_wall_angle:
var dir_dot = normal.dot(motion.normalized())
if acos(dir_dot) >= direction_angle:
state.apply_central_impulse(Vector3.UP * 1.0)
break
func _set_position(value: Vector3) -> void:
_transform.origin = value
_old_transform = _transform
PS.body_set_state(_body_rid, PhysicsServer3D.BODY_STATE_TRANSFORM, _transform)
func _get_position() -> Vector3:
return _transform.origin
func _interpolate_transforms() -> void:
var fraction = Engine.get_physics_interpolation_fraction()
_transform_interp.origin = _old_transform.origin.lerp(_transform.origin, fraction)
RS.instance_set_transform(_instance_rid, _transform_interp)
func _on_state_sync(state: PhysicsDirectBodyState3D) -> void:
_old_transform = _transform
_transform = state.transform
