diff --git a/scenes/Main.tscn b/scenes/Main.tscn index a195a36..1a9b4d0 100644 --- a/scenes/Main.tscn +++ b/scenes/Main.tscn @@ -20,8 +20,8 @@ sky_material = SubResource("ProceduralSkyMaterial_d6oc0") background_mode = 2 sky = SubResource("Sky_ggc3l") -[sub_resource type="CylinderShape3D" id="CylinderShape3D_s5kna"] -radius = 5.0 +[sub_resource type="BoxShape3D" id="BoxShape3D_7ll4w"] +size = Vector3(5, 5, 5) [sub_resource type="PlaneMesh" id="PlaneMesh_usmhe"] size = Vector2(25, 25) @@ -45,8 +45,8 @@ transform = Transform3D(1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, 0, 2, [node name="WorldEnvironment" type="WorldEnvironment" parent="."] environment = SubResource("Environment_ptyca") -[node name="Boids" type="Node3D" parent="." node_paths=PackedStringArray("resources")] -transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 5, 0) +[node name="Boids" type="Node3D" parent="." node_paths=PackedStringArray("resources", "target")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2.5, 0) script = ExtResource("1_v84o5") resources = NodePath("../ResourcePreloader") start_count = 20 @@ -57,7 +57,11 @@ avoid_factor = 1.0 matching_factor = 0.02 min_speed = 5.0 max_speed = 10.0 -shape = SubResource("CylinderShape3D_s5kna") +shape = SubResource("BoxShape3D_7ll4w") +enable_wind = true +wind_velocity = Vector3(-2, 0, 0) +target = NodePath("../Magnet") +enable_perching = true [node name="ResourcePreloader" type="ResourcePreloader" parent="."] resources = [PackedStringArray("Boid", "Seagull", "SeagullTwo"), [ExtResource("2_did81"), ExtResource("3_4pkn6"), ExtResource("4_4rbpg")]] @@ -84,5 +88,8 @@ layout_mode = 2 text = "Touch events:" [node name="CSGMesh3D" type="CSGMesh3D" parent="."] +visible = false material_override = ExtResource("6_7a1uq") mesh = SubResource("PlaneMesh_usmhe") + +[node name="Magnet" type="Node3D" parent="."] diff --git a/scripts/Boid.gd b/scripts/Boid.gd index b137bc4..4f08641 100644 --- a/scripts/Boid.gd +++ b/scripts/Boid.gd @@ -5,6 +5,9 @@ class_name Boid @export var min_timer : float = 5 var velocity : Vector3 = Vector3(0, 0, 0) +var is_perching : bool = false + +var perching_timer : Timer = Timer.new() func _ready(): $Timer.wait_time = randf_range(min_timer, max_timer) @@ -18,3 +21,21 @@ func _on_timer_timeout(): $SeagullSound2.play() else: $SeagullSound1.play() + +func start_perching(min_perch_time : float, max_perch_time : float): + if is_perching: + return + + if not perching_timer.timeout.is_connected(stop_perching): + add_child(perching_timer) + perching_timer.one_shot = true + perching_timer.timeout.connect(stop_perching) + + is_perching = true + velocity = Vector3.UP + perching_timer.wait_time = randf_range(min_perch_time, max_perch_time) + perching_timer.start() + +func stop_perching(): + perching_timer.stop() + is_perching = false diff --git a/scripts/BoidsManager.gd b/scripts/BoidsManager.gd index a8b1256..b019bd1 100644 --- a/scripts/BoidsManager.gd +++ b/scripts/BoidsManager.gd @@ -1,5 +1,7 @@ extends Node3D +# Cool resources : http://www.kfish.org/boids/pseudocode.html + @export var resources : ResourcePreloader @export_subgroup("Initialisation") @@ -29,6 +31,23 @@ extends Node3D @export var gravity_vector : Vector3 = Vector3(0, -9.807, 0) @export var gravity_factor : float = 0.1 +@export_subgroup("Wind") + +@export var enable_wind : bool = false +@export var wind_velocity : Vector3 = Vector3.LEFT + +@export_subgroup("Magnet") + +@export var enable_magnet : bool = false +@export var target : Node3D +@export var magnet_factor : float = 1.0 + +@export_subgroup("Perching") + +@export var enable_perching : bool = false +@export var perching_max_time : float = 5.0 +@export var perching_min_time : float = 3.0 + var boids : Array[Boid] var boid_scene : PackedScene @@ -53,6 +72,15 @@ func _ready(): func _process(delta): for boid in boids: + # Perching strategy, only works if magnet not enabled + if boid.global_position.y < 0 and enable_perching and not enable_magnet: + boid.global_position.y = 0 + boid.start_perching(perching_min_time, perching_max_time) + + # Go to next boid if this one is perching, a timer on the boid will unperch it after some time + if boid.is_perching: + continue + var neighboring_boids : int = 0 var close : Vector3 = Vector3.ZERO @@ -86,18 +114,39 @@ func _process(delta): position_avg /= neighboring_boids velocity_avg /= neighboring_boids - boid.velocity = (boid.velocity + - (position_avg - boid.position) * centering_factor + - (velocity_avg - boid.velocity) * matching_factor - ) + # rule 1 : cohesion + # position_avg is the center of mass of other boids in view + var cohesion_velocity : Vector3 = (position_avg - boid.position) * centering_factor + # rule 2 : alignment + # velocity_avg is the "perceived velocity" of other boids in view + var alignment_velocity : Vector3 = (velocity_avg - boid.velocity) * matching_factor + + boid.velocity = (boid.velocity + + cohesion_velocity + + alignment_velocity + ) + + # rule 3 : avoidance # Add avoidance contribution to velocity boid.velocity = boid.velocity + (close * avoid_factor) # Edge avoidance boid.velocity = apply_edge_avoidance(boid.position, boid.velocity) + + # Gravity + if use_gravity: + boid.velocity = apply_gravity(boid.velocity, delta) - boid.velocity = apply_gravity(boid.velocity, delta) + # Wind + if enable_wind: + boid.velocity += wind_velocity * delta + + # Magnet + if enable_magnet and target != null: + boid.velocity += (target.global_position - boid.global_position).normalized() + + # Speed limits boid.velocity = apply_speed_limits(boid.velocity) # Update position, finally! @@ -161,9 +210,7 @@ func apply_sphere_edge_avoidance(position : Vector3, velocity : Vector3, sphere func apply_gravity(velocity : Vector3, delta : float) -> Vector3: - if use_gravity: - velocity += gravity_vector * gravity_factor * delta - + velocity += gravity_vector * gravity_factor * delta return velocity diff --git a/scripts/CameraControl.gd b/scripts/CameraControl.gd index b521683..9a92292 100644 --- a/scripts/CameraControl.gd +++ b/scripts/CameraControl.gd @@ -23,6 +23,11 @@ func _process(delta): func _unhandled_input(event): + if event is InputEventMouseButton: + if event.button_index == MouseButton.MOUSE_BUTTON_WHEEL_UP: + $Tilt/Camera3D.position.z -= 0.1 + elif event.button_index == MouseButton.MOUSE_BUTTON_WHEEL_DOWN: + $Tilt/Camera3D.position.z += 0.1 if event is InputEventScreenTouch: if event.pressed: events[event.index] = event @@ -42,6 +47,6 @@ func _unhandled_input(event): drag_label.text = "Drag distance: {dist}".format({"dist": drag_distance}) if abs(drag_distance - last_drag_distance) > zoom_sensitivity: var new_zoom = zoom_speed if drag_distance < last_drag_distance else zoom_speed - new_zoom = clamp($Camera3D.position.z + new_zoom, min_zoom, max_zoom) - $Camera3D.position.z = new_zoom + new_zoom = clamp($Tilt/Camera3D.position.z + new_zoom, min_zoom, max_zoom) + $Tilt/Camera3D.position.z = new_zoom last_drag_distance = drag_distance