群集行为
七十二个 boid 对小半径内的同伴执行分离、对齐和聚合。每个的颜色取决于它能看到多少邻居。
上方的编辑器正在运行这个项目。改一行代码,它就会重新加载。
用到的 Godot 类
Polygon2DLine2DNode2D
代码
scripts/main.gd
extends Node
const COUNT: int = 72
const MAX_SPEED: float = 200.0
const MAX_FORCE: float = 330.0
const NEIGHBOR_RANGE: float = 110.0
const SEPARATION_RANGE: float = 36.0
const TRAIL_POINTS: int = 16
const SEPARATION: float = 1.7
const ALIGNMENT: float = 1.1
const COHESION: float = 0.9
const EDGE_MARGIN: float = 110.0
const EDGE_FORCE: float = 460.0
var positions: PackedVector2Array = PackedVector2Array()
var velocities: PackedVector2Array = PackedVector2Array()
var hulls: Array[Polygon2D] = []
var trails: Array[Line2D] = []
var view: Vector2 = Vector2.ZERO
func _ready() -> void:
_add_background()
var rng: RandomNumberGenerator = RandomNumberGenerator.new()
rng.seed = 91177
view = get_viewport().get_visible_rect().size
for i in COUNT:
positions.append(Vector2(rng.randf_range(EDGE_MARGIN, view.x - EDGE_MARGIN), rng.randf_range(EDGE_MARGIN, view.y - EDGE_MARGIN)))
velocities.append(Vector2.RIGHT.rotated(rng.randf_range(0, TAU)) * MAX_SPEED)
var trail: Line2D = Line2D.new()
trail.width = 2.0
trail.default_color = Color(0.55, 0.65, 0.95, 0.5)
add_child(trail)
trails.append(trail)
var hull: Polygon2D = Polygon2D.new()
hull.polygon = PackedVector2Array([Vector2(15, 0), Vector2(-10, 8), Vector2(-10, -8)])
add_child(hull)
hulls.append(hull)
var legend: Label = Label.new()
legend.text = 'red: nobody in range yellow: up to three neighbors green: four or more'
legend.position = Vector2(60, view.y - 56)
legend.add_theme_font_size_override('font_size', 19)
legend.add_theme_color_override('font_color', Color('#f3d48d'))
add_child(legend)
print('separation %.1f, alignment %.1f, cohesion %.1f over %d boids, each one only sees %d pixels around it' % [SEPARATION, ALIGNMENT, COHESION, COUNT, int(NEIGHBOR_RANGE)])
func _process(delta: float) -> void:
for i in COUNT:
var push: Vector2 = Vector2.ZERO
var heading: Vector2 = Vector2.ZERO
var center: Vector2 = Vector2.ZERO
var neighbors: int = 0
for j in COUNT:
if j == i:
continue
var offset: Vector2 = positions[j] - positions[i]
var distance: float = offset.length()
if distance > NEIGHBOR_RANGE:
continue
heading += velocities[j]
center += positions[j]
neighbors += 1
if distance < SEPARATION_RANGE and distance > 0.01:
push -= offset / distance * (SEPARATION_RANGE - distance)
var force: Vector2 = _edge_force(positions[i])
if neighbors > 0:
force += _steer(heading / float(neighbors), velocities[i]) * ALIGNMENT
force += _steer(center / float(neighbors) - positions[i], velocities[i]) * COHESION
if push.length_squared() > 0.0:
force += _steer(push, velocities[i]) * SEPARATION
velocities[i] = (velocities[i] + force * delta).limit_length(MAX_SPEED)
positions[i] += velocities[i] * delta
hulls[i].position = positions[i]
hulls[i].rotation = velocities[i].angle()
hulls[i].color = _crowd_color(neighbors)
trails[i].add_point(positions[i])
while trails[i].get_point_count() > TRAIL_POINTS:
trails[i].remove_point(0)
func _crowd_color(neighbors: int) -> Color:
if neighbors == 0:
return Color('#fc7f7f')
return Color('#f3d48d') if neighbors < 4 else Color('#8eef97')
func _steer(desired: Vector2, current: Vector2) -> Vector2:
return (desired.normalized() * MAX_SPEED - current).limit_length(MAX_FORCE)
func _edge_force(at: Vector2) -> Vector2:
var force: Vector2 = Vector2.ZERO
if at.x < EDGE_MARGIN:
force.x = EDGE_FORCE
elif at.x > view.x - EDGE_MARGIN:
force.x = -EDGE_FORCE
if at.y < EDGE_MARGIN:
force.y = EDGE_FORCE
elif at.y > view.y - EDGE_MARGIN:
force.y = -EDGE_FORCE
return force
func _add_background() -> void:
var background: ColorRect = ColorRect.new()
background.color = Color('#12141a')
add_child(background)
background.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)