feat: rewrite in a more performant way, support 3d
@ -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()
|
@ -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"]
|
@ -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")
|
@ -1,7 +0,0 @@
|
||||
[plugin]
|
||||
|
||||
name="Boid2D"
|
||||
description="Addon for implementing boids / flocking in Godot."
|
||||
author="yusdacra"
|
||||
version="0.1"
|
||||
script="boid_2d.gd"
|
24
addons/boids/boid_2d/boid_2d.gd
Normal file
@ -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)
|
68
addons/boids/boid_2d/boid_2d.svg
Normal file
@ -0,0 +1,68 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16.000001"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
|
||||
sodipodi:docname="boid_2d.svg"
|
||||
inkscape:export-filename="..\..\resources\boid_2d.png"
|
||||
inkscape:export-xdpi="768"
|
||||
inkscape:export-ydpi="768"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:document-units="px"
|
||||
showgrid="true"
|
||||
inkscape:zoom="34.711562"
|
||||
inkscape:cx="6.4387768"
|
||||
inkscape:cy="7.7639836"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1027"
|
||||
inkscape:window-x="1912"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1">
|
||||
<inkscape:grid
|
||||
id="grid1"
|
||||
units="px"
|
||||
originx="0"
|
||||
originy="0"
|
||||
spacingx="1"
|
||||
spacingy="1.0000001"
|
||||
empcolor="#0099e5"
|
||||
empopacity="0.30196078"
|
||||
color="#0099e5"
|
||||
opacity="0.14901961"
|
||||
empspacing="5"
|
||||
dotted="false"
|
||||
gridanglex="30"
|
||||
gridanglez="30"
|
||||
visible="true" />
|
||||
</sodipodi:namedview>
|
||||
<defs
|
||||
id="defs1" />
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<path
|
||||
style="fill:#8da5f3;fill-opacity:1;stroke-width:52.913;stroke-linejoin:bevel"
|
||||
d="m 1,2.3333337 c 0,0 14,4.6666666 14,5.9464786 C 15,9.3333343 1,14.000001 1,14.000001 5.666666,9.3333343 5.666666,7.0000003 1,2.3333337 Z"
|
||||
id="path1"
|
||||
sodipodi:nodetypes="cscc" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.0 KiB |
@ -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]
|
||||
|
23
addons/boids/boid_3d/boid_3d.gd
Normal file
@ -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
|
68
addons/boids/boid_3d/boid_3d.svg
Normal file
@ -0,0 +1,68 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16.000001"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
|
||||
sodipodi:docname="boid_3d.svg"
|
||||
inkscape:export-filename="..\..\resources\boid_2d.png"
|
||||
inkscape:export-xdpi="768"
|
||||
inkscape:export-ydpi="768"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:document-units="px"
|
||||
showgrid="true"
|
||||
inkscape:zoom="34.711562"
|
||||
inkscape:cx="6.4387768"
|
||||
inkscape:cy="7.7639837"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1027"
|
||||
inkscape:window-x="1912"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1">
|
||||
<inkscape:grid
|
||||
id="grid1"
|
||||
units="px"
|
||||
originx="0"
|
||||
originy="0"
|
||||
spacingx="1"
|
||||
spacingy="1.0000001"
|
||||
empcolor="#0099e5"
|
||||
empopacity="0.30196078"
|
||||
color="#0099e5"
|
||||
opacity="0.14901961"
|
||||
empspacing="5"
|
||||
dotted="false"
|
||||
gridanglex="30"
|
||||
gridanglez="30"
|
||||
visible="true" />
|
||||
</sodipodi:namedview>
|
||||
<defs
|
||||
id="defs1" />
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<path
|
||||
style="fill:#fc7f7f;fill-opacity:1;stroke-width:52.913;stroke-linejoin:bevel"
|
||||
d="m 1,2.3333337 c 0,0 14,4.6666666 14,5.9464786 C 15,9.3333343 1,14.000001 1,14.000001 5.666666,9.3333343 5.666666,7.0000003 1,2.3333337 Z"
|
||||
id="path1"
|
||||
sodipodi:nodetypes="cscc" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.0 KiB |
38
addons/boids/boid_3d/boid_3d.svg.import
Normal file
@ -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
|
147
addons/boids/boid_manager.gd
Normal file
@ -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
|
17
addons/boids/boid_properties/boid_properties.gd
Normal file
@ -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
|
@ -2,20 +2,23 @@
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="48"
|
||||
height="24"
|
||||
viewBox="0 0 48 24.000001"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16.000001"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
|
||||
sodipodi:docname="example_boid.svg"
|
||||
sodipodi:docname="boid.svg"
|
||||
inkscape:export-filename="..\..\resources\boid_2d.png"
|
||||
inkscape:export-xdpi="768"
|
||||
inkscape:export-ydpi="768"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#5d6c8e"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
@ -24,11 +27,11 @@
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:document-units="px"
|
||||
showgrid="true"
|
||||
inkscape:zoom="17.355781"
|
||||
inkscape:cx="20.886412"
|
||||
inkscape:cy="8.3833737"
|
||||
inkscape:window-width="1858"
|
||||
inkscape:window-height="1057"
|
||||
inkscape:zoom="34.711562"
|
||||
inkscape:cx="6.4387768"
|
||||
inkscape:cy="7.7639837"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1027"
|
||||
inkscape:window-x="1912"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
@ -39,7 +42,7 @@
|
||||
originx="0"
|
||||
originy="0"
|
||||
spacingx="1"
|
||||
spacingy="1"
|
||||
spacingy="1.0000001"
|
||||
empcolor="#0099e5"
|
||||
empopacity="0.30196078"
|
||||
color="#0099e5"
|
||||
@ -57,9 +60,9 @@
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<path
|
||||
style="fill:#ffffff;stroke-width:22.677;stroke-linejoin:bevel"
|
||||
d="M 48,12 C 48,-4.9999998e-7 0,0 0,0 c 30,9.9999996 30,14.999999 0,23.999999 0,0 48,0 48,-11.999999 z"
|
||||
style="fill:#e0e0e0;fill-opacity:1;stroke-width:52.913;stroke-linejoin:bevel"
|
||||
d="m 1,2.3333337 c 0,0 14,4.6666666 14,5.9464786 C 15,9.3333343 1,14.000001 1,14.000001 5.666666,9.3333343 5.666666,7.0000003 1,2.3333337 Z"
|
||||
id="path1"
|
||||
sodipodi:nodetypes="sccs" />
|
||||
sodipodi:nodetypes="cscc" />
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 2.0 KiB |
38
addons/boids/boid_properties/boid_properties.svg.import
Normal file
@ -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
|
18
addons/boids/boids.gd
Normal file
@ -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")
|
34
addons/boids/flock/flock.gd
Normal file
@ -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")
|
@ -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">
|
||||
<path
|
||||
style="fill:#8da5f3;fill-opacity:1;stroke-width:26.4565;stroke-linejoin:bevel"
|
||||
style="fill:#e0e0e0;fill-opacity:1;stroke-width:26.4565;stroke-linejoin:bevel"
|
||||
d="m 1,0.47939285 c 0,0 7,2.33333335 7,2.97323935 0,0.526761 -7,2.860094 -7,2.860094 2.333333,-2.333333 2.333333,-3.5 0,-5.83333335 z"
|
||||
id="path1"
|
||||
sodipodi:nodetypes="cscc" />
|
||||
<path
|
||||
style="fill:#8da5f3;fill-opacity:1;stroke-width:26.4565;stroke-linejoin:bevel"
|
||||
style="fill:#e0e0e0;fill-opacity:1;stroke-width:26.4565;stroke-linejoin:bevel"
|
||||
d="m 8.5092732,5.0267612 c 0,0 6.9999998,2.3333333 6.9999998,2.9732393 0,0.526761 -6.9999998,2.8600945 -6.9999998,2.8600945 2.3333328,-2.3333335 2.3333328,-3.5000005 0,-5.8333338 z"
|
||||
id="path2"
|
||||
sodipodi:nodetypes="cscc" />
|
||||
<path
|
||||
style="fill:#8da5f3;fill-opacity:1;stroke-width:26.4565;stroke-linejoin:bevel"
|
||||
style="fill:#e0e0e0;fill-opacity:1;stroke-width:26.4565;stroke-linejoin:bevel"
|
||||
d="m 2.4897504,9.4619873 c 0,0 7,2.3333327 7,2.9732387 0,0.526761 -7,2.860095 -7,2.860095 2.333333,-2.333334 2.333333,-3.500001 0,-5.8333337 z"
|
||||
id="path3"
|
||||
sodipodi:nodetypes="cscc" />
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
38
addons/boids/flock/flock.svg.import
Normal file
@ -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
|
7
addons/boids/plugin.cfg
Normal file
@ -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"
|
@ -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")
|
@ -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)
|
69
examples/boids/2d/example_boid.svg
Normal file
@ -0,0 +1,69 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="14"
|
||||
height="11.666667"
|
||||
viewBox="0 0 14 11.666668"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
|
||||
sodipodi:docname="example_boid.svg"
|
||||
inkscape:export-filename="..\..\resources\boid_2d.png"
|
||||
inkscape:export-xdpi="768"
|
||||
inkscape:export-ydpi="768"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:document-units="px"
|
||||
showgrid="true"
|
||||
inkscape:zoom="34.711562"
|
||||
inkscape:cx="5.4304672"
|
||||
inkscape:cy="5.4304672"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1027"
|
||||
inkscape:window-x="1912"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1">
|
||||
<inkscape:grid
|
||||
id="grid1"
|
||||
units="px"
|
||||
originx="-1"
|
||||
originy="-2.3333339"
|
||||
spacingx="1"
|
||||
spacingy="1.0000001"
|
||||
empcolor="#0099e5"
|
||||
empopacity="0.30196078"
|
||||
color="#0099e5"
|
||||
opacity="0.14901961"
|
||||
empspacing="5"
|
||||
dotted="false"
|
||||
gridanglex="30"
|
||||
gridanglez="30"
|
||||
visible="true" />
|
||||
</sodipodi:namedview>
|
||||
<defs
|
||||
id="defs1" />
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-1,-2.3333337)">
|
||||
<path
|
||||
style="fill:#8da5f3;fill-opacity:1;stroke-width:52.913;stroke-linejoin:bevel"
|
||||
d="m 1,2.3333337 c 0,0 14,4.6666666 14,5.9464786 C 15,9.3333343 1,14.000001 1,14.000001 5.666666,9.3333343 5.666666,7.0000003 1,2.3333337 Z"
|
||||
id="path1"
|
||||
sodipodi:nodetypes="cscc" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.1 KiB |
@ -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
|
34
examples/boids/2d/example_boid.tscn
Normal file
@ -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")
|
@ -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)
|
@ -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")
|
@ -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)
|
22
examples/boids/2d/simple/example.tscn
Normal file
@ -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")
|
@ -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
|
||||
|