diff --git a/project/demos/AvoidCollisions/AvoidCollisionsDemo.gd b/project/demos/AvoidCollisions/AvoidCollisionsDemo.gd new file mode 100644 index 0000000..bcb89c3 --- /dev/null +++ b/project/demos/AvoidCollisions/AvoidCollisionsDemo.gd @@ -0,0 +1,41 @@ +extends Node2D + + +export(float, 0, 2000, 40) var max_linear_speed := 350.0 setget set_max_linear_speed +export(float, 0, 100, 2) var max_linear_acceleration := 40.0 setget set_max_linear_accel +export(float, 0, 500, 10) var proximity_radius := 140.0 setget set_proximity_radius +export var draw_proximity := true setget set_draw_proximity + +onready var spawner := $Spawner + + +func set_max_linear_speed(value: float) -> void: + max_linear_speed = value + if not is_inside_tree(): + return + + spawner.set_max_linear_speed(value) + + +func set_max_linear_accel(value: float) -> void: + max_linear_acceleration = value + if not is_inside_tree(): + return + + spawner.set_max_linear_accel(value) + + +func set_proximity_radius(value: float) -> void: + proximity_radius = value + if not is_inside_tree(): + return + + spawner.set_proximity_radius(value) + + +func set_draw_proximity(value: bool) -> void: + draw_proximity = value + if not is_inside_tree(): + return + + spawner.set_draw_proximity(value) diff --git a/project/demos/AvoidCollisions/AvoidCollisionsDemo.tscn b/project/demos/AvoidCollisions/AvoidCollisionsDemo.tscn new file mode 100644 index 0000000..06e81bc --- /dev/null +++ b/project/demos/AvoidCollisions/AvoidCollisionsDemo.tscn @@ -0,0 +1,42 @@ +[gd_scene load_steps=5 format=2] + +[ext_resource path="res://demos/AvoidCollisions/Spawner.gd" type="Script" id=1] +[ext_resource path="res://demos/AvoidCollisions/AvoidCollisionsDemo.gd" type="Script" id=2] +[ext_resource path="res://demos/AvoidCollisions/Avoider.tscn" type="PackedScene" id=3] +[ext_resource path="res://assets/theme/gdquest.theme" type="Theme" id=4] + +[node name="AvoidCollisionsDemo" type="Node2D"] +script = ExtResource( 2 ) +max_linear_speed = 360.0 +proximity_radius = 100.0 + +[node name="Spawner" type="Node2D" parent="."] +script = ExtResource( 1 ) +avoider_template = ExtResource( 3 ) +normal_color = Color( 0.94902, 0.0588235, 0.0588235, 1 ) +highlight_color = Color( 0.0901961, 0.929412, 0.929412, 1 ) + +[node name="GUI" type="PanelContainer" parent="."] +margin_right = 1024.0 +margin_bottom = 14.0 +theme = ExtResource( 4 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="MarginContainer" type="MarginContainer" parent="GUI"] +margin_right = 1024.0 +margin_bottom = 116.0 + +[node name="RichTextLabel" type="RichTextLabel" parent="GUI/MarginContainer"] +margin_left = 16.0 +margin_top = 16.0 +margin_right = 1008.0 +margin_bottom = 100.0 +rect_min_size = Vector2( 0, 84 ) +bbcode_enabled = true +bbcode_text = "Avoid Collisions Demo +Watch each agent try to keep traveling in a particular direction, but prioritize avoiding collisions with other agents." +text = "Avoid Collisions Demo +Watch each agent try to keep traveling in a particular direction, but prioritize avoiding collisions with other agents." +scroll_active = false diff --git a/project/demos/AvoidCollisions/Avoider.gd b/project/demos/AvoidCollisions/Avoider.gd new file mode 100644 index 0000000..54b19cb --- /dev/null +++ b/project/demos/AvoidCollisions/Avoider.gd @@ -0,0 +1,95 @@ +extends KinematicBody2D + + +onready var collision := $CollisionShape2D +onready var agent := GSTSteeringAgent.new() +onready var proximity := GSTRadiusProximity.new(agent, [], 140) +onready var avoid := GSTAvoidCollisions.new(agent, proximity) +onready var target := GSTAgentLocation.new() +onready var seek := GSTSeek.new(agent, target) +onready var priority := GSTPriority.new(agent, 0.0001) +onready var sprite := $Sprite + +var draw_proximity: bool + +var _boundary_right: float +var _boundary_bottom: float +var _radius: float +var _accel := GSTTargetAcceleration.new() +var _velocity := Vector2.ZERO +var _direction := Vector2() +var _drag: = 0.1 + + +func _draw() -> void: + if draw_proximity: + draw_circle(Vector2.ZERO, proximity.radius, Color(0, 1, 0, 0.1)) + + +func _physics_process(delta: float) -> void: + _update_agent() + _accel = priority.calculate_steering(_accel) + _velocity += Vector2(_accel.linear.x, _accel.linear.y) + _velocity = _velocity.linear_interpolate(Vector2.ZERO, _drag) + _velocity = _velocity.clamped(agent.max_linear_speed) + _velocity = move_and_slide(_velocity) + + +func setup( + max_linear_speed: float, + max_linear_accel: float, + proximity_radius: float, + boundary_right: float, + boundary_bottom: float, + draw_proximity: bool, + rng: RandomNumberGenerator + ) -> void: + rng.randomize() + _direction = Vector2(rand_range(-1, 1), rand_range(-1, 1)).normalized() + _update_agent() + agent.max_linear_speed = max_linear_speed + agent.max_linear_acceleration = max_linear_accel + proximity.radius = proximity_radius + _boundary_bottom = boundary_bottom + _boundary_right = boundary_right + _radius = collision.shape.radius + agent.bounding_radius = _radius + + self.draw_proximity = draw_proximity + + priority.add(avoid) + priority.add(seek) + + +func set_proximity_agents(agents: Array) -> void: + proximity.agents = agents + + +func set_random_nonoverlapping_position(others: Array, min_distance_from_boundary: float) -> void: + var rng := RandomNumberGenerator.new() + rng.randomize() + var max_tries := max(100, others.size() * others.size()) + while max_tries >= 0: + max_tries -= 1 + global_position.x = rng.randf_range( + min_distance_from_boundary, _boundary_right-min_distance_from_boundary + ) + global_position.y = rng.randf_range( + min_distance_from_boundary, _boundary_bottom-min_distance_from_boundary + ) + var done := true + for i in range(others.size()): + var other: Node2D = others[i] + if other.global_position.distance_to(position) <= _radius*2 + min_distance_from_boundary: + done = false + if done: + break + + +func _update_agent() -> void: + agent.position.x = global_position.x + agent.position.y = global_position.y + agent.linear_velocity.x = _velocity.x + agent.linear_velocity.y = _velocity.y + target.position.x = agent.position.x + _direction.x*_radius + target.position.y = agent.position.y + _direction.y*_radius diff --git a/project/demos/AvoidCollisions/Avoider.tscn b/project/demos/AvoidCollisions/Avoider.tscn new file mode 100644 index 0000000..0e3386f --- /dev/null +++ b/project/demos/AvoidCollisions/Avoider.tscn @@ -0,0 +1,17 @@ +[gd_scene load_steps=4 format=2] + +[ext_resource path="res://assets/sprites/small_circle.png" type="Texture" id=1] +[ext_resource path="res://demos/AvoidCollisions/Avoider.gd" type="Script" id=2] + +[sub_resource type="CircleShape2D" id=1] +radius = 16.0 + +[node name="Avoider" type="KinematicBody2D"] +script = ExtResource( 2 ) + +[node name="CollisionShape2D" type="CollisionShape2D" parent="."] +shape = SubResource( 1 ) + +[node name="Sprite" type="Sprite" parent="."] +modulate = Color( 0.94902, 0.211765, 0.0901961, 1 ) +texture = ExtResource( 1 ) diff --git a/project/demos/AvoidCollisions/Spawner.gd b/project/demos/AvoidCollisions/Spawner.gd new file mode 100644 index 0000000..56c889b --- /dev/null +++ b/project/demos/AvoidCollisions/Spawner.gd @@ -0,0 +1,65 @@ +extends Node2D + + +export var avoider_template: PackedScene +export var normal_color := Color() +export var highlight_color := Color() + +var boundaries: Vector2 + + +func _ready() -> void: + boundaries = Vector2(ProjectSettings["display/window/size/width"], + ProjectSettings["display/window/size/height"]) + var rng: = RandomNumberGenerator.new() + var avoiders := [] + var avoider_agents := [] + for i in range(60): + var avoider := avoider_template.instance() + add_child(avoider) + avoider.setup( + owner.max_linear_speed, + owner.max_linear_acceleration, + owner.proximity_radius, + boundaries.x, + boundaries.y, + true if i == 0 and owner.draw_proximity else false, + rng + ) + avoider_agents.append(avoider.agent) + avoider.set_random_nonoverlapping_position(avoiders, 16) + avoider.sprite.modulate = normal_color if i != 0 or not owner.draw_proximity else highlight_color + avoiders.append(avoider) + for child in get_children(): + child.set_proximity_agents(avoider_agents) + + +func _physics_process(delta: float) -> void: + for child in get_children(): + child.global_position = child.global_position.posmodv(boundaries) + + +func set_max_linear_speed(value: float) -> void: + for child in get_children(): + child.agent.max_linear_speed = value + + +func set_max_linear_accel(value: float) -> void: + for child in get_children(): + child.agent.max_linear_acceleration = value + + +func set_proximity_radius(value: float) -> void: + for child in get_children(): + child.proximity.radius = value + get_child(0).update() + + +func set_draw_proximity(value: bool) -> void: + var child := get_child(0) + child.draw_proximity = value + if not value: + child.sprite.modulate = normal_color + else: + child.sprite.modulate = highlight_color + child.update() diff --git a/project/project.godot b/project/project.godot index dfc6310..d615c5b 100644 --- a/project/project.godot +++ b/project/project.godot @@ -161,6 +161,10 @@ _global_script_class_icons={ config/name="SteeringToolkit" config/icon="res://icon.png" +[display] + +window/size/always_on_top=true + [input] sf_left={ diff --git a/project/src/Behaviors/GSTArrive.gd b/project/src/Behaviors/GSTArrive.gd index a451bd9..8c53a7b 100644 --- a/project/src/Behaviors/GSTArrive.gd +++ b/project/src/Behaviors/GSTArrive.gd @@ -1,5 +1,5 @@ -extends GSTSteeringBehavior class_name GSTArrive +extends GSTSteeringBehavior # Calculates acceleration to take an agent to its target's location. # The calculation will attempt to arrive with zero remaining velocity. diff --git a/project/src/Behaviors/GSTAvoidCollisions.gd b/project/src/Behaviors/GSTAvoidCollisions.gd index 2562b30..8fd9089 100644 --- a/project/src/Behaviors/GSTAvoidCollisions.gd +++ b/project/src/Behaviors/GSTAvoidCollisions.gd @@ -1,6 +1,6 @@ -extends GSTGroupBehavior class_name GSTAvoidCollisions -# Behavior that steers the agent to avoid obstacles lying in its path, approximated by a sphere. +extends GSTGroupBehavior +# Behavior that steers the agent to avoid obstacles lying in its path as approximated by a sphere. var first_neighbor: GSTSteeringAgent diff --git a/project/src/Behaviors/GSTBlend.gd b/project/src/Behaviors/GSTBlend.gd index 310c6fa..e84b5cd 100644 --- a/project/src/Behaviors/GSTBlend.gd +++ b/project/src/Behaviors/GSTBlend.gd @@ -1,5 +1,5 @@ -extends GSTSteeringBehavior class_name GSTBlend +extends GSTSteeringBehavior # Blends multiple steering behaviors into one, and returns acceleration combining all of them. # # Each behavior is associated with a weight - a modifier by which the result will be multiplied by, diff --git a/project/src/Behaviors/GSTCohesion.gd b/project/src/Behaviors/GSTCohesion.gd index 12fe0ac..38b8cb4 100644 --- a/project/src/Behaviors/GSTCohesion.gd +++ b/project/src/Behaviors/GSTCohesion.gd @@ -1,5 +1,5 @@ -extends GSTGroupBehavior class_name GSTCohesion +extends GSTGroupBehavior # Group behavior that produces linear acceleration that attempts to move the agent towards the # center of mass of the agents in the area defined by the defined Proximity. diff --git a/project/src/Behaviors/GSTEvade.gd b/project/src/Behaviors/GSTEvade.gd index b8448fd..9f257ab 100644 --- a/project/src/Behaviors/GSTEvade.gd +++ b/project/src/Behaviors/GSTEvade.gd @@ -1,5 +1,5 @@ -extends GSTPursue class_name GSTEvade +extends GSTPursue # Calculates acceleration to take an agent away from where a target agent will be. # # The `max_predict_time` variable represents how far ahead to calculate the intersection point. diff --git a/project/src/Behaviors/GSTFace.gd b/project/src/Behaviors/GSTFace.gd index 853acaa..45e7983 100644 --- a/project/src/Behaviors/GSTFace.gd +++ b/project/src/Behaviors/GSTFace.gd @@ -1,5 +1,5 @@ -extends GSTMatchOrientation class_name GSTFace +extends GSTMatchOrientation # Calculates angular acceleration to rotate a target to face its target's position. # The acceleration will attempt to arrive with zero remaining angular velocity. diff --git a/project/src/Behaviors/GSTFlee.gd b/project/src/Behaviors/GSTFlee.gd index 021ed24..b5565cd 100644 --- a/project/src/Behaviors/GSTFlee.gd +++ b/project/src/Behaviors/GSTFlee.gd @@ -1,5 +1,5 @@ -extends GSTSeek class_name GSTFlee +extends GSTSeek # Calculates acceleration to take an agent directly away from a target agent. diff --git a/project/src/Behaviors/GSTFollowPath.gd b/project/src/Behaviors/GSTFollowPath.gd index 9c31627..d4b2461 100644 --- a/project/src/Behaviors/GSTFollowPath.gd +++ b/project/src/Behaviors/GSTFollowPath.gd @@ -1,5 +1,5 @@ -extends GSTArrive class_name GSTFollowPath +extends GSTArrive # Produces a linear acceleration that moves the agent along the specified path. diff --git a/project/src/Behaviors/GSTLookWhereYouGo.gd b/project/src/Behaviors/GSTLookWhereYouGo.gd index 9c03af4..460233b 100644 --- a/project/src/Behaviors/GSTLookWhereYouGo.gd +++ b/project/src/Behaviors/GSTLookWhereYouGo.gd @@ -1,5 +1,5 @@ -extends GSTMatchOrientation class_name GSTLookWhereYouGo +extends GSTMatchOrientation # Calculates an angular acceleration to match an agent's orientation to its direction of travel. diff --git a/project/src/Behaviors/GSTMatchOrientation.gd b/project/src/Behaviors/GSTMatchOrientation.gd index ac89974..bb0e4dd 100644 --- a/project/src/Behaviors/GSTMatchOrientation.gd +++ b/project/src/Behaviors/GSTMatchOrientation.gd @@ -1,5 +1,5 @@ -extends GSTSteeringBehavior class_name GSTMatchOrientation +extends GSTSteeringBehavior # Calculates an angular acceleration to match an agent's orientation to its target's. # The calculation will attempt to arrive with zero remaining angular velocity. diff --git a/project/src/Behaviors/GSTPriority.gd b/project/src/Behaviors/GSTPriority.gd index cbe91ea..49c89a6 100644 --- a/project/src/Behaviors/GSTPriority.gd +++ b/project/src/Behaviors/GSTPriority.gd @@ -1,5 +1,5 @@ -extends GSTSteeringBehavior class_name GSTPriority +extends GSTSteeringBehavior # Contains multiple steering behaviors and returns only the result of the first that has a non-zero # acceleration. diff --git a/project/src/Behaviors/GSTPursue.gd b/project/src/Behaviors/GSTPursue.gd index 1e0f53b..16c4c04 100644 --- a/project/src/Behaviors/GSTPursue.gd +++ b/project/src/Behaviors/GSTPursue.gd @@ -1,5 +1,5 @@ -extends GSTSteeringBehavior class_name GSTPursue +extends GSTSteeringBehavior # Calculates acceleration to take an agent to intersect with where a target agent will be. # # The `max_predict_time` variable represents how far ahead to calculate the intersection point. diff --git a/project/src/Behaviors/GSTSeek.gd b/project/src/Behaviors/GSTSeek.gd index 7d9ee30..f2b78e4 100644 --- a/project/src/Behaviors/GSTSeek.gd +++ b/project/src/Behaviors/GSTSeek.gd @@ -1,5 +1,5 @@ -extends GSTSteeringBehavior class_name GSTSeek +extends GSTSteeringBehavior # Calculates acceleration to take an agent to a target agent's position as directly as possible diff --git a/project/src/Behaviors/GSTSeparation.gd b/project/src/Behaviors/GSTSeparation.gd index 7aa6692..110e769 100644 --- a/project/src/Behaviors/GSTSeparation.gd +++ b/project/src/Behaviors/GSTSeparation.gd @@ -1,5 +1,5 @@ -extends GSTGroupBehavior class_name GSTSeparation +extends GSTGroupBehavior # Group behavior that produces acceleration repelling from the other neighbors that are in the # immediate area defined by the given `GSTProximity`.