|
extends Node3D |
|
|
|
## The target Node3D each entity will follow |
|
@export var target: Node3D |
|
## The initial entities to spawn |
|
@export_range(0, 8000, 1, "or_greater") var initial_entities: int = 1_000 |
|
## The amount of entities to spawn when pressing "spawn" |
|
@export_range(0, 1000, 1, "or_greater") var entities_to_spawn: int = 1_00 |
|
|
|
@export_group("Mesh Settings") |
|
## The entity mesh |
|
@export var mesh: Mesh |
|
## The mesh material |
|
@export var material: Material |
|
|
|
@export_group("Physics Settings") |
|
## The shape. Must be a SphereShape3D as it's the most performant shape. |
|
@export var shape: SphereShape3D |
|
## The offset of the shape with respect of the mesh |
|
@export var shape_offset: Vector3 |
|
## The jump force. Only works if max_contact_reported is > 0 |
|
@export_range(0, 10, 0.1, "or_greater") var jump_force: float = 1.0 |
|
## The body's mass |
|
@export_range(0, 10, 0.1, "or_greater") var mass: float = 2 |
|
@export_flags_3d_physics var collision_layer: int |
|
@export_flags_3d_physics var collision_mask: int |
|
|
|
@export_group("Limits") |
|
## How many updates to the direction will be with relation to the physics frames: physics_frame % update_direction_frames == 0 |
|
@export_range(1, 30, 1, "or_greater") var update_direction_frames: int = 8 |
|
## How many updates to the speed will be with relation to the physics frames: physics_frame % update_speed == 0 |
|
@export_range(1, 60, 1, "or_greater") var update_speed_frames: int = 30 |
|
## The amount of max contacts to report |
|
@export_range(0, 32, 1, "prefer_slider") var max_contacts_reported: int = 2 |
|
## The angle to detect a wall. |
|
@export_range(0, 360, 0.001, "radians_as_degrees") var floor_wall_angle = deg_to_rad(65) |
|
## The angle to detect the direction of travel. This will limit when the entity will jump. |
|
@export_range(0, 360, 0.001, "radians_as_degrees") var direction_angle = deg_to_rad(120) |
|
|
|
var _body_rids: Array[RID] |
|
var _instance_rids: Array[RID] |
|
var _speeds: PackedFloat32Array |
|
var _positions: PackedVector3Array |
|
var _prev_positions: PackedVector3Array |
|
var _prev_directions: PackedVector3Array |
|
var _directions: PackedVector3Array |
|
var _instances_visible := true |
|
|
|
var PS = PhysicsServer3D |
|
var RS = RenderingServer |
|
|
|
@onready var entity_count_label: Label = $EntityCountLabel |
|
|
|
|
|
func _ready() -> void: |
|
RS.frame_pre_draw.connect(_interpolate_transforms) |
|
spawn(initial_entities) |
|
|
|
func _notification(what: int) -> void: |
|
if what == NOTIFICATION_PREDELETE: |
|
if RS.frame_pre_draw.is_connected(_interpolate_transforms): |
|
RS.frame_pre_draw.disconnect(_interpolate_transforms) |
|
|
|
for rid in _body_rids: |
|
if rid.is_valid(): |
|
PS.free_rid(rid) |
|
|
|
for rid in _instance_rids: |
|
if rid.is_valid(): |
|
RS.free_rid(rid) |
|
|
|
|
|
func _physics_process(_delta: float) -> void: |
|
var target_position = target.global_position |
|
var update_directions = Engine.get_physics_frames() % update_direction_frames == 0 |
|
var update_speed = Engine.get_physics_frames() % update_speed_frames == 0 |
|
for i in _body_rids.size(): |
|
var body_rid = _body_rids[i] |
|
var speed = _speeds[i] |
|
var current_position = _positions[i] |
|
var direction = _directions[i] |
|
if update_directions: |
|
_prev_directions.set(i, direction) |
|
direction = current_position.direction_to(target_position) |
|
direction.y = 0.0 |
|
_directions.set(i, direction) |
|
|
|
if update_speed: |
|
speed = randf_range(3, 10) |
|
_speeds.set(i, speed) |
|
var instance_rid = _instance_rids[i] |
|
if not RenderingServer.get_current_rendering_method() == "gl_compatibility": |
|
RS.instance_geometry_set_shader_parameter(instance_rid, "speed", speed * 10.0) |
|
|
|
var state = PS.body_get_direct_state(body_rid) |
|
|
|
var motion = direction.normalized() * speed |
|
|
|
motion.y = state.linear_velocity.y |
|
state.linear_velocity = motion |
|
|
|
var collisions = state.get_contact_count() |
|
for collision_index in collisions: |
|
|
|
var normal = state.get_contact_local_normal(collision_index) |
|
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 * jump_force) |
|
break |
|
|
|
|
|
func _unhandled_input(event: InputEvent) -> void: |
|
if event.is_action_pressed(&"spawn"): |
|
spawn(entities_to_spawn) |
|
|
|
if event.is_action_pressed(&"toggle_visible"): |
|
_instances_visible = not _instances_visible |
|
for rid in _instance_rids: |
|
RS.instance_set_visible(rid, _instances_visible) |
|
|
|
|
|
func spawn(amount: int) -> void: |
|
for i in amount: |
|
var min_offset = -50 |
|
var max_offset = 50 |
|
var initial_position = Vector3(randf_range(min_offset, max_offset), 3.0, randf_range(min_offset, max_offset)) |
|
_create_entity(initial_position) |
|
entity_count_label.text = "E: %s" % _body_rids.size() |
|
|
|
|
|
func _create_entity(initial_position: Vector3) -> void: |
|
var world = get_world_3d() |
|
|
|
var speed = randf_range(3, 10) |
|
var temp_transform = Transform3D() |
|
|
|
var 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_max_contacts_reported(body_rid, max_contacts_reported) |
|
PS.body_set_collision_layer(body_rid, collision_layer) |
|
PS.body_set_collision_mask(body_rid, collision_mask) |
|
|
|
PS.body_add_shape(body_rid, shape.get_rid()) |
|
temp_transform.origin = shape_offset |
|
PS.body_set_shape_transform(body_rid, 0, temp_transform) |
|
|
|
PS.body_reset_mass_properties(body_rid) |
|
PS.body_set_param(body_rid, PhysicsServer3D.BODY_PARAM_MASS, mass) |
|
PS.body_set_param(body_rid, PhysicsServer3D.BODY_PARAM_FRICTION, 0.0) |
|
PS.body_set_param(body_rid, PhysicsServer3D.BODY_PARAM_LINEAR_DAMP, 0.0) |
|
PS.body_set_param(body_rid, PhysicsServer3D.BODY_PARAM_LINEAR_DAMP_MODE, PhysicsServer3D.BODY_DAMP_MODE_REPLACE) |
|
|
|
_body_rids.append(body_rid) |
|
|
|
var idx = _body_rids.size() - 1 |
|
|
|
PS.body_attach_object_instance_id(body_rid, idx) |
|
|
|
PS.body_set_state_sync_callback(body_rid, _on_state_sync.bind(idx)) |
|
|
|
_positions.append(initial_position) |
|
_prev_positions.append(initial_position) |
|
|
|
temp_transform.origin = initial_position |
|
PS.body_set_state(body_rid, PhysicsServer3D.BODY_STATE_TRANSFORM, temp_transform) |
|
|
|
var instance_rid = RS.instance_create() |
|
RS.instance_set_scenario(instance_rid, world.scenario) |
|
RS.instance_set_base(instance_rid, mesh.get_rid()) |
|
if is_instance_valid(material): |
|
RS.instance_set_surface_override_material(instance_rid, 0, material.get_rid()) |
|
RS.instance_set_visible(instance_rid, _instances_visible) |
|
RS.instance_geometry_set_lod_bias(instance_rid, 1) |
|
|
|
if not RenderingServer.get_current_rendering_method() == "gl_compatibility": |
|
RS.instance_geometry_set_shader_parameter(instance_rid, "speed", speed * 10.0) |
|
|
|
_instance_rids.append(instance_rid) |
|
_speeds.append(speed) |
|
|
|
_prev_directions.resize(_body_rids.size()) |
|
_directions.resize(_body_rids.size()) |
|
|
|
|
|
func _on_state_sync(state: PhysicsDirectBodyState3D, idx: int) -> void: |
|
_prev_positions[idx] = _positions[idx] |
|
_positions[idx] = state.transform.origin |
|
|
|
|
|
func _interpolate_transforms() -> void: |
|
var fraction = Engine.get_physics_interpolation_fraction() |
|
var dir_delta = remap(Engine.get_physics_frames() % update_direction_frames, 0, update_direction_frames, 0, 1) |
|
for i in _instance_rids.size(): |
|
var rid = _instance_rids[i] |
|
var prev_position = _prev_positions[i] |
|
var current_position = _positions[i] |
|
var direction = _prev_directions[i].lerp(_directions[i], dir_delta) |
|
var transform_interpolated = Transform3D() |
|
if not direction.is_zero_approx(): |
|
transform_interpolated = transform_interpolated.looking_at(direction, Vector3.UP, true) |
|
transform_interpolated.origin = prev_position.lerp(current_position, fraction) |
|
RS.instance_set_transform(rid, transform_interpolated) |