Boids

Soixante-douze boids appliquant séparation, alignement et cohésion à tout ce qui se trouve dans un petit rayon. Chacun est coloré selon le nombre de voisins qu'il voit.

L'éditeur ci-dessus fait tourner ce projet. Changez une ligne et il se recharge.

Classes Godot utilisées

Polygon2DLine2DNode2D

Le code

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)

Plus d'exemples de Navigation