diff --git a/addons/boid_2d/boid.gd b/addons/boid_2d/boid.gd
deleted file mode 100644
index 1c8d3b5..0000000
--- a/addons/boid_2d/boid.gd
+++ /dev/null
@@ -1,121 +0,0 @@
-extends Area2D
-class_name Boid
-
-
-## sets the `RayCast2D` used to detect walls.
-@export var wallcast: RayCast2D
-## sets the `Area2D` used for vision (seeing other boids).
-@export var vision: Area2D
-## sets the rotate timer, allowing boids to perform random rotations based on the timer's timeout signal.
-@export var rotate_timer: Timer
-
-@export_group("properties")
-## controls the target (max) speed.
-@export var target_speed := 6.0
-## controls how much other boids affect this boid.
-## higher values will make them more dispersed.
-@export var steer_away_factor := 40
-## controls whether or not to run collisions before running boid calculations.
-## enabling this can help reduce boids escaping colliders, especially if they are following something.
-@export var collide_first := false
-
-@export_group("follow")
-## controls which node to try and follow, if any
-@export var follow_point: Node2D
-## controls the radius at which the boid will target, instead of the target directly
-@export var follow_radius := 100.0
-
-var last_follow_pos: Vector2 = Vector2.ZERO
-var follow_target: Vector2
-var speed := target_speed
-var vel := Vector2.ZERO
-var boidsSeen: Dictionary = {}
-
-
-func _ready() -> void:
- assert(wallcast, "boid invalid: wallcast (RayCast3D) not assigned")
- assert(vision, "boid invalid: vision (Area2D) not assigned")
- if rotate_timer:
- rotate_timer.timeout.connect(_on_rotate_timer_timeout)
-
-
-func _physics_process(delta: float) -> void:
- if collide_first:
- _process_collision()
- _process_boids()
- else:
- _process_boids()
- _process_collision()
- # move boid
- var vel_dir := vel.normalized()
- # fix if a boid stops by getting seperated and its vel being cancelled at the same time
- if vel_dir.is_zero_approx(): vel_dir = Vector2.RIGHT
- vel = vel_dir * speed
- global_position += vel
- # rotate boid
- global_rotation = atan2(vel_dir.y, vel_dir.x)
-
-
-func _process_boids() -> void:
- var numOfBoids := boidsSeen.size()
- var avgVel := Vector2.ZERO
- var avgPos := Vector2.ZERO
- var steerAway := Vector2.ZERO
- if numOfBoids > 0:
- for boid: Boid in boidsSeen.values():
- avgVel += boid.vel; avgPos += boid.global_position
- var dist := boid.global_position - global_position
- steerAway -= dist * (steer_away_factor / dist.length())
-
- # apply follow point vel
- if follow_point:
- var dist_to_follow := global_position.distance_to(follow_point.global_position)
- if global_position.distance_to(follow_target) < 10.0 or dist_to_follow > follow_radius:
- _calc_follow_target()
- # slow down speed when nearing target
- speed = maxf(0.0, lerpf(target_speed, 0.0, follow_radius / dist_to_follow))
- var target_vel := (follow_point.global_position - last_follow_pos) * Engine.physics_ticks_per_second
- avgVel += target_vel
- avgPos += follow_target
- var dist := follow_target - global_position
- steerAway -= dist * ((steer_away_factor + follow_radius) / dist.length())
- numOfBoids += 1
- last_follow_pos = follow_point.global_position
-
- if numOfBoids > 0:
- avgVel /= numOfBoids
- vel += (avgVel - vel) / 2
-
- avgPos /= numOfBoids
- vel += avgPos - global_position
-
- steerAway /= numOfBoids
- vel += steerAway
-
-
-func _calc_follow_target() -> void:
- var follow_vec := follow_point.global_position - global_position
- var target_length := follow_vec.length() + follow_radius
- follow_target = global_position + follow_vec.normalized() * target_length
-
-
-func _process_collision() -> void:
- wallcast.force_raycast_update()
- if not wallcast.is_colliding(): return
-
- var col_normal: Vector2 = wallcast.get_collision_normal()
- vel = vel.bounce(col_normal)
-
-
-func _on_vision_area_entered(area: Area2D) -> void:
- if area == self: return
- boidsSeen[area.get_instance_id()] = area
-
-
-func _on_vision_area_exited(area: Area2D) -> void:
- boidsSeen.erase(area.get_instance_id())
-
-
-func _on_rotate_timer_timeout() -> void:
- vel -= Vector2(randf(), randf()) * speed
- rotate_timer.start()
diff --git a/addons/boid_2d/boid.tscn b/addons/boid_2d/boid.tscn
deleted file mode 100644
index 0217c0e..0000000
--- a/addons/boid_2d/boid.tscn
+++ /dev/null
@@ -1,34 +0,0 @@
-[gd_scene load_steps=4 format=3 uid="uid://bq7s2yf0fohes"]
-
-[ext_resource type="Script" path="res://addons/boid_2d/boid.gd" id="1_xwhwb"]
-
-[sub_resource type="RectangleShape2D" id="RectangleShape2D_ackok"]
-size = Vector2(32, 16)
-
-[sub_resource type="RectangleShape2D" id="RectangleShape2D_ipkm3"]
-size = Vector2(60, 60)
-
-[node name="Boid" type="Area2D" node_paths=PackedStringArray("wallcast", "vision")]
-collision_mask = 0
-monitoring = false
-script = ExtResource("1_xwhwb")
-wallcast = NodePath("WallCast")
-vision = NodePath("Vision")
-
-[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
-shape = SubResource("RectangleShape2D_ackok")
-
-[node name="WallCast" type="RayCast2D" parent="."]
-enabled = false
-target_position = Vector2(50, 0)
-
-[node name="Vision" type="Area2D" parent="."]
-collision_layer = 0
-monitorable = false
-
-[node name="CollisionShape2D" type="CollisionShape2D" parent="Vision"]
-position = Vector2(42, 0)
-shape = SubResource("RectangleShape2D_ipkm3")
-
-[connection signal="area_entered" from="Vision" to="." method="_on_vision_area_entered"]
-[connection signal="area_exited" from="Vision" to="." method="_on_vision_area_exited"]
diff --git a/addons/boid_2d/boid_2d.gd b/addons/boid_2d/boid_2d.gd
deleted file mode 100644
index 1909b35..0000000
--- a/addons/boid_2d/boid_2d.gd
+++ /dev/null
@@ -1,10 +0,0 @@
-@tool
-extends EditorPlugin
-
-
-func _enter_tree() -> void:
- add_custom_type("Boid2D", "Area2D", preload("boid.gd"), preload("boid_2d.svg"))
-
-
-func _exit_tree() -> void:
- remove_custom_type("Boid2D")
diff --git a/addons/boid_2d/plugin.cfg b/addons/boid_2d/plugin.cfg
deleted file mode 100644
index e80610e..0000000
--- a/addons/boid_2d/plugin.cfg
+++ /dev/null
@@ -1,7 +0,0 @@
-[plugin]
-
-name="Boid2D"
-description="Addon for implementing boids / flocking in Godot."
-author="yusdacra"
-version="0.1"
-script="boid_2d.gd"
diff --git a/addons/boids/boid_2d/boid_2d.gd b/addons/boids/boid_2d/boid_2d.gd
new file mode 100644
index 0000000..c786ae9
--- /dev/null
+++ b/addons/boids/boid_2d/boid_2d.gd
@@ -0,0 +1,24 @@
+extends Node2D
+class_name Boid2D
+
+## controls the properties of this boid, deciding how it will behave.
+@export var properties: BoidProperties
+
+# position is .position since this is base Node2D
+var velocity := Vector2.ZERO
+
+# this is assigned by the flock, if this boid is a child of it
+var flock: Flock
+
+## applies some force to this boid.
+func apply_force(spatial_force: Vector3) -> void:
+ var force := Vector2(spatial_force.x, spatial_force.y)
+ velocity += force
+ velocity = velocity.limit_length(properties.max_speed)
+ position += velocity * BoidManager.SIMULATION_RATE
+
+func _get_boid_position() -> Vector3:
+ return Vector3(position.x, position.y, 0.0)
+
+func _get_boid_velocity() -> Vector3:
+ return Vector3(velocity.x, velocity.y, 0.0)
diff --git a/addons/boids/boid_2d/boid_2d.svg b/addons/boids/boid_2d/boid_2d.svg
new file mode 100644
index 0000000..78121d3
--- /dev/null
+++ b/addons/boids/boid_2d/boid_2d.svg
@@ -0,0 +1,68 @@
+
+
+
+
diff --git a/addons/boid_2d/boid_2d.svg.import b/addons/boids/boid_2d/boid_2d.svg.import
similarity index 75%
rename from addons/boid_2d/boid_2d.svg.import
rename to addons/boids/boid_2d/boid_2d.svg.import
index a686f80..4e23251 100644
--- a/addons/boid_2d/boid_2d.svg.import
+++ b/addons/boids/boid_2d/boid_2d.svg.import
@@ -3,7 +3,7 @@
importer="texture"
type="CompressedTexture2D"
uid="uid://c32akbxu1rkj8"
-path="res://.godot/imported/boid_2d.svg-172f46790795ecd4cef523288fe60368.ctex"
+path="res://.godot/imported/boid_2d.svg-4548c85817be0f2e607fa4357493b734.ctex"
metadata={
"has_editor_variant": true,
"vram_texture": false
@@ -11,8 +11,8 @@ metadata={
[deps]
-source_file="res://addons/boid_2d/boid_2d.svg"
-dest_files=["res://.godot/imported/boid_2d.svg-172f46790795ecd4cef523288fe60368.ctex"]
+source_file="res://addons/boids/boid_2d/boid_2d.svg"
+dest_files=["res://.godot/imported/boid_2d.svg-4548c85817be0f2e607fa4357493b734.ctex"]
[params]
diff --git a/addons/boids/boid_3d/boid_3d.gd b/addons/boids/boid_3d/boid_3d.gd
new file mode 100644
index 0000000..f1c748c
--- /dev/null
+++ b/addons/boids/boid_3d/boid_3d.gd
@@ -0,0 +1,23 @@
+extends Node3D
+class_name Boid3D
+
+## controls the properties of this boid, deciding how it will behave.
+@export var properties: BoidProperties
+
+# position is .position since this is base Node2D
+var velocity := Vector3.ZERO
+
+# this is assigned by the flock, if this boid is a child of it
+var flock: Flock
+
+## applies some force to this boid.
+func apply_force(force: Vector3) -> void:
+ velocity += force
+ velocity = velocity.limit_length(properties.max_speed)
+ position += velocity * BoidManager.SIMULATION_RATE
+
+func _get_boid_position() -> Vector3:
+ return position
+
+func _get_boid_velocity() -> Vector3:
+ return velocity
diff --git a/addons/boids/boid_3d/boid_3d.svg b/addons/boids/boid_3d/boid_3d.svg
new file mode 100644
index 0000000..9cc976b
--- /dev/null
+++ b/addons/boids/boid_3d/boid_3d.svg
@@ -0,0 +1,68 @@
+
+
+
+
diff --git a/addons/boids/boid_3d/boid_3d.svg.import b/addons/boids/boid_3d/boid_3d.svg.import
new file mode 100644
index 0000000..9974223
--- /dev/null
+++ b/addons/boids/boid_3d/boid_3d.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://drjkd138np2bs"
+path="res://.godot/imported/boid_3d.svg-88c9926717ca573fea33e015b5e5eabe.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/boids/boid_3d/boid_3d.svg"
+dest_files=["res://.godot/imported/boid_3d.svg-88c9926717ca573fea33e015b5e5eabe.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/boids/boid_manager.gd b/addons/boids/boid_manager.gd
new file mode 100644
index 0000000..736b629
--- /dev/null
+++ b/addons/boids/boid_manager.gd
@@ -0,0 +1,147 @@
+extends Node
+
+# parallelize the work into a new task per n boids
+# this seems to help with 1000 boids in a single flock from 400ms to 180ms (before quadtrees)
+const PARALLELIZATION_RATE: int = 50 # 50 seems to be the best value?
+const EPSILON: float = 0.00001
+# simulate per n physics frame ticks
+var SIMULATION_RATE: int = 1
+
+var flocks: Dictionary = {}
+
+func _ready() -> void:
+ get_tree().node_added.connect(_register_flock)
+ get_tree().node_removed.connect(_unregister_flock)
+
+ _init_register_flock()
+
+func _init_register_flock(node: Node = get_tree().root) -> void:
+ _register_flock(node)
+ for child: Node in node.get_children():
+ _init_register_flock(child)
+
+func _register_flock(maybe_flock: Node) -> void:
+ if maybe_flock is not Flock: return
+ flocks[maybe_flock.get_instance_id()] = maybe_flock
+ print_verbose("[BoidManager] flock ", maybe_flock, " registered")
+
+func _unregister_flock(maybe_flock: Node) -> void:
+ if maybe_flock is not Flock: return
+ flocks.erase(maybe_flock.get_instance_id())
+ print_verbose("[BoidManager] flock ", maybe_flock, " unregistered")
+
+func _physics_process(delta: float) -> void:
+ # run the simulation at a given rate
+ if Engine.get_physics_frames() % SIMULATION_RATE == 0:
+ _process_boids()
+
+func _process_boids() -> void:
+ # organize the work into tasks
+ var boid_count := 0
+ var boids_array_idx := 0
+ var args_arrays: Array[Array] = [[]]
+ var force_arrays: Array[PackedVector3Array] = [PackedVector3Array([])]
+ for flock: Flock in flocks.values():
+ var flock_args := _pack_calc_args_flock(flock)
+ for boid in flock.boids.values():
+ var args := _pack_calc_args_boid(boid, flock_args.duplicate())
+ args_arrays[boids_array_idx].append(args)
+ force_arrays[boids_array_idx].append(Vector3.ZERO)
+ boid_count += 1
+ if boid_count > PARALLELIZATION_RATE:
+ boid_count = 0
+ boids_array_idx += 1
+ args_arrays.append([])
+ force_arrays.append(PackedVector3Array([]))
+
+ # distribute tasks to threads
+ # TODO: calculate on main thread if there arent enough boids to warrant doing this
+ var calc_task := WorkerThreadPool.add_group_task(
+ _calculate_boid_parallel.bind(args_arrays, force_arrays),
+ args_arrays.size(),
+ args_arrays.size(),
+ true,
+ )
+ WorkerThreadPool.wait_for_group_task_completion(calc_task)
+
+ # apply the forces
+ for idx in args_arrays.size():
+ var args = args_arrays[idx]
+ var forces = force_arrays[idx]
+ for iidx in args.size():
+ args[iidx].boid.apply_force(forces[iidx])
+
+func _pack_calc_args_flock(flock: Flock) -> Dictionary:
+ var others_pos := PackedVector3Array([])
+ var others_vel := PackedVector3Array([])
+ for aboid in flock.boids.values():
+ others_pos.append(aboid._get_boid_position())
+ others_vel.append(aboid._get_boid_velocity())
+ var flock_args := {
+ 'others_pos': others_pos,
+ 'others_vel': others_vel,
+ 'goal_seperation': flock.goal_seperation,
+ 'goal_alignment': flock.goal_alignment,
+ 'goal_cohesion': flock.goal_cohesion,
+ }
+ if flock.target != null:
+ flock_args['target_position'] = flock.target.global_position
+ return flock_args
+
+func _pack_calc_args_boid(boid, args: Dictionary) -> Dictionary:
+ args['boid'] = boid
+ args['self_props'] = boid.properties
+ args['self_vel'] = boid._get_boid_velocity()
+ args['self_pos'] = boid._get_boid_position()
+ return args
+
+func _calculate_boid_parallel(idx: int, read_from: Array[Array], write_to: Array[PackedVector3Array]) -> void:
+ var args = read_from[idx]
+ var forces = write_to[idx]
+ for iidx in args.size():
+ var force = _calculate_boid(args[iidx])
+ forces[iidx] = force
+
+func _calculate_boid(args: Dictionary) -> Vector3:
+ var boid_properties: BoidProperties = args.self_props
+ var boid_pos: Vector3 = args.self_pos
+ var boid_vel: Vector3 = args.self_vel
+
+ var steer := Vector3.ZERO
+ var align := Vector3.ZERO
+ var cohere := Vector3.ZERO
+
+ var steer_count := 0
+ var align_count := 0
+ var cohere_count := 0
+
+ var aboid_idx := 0
+ for aboid_pos in args.others_pos:
+ var dist = boid_pos.distance_to(aboid_pos)
+ if dist >= EPSILON:
+ var diff = (boid_pos - aboid_pos).normalized() / dist
+ if dist < args.goal_seperation: steer += diff; steer_count += 1
+ if dist < args.goal_alignment: align += args.others_vel[aboid_idx]; align_count += 1
+ if dist < args.goal_cohesion: cohere += aboid_pos; cohere_count += 1
+ aboid_idx += 1
+
+ if steer_count > 0: steer /= steer_count
+ if align_count > 0: align /= align_count
+ if cohere_count > 0: cohere /= cohere_count; cohere -= boid_pos
+
+ if align.length() > 0.0: align = (align.normalized() * boid_properties.max_speed - boid_vel).limit_length(boid_properties.max_force)
+ if steer.length() > 0.0: steer = (steer.normalized() * boid_properties.max_speed - boid_vel).limit_length(boid_properties.max_force)
+ if cohere.length() > 0.0: cohere = (cohere.normalized() * boid_properties.max_speed - boid_vel).limit_length(boid_properties.max_force)
+
+ var target := Vector3.ZERO
+ var target_position := args.get('target_position')
+ if target_position != null:
+ target = ((target_position - boid_pos) - boid_vel).limit_length(boid_properties.max_force)
+
+ var steer_force := steer * boid_properties.seperation
+ var align_force := align * boid_properties.alignment
+ var cohere_force := cohere * boid_properties.cohesion
+ var target_force := target * boid_properties.targeting
+ var force := steer_force + align_force + cohere_force + target_force
+
+ return force
diff --git a/addons/boids/boid_properties/boid_properties.gd b/addons/boids/boid_properties/boid_properties.gd
new file mode 100644
index 0000000..93a451f
--- /dev/null
+++ b/addons/boids/boid_properties/boid_properties.gd
@@ -0,0 +1,17 @@
+extends Resource
+class_name BoidProperties
+
+## controls the maximum speed.
+@export var max_speed := 4.0
+## controls the maximum force.
+@export var max_force := 1.0
+
+@export_group("weights")
+## controls how inclined the boid will be to align with the rest of it's flock.
+@export var alignment := 1.5
+## controls how inclined the boid will be to cohere together with the rest of it's flock.
+@export var cohesion := 1.0
+## controls how inclined the boid will be to separate from the rest of it's flock.
+@export var seperation := 1.2
+## controls how inclined the boid will be to go to a target (defined by a flock).
+@export var targeting := 0.8
diff --git a/examples/boid_2d/example_boid.svg b/addons/boids/boid_properties/boid_properties.svg
similarity index 66%
rename from examples/boid_2d/example_boid.svg
rename to addons/boids/boid_properties/boid_properties.svg
index ffc25c7..d977b04 100644
--- a/examples/boid_2d/example_boid.svg
+++ b/addons/boids/boid_properties/boid_properties.svg
@@ -2,20 +2,23 @@
diff --git a/addons/boids/boid_properties/boid_properties.svg.import b/addons/boids/boid_properties/boid_properties.svg.import
new file mode 100644
index 0000000..5b61bf9
--- /dev/null
+++ b/addons/boids/boid_properties/boid_properties.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bjix5s2wjg1te"
+path="res://.godot/imported/boid_properties.svg-65ee4ea68118f8a485d6b21ab00db988.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/boids/boid_properties/boid_properties.svg"
+dest_files=["res://.godot/imported/boid_properties.svg-65ee4ea68118f8a485d6b21ab00db988.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/boids/boids.gd b/addons/boids/boids.gd
new file mode 100644
index 0000000..14af6ab
--- /dev/null
+++ b/addons/boids/boids.gd
@@ -0,0 +1,18 @@
+@tool
+extends EditorPlugin
+
+
+func _enter_tree() -> void:
+ add_custom_type("BoidProperties", "Resource", preload("boid_properties/boid_properties.gd"), preload("boid_properties/boid_properties.svg"))
+ add_custom_type("Flock", "Node", preload("flock/flock.gd"), preload("flock/flock.svg"))
+ add_custom_type("Boid2D", "Node2D", preload("boid_2d/boid_2d.gd"), preload("boid_2d/boid_2d.svg"))
+ add_custom_type("Boid3D", "Node3D", preload("boid_3d/boid_3d.gd"), preload("boid_3d/boid_3d.svg"))
+ add_autoload_singleton("BoidManager", "res://addons/boids/boid_manager.gd")
+
+
+func _exit_tree() -> void:
+ remove_custom_type("Flock")
+ remove_custom_type("Boid2D")
+ remove_custom_type("Boid3D")
+ remove_custom_type("BoidProperties")
+ remove_autoload_singleton("BoidManager")
diff --git a/addons/boids/flock/flock.gd b/addons/boids/flock/flock.gd
new file mode 100644
index 0000000..7ac2400
--- /dev/null
+++ b/addons/boids/flock/flock.gd
@@ -0,0 +1,34 @@
+extends Node
+class_name Flock
+
+@export var goal_seperation: float = 25.0
+@export var goal_alignment: float = 50.0
+@export var goal_cohesion: float = 50.0
+
+var boids: Dictionary = {}
+
+## a node that the flock will try to follow.
+## target should be either a Node2D or a Node3D (or any inheritors of these two).
+@export var target: Node
+
+func _ready() -> void:
+ self.child_entered_tree.connect(_register_boid)
+ self.child_exiting_tree.connect(_unregister_boid)
+
+ _init_register_boid()
+
+func _init_register_boid(node: Node = self) -> void:
+ _register_boid(node)
+ for child: Node in node.get_children():
+ _init_register_boid(child)
+
+func _register_boid(maybe_boid: Node) -> void:
+ if maybe_boid is not Boid2D and maybe_boid is not Boid3D: return
+ maybe_boid.flock = self
+ boids[maybe_boid.get_instance_id()] = maybe_boid
+ print_verbose("[", self, "]", " boid ", maybe_boid, " registered")
+
+func _unregister_boid(maybe_boid: Node) -> void:
+ if maybe_boid is not Boid2D and maybe_boid is not Boid3D: return
+ boids.erase(maybe_boid.get_instance_id())
+ print_verbose("[", self, "]", " boid ", maybe_boid, " unregistered")
diff --git a/addons/boid_2d/boid_2d.svg b/addons/boids/flock/flock.svg
similarity index 87%
rename from addons/boid_2d/boid_2d.svg
rename to addons/boids/flock/flock.svg
index ca169d2..5f12e01 100644
--- a/addons/boid_2d/boid_2d.svg
+++ b/addons/boids/flock/flock.svg
@@ -8,7 +8,7 @@
version="1.1"
id="svg1"
inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
- sodipodi:docname="boid_2d.svg"
+ sodipodi:docname="flock.svg"
inkscape:export-filename="..\..\resources\boid_2d.png"
inkscape:export-xdpi="768"
inkscape:export-ydpi="768"
@@ -29,9 +29,9 @@
showgrid="true"
inkscape:zoom="24.544781"
inkscape:cx="9.513224"
- inkscape:cy="4.0538149"
- inkscape:window-width="1858"
- inkscape:window-height="1057"
+ inkscape:cy="7.3131636"
+ inkscape:window-width="1920"
+ inkscape:window-height="1027"
inkscape:window-x="1912"
inkscape:window-y="-8"
inkscape:window-maximized="1"
@@ -60,17 +60,17 @@
inkscape:groupmode="layer"
id="layer1">
diff --git a/addons/boids/flock/flock.svg.import b/addons/boids/flock/flock.svg.import
new file mode 100644
index 0000000..72000a3
--- /dev/null
+++ b/addons/boids/flock/flock.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://b8kh6tdumqytn"
+path="res://.godot/imported/flock.svg-e863ec0929f57a6863c3b7914e0d4cd3.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/boids/flock/flock.svg"
+dest_files=["res://.godot/imported/flock.svg-e863ec0929f57a6863c3b7914e0d4cd3.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/boids/plugin.cfg b/addons/boids/plugin.cfg
new file mode 100644
index 0000000..ca55d10
--- /dev/null
+++ b/addons/boids/plugin.cfg
@@ -0,0 +1,7 @@
+[plugin]
+
+name="Boids"
+description="Addon that implements 2D/3D boids / flocking in Godot."
+author="yusdacra"
+version="0.1"
+script="boids.gd"
diff --git a/examples/boid_2d/example_boid.tscn b/examples/boid_2d/example_boid.tscn
deleted file mode 100644
index ac55e9d..0000000
--- a/examples/boid_2d/example_boid.tscn
+++ /dev/null
@@ -1,15 +0,0 @@
-[gd_scene load_steps=3 format=3 uid="uid://bcyffgnn2ahl3"]
-
-[ext_resource type="PackedScene" uid="uid://bq7s2yf0fohes" path="res://addons/boid_2d/boid.tscn" id="1_825c0"]
-[ext_resource type="Texture2D" uid="uid://rk5u1wthr0n0" path="res://examples/boid_2d/example_boid.svg" id="2_qfbgc"]
-
-[node name="Boid" instance=ExtResource("1_825c0")]
-collision_layer = 2
-
-[node name="Vision" parent="." index="2"]
-collision_mask = 2
-
-[node name="Sprite2D" type="Sprite2D" parent="." index="3"]
-position = Vector2(-2.17226e-06, 2.38419e-07)
-scale = Vector2(0.111111, 0.111111)
-texture = ExtResource("2_qfbgc")
diff --git a/examples/boid_2d/simple/example.tscn b/examples/boid_2d/simple/example.tscn
deleted file mode 100644
index 3652ef8..0000000
--- a/examples/boid_2d/simple/example.tscn
+++ /dev/null
@@ -1,12 +0,0 @@
-[gd_scene load_steps=2 format=3 uid="uid://op0qicvpbjt6"]
-
-[ext_resource type="Script" path="res://examples/boid_2d/simple/example.gd" id="1_3gcrf"]
-
-[node name="Example" type="Node2D"]
-script = ExtResource("1_3gcrf")
-
-[node name="StaticBody2D" type="StaticBody2D" parent="."]
-collision_priority = 4.0
-
-[node name="CollisionPolygon2D" type="CollisionPolygon2D" parent="StaticBody2D"]
-polygon = PackedVector2Array(1152, 0, 1152, 648, 0, 648, 0, 0, 1088, 0, 1088, 64, 64, 64, 64, 576, 1088, 576, 1088, 0)
diff --git a/examples/boids/2d/example_boid.svg b/examples/boids/2d/example_boid.svg
new file mode 100644
index 0000000..d8629cc
--- /dev/null
+++ b/examples/boids/2d/example_boid.svg
@@ -0,0 +1,69 @@
+
+
+
+
diff --git a/examples/boid_2d/example_boid.svg.import b/examples/boids/2d/example_boid.svg.import
similarity index 72%
rename from examples/boid_2d/example_boid.svg.import
rename to examples/boids/2d/example_boid.svg.import
index 845b073..cd13031 100644
--- a/examples/boid_2d/example_boid.svg.import
+++ b/examples/boids/2d/example_boid.svg.import
@@ -3,15 +3,15 @@
importer="texture"
type="CompressedTexture2D"
uid="uid://rk5u1wthr0n0"
-path="res://.godot/imported/example_boid.svg-ebae3589d3b59182aead052ab0bb5c16.ctex"
+path="res://.godot/imported/example_boid.svg-6de905ebc2379a658bac4d710ea0dc0b.ctex"
metadata={
"vram_texture": false
}
[deps]
-source_file="res://examples/boid_2d/example_boid.svg"
-dest_files=["res://.godot/imported/example_boid.svg-ebae3589d3b59182aead052ab0bb5c16.ctex"]
+source_file="res://examples/boids/2d/example_boid.svg"
+dest_files=["res://.godot/imported/example_boid.svg-6de905ebc2379a658bac4d710ea0dc0b.ctex"]
[params]
@@ -32,6 +32,6 @@ process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
-svg/scale=6.0
+svg/scale=1.2
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false
diff --git a/examples/boids/2d/example_boid.tscn b/examples/boids/2d/example_boid.tscn
new file mode 100644
index 0000000..7bd3ecf
--- /dev/null
+++ b/examples/boids/2d/example_boid.tscn
@@ -0,0 +1,34 @@
+[gd_scene load_steps=6 format=3 uid="uid://b2sg3n42rkbx8"]
+
+[ext_resource type="Script" path="res://addons/boids/boid_2d/boid_2d.gd" id="1_vh1uc"]
+[ext_resource type="Texture2D" uid="uid://rk5u1wthr0n0" path="res://examples/boids/2d/example_boid.svg" id="2_jx2vb"]
+[ext_resource type="Script" path="res://addons/boids/boid_properties/boid_properties.gd" id="2_up2nk"]
+
+[sub_resource type="Resource" id="Resource_m74bv"]
+script = ExtResource("2_up2nk")
+max_speed = 4.0
+max_force = 1.0
+alignment = 1.5
+cohesion = 1.0
+seperation = 1.2
+targeting = 0.8
+
+[sub_resource type="GDScript" id="GDScript_ldfpo"]
+resource_name = "example_boid_sprite"
+script/source = "extends Sprite2D
+
+@onready var boid: Boid2D = get_parent()
+
+func _process(delta: float) -> void:
+ var dir := boid.velocity.normalized()
+ var target_rot := atan2(dir.y, dir.x)
+ rotation = move_toward(rotation, target_rot, delta * PI * 2.0 * absf(target_rot - rotation))
+"
+
+[node name="ExampleBoid" type="Node2D"]
+script = ExtResource("1_vh1uc")
+properties = SubResource("Resource_m74bv")
+
+[node name="Sprite2D" type="Sprite2D" parent="."]
+texture = ExtResource("2_jx2vb")
+script = SubResource("GDScript_ldfpo")
diff --git a/examples/boid_2d/follow/example.gd b/examples/boids/2d/follow/example.gd
similarity index 75%
rename from examples/boid_2d/follow/example.gd
rename to examples/boids/2d/follow/example.gd
index c6c5cd2..753abb4 100644
--- a/examples/boid_2d/follow/example.gd
+++ b/examples/boids/2d/follow/example.gd
@@ -1,18 +1,14 @@
extends Node2D
-
func _ready() -> void:
for i in 40: spawnBoid()
-
func _process(delta: float) -> void:
$Path2D/PathFollow2D.progress_ratio += delta * 0.1
-
func spawnBoid() -> void:
- var boid: Boid = preload("../example_boid.tscn").instantiate()
+ var boid: Boid2D = preload("../example_boid.tscn").instantiate()
var screensize := get_viewport_rect().size
boid.modulate = Color(randf(), randf(), randf(), 1)
boid.global_position = Vector2((randf_range(200, screensize.x - 200)), (randf_range(200, screensize.y - 200)))
- boid.follow_point = $Path2D/PathFollow2D
- add_child(boid)
+ $Flock.add_child(boid)
diff --git a/examples/boid_2d/follow/example.tscn b/examples/boids/2d/follow/example.tscn
similarity index 60%
rename from examples/boid_2d/follow/example.tscn
rename to examples/boids/2d/follow/example.tscn
index 14a1d93..cefd916 100644
--- a/examples/boid_2d/follow/example.tscn
+++ b/examples/boids/2d/follow/example.tscn
@@ -1,6 +1,7 @@
-[gd_scene load_steps=3 format=3 uid="uid://ckc0dhvrksfh4"]
+[gd_scene load_steps=4 format=3 uid="uid://ckc0dhvrksfh4"]
-[ext_resource type="Script" path="res://examples/boid_2d/follow/example.gd" id="1_cb4mx"]
+[ext_resource type="Script" path="res://examples/boids/2d/follow/example.gd" id="1_cb4mx"]
+[ext_resource type="Script" path="res://addons/boids/flock/flock.gd" id="2_i4bjg"]
[sub_resource type="Curve2D" id="Curve2D_ncwi0"]
_data = {
@@ -17,3 +18,7 @@ curve = SubResource("Curve2D_ncwi0")
[node name="PathFollow2D" type="PathFollow2D" parent="Path2D"]
position = Vector2(1117, 34)
rotation = -2.44507
+
+[node name="Flock" type="Node" parent="." node_paths=PackedStringArray("target")]
+script = ExtResource("2_i4bjg")
+target = NodePath("../Path2D/PathFollow2D")
diff --git a/examples/boid_2d/simple/example.gd b/examples/boids/2d/simple/example.gd
similarity index 56%
rename from examples/boid_2d/simple/example.gd
rename to examples/boids/2d/simple/example.gd
index fe1c074..cc82b19 100644
--- a/examples/boid_2d/simple/example.gd
+++ b/examples/boids/2d/simple/example.gd
@@ -1,13 +1,12 @@
extends Node2D
-
func _ready() -> void:
- for i in 100: spawnBoid()
+ for flock in get_children():
+ for i in 100: spawnBoid(flock)
-
-func spawnBoid() -> void:
- var boid: Boid = preload("../example_boid.tscn").instantiate()
+func spawnBoid(flock: Flock) -> void:
+ var boid: Boid2D = preload("../example_boid.tscn").instantiate()
var screensize := get_viewport_rect().size
boid.modulate = Color(randf(), randf(), randf(), 1)
boid.global_position = Vector2((randf_range(200, screensize.x - 200)), (randf_range(200, screensize.y - 200)))
- add_child(boid)
+ flock.add_child(boid)
diff --git a/examples/boids/2d/simple/example.tscn b/examples/boids/2d/simple/example.tscn
new file mode 100644
index 0000000..bf244df
--- /dev/null
+++ b/examples/boids/2d/simple/example.tscn
@@ -0,0 +1,22 @@
+[gd_scene load_steps=3 format=3 uid="uid://op0qicvpbjt6"]
+
+[ext_resource type="Script" path="res://examples/boids/2d/simple/example.gd" id="1_3gcrf"]
+[ext_resource type="Script" path="res://addons/boids/flock/flock.gd" id="2_1xeeb"]
+
+[node name="Example" type="Node2D"]
+script = ExtResource("1_3gcrf")
+
+[node name="Flock" type="Node" parent="."]
+script = ExtResource("2_1xeeb")
+
+[node name="Flock2" type="Node" parent="."]
+script = ExtResource("2_1xeeb")
+
+[node name="Flock3" type="Node" parent="."]
+script = ExtResource("2_1xeeb")
+
+[node name="Flock4" type="Node" parent="."]
+script = ExtResource("2_1xeeb")
+
+[node name="Flock5" type="Node" parent="."]
+script = ExtResource("2_1xeeb")
diff --git a/project.godot b/project.godot
index 8372d9d..3b3c741 100644
--- a/project.godot
+++ b/project.godot
@@ -11,14 +11,31 @@ config_version=5
[application]
config/name="Boid Addon"
-run/main_scene="res://examples/boid_2d/simple/example.tscn"
+run/main_scene="res://examples/boids/2d/simple/example.tscn"
config/features=PackedStringArray("4.3", "GL Compatibility")
+[autoload]
+
+BoidManager="*res://addons/boids/boid_manager.gd"
+
+[debug]
+
+settings/stdout/verbose_stdout=true
+
[editor_plugins]
-enabled=PackedStringArray("res://addons/boid_2d/plugin.cfg")
+enabled=PackedStringArray("res://addons/boids/plugin.cfg")
+
+[physics]
+
+2d/run_on_separate_thread=true
+3d/run_on_separate_thread=true
[rendering]
renderer/rendering_method="gl_compatibility"
renderer/rendering_method.mobile="gl_compatibility"
+
+[threading]
+
+worker_pool/max_threads=20