Added the full project again into a different folder.

This commit is contained in:
Relintai 2023-01-14 10:05:13 +01:00
parent 2a6d8ba550
commit 8ac2c60a4d
115 changed files with 4989 additions and 0 deletions

View File

@ -0,0 +1,51 @@
extends Node
export (float, 0, 3200, 100) var linear_speed_max := 800.0 setget set_linear_speed_max
export (float, 0, 10000, 100) var linear_acceleration_max := 80.0 setget set_linear_acceleration_max
export (float, 0, 100, 0.1) var arrival_tolerance := 25.0 setget set_arrival_tolerance
export (float, 0, 500, 10) var deceleration_radius := 125.0 setget set_deceleration_radius
onready var arriver := $Arriver
onready var target_drawer := $TargetDrawer
func _ready() -> void:
arriver.setup(linear_speed_max, linear_acceleration_max, arrival_tolerance, deceleration_radius)
func _unhandled_input(event: InputEvent) -> void:
if event is InputEventMouseButton and event.button_index == BUTTON_LEFT and event.is_pressed():
arriver.target.position = Vector3(event.position.x, event.position.y, 0)
target_drawer.update()
func set_arrival_tolerance(value: float) -> void:
arrival_tolerance = value
if not is_inside_tree():
return
arriver.arrive.arrival_tolerance = value
func set_deceleration_radius(value: float) -> void:
deceleration_radius = value
if not is_inside_tree():
return
arriver.arrive.deceleration_radius = value
func set_linear_speed_max(value: float) -> void:
linear_speed_max = value
if not is_inside_tree():
return
arriver.agent.linear_speed_max = value
func set_linear_acceleration_max(value: float) -> void:
linear_acceleration_max = value
if not is_inside_tree():
return
arriver.agent.linear_acceleration_max = value

View File

@ -0,0 +1,39 @@
[gd_scene load_steps=8 format=2]
[ext_resource path="res://Demos/Arrive/Arriver.gd" type="Script" id=1]
[ext_resource path="res://Demos/Utils/DemoInterface.tscn" type="PackedScene" id=2]
[ext_resource path="res://Demos/Arrive/ArriveDemo.gd" type="Script" id=3]
[ext_resource path="res://Demos/Utils/BackgroudLayer.tscn" type="PackedScene" id=4]
[ext_resource path="res://Demos/Arrive/TargetDrawer.gd" type="Script" id=5]
[ext_resource path="res://Demos/Utils/CircleDraw.gd" type="Script" id=6]
[sub_resource type="CircleShape2D" id=1]
radius = 23.2163
[node name="ArriveDemo" type="Node"]
script = ExtResource( 3 )
linear_speed_max = 1600.0
linear_acceleration_max = 5000.0
arrival_tolerance = 35.0
deceleration_radius = 180.0
[node name="BackgroudLayer" parent="." instance=ExtResource( 4 )]
[node name="TargetDrawer" type="Node2D" parent="."]
script = ExtResource( 5 )
[node name="Arriver" type="KinematicBody2D" parent="."]
show_behind_parent = true
position = Vector2( 960, 540 )
script = ExtResource( 1 )
[node name="CollisionShape2D" type="CollisionShape2D" parent="Arriver"]
shape = SubResource( 1 )
script = ExtResource( 6 )
inner_color = Color( 0.235294, 0.639216, 0.439216, 1 )
outer_color = Color( 0.560784, 0.870588, 0.364706, 1 )
stroke = 6.0
[node name="DemoInterface" parent="." instance=ExtResource( 2 )]
text_bbcode = "Arrive Demo
Mouse click to make the [color=lime]green \"Player\"[/color] move to the [color=fuchsia]purple target[/color]"

View File

@ -0,0 +1,32 @@
extends KinematicBody2D
var agent := GSAIKinematicBody2DAgent.new()
var target := GSAIAgentLocation.new()
var arrive := GSAIArrive.new()
var _accel := GSAITargetAcceleration.new()
var _velocity := Vector2()
var _drag := 0.1
func _init() -> void:
agent.body = self
arrive.agent = agent
arrive.target = target
func _physics_process(delta: float) -> void:
arrive.calculate_steering(_accel)
agent.apply_steering(_accel, delta)
func setup(
linear_speed_max: float,
linear_acceleration_max: float,
arrival_tolerance: float,
deceleration_radius: float
) -> void:
agent.linear_speed_max = linear_speed_max
agent.linear_acceleration_max = linear_acceleration_max
agent.linear_drag_percentage = _drag
arrive.deceleration_radius = deceleration_radius
arrive.arrival_tolerance = arrival_tolerance
target.position = agent.position

View File

@ -0,0 +1,17 @@
extends Node2D
var deceleration_radius_color : Color = Color(1.0, 0.419, 0.592, 0.5)
var arrival_tolerance_color : Color = Color(0.278, 0.231, 0.47, 0.3)
var arriver: Node2D
func _ready() -> void:
yield(owner, "ready")
arriver = owner.arriver
func _draw():
var target_position := GSAIUtils.to_vector2(arriver.target.position)
draw_circle(target_position, owner.deceleration_radius, deceleration_radius_color)
draw_circle(target_position, owner.arrival_tolerance, arrival_tolerance_color)

View File

@ -0,0 +1,96 @@
[gd_scene load_steps=14 format=2]
[ext_resource path="res://Demos/Utils/DemoInterface.tscn" type="PackedScene" id=1]
[ext_resource path="res://Demos/Arrive3d/Camera.gd" type="Script" id=2]
[ext_resource path="res://Demos/Arrive3d/Seek3dDemo.gd" type="Script" id=3]
[ext_resource path="res://Demos/Arrive3d/Seeker.gd" type="Script" id=4]
[ext_resource path="res://Demos/Arrive3d/SeekerMat.tres" type="Material" id=5]
[sub_resource type="CapsuleShape" id=1]
[sub_resource type="CapsuleMesh" id=2]
[sub_resource type="CubeMesh" id=3]
material = ExtResource( 5 )
size = Vector3( 0.5, 0.5, 1 )
[sub_resource type="CylinderMesh" id=4]
top_radius = 2.0
bottom_radius = 2.0
height = 0.1
[sub_resource type="SpatialMaterial" id=5]
albedo_color = Color( 0.945098, 0.85098, 0.0745098, 1 )
[sub_resource type="BoxShape" id=6]
extents = Vector3( 1000, 0.1, 1000 )
[sub_resource type="PlaneMesh" id=7]
size = Vector2( 250, 250 )
[sub_resource type="SpatialMaterial" id=8]
albedo_color = Color( 0.0941176, 0.235294, 0.486275, 1 )
[node name="Arrive3dDemo" type="Node"]
script = ExtResource( 3 )
linear_speed_max = 50.0
linear_acceleration_max = 53.2
deceleration_radius = 10.8
angular_speed_max = 550
angular_accel_max = 910
[node name="Arriver" type="KinematicBody" parent="."]
script = ExtResource( 4 )
[node name="CollisionShape" type="CollisionShape" parent="Arriver"]
transform = Transform( 1, 0, 0, 0, -1.62921e-07, 1, 0, -1, -1.62921e-07, 0, 1.5, 0 )
shape = SubResource( 1 )
[node name="Capsule" type="MeshInstance" parent="Arriver"]
transform = Transform( 1, 0, 0, 0, -1.62921e-07, 1, 0, -1, -1.62921e-07, 0, 1.5, 0 )
mesh = SubResource( 2 )
material/0 = ExtResource( 5 )
[node name="Nose" type="MeshInstance" parent="Arriver"]
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2, 1.25 )
mesh = SubResource( 3 )
material/0 = null
[node name="Camera" type="Camera" parent="."]
transform = Transform( 0.989952, 0.0720094, -0.121693, 0.0339305, 0.714503, 0.69881, 0.137271, -0.695917, 0.70488, -7.68317, 14.1265, 25.616 )
current = true
script = ExtResource( 2 )
[node name="RayCast" type="RayCast" parent="Camera"]
enabled = true
cast_to = Vector3( -627, 200, -777 )
collision_mask = 2
[node name="MouseTarget" type="Spatial" parent="."]
transform = Transform( 1, 0, 7.45058e-09, 0, 1, 0, 7.45058e-09, 0, 1, -4.76837e-07, 9.53674e-07, 1.90735e-06 )
[node name="MeshInstance" type="MeshInstance" parent="MouseTarget"]
mesh = SubResource( 4 )
material/0 = SubResource( 5 )
[node name="StaticBody" type="StaticBody" parent="."]
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.1, 0 )
collision_layer = 2
collision_mask = 2
[node name="CollisionShape" type="CollisionShape" parent="StaticBody"]
shape = SubResource( 6 )
[node name="Ground" type="MeshInstance" parent="."]
mesh = SubResource( 7 )
material/0 = SubResource( 8 )
[node name="DirectionalLight" type="DirectionalLight" parent="."]
transform = Transform( -0.588165, 0.462179, -0.663666, -0.804031, -0.245728, 0.541436, 0.087159, 0.852061, 0.516134, -17.6076, 12.1748, 0 )
light_energy = 0.5
shadow_enabled = true
[node name="DemoInterface" parent="." instance=ExtResource( 1 )]
mouse_filter = 2
text_bbcode = "3D Arrive Demo
Move the mouse about the field to have the agent turn towards and smoothly arrive at the target marker."

View File

@ -0,0 +1,24 @@
extends Camera
var target: Spatial
onready var ray := $RayCast
func _unhandled_input(event: InputEvent) -> void:
if event is InputEventMouseMotion:
_set_target_position(event.position)
func setup(_target: Spatial) -> void:
self.target = _target
_set_target_position(get_viewport().get_mouse_position())
func _set_target_position(position: Vector2) -> void:
var to = project_local_ray_normal(position) * 10000
ray.cast_to = to
ray.force_raycast_update()
if ray.is_colliding():
var point = ray.get_collision_point()
target.transform.origin = point

View File

@ -0,0 +1,92 @@
extends Node
export (float, 0, 100, 5) var linear_speed_max := 10.0 setget set_linear_speed_max
export (float, 0, 100, 0.1) var linear_acceleration_max := 1.0 setget set_linear_acceleration_max
export (float, 0, 50, 0.1) var arrival_tolerance := 0.5 setget set_arrival_tolerance
export (float, 0, 50, 0.1) var deceleration_radius := 5.0 setget set_deceleration_radius
export (int, 0, 1080, 10) var angular_speed_max := 270 setget set_angular_speed_max
export (int, 0, 2048, 10) var angular_accel_max := 45 setget set_angular_accel_max
export (int, 0, 178, 2) var align_tolerance := 5 setget set_align_tolerance
export (int, 0, 180, 2) var angular_deceleration_radius := 45 setget set_angular_deceleration_radius
onready var target := $MouseTarget
onready var arriver := $Arriver
func _ready() -> void:
arriver.setup(
deg2rad(align_tolerance),
deg2rad(angular_deceleration_radius),
deg2rad(angular_accel_max),
deg2rad(angular_speed_max),
deceleration_radius,
arrival_tolerance,
linear_acceleration_max,
linear_speed_max,
target
)
$Camera.setup(target)
func set_align_tolerance(value: int) -> void:
align_tolerance = value
if not is_inside_tree():
return
arriver.face.alignment_tolerance = deg2rad(value)
func set_angular_deceleration_radius(value: int) -> void:
deceleration_radius = value
if not is_inside_tree():
return
arriver.face.deceleration_radius = deg2rad(value)
func set_angular_accel_max(value: int) -> void:
angular_accel_max = value
if not is_inside_tree():
return
arriver.agent.angular_acceleration_max = deg2rad(value)
func set_angular_speed_max(value: int) -> void:
angular_speed_max = value
if not is_inside_tree():
return
arriver.agent.angular_speed_max = deg2rad(value)
func set_arrival_tolerance(value: float) -> void:
arrival_tolerance = value
if not is_inside_tree():
return
arriver.arrive.arrival_tolerance = value
func set_deceleration_radius(value: float) -> void:
deceleration_radius = value
if not is_inside_tree():
return
arriver.arrive.deceleration_radius = value
func set_linear_speed_max(value: float) -> void:
linear_speed_max = value
if not is_inside_tree():
return
arriver.agent.linear_speed_max = value
func set_linear_acceleration_max(value: float) -> void:
linear_acceleration_max = value
if not is_inside_tree():
return
arriver.agent.linear_acceleration_max = value

View File

@ -0,0 +1,64 @@
extends KinematicBody
var target_node: Spatial
var agent : GSAIKinematicBody3DAgent = null
var target : GSAIAgentLocation = null
var accel : GSAITargetAcceleration = null
var blend : GSAIBlend = null
var face : GSAIFace = null
var arrive : GSAIArrive = null
func _init() -> void:
agent = GSAIKinematicBody3DAgent.new()
agent.body = self
target = GSAIAgentLocation.new()
accel = GSAITargetAcceleration.new()
blend = GSAIBlend.new()
blend.agent = agent
face = GSAIFace.new()
face.agent = agent
face.target = target
face.use_z = true
arrive = GSAIArrive.new()
arrive.agent = agent
arrive.target = target
func _physics_process(delta: float) -> void:
target.position = target_node.transform.origin
target.position.y = transform.origin.y
blend.calculate_steering(accel)
agent.apply_steering(accel, delta)
func setup(
align_tolerance: float,
angular_deceleration_radius: float,
angular_accel_max: float,
angular_speed_max: float,
deceleration_radius: float,
arrival_tolerance: float,
linear_acceleration_max: float,
linear_speed_max: float,
_target: Spatial
) -> void:
agent.linear_speed_max = linear_speed_max
agent.linear_acceleration_max = linear_acceleration_max
agent.linear_drag_percentage = 0.05
agent.angular_acceleration_max = angular_accel_max
agent.angular_speed_max = angular_speed_max
agent.angular_drag_percentage = 0.1
arrive.arrival_tolerance = arrival_tolerance
arrive.deceleration_radius = deceleration_radius
face.alignment_tolerance = align_tolerance
face.deceleration_radius = angular_deceleration_radius
target_node = _target
self.target.position = target_node.transform.origin
blend.add_behavior(arrive, 1)
blend.add_behavior(face, 1)

View File

@ -0,0 +1,4 @@
[gd_resource type="SpatialMaterial" format=2]
[resource]
albedo_color = Color( 0.152941, 0.764706, 0.247059, 1 )

View File

@ -0,0 +1,40 @@
extends Node
export (float, 0, 1000, 40) var linear_speed_max := 350.0 setget set_linear_speed_max
export (float, 0, 4000, 2) var linear_acceleration_max := 40.0 setget set_linear_accel_max
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_linear_speed_max(value: float) -> void:
linear_speed_max = value
if not is_inside_tree():
return
spawner.set_linear_speed_max(value)
func set_linear_accel_max(value: float) -> void:
linear_acceleration_max = value
if not is_inside_tree():
return
spawner.set_linear_accel_max(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)

View File

@ -0,0 +1,26 @@
[gd_scene load_steps=6 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://Demos/Utils/DemoInterface.tscn" type="PackedScene" id=4]
[ext_resource path="res://Demos/Utils/BackgroudLayer.tscn" type="PackedScene" id=5]
[node name="AvoidCollisionsDemo" type="Node"]
script = ExtResource( 2 )
linear_speed_max = 520.0
linear_acceleration_max = 2250.0
proximity_radius = 100.0
[node name="BackgroudLayer" parent="." instance=ExtResource( 5 )]
[node name="Spawner" type="Node2D" parent="."]
script = ExtResource( 1 )
avoider_template = ExtResource( 3 )
inner_color = Color( 0.235294, 0.639216, 0.439216, 1 )
outer_color = Color( 0.560784, 0.870588, 0.364706, 1 )
agent_count = 80
[node name="DemoInterface" parent="." instance=ExtResource( 4 )]
text_bbcode = "Avoid Collisions Demo
Watch each agent try to keep traveling in a particular direction, but prioritize avoiding collisions with other agents."

View File

@ -0,0 +1,112 @@
extends KinematicBody2D
var draw_proximity: bool
var _boundary_right: float
var _boundary_bottom: float
var _radius: float
var _accel := GSAITargetAcceleration.new()
var _velocity := Vector2.ZERO
var _direction := Vector2()
var _drag := 0.1
var _color := Color(0.4, 1.0, 0.89, 0.3)
onready var collision := $CollisionShape2D
var agent :GSAIKinematicBody2DAgent= null
var proximity :GSAIRadiusProximity= null
var avoid :GSAIAvoidCollisions= null
var target :GSAIAgentLocation= null
var seek :GSAISeek= null
var priority :GSAIPriority= null
func _ready() -> void:
agent = GSAIKinematicBody2DAgent.new()
agent.body = self
proximity = GSAIRadiusProximity.new()
proximity.agent = agent
proximity.radius = 140
avoid = GSAIAvoidCollisions.new()
avoid.agent = agent
avoid.proximity = proximity
target = GSAIAgentLocation.new()
seek = GSAISeek.new()
seek.agent = agent
seek.target = target
priority = GSAIPriority.new()
priority.agent = agent
priority.zero_threshold = 0.0001
func _draw() -> void:
if draw_proximity:
draw_circle(Vector2.ZERO, proximity.radius, _color)
func _physics_process(delta: float) -> void:
target.position.x = agent.position.x + _direction.x * _radius
target.position.y = agent.position.y + _direction.y * _radius
priority.calculate_steering(_accel)
agent.apply_steering(_accel, delta)
func setup(
linear_speed_max: float,
linear_accel_max: float,
proximity_radius: float,
boundary_right: float,
boundary_bottom: float,
_draw_proximity: bool,
rng: RandomNumberGenerator
) -> void:
rng.randomize()
_direction = Vector2(rng.randf_range(-1, 1), rng.randf_range(-1, 1)).normalized()
agent.linear_speed_max = linear_speed_max
agent.linear_acceleration_max = linear_accel_max
proximity.radius = proximity_radius
_boundary_bottom = boundary_bottom
_boundary_right = boundary_right
_radius = collision.shape.radius
agent.bounding_radius = _radius
agent.linear_drag_percentage = _drag
self.draw_proximity = _draw_proximity
priority.add_behavior(avoid)
priority.add_behavior(seek)
func set_proximity_agents(agents: Array) -> void:
proximity.agents = agents
func set_random_nonoverlapping_position(others: Array, distance_from_boundary_min: float) -> void:
var rng := RandomNumberGenerator.new()
rng.randomize()
var tries_max := max(100, others.size() * others.size())
while tries_max > 0:
tries_max -= 1
global_position.x = rng.randf_range(
distance_from_boundary_min, _boundary_right - distance_from_boundary_min
)
global_position.y = rng.randf_range(
distance_from_boundary_min, _boundary_bottom - distance_from_boundary_min
)
var done := true
for i in range(others.size()):
var other: Node2D = others[i]
if (
other.global_position.distance_to(position)
<= _radius * 2 + distance_from_boundary_min
):
done = false
if done:
break

View File

@ -0,0 +1,17 @@
[gd_scene load_steps=4 format=2]
[ext_resource path="res://Demos/Utils/CircleDraw.gd" type="Script" id=1]
[ext_resource path="res://Demos/AvoidCollisions/Avoider.gd" type="Script" id=2]
[sub_resource type="CircleShape2D" id=1]
radius = 21.3503
[node name="Avoider" type="KinematicBody2D"]
script = ExtResource( 2 )
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
shape = SubResource( 1 )
script = ExtResource( 1 )
inner_color = Color( 0.890196, 0.411765, 0.337255, 1 )
outer_color = Color( 1, 0.709804, 0.439216, 1 )
stroke = 5.0

View File

@ -0,0 +1,66 @@
extends Node2D
export var avoider_template: PackedScene
export var inner_color := Color()
export var outer_color := Color()
export var agent_count := 60
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(agent_count):
var avoider := avoider_template.instance()
add_child(avoider)
avoider.setup(
owner.linear_speed_max,
owner.linear_acceleration_max,
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)
if i == 0:
avoider.collision.inner_color = inner_color
avoider.collision.outer_color = outer_color
avoiders.append(avoider)
if i % 10 == 0:
yield(get_tree(), "idle_frame")
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_linear_speed_max(value: float) -> void:
for child in get_children():
child.agent.linear_speed_max = value
func set_linear_accel_max(value: float) -> void:
for child in get_children():
child.agent.linear_acceleration_max = 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
child.update()

View File

@ -0,0 +1,28 @@
class_name DemoPickerUI
extends Control
# warning-ignore:unused_signal
signal demo_requested
var demo_path := "" setget set_demo_path
onready var list: ItemList = $VBoxContainer/ItemList
onready var button: Button = $VBoxContainer/Button
func _ready() -> void:
# warning-ignore:return_value_discarded
list.connect("demo_selected", self, "set_demo_path")
# warning-ignore:return_value_discarded
list.connect("item_activated", self, "_on_ItemList_item_activated")
# warning-ignore:return_value_discarded
button.connect("pressed", self, "emit_signal", ["demo_requested"])
demo_path = list.file_paths[0]
func set_demo_path(value: String) -> void:
demo_path = value
func _on_ItemList_item_activated(_index: int) -> void:
emit_signal("demo_requested")

View File

@ -0,0 +1,16 @@
extends Node2D
func load_demo(scene_path: String) -> void:
if not scene_path:
return
var demo = load(scene_path)
if demo:
add_child(demo.instance())
func unload() -> void:
for node in get_children():
call_deferred("remove_child", node)
node.queue_free()

View File

@ -0,0 +1,81 @@
[gd_scene load_steps=7 format=2]
[ext_resource path="res://Demos/PopulateItemList.gd" type="Script" id=1]
[ext_resource path="res://assets/theme/gdquest.theme" type="Theme" id=2]
[ext_resource path="res://assets/sprites/background.png" type="Texture" id=3]
[ext_resource path="res://Demos/DemoPickerUI.gd" type="Script" id=4]
[ext_resource path="res://Demos/DemoPlayer.gd" type="Script" id=5]
[ext_resource path="res://Demos/Demos.gd" type="Script" id=6]
[node name="Demos" type="Node"]
script = ExtResource( 6 )
[node name="DemoPlayer" type="Node2D" parent="."]
script = ExtResource( 5 )
[node name="DemoPickerUI" type="Control" parent="."]
anchor_right = 1.0
anchor_bottom = 1.0
theme = ExtResource( 2 )
script = ExtResource( 4 )
[node name="TextureRect" type="TextureRect" parent="DemoPickerUI"]
anchor_right = 1.0
anchor_bottom = 1.0
rect_min_size = Vector2( 1024, 600 )
size_flags_horizontal = 3
size_flags_vertical = 3
texture = ExtResource( 3 )
expand = true
stretch_mode = 2
[node name="VBoxContainer" type="VBoxContainer" parent="DemoPickerUI"]
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
margin_left = -341.0
margin_top = -290.0
margin_right = 341.0
margin_bottom = 290.0
rect_min_size = Vector2( 682, 0 )
size_flags_horizontal = 3
size_flags_vertical = 3
alignment = 1
__meta__ = {
"_edit_use_anchors_": false
}
[node name="ItemList" type="ItemList" parent="DemoPickerUI/VBoxContainer"]
margin_top = 231.0
margin_right = 682.0
margin_bottom = 240.0
auto_height = true
script = ExtResource( 1 )
[node name="Button" type="Button" parent="DemoPickerUI/VBoxContainer"]
margin_left = 201.0
margin_top = 248.0
margin_right = 481.0
margin_bottom = 348.0
rect_min_size = Vector2( 280, 100 )
size_flags_horizontal = 4
size_flags_vertical = 13
text = "Load scene"
[node name="ButtonGoBack" type="Button" parent="."]
visible = false
anchor_top = 1.0
anchor_bottom = 1.0
margin_left = 48.0
margin_top = -156.0
margin_right = 328.0
margin_bottom = -56.0
rect_min_size = Vector2( 280, 100 )
size_flags_horizontal = 4
size_flags_vertical = 13
theme = ExtResource( 2 )
text = "Go back"
__meta__ = {
"_edit_use_anchors_": false
}

View File

@ -0,0 +1,30 @@
extends Node
onready var demo_picker: DemoPickerUI = $DemoPickerUI
onready var demo_player := $DemoPlayer
onready var button_go_back: Button = $ButtonGoBack
func _ready() -> void:
# warning-ignore:return_value_discarded
demo_picker.connect("demo_requested", self, "_on_DemoPickerUI_demo_requested")
# warning-ignore:return_value_discarded
button_go_back.connect("pressed", self, "_on_ButtonGoBack_pressed")
func _input(event: InputEvent) -> void:
if event.is_action_pressed("toggle_fullscreen"):
OS.window_fullscreen = not OS.window_fullscreen
get_tree().set_input_as_handled()
func _on_DemoPickerUI_demo_requested() -> void:
demo_player.load_demo(demo_picker.demo_path)
demo_picker.hide()
button_go_back.show()
func _on_ButtonGoBack_pressed() -> void:
demo_player.unload()
button_go_back.hide()
demo_picker.show()

View File

@ -0,0 +1,61 @@
extends Node
export (int, 0, 1080, 2) var angular_speed_max := 120 setget set_angular_speed_max
export (int, 0, 2048, 2) var angular_accel_max := 10 setget set_angular_accel_max
export (int, 0, 180, 2) var align_tolerance := 5 setget set_align_tolerance
export (int, 0, 359, 2) var deceleration_radius := 45 setget set_deceleration_radius
export (float, 0, 1000, 40) var player_speed := 600.0 setget set_player_speed
onready var player := $Player
onready var turret := $Turret
func _ready() -> void:
player.speed = player_speed
turret.setup(
player.agent,
deg2rad(align_tolerance),
deg2rad(deceleration_radius),
deg2rad(angular_accel_max),
deg2rad(angular_speed_max)
)
func set_align_tolerance(value: int) -> void:
align_tolerance = value
if not is_inside_tree():
return
turret.face.alignment_tolerance = deg2rad(value)
func set_deceleration_radius(value: int) -> void:
deceleration_radius = value
if not is_inside_tree():
return
turret.face.deceleration_radius = deg2rad(value)
func set_angular_accel_max(value: int) -> void:
angular_accel_max = value
if not is_inside_tree():
return
turret.agent.angular_acceleration_max = deg2rad(value)
func set_angular_speed_max(value: int) -> void:
angular_speed_max = value
if not is_inside_tree():
return
turret.agent.angular_speed_max = deg2rad(value)
func set_player_speed(value: float) -> void:
player_speed = value
if not is_inside_tree():
return
player.speed = player_speed

View File

@ -0,0 +1,48 @@
[gd_scene load_steps=9 format=2]
[ext_resource path="res://Demos/Face/Turret.gd" type="Script" id=1]
[ext_resource path="res://Demos/Face/FaceDemo.gd" type="Script" id=2]
[ext_resource path="res://Demos/Face/Player.gd" type="Script" id=3]
[ext_resource path="res://Demos/Utils/DemoInterface.tscn" type="PackedScene" id=4]
[ext_resource path="res://Demos/Utils/BackgroudLayer.tscn" type="PackedScene" id=5]
[ext_resource path="res://Demos/Utils/CircleDraw.gd" type="Script" id=8]
[sub_resource type="CircleShape2D" id=1]
radius = 20.2633
[sub_resource type="CircleShape2D" id=2]
radius = 37.1052
[node name="FaceDemo" type="Node"]
script = ExtResource( 2 )
angular_speed_max = 662
angular_accel_max = 924
deceleration_radius = 136
[node name="BackgroudLayer" parent="." instance=ExtResource( 5 )]
[node name="Player" type="KinematicBody2D" parent="."]
position = Vector2( 687.363, 351.005 )
script = ExtResource( 3 )
[node name="CollisionShape2D" type="CollisionShape2D" parent="Player"]
shape = SubResource( 1 )
script = ExtResource( 8 )
inner_color = Color( 0.235294, 0.639216, 0.439216, 1 )
outer_color = Color( 0.560784, 0.870588, 0.364706, 1 )
stroke = 6.0
[node name="Turret" type="KinematicBody2D" parent="."]
position = Vector2( 984.348, 571.959 )
script = ExtResource( 1 )
[node name="CollisionShape2D" type="CollisionShape2D" parent="Turret"]
shape = SubResource( 2 )
script = ExtResource( 8 )
inner_color = Color( 0.890196, 0.411765, 0.337255, 1 )
outer_color = Color( 1, 0.709804, 0.439216, 1 )
stroke = 8.0
[node name="DemoInterface" parent="." instance=ExtResource( 4 )]
text_bbcode = "Face Demo
Move the [color=lime]green player[/color] around with WASD and notice the [color=#ffb570]orange turret[/color] orient itself"

View File

@ -0,0 +1,18 @@
extends KinematicBody2D
var speed: float
onready var agent := GSAIAgentLocation.new()
func _physics_process(_delta: float) -> void:
var movement := _get_movement()
move_and_slide(movement * speed)
agent.position = Vector3(global_position.x, global_position.y, 0)
func _get_movement() -> Vector2:
return Vector2(
Input.get_action_strength("sf_right") - Input.get_action_strength("sf_left"),
Input.get_action_strength("sf_down") - Input.get_action_strength("sf_up")
)

View File

@ -0,0 +1,47 @@
extends KinematicBody2D
var face: GSAIFace
var agent := GSAIKinematicBody2DAgent.new()
var _accel := GSAITargetAcceleration.new()
var _angular_drag := 0.1
var _cannon: Rect2
var _color: Color
onready var collision_shape := $CollisionShape2D
func _ready() -> void:
agent.body = self
var radius = collision_shape.shape.radius
_cannon = Rect2(Vector2(-5, 0), Vector2(10, -radius * 2))
_color = collision_shape.outer_color
func _physics_process(delta: float) -> void:
face.calculate_steering(_accel)
agent.apply_steering(_accel, delta)
func _draw() -> void:
draw_rect(_cannon, _color)
func setup(
player_agent: GSAIAgentLocation,
align_tolerance: float,
deceleration_radius: float,
angular_accel_max: float,
angular_speed_max: float
) -> void:
face = GSAIFace.new()
face.agent = agent
face.target = player_agent
face.alignment_tolerance = align_tolerance
face.deceleration_radius = deceleration_radius
agent.angular_acceleration_max = angular_accel_max
agent.angular_speed_max = angular_speed_max
agent.angular_drag_percentage = _angular_drag

View File

@ -0,0 +1,53 @@
extends Node2D
signal path_established(points)
var active_points := []
var is_drawing := false
var distance_threshold := 100.0
func _unhandled_input(event: InputEvent) -> void:
if event is InputEventMouseMotion:
if is_drawing:
active_points.append(event.position)
update()
elif event is InputEventMouseButton:
if event.pressed and event.button_index == BUTTON_LEFT:
active_points.clear()
active_points.append(event.position)
is_drawing = true
update()
elif not event.pressed:
is_drawing = false
if active_points.size() >= 2:
_simplify()
func _draw() -> void:
if is_drawing:
for point in active_points:
draw_circle(point, 2, Color.red)
else:
if active_points.size() > 0:
draw_circle(active_points.front(), 2, Color.red)
draw_circle(active_points.back(), 2, Color.yellow)
draw_polyline(active_points, Color.skyblue, 1.0)
func _simplify() -> void:
var first: Vector2 = active_points.front()
var last: Vector2 = active_points.back()
var key := first
var simplified_path := [first]
for i in range(1, active_points.size()):
var point: Vector2 = active_points[i]
var distance := point.distance_to(key)
if distance > distance_threshold:
key = point
simplified_path.append(key)
active_points = simplified_path
if active_points.back() != last:
active_points.append(last)
update()
emit_signal("path_established", active_points)

View File

@ -0,0 +1,70 @@
extends Node
export (float, 0, 2000, 40) var linear_speed_max := 600.0 setget set_linear_speed_max
export (float, 0, 9000, 10.0) var linear_acceleration_max := 40.0 setget set_linear_acceleration_max
export (float, 0, 100, 0.1) var arrival_tolerance := 10.0 setget set_arrival_tolerance
export (float, 0, 500, 10) var deceleration_radius := 100.0 setget set_deceleration_radius
export (float, 0, 5, 0.1) var predict_time := 0.3 setget set_predict_time
export (float, 0, 200, 10.0) var path_offset := 20.0 setget set_path_offset
onready var drawer := $Drawer
onready var follower := $PathFollower
func _ready() -> void:
follower.setup(
path_offset,
predict_time,
linear_acceleration_max,
linear_speed_max,
deceleration_radius,
arrival_tolerance
)
func set_linear_speed_max(value: float) -> void:
linear_speed_max = value
if not is_inside_tree():
return
follower.agent.linear_speed_max = value
func set_linear_acceleration_max(value: float) -> void:
linear_acceleration_max = value
if not is_inside_tree():
return
follower.agent.linear_acceleration_max = value
func set_arrival_tolerance(value: float) -> void:
arrival_tolerance = value
if not is_inside_tree():
return
follower.follow.arrival_tolerance = value
func set_deceleration_radius(value: float) -> void:
deceleration_radius = value
if not is_inside_tree():
return
follower.follow.deceleration_radius = value
func set_predict_time(value: float) -> void:
predict_time = value
if not is_inside_tree():
return
follower.follow.prediction_time = value
func set_path_offset(value: float) -> void:
path_offset = value
if not is_inside_tree():
return
follower.follow.path_offset = value

View File

@ -0,0 +1,37 @@
[gd_scene load_steps=8 format=2]
[ext_resource path="res://Demos/FollowPath/Drawer.gd" type="Script" id=1]
[ext_resource path="res://Demos/Utils/DemoInterface.tscn" type="PackedScene" id=2]
[ext_resource path="res://Demos/FollowPath/PathFollower.gd" type="Script" id=3]
[ext_resource path="res://Demos/FollowPath/FollowPathDemo.gd" type="Script" id=4]
[ext_resource path="res://Demos/Utils/BackgroudLayer.tscn" type="PackedScene" id=5]
[ext_resource path="res://Demos/Utils/CircleDraw.gd" type="Script" id=6]
[sub_resource type="CircleShape2D" id=1]
radius = 24.1954
[node name="FollowPathDemo" type="Node"]
script = ExtResource( 4 )
linear_speed_max = 920.0
linear_acceleration_max = 3740.0
deceleration_radius = 200.0
[node name="BackgroudLayer" parent="." instance=ExtResource( 5 )]
[node name="Drawer" type="Node2D" parent="."]
script = ExtResource( 1 )
[node name="PathFollower" type="KinematicBody2D" parent="."]
position = Vector2( 960, 540 )
script = ExtResource( 3 )
[node name="CollisionShape2D" type="CollisionShape2D" parent="PathFollower"]
shape = SubResource( 1 )
script = ExtResource( 6 )
inner_color = Color( 0.235294, 0.639216, 0.439216, 1 )
outer_color = Color( 0.560784, 0.870588, 0.364706, 1 )
stroke = 6.0
[node name="DemoInterface" parent="." instance=ExtResource( 2 )]
text_bbcode = "Follow Path Demo
Use the mouse to draw a path on screen and watch the [color=lime]green \"Agent\"[/color] follow it to the end."

View File

@ -0,0 +1,57 @@
extends KinematicBody2D
var _velocity := Vector2.ZERO
var _accel := GSAITargetAcceleration.new()
var _valid := false
var _drag := 0.1
var agent := GSAIKinematicBody2DAgent.new()
var path := GSAIPath.new()
var follow := GSAIFollowPath.new()
func _ready() -> void:
agent.body = self
path.initialize(
[
Vector3(global_position.x, global_position.y, 0),
Vector3(global_position.x, global_position.y, 0)
],
true
)
follow.agent = agent
follow.path = path
follow.agent = agent
func setup(
path_offset: float,
predict_time: float,
accel_max: float,
speed_max: float,
decel_radius: float,
arrival_tolerance: float
) -> void:
owner.drawer.connect("path_established", self, "_on_Drawer_path_established")
follow.path_offset = path_offset
follow.prediction_time = predict_time
follow.deceleration_radius = decel_radius
follow.arrival_tolerance = arrival_tolerance
agent.linear_acceleration_max = accel_max
agent.linear_speed_max = speed_max
agent.linear_drag_percentage = _drag
func _physics_process(delta: float) -> void:
if _valid:
follow.calculate_steering(_accel)
agent.apply_steering(_accel, delta)
func _on_Drawer_path_established(points: Array) -> void:
var positions := PoolVector3Array()
for p in points:
positions.append(Vector3(p.x, p.y, 0))
path.create_path(positions)
_valid = true

View File

@ -0,0 +1,79 @@
extends Node
onready var spawner := $Spawner
export (float, 0, 2000, 40.0) var linear_speed_max := 600.0 setget set_linear_speed_max
export (float, 0, 9000, 2.0) var linear_accel_max := 40.0 setget set_linear_accel_max
export (float, 0, 300, 2.0) var proximity_radius := 140.0 setget set_proximity_radius
export (float, 0, 200000, 250) var separation_decay_coefficient := 2000.0 setget set_separation_decay_coef
export (float, 0, 2, 0.1) var cohesion_strength := 0.1 setget set_cohesion_strength
export (float, 0, 10, 0.2) var separation_strength := 1.5 setget set_separation_strength
export var show_proximity_radius := true setget set_show_proximity_radius
func _ready() -> void:
spawner.setup(
linear_speed_max,
linear_accel_max,
proximity_radius,
separation_decay_coefficient,
cohesion_strength,
separation_strength,
show_proximity_radius
)
func set_linear_speed_max(value: float) -> void:
linear_speed_max = value
if not is_inside_tree():
return
spawner.set_linear_speed_max(value)
func set_linear_accel_max(value: float) -> void:
linear_accel_max = value
if not is_inside_tree():
return
spawner.set_linear_accel_max(value)
func set_proximity_radius(value: float) -> void:
proximity_radius = value
if not is_inside_tree():
return
spawner.set_proximity_radius(value)
func set_show_proximity_radius(value: bool) -> void:
show_proximity_radius = value
if not is_inside_tree():
return
spawner.set_show_proximity_radius(value)
func set_separation_decay_coef(value: float) -> void:
separation_decay_coefficient = value
if not is_inside_tree():
return
spawner.set_separation_decay_coef(value)
func set_cohesion_strength(value: float) -> void:
cohesion_strength = value
if not is_inside_tree():
return
spawner.set_cohesion_strength(value)
func set_separation_strength(value: float) -> void:
separation_strength = value
if not is_inside_tree():
return
spawner.set_separation_strength(value)

View File

@ -0,0 +1,29 @@
[gd_scene load_steps=6 format=2]
[ext_resource path="res://Demos/GroupBehaviors/Member.tscn" type="PackedScene" id=1]
[ext_resource path="res://Demos/GroupBehaviors/Spawner.gd" type="Script" id=2]
[ext_resource path="res://Demos/GroupBehaviors/GroupBehaviorsDemo.gd" type="Script" id=3]
[ext_resource path="res://Demos/Utils/DemoInterface.tscn" type="PackedScene" id=4]
[ext_resource path="res://Demos/Utils/BackgroudLayer.tscn" type="PackedScene" id=5]
[node name="GroupBehaviorsDemo" type="Node"]
script = ExtResource( 3 )
linear_accel_max = 4234.0
proximity_radius = 158.0
separation_decay_coefficient = 121500.0
cohesion_strength = 0.2
separation_strength = 8.8
[node name="BackgroudLayer" parent="." instance=ExtResource( 5 )]
[node name="Spawner" type="Node2D" parent="."]
position = Vector2( 973.261, 528.829 )
script = ExtResource( 2 )
member = ExtResource( 1 )
[node name="DemoInterface" parent="." instance=ExtResource( 4 )]
mouse_filter = 2
text_bbcode = "Group Behavior Demo
Each of the \"Agents\" are both attempting to stay separated from each other but within reach of their nearest group's center of mass.
Click on agent to see it's proximity."

View File

@ -0,0 +1,69 @@
extends KinematicBody2D
var separation: GSAISeparation
var cohesion: GSAICohesion
var proximity: GSAIRadiusProximity
var agent :GSAIKinematicBody2DAgent = null
var blend : GSAIBlend = null
var acceleration : GSAITargetAcceleration = null
var draw_proximity := false
var _color := Color.red
var _velocity := Vector2()
onready var collision_shape := $CollisionShape2D
func _init() -> void:
agent = GSAIKinematicBody2DAgent.new()
agent.body = self
blend = GSAIBlend.new()
blend.agent = agent
acceleration = GSAITargetAcceleration.new()
func setup(
linear_speed_max: float,
linear_accel_max: float,
proximity_radius: float,
separation_decay_coefficient: float,
cohesion_strength: float,
separation_strength: float
) -> void:
_color = Color(rand_range(0.5, 1), rand_range(0.25, 1), rand_range(0, 1))
collision_shape.inner_color = _color
agent.linear_acceleration_max = linear_accel_max
agent.linear_speed_max = linear_speed_max
agent.linear_drag_percentage = 0.1
proximity = GSAIRadiusProximity.new()
proximity.agent = agent
proximity.radius = proximity_radius
separation = GSAISeparation.new()
separation.agent = agent
separation.proximity = proximity
separation.decay_coefficient = separation_decay_coefficient
cohesion = GSAICohesion.new()
cohesion.agent = agent
cohesion.proximity = proximity
blend.add_behavior(separation, separation_strength)
blend.add_behavior(cohesion, cohesion_strength)
func _draw() -> void:
if draw_proximity:
draw_circle(Vector2.ZERO, proximity.radius, Color(0.4, 1.0, 0.89, 0.3))
func _physics_process(delta: float) -> void:
if blend:
blend.calculate_steering(acceleration)
agent.apply_steering(acceleration, delta)
func set_neighbors(neighbor: Array) -> void:
proximity.agents = neighbor

View File

@ -0,0 +1,18 @@
[gd_scene load_steps=4 format=2]
[ext_resource path="res://Demos/GroupBehaviors/Member.gd" type="Script" id=1]
[ext_resource path="res://Demos/Utils/CircleDraw.gd" type="Script" id=3]
[sub_resource type="CircleShape2D" id=1]
radius = 16.0
[node name="Member" type="KinematicBody2D"]
input_pickable = true
script = ExtResource( 1 )
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
shape = SubResource( 1 )
script = ExtResource( 3 )
outer_color = Color( 0.301961, 0.65098, 1, 1 )
stroke = 4.0

View File

@ -0,0 +1,92 @@
extends Node2D
export var member: PackedScene
func follower_input_event(
viewport: Node,
event: InputEvent,
shape_idx: int,
follower: KinematicBody2D
) -> void:
if event.is_action_pressed("click"):
for other in get_children():
if other.draw_proximity:
other.draw_proximity = false
other.update()
follower.draw_proximity = true
follower.update()
move_child(follower, get_child_count())
func setup(
linear_speed_max: float,
linear_accel_max: float,
proximity_radius: float,
separation_decay_coefficient: float,
cohesion_strength: float,
separation_strength: float,
show_proximity_radius: bool
) -> void:
var followers := []
for i in range(19):
var follower : KinematicBody2D = member.instance()
add_child(follower)
follower.position += Vector2(rand_range(-60, 60), rand_range(-60, 60))
followers.append(follower)
follower.setup(
linear_speed_max,
linear_accel_max,
proximity_radius,
separation_decay_coefficient,
cohesion_strength,
separation_strength
)
if i == 0 and show_proximity_radius:
follower.draw_proximity = true
follower.update()
follower.connect("input_event", self, "follower_input_event", [follower])
var agents := []
for i in followers:
agents.append(i.agent)
for i in followers:
i.proximity.agents = agents
func set_linear_speed_max(value: float) -> void:
for child in get_children():
child.agent.linear_speed_max = value
func set_linear_accel_max(value: float) -> void:
for child in get_children():
child.agent.linear_acceleration_max = value
func set_proximity_radius(value: float) -> void:
for child in get_children():
child.proximity.radius = value
if child == get_child(0):
child.update()
func set_show_proximity_radius(value: bool) -> void:
get_child(0).draw_proximity = value
get_child(0).update()
func set_separation_decay_coef(value: float) -> void:
for child in get_children():
child.separation.decay_coefficient = value
func set_cohesion_strength(value: float) -> void:
for child in get_children():
child.blend.get_behavior(1).weight = value
func set_separation_strength(value: float) -> void:
for child in get_children():
child.blend.get_behavior(0).weight = value

View File

@ -0,0 +1,68 @@
extends ItemList
signal demo_selected(scene_path)
var file_paths := PoolStringArray()
func _ready() -> void:
# warning-ignore:return_value_discarded
self.connect("item_selected", self, "_on_item_selected")
var this_directory: String = get_tree().current_scene.filename.rsplit("/", false, 1)[0]
file_paths = _find_files(this_directory, ["*Demo.tscn"], true)
populate(file_paths)
select(0)
func populate(demos: PoolStringArray) -> void:
for path in demos:
var demo_name: String = path.rsplit("/", true, 1)[-1]
demo_name = demo_name.rsplit("Demo", true, 1)[0]
demo_name = sentencify(demo_name)
add_item(demo_name)
func sentencify(line: String) -> String:
var regex := RegEx.new()
# warning-ignore:return_value_discarded
regex.compile("[A-Z]")
line = line.split(".", true, 1)[0]
line = regex.sub(line, " $0", true)
return line
func _find_files(
dirpath := "", patterns := PoolStringArray(), is_recursive := false, do_skip_hidden := true
) -> PoolStringArray:
var paths := PoolStringArray()
var directory := Directory.new()
if not directory.dir_exists(dirpath):
printerr("The directory does not exist: %s" % dirpath)
return paths
if not directory.open(dirpath) == OK:
printerr("Could not open the following dirpath: %s" % dirpath)
return paths
# warning-ignore:return_value_discarded
directory.list_dir_begin(true, do_skip_hidden)
var file_name := directory.get_next()
while file_name != "":
if directory.current_is_dir() and is_recursive:
var subdirectory := dirpath.plus_file(file_name)
paths.append_array(_find_files(subdirectory, patterns, is_recursive))
else:
for pattern in patterns:
if file_name.match(pattern):
paths.append(dirpath.plus_file(file_name))
file_name = directory.get_next()
directory.list_dir_end()
return paths
func _on_item_selected(index: int) -> void:
var demo_path := file_paths[index]
emit_signal("demo_selected", demo_path)

View File

@ -0,0 +1,15 @@
extends Node2D
# Wraps the ships' positions around the world border.
var _world_bounds: Vector2
func _ready() -> void:
_world_bounds = Vector2(
ProjectSettings["display/window/size/width"], ProjectSettings["display/window/size/height"]
)
func _physics_process(_delta: float) -> void:
for ship in get_children():
ship.position = ship.position.posmodv(_world_bounds)

View File

@ -0,0 +1,96 @@
extends KinematicBody2D
# Controls the player ship's movements based on player input.
export var thruster_strength := 175.0
export var side_thruster_strength := 10.0
export var velocity_max := 300.0
export var angular_velocity_max := 2.0
export var angular_drag := 0.025
export var linear_drag := 0.025
var _linear_velocity := Vector2()
var _angular_velocity := 0.0
onready var agent := GSAISteeringAgent.new()
func _physics_process(delta: float) -> void:
var movement := _get_movement()
_angular_velocity = _calculate_angular_velocity(
movement.x,
_angular_velocity,
side_thruster_strength,
angular_velocity_max,
angular_drag,
delta
)
rotation += _angular_velocity * delta
_linear_velocity = _calculate_linear_velocity(
movement.y,
_linear_velocity,
Vector2.UP.rotated(rotation),
linear_drag,
thruster_strength,
velocity_max,
delta
)
_linear_velocity = move_and_slide(_linear_velocity)
_update_agent()
func _calculate_angular_velocity(
horizontal_movement: float,
current_velocity: float,
_thruster_strength: float,
_velocity_max: float,
ship_drag: float,
delta: float
) -> float:
var velocity := clamp(
current_velocity + _thruster_strength * horizontal_movement * delta,
-_velocity_max,
_velocity_max
)
velocity = lerp(velocity, 0, ship_drag)
return velocity
func _calculate_linear_velocity(
vertical_movement: float,
current_velocity: Vector2,
facing_direction: Vector2,
ship_drag_coefficient: float,
strength: float,
speed_max: float,
delta: float
) -> Vector2:
var actual_strength := 0.0
if vertical_movement > 0:
actual_strength = strength
elif vertical_movement < 0:
actual_strength = -strength / 1.5
var velocity := current_velocity + facing_direction * actual_strength * delta
velocity = velocity.linear_interpolate(Vector2.ZERO, ship_drag_coefficient)
return velocity.clamped(speed_max)
func _get_movement() -> Vector2:
return Vector2(
Input.get_action_strength("sf_right") - Input.get_action_strength("sf_left"),
Input.get_action_strength("sf_up") - Input.get_action_strength("sf_down")
)
func _update_agent() -> void:
agent.position.x = global_position.x
agent.position.y = global_position.y
agent.linear_velocity.x = _linear_velocity.x
agent.linear_velocity.y = _linear_velocity.y
agent.angular_velocity = _angular_velocity
agent.orientation = rotation

View File

@ -0,0 +1,39 @@
extends Node
export (float, 0, 2000, 40) var linear_speed_max := 120.0 setget set_linear_speed_max
export (float, 0, 2000, 20) var linear_accel_max := 10.0 setget set_linear_accel_max
export (float, 0, 5, 0.1) var predict_time := 1.0 setget set_predict_time
onready var pursuer := $BoundaryManager/Pursuer
onready var seeker := $BoundaryManager/Seeker
func _ready() -> void:
pursuer.setup(predict_time, linear_speed_max, linear_accel_max)
seeker.setup(predict_time, linear_speed_max, linear_accel_max)
func set_linear_speed_max(value: float) -> void:
linear_speed_max = value
if not is_inside_tree():
return
pursuer.agent.linear_speed_max = value
seeker.agent.linear_speed_max = value
func set_linear_accel_max(value: float) -> void:
linear_accel_max = value
if not is_inside_tree():
return
pursuer.agent.linear_acceleration_max = value
seeker.agent.linear_acceleration_max = value
func set_predict_time(value: float) -> void:
predict_time = value
if not is_inside_tree():
return
pursuer._behavior.predict_time_max = value

View File

@ -0,0 +1,92 @@
[gd_scene load_steps=8 format=2]
[ext_resource path="res://Demos/PursueSeek/Pursuer.gd" type="Script" id=1]
[ext_resource path="res://Demos/PursueSeek/Player.gd" type="Script" id=2]
[ext_resource path="res://Demos/PursueSeek/BoundaryManager.gd" type="Script" id=3]
[ext_resource path="res://Demos/PursueSeek/PursueAndSeekDemo.gd" type="Script" id=4]
[ext_resource path="res://Demos/Utils/DemoInterface.tscn" type="PackedScene" id=5]
[ext_resource path="res://Demos/Utils/Line2DDraw.gd" type="Script" id=6]
[ext_resource path="res://Demos/Utils/BackgroudLayer.tscn" type="PackedScene" id=7]
[node name="PursueVSSeekDemo" type="Node"]
script = ExtResource( 4 )
linear_speed_max = 1280.0
linear_accel_max = 1040.0
predict_time = 1.1
[node name="BackgroudLayer" parent="." instance=ExtResource( 7 )]
[node name="BoundaryManager" type="Node2D" parent="."]
script = ExtResource( 3 )
[node name="Player" type="KinematicBody2D" parent="BoundaryManager"]
position = Vector2( 307.552, 555.999 )
rotation = 1.5708
collision_mask = 2
script = ExtResource( 2 )
__meta__ = {
"_edit_group_": true
}
thruster_strength = 1000.0
side_thruster_strength = 40.0
velocity_max = 450.0
angular_velocity_max = 3.0
[node name="CollisionPolygon2D" type="CollisionPolygon2D" parent="BoundaryManager/Player"]
polygon = PoolVector2Array( 0, -32, -24, 32, 24, 32 )
[node name="Line2D" type="Line2D" parent="BoundaryManager/Player"]
points = PoolVector2Array( 0, 32, 24, 32, 0, -32, -24, 32, 0, 32 )
width = 8.0
default_color = Color( 0.560784, 0.870588, 0.364706, 1 )
joint_mode = 2
antialiased = true
script = ExtResource( 6 )
inner_color = Color( 0.235294, 0.639216, 0.439216, 1 )
[node name="Pursuer" type="KinematicBody2D" parent="BoundaryManager"]
position = Vector2( 1240.22, 866.784 )
collision_layer = 2
script = ExtResource( 1 )
__meta__ = {
"_edit_group_": true
}
[node name="CollisionPolygon2D" type="CollisionPolygon2D" parent="BoundaryManager/Pursuer"]
polygon = PoolVector2Array( 0, -32, -24, 32, 24, 32 )
[node name="Line2D" type="Line2D" parent="BoundaryManager/Pursuer"]
points = PoolVector2Array( 0, 32, 24, 32, 0, -32, -24, 32, 0, 32 )
width = 8.0
default_color = Color( 1, 0.709804, 0.439216, 1 )
joint_mode = 2
antialiased = true
script = ExtResource( 6 )
inner_color = Color( 0.890196, 0.411765, 0.337255, 1 )
[node name="Seeker" type="KinematicBody2D" parent="BoundaryManager"]
position = Vector2( 1240.22, 280.108 )
rotation = 3.14159
collision_layer = 2
script = ExtResource( 1 )
__meta__ = {
"_edit_group_": true
}
use_seek = true
[node name="CollisionPolygon2D" type="CollisionPolygon2D" parent="BoundaryManager/Seeker"]
polygon = PoolVector2Array( 0, -32, -24, 32, 24, 32 )
[node name="Line2D" type="Line2D" parent="BoundaryManager/Seeker"]
points = PoolVector2Array( 0, 32, 24, 32, 0, -32, -24, 32, 0, 32 )
width = 8.0
default_color = Color( 0.301961, 0.65098, 1, 1 )
joint_mode = 2
antialiased = true
script = ExtResource( 6 )
inner_color = Color( 0.294118, 0.356863, 0.670588, 1 )
[node name="DemoInterface" parent="." instance=ExtResource( 5 )]
text_bbcode = "Pursue vs. Seek Demo
Move the player around with WASD and notice the [color=#ffb570]orange Pursuer[/color] and the [color=aqua]blue Seeker[/color] follow
the [color=lime]green \"Ship\"[/color] around"

View File

@ -0,0 +1,80 @@
extends KinematicBody2D
# Represents a ship that chases after the player.
export var use_seek: bool = false
var _blend: GSAIBlend
var _linear_drag_coefficient := 0.025
var _angular_drag := 0.1
var _direction_face := GSAIAgentLocation.new()
var agent : GSAIKinematicBody2DAgent= null
var accel : GSAITargetAcceleration = null
var player_agent : GSAISteeringAgent = null
func _ready() -> void:
agent = GSAIKinematicBody2DAgent.new()
agent.body = self
accel = GSAITargetAcceleration.new()
player_agent = owner.find_node("Player", true, false).agent
agent.calculate_velocities = false
set_physics_process(false)
func _physics_process(delta: float) -> void:
_direction_face.position = agent.position + accel.linear.normalized()
_blend.calculate_steering(accel)
agent.angular_velocity = clamp(
agent.angular_velocity + accel.angular * delta, -agent.angular_speed_max, agent.angular_speed_max
)
agent.angular_velocity = lerp(agent.angular_velocity, 0, _angular_drag)
rotation += agent.angular_velocity * delta
var linear_velocity := (
GSAIUtils.to_vector2(agent.linear_velocity)
+ (GSAIUtils.angle_to_vector2(rotation) * -agent.linear_acceleration_max * delta)
)
linear_velocity = linear_velocity.clamped(agent.linear_speed_max)
linear_velocity = linear_velocity.linear_interpolate(Vector2.ZERO, _linear_drag_coefficient)
linear_velocity = move_and_slide(linear_velocity)
agent.linear_velocity = GSAIUtils.to_vector3(linear_velocity)
func setup(predict_time: float, linear_speed_max: float, linear_accel_max: float) -> void:
var behavior: GSAISteeringBehavior
if use_seek:
behavior = GSAISeek.new()
behavior.agent = agent
behavior.target = player_agent
else:
behavior = GSAIPursue.new()
behavior.agent = agent
behavior.target = player_agent
behavior.predict_time_max = predict_time
var orient_behavior : GSAIFace = GSAIFace.new()
orient_behavior.agent = agent
orient_behavior.target = _direction_face
orient_behavior.alignment_tolerance = deg2rad(5)
orient_behavior.deceleration_radius = deg2rad(30)
_blend = GSAIBlend.new()
_blend.agent = agent
_blend.add_behavior(behavior, 1)
_blend.add_behavior(orient_behavior, 1)
agent.angular_acceleration_max = deg2rad(1080)
agent.angular_speed_max = deg2rad(360)
agent.linear_acceleration_max = linear_accel_max
agent.linear_speed_max = linear_speed_max
set_physics_process(true)

View File

@ -0,0 +1,195 @@
extends KinematicBody2D
# Maximum possible linear velocity
export var speed_max := 450.0
# Maximum change in linear velocity
export var acceleration_max := 50.0
# Maximum rotation velocity represented in degrees
export var angular_speed_max := 240
# Maximum change in rotation velocity represented in degrees
export var angular_acceleration_max := 40
export var health_max := 100
export var flee_health_threshold := 20
var velocity := Vector2.ZERO
var angular_velocity := 0.0
var linear_drag := 0.1
var angular_drag := 0.1
# Holds the linear and angular components calculated by our steering behaviors.
var acceleration : GSAITargetAcceleration = null
onready var current_health := health_max
# GSAISteeringAgent holds our agent's position, orientation, maximum speed and acceleration.
var agent : GSAISteeringAgent = null
var player: Node = null
# This assumes that our player class will keep its own agent updated.
var player_agent : GSAISteeringAgent = null
# Proximities represent an area with which an agent can identify where neighbors in its relevant
# group are. In our case, the group will feature the player, which will be used to avoid a
# collision with them. We use a radius proximity so the player is only relevant inside 100 pixels.
var proximity : GSAIRadiusProximity = null
# GSAIBlend combines behaviors together, calculating all of their acceleration together and adding
# them together, multiplied by a strength. We will have one for fleeing, and one for pursuing,
# toggling them depending on the agent's health. Since we want the agent to rotate AND move, then
# we aim to blend them together.
var flee_blend : GSAIBlend = null
var pursue_blend : GSAIBlend = null
# GSAIPriority will be the main steering behavior we use. It holds sub-behaviors and will pick the
# first one that returns non-zero acceleration, ignoring any afterwards.
var priority : GSAIPriority = null
func _ready() -> void:
acceleration = GSAITargetAcceleration.new()
agent = GSAISteeringAgent.new()
player = get_tree().get_nodes_in_group("Player")[0]
player_agent = player.agent
proximity = GSAIRadiusProximity.new()
proximity.agent = agent
proximity.agents = [ player_agent ]
proximity.radius = 100
flee_blend = GSAIBlend.new()
flee_blend.agent = agent
pursue_blend = GSAIBlend.new()
pursue_blend.agent = agent
priority = GSAIPriority.new()
priority.agent = agent
# ---------- Configuration for our agent ----------
agent.linear_speed_max = speed_max
agent.linear_acceleration_max = acceleration_max
agent.angular_speed_max = deg2rad(angular_speed_max)
agent.angular_acceleration_max = deg2rad(angular_acceleration_max)
agent.bounding_radius = calculate_radius($CollisionPolygon2D.polygon)
update_agent()
# ---------- Configuration for our behaviors ----------
# Pursue will happen while the agent is in good health. It produces acceleration that takes
# the agent on an intercept course with the target, predicting its position in the future.
var pursue : GSAIPursue = GSAIPursue.new()
pursue.agent = agent
pursue.target = player_agent
pursue.predict_time_max = 1.5
# Flee will happen while the agent is in bad health, so will start disabled. It produces
# acceleration that takes the agent directly away from the target with no prediction.
var flee : GSAIFlee = GSAIFlee.new()
flee.agent = agent
flee.target = player_agent
# AvoidCollision tries to keep the agent from running into any of the neighbors found in its
# proximity group. In our case, this will be the player, if they are close enough.
var avoid : GSAIAvoidCollisions = GSAIAvoidCollisions.new()
avoid.agent = agent
avoid.proximity = proximity
# Face turns the agent to keep looking towards its target. It will be enabled while the agent
# is not fleeing due to low health. It tries to arrive 'on alignment' with 0 remaining velocity.
var face : GSAIFace = GSAIFace.new()
face.agent = agent
face.target = player_agent
# We use deg2rad because the math in the toolkit assumes radians.
# How close for the agent to be 'aligned', if not exact.
face.alignment_tolerance = deg2rad(5)
# When to start slowing down
face.deceleration_radius = deg2rad(60)
# LookWhereYouGo turns the agent to keep looking towards its direction of travel. It will only
# be enabled while the agent is at low health.
var look : GSAILookWhereYouGo = GSAILookWhereYouGo.new()
look.agent = agent
# How close for the agent to be 'aligned', if not exact
look.alignment_tolerance = deg2rad(5)
# When to start slowing down.
look.deceleration_radius = deg2rad(60)
# Behaviors that are not enabled produce 0 acceleration.
# Adding our fleeing behaviors to a blend. The order does not matter.
flee_blend.is_enabled = false
flee_blend.add_behavior(look, 1)
flee_blend.add_behavior(flee, 1)
# Adding our pursuit behaviors to a blend. The order does not matter.
pursue_blend.add_behavior(face, 1)
pursue_blend.add_behavior(pursue, 1)
# Adding our final behaviors to the main priority behavior. The order does matter here.
# We want to avoid collision with the player first, flee from the player second when enabled,
# and pursue the player last when enabled.
priority.add_behavior(avoid)
priority.add_behavior(flee_blend)
priority.add_behavior(pursue_blend)
func _physics_process(delta: float) -> void:
# Make sure any change in position and speed has been recorded.
update_agent()
if current_health <= flee_health_threshold:
pursue_blend.is_enabled = false
flee_blend.is_enabled = true
# Calculate the desired acceleration.
priority.calculate_steering(acceleration)
# We add the discovered acceleration to our linear velocity. The toolkit does not limit
# velocity, just acceleration, so we clamp the result ourselves here.
velocity = (velocity + Vector2(acceleration.linear.x, acceleration.linear.y) * delta).clamped(
agent.linear_speed_max
)
# This applies drag on the agent's motion, helping it to slow down naturally.
velocity = velocity.linear_interpolate(Vector2.ZERO, linear_drag)
# And since we're using a KinematicBody2D, we use Godot's excellent move_and_slide to actually
# apply the final movement, and record any change in velocity the physics engine discovered.
velocity = move_and_slide(velocity)
# We then do something similar to apply our agent's rotational speed.
angular_velocity = clamp(
angular_velocity + acceleration.angular * delta, -agent.angular_speed_max, agent.angular_speed_max
)
# This applies drag on the agent's rotation, helping it slow down naturally.
angular_velocity = lerp(angular_velocity, 0, angular_drag)
rotation += angular_velocity * delta
# In order to support both 2D and 3D, the toolkit uses Vector3, so the conversion is required
# when using 2D nodes. The Z component can be left to 0 safely.
func update_agent() -> void:
agent.position.x = global_position.x
agent.position.y = global_position.y
agent.orientation = rotation
agent.linear_velocity.x = velocity.x
agent.linear_velocity.y = velocity.y
agent.angular_velocity = angular_velocity
# We calculate the radius from the collision shape - this will approximate the agent's size in the
# game world, to avoid collisions with the player.
func calculate_radius(polygon: PoolVector2Array) -> float:
var furthest_point := Vector2(-INF, -INF)
for p in polygon:
if abs(p.x) > furthest_point.x:
furthest_point.x = p.x
if abs(p.y) > furthest_point.y:
furthest_point.y = p.y
return furthest_point.length()
func damage(amount: int) -> void:
current_health -= amount
if current_health <= 0:
queue_free()

View File

@ -0,0 +1,33 @@
extends KinematicBody2D
export var speed := 1500.0
var velocity := Vector2.ZERO
var player: Node
onready var timer := $Lifetime
func _ready() -> void:
timer.connect("timeout", self, "_on_Lifetime_timeout")
timer.start()
func _physics_process(delta: float) -> void:
var collision := move_and_collide(velocity * delta)
if collision:
timer.stop()
clear()
collision.collider.damage(10)
func start(direction: Vector2) -> void:
velocity = direction * speed
func clear() -> void:
queue_free()
func _on_Lifetime_timeout() -> void:
clear()

View File

@ -0,0 +1,23 @@
[gd_scene load_steps=4 format=2]
[ext_resource path="res://Demos/Utils/CircleDraw.gd" type="Script" id=1]
[ext_resource path="res://Demos/Quickstart/Bullet.gd" type="Script" id=2]
[sub_resource type="CircleShape2D" id=1]
radius = 4.0
[node name="Bullet" type="KinematicBody2D"]
collision_layer = 4
collision_mask = 2
script = ExtResource( 2 )
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
shape = SubResource( 1 )
script = ExtResource( 1 )
inner_color = Color( 0.235294, 0.639216, 0.439216, 1 )
outer_color = Color( 0.560784, 0.870588, 0.364706, 1 )
stroke = 2.0
[node name="Lifetime" type="Timer" parent="."]
process_mode = 0
wait_time = 3.0

View File

@ -0,0 +1,99 @@
extends KinematicBody2D
export var speed_max := 650.0
export var acceleration_max := 70.0
export var rotation_speed_max := 240
export var rotation_accel_max := 40
export var bullet: PackedScene
var velocity := Vector2.ZERO
var angular_velocity := 0.0
var direction := Vector2.RIGHT
var agent : GSAISteeringAgent = null
var proxy_target : GSAIAgentLocation = null
var face : GSAIFace = null
onready var accel := GSAITargetAcceleration.new()
onready var bullets := owner.get_node("Bullets")
func _ready() -> void:
agent = GSAISteeringAgent.new()
proxy_target = GSAIAgentLocation.new()
face = GSAIFace.new()
face.agent = agent
face.target = proxy_target
agent.linear_speed_max = speed_max
agent.linear_acceleration_max = acceleration_max
agent.angular_speed_max = deg2rad(rotation_speed_max)
agent.angular_acceleration_max = deg2rad(rotation_accel_max)
agent.bounding_radius = calculate_radius($CollisionPolygon2D.polygon)
update_agent()
var mouse_pos := get_global_mouse_position()
proxy_target.position.x = mouse_pos.x
proxy_target.position.y = mouse_pos.y
face.alignment_tolerance = deg2rad(5)
face.deceleration_radius = deg2rad(45)
func _physics_process(delta: float) -> void:
update_agent()
var movement := get_movement()
direction = GSAIUtils.angle_to_vector2(rotation)
velocity += direction * acceleration_max * movement * delta
velocity = velocity.clamped(speed_max)
velocity = velocity.linear_interpolate(Vector2.ZERO, 0.1)
velocity = move_and_slide(velocity)
face.calculate_steering(accel)
angular_velocity += accel.angular * delta
angular_velocity = clamp(angular_velocity, -agent.angular_speed_max, agent.angular_speed_max)
angular_velocity = lerp(angular_velocity, 0, 0.1)
rotation += angular_velocity * delta
func _unhandled_input(event: InputEvent) -> void:
if event is InputEventMouseMotion:
var mouse_pos: Vector2 = event.position
proxy_target.position.x = mouse_pos.x
proxy_target.position.y = mouse_pos.y
elif event is InputEventMouseButton:
if event.button_index == BUTTON_LEFT and event.pressed:
var next_bullet := bullet.instance()
next_bullet.global_position = (
global_position
- direction * (agent.bounding_radius - 5)
)
next_bullet.player = self
next_bullet.start(-direction)
bullets.add_child(next_bullet)
func get_movement() -> float:
return Input.get_action_strength("sf_down") - Input.get_action_strength("sf_up")
func update_agent() -> void:
agent.position.x = global_position.x
agent.position.y = global_position.y
agent.orientation = rotation
agent.linear_velocity.x = velocity.x
agent.linear_velocity.y = velocity.y
agent.angular_velocity = angular_velocity
func calculate_radius(polygon: PoolVector2Array) -> float:
var furthest_point := Vector2(-INF, -INF)
for p in polygon:
if abs(p.x) > furthest_point.x:
furthest_point.x = p.x
if abs(p.y) > furthest_point.y:
furthest_point.y = p.y
return furthest_point.length()

View File

@ -0,0 +1,61 @@
[gd_scene load_steps=6 format=2]
[ext_resource path="res://Demos/Utils/Line2DDraw.gd" type="Script" id=1]
[ext_resource path="res://Demos/Quickstart/Agent.gd" type="Script" id=2]
[ext_resource path="res://Demos/Quickstart/Player.gd" type="Script" id=3]
[ext_resource path="res://Demos/Quickstart/Bullet.tscn" type="PackedScene" id=4]
[ext_resource path="res://Demos/Utils/BackgroudLayer.tscn" type="PackedScene" id=5]
[node name="QuickStartDemo" type="Node"]
[node name="BackgroudLayer" parent="." instance=ExtResource( 5 )]
[node name="Player" type="KinematicBody2D" parent="." groups=[
"Player",
]]
position = Vector2( 402.346, 573.791 )
rotation = 1.5708
collision_mask = 2
script = ExtResource( 3 )
speed_max = 900.0
acceleration_max = 4200.0
rotation_speed_max = 360
rotation_accel_max = 1280
bullet = ExtResource( 4 )
[node name="CollisionPolygon2D" type="CollisionPolygon2D" parent="Player"]
polygon = PoolVector2Array( 0, -32, -24, 32, 24, 32 )
[node name="Line2D" type="Line2D" parent="Player"]
points = PoolVector2Array( 0, 32, 24, 32, 0, -32, -24, 32, 0, 32 )
width = 8.0
default_color = Color( 0.560784, 0.870588, 0.364706, 1 )
joint_mode = 2
antialiased = true
script = ExtResource( 1 )
inner_color = Color( 0.235294, 0.639216, 0.439216, 1 )
[node name="Agent" type="KinematicBody2D" parent="."]
position = Vector2( 974.675, 266.224 )
rotation = 1.5708
collision_layer = 2
collision_mask = 5
script = ExtResource( 2 )
speed_max = 600.0
acceleration_max = 2800.0
angular_speed_max = 360
angular_acceleration_max = 1280
[node name="CollisionPolygon2D" type="CollisionPolygon2D" parent="Agent"]
polygon = PoolVector2Array( 0, -32, -24, 32, 24, 32 )
[node name="Line2D" type="Line2D" parent="Agent"]
points = PoolVector2Array( 0, 32, 24, 32, 0, -32, -24, 32, 0, 32 )
width = 8.0
default_color = Color( 1, 0.709804, 0.439216, 1 )
joint_mode = 2
antialiased = true
script = ExtResource( 1 )
inner_color = Color( 0.890196, 0.411765, 0.337255, 1 )
[node name="Bullets" type="Node2D" parent="."]

View File

@ -0,0 +1,32 @@
extends Node2D
const COLOR := Color("8fde5d")
func _ready() -> void:
get_tree().root.connect("size_changed", self, "_on_SceneTree_size_changed")
_on_SceneTree_size_changed()
func _draw() -> void:
for b in get_children():
var extents: Vector2 = b.get_node("CollisionShape2D").shape.extents
draw_rect(Rect2(b.global_position - extents, extents * 2), COLOR)
func _on_SceneTree_size_changed() -> void:
var size := Vector2(
ProjectSettings["display/window/size/width"], ProjectSettings["display/window/size/height"]
)
for b in get_children():
var boundary: String = str(b.name).rsplit("Boundary")[0]
match boundary:
"Left":
b.global_position = Vector2(0, size.y / 2)
"Right":
b.global_position = Vector2(size.x, size.y / 2)
"Top":
b.global_position = Vector2(size.x / 2, 0)
"Bottom":
b.global_position = Vector2(size.x / 2, size.y)
update()

View File

@ -0,0 +1,26 @@
extends KinematicBody2D
# Class to control the player in basic left/right up/down movement.
var speed: float
onready var agent := GSAIAgentLocation.new()
func _ready() -> void:
agent.position = GSAIUtils.to_vector3(global_position)
func _physics_process(_delta: float) -> void:
var movement := _get_movement()
if movement.length_squared() < 0.01:
return
# warning-ignore:return_value_discarded
move_and_slide(movement * speed)
agent.position = GSAIUtils.to_vector3(global_position)
func _get_movement() -> Vector2:
return Vector2(
Input.get_action_strength("sf_right") - Input.get_action_strength("sf_left"),
Input.get_action_strength("sf_down") - Input.get_action_strength("sf_up")
)

View File

@ -0,0 +1,82 @@
extends Node
# Access helper class for children to access window boundaries.
enum Mode { FLEE, SEEK }
export (Mode) var behavior_mode := Mode.SEEK setget set_behavior_mode
export (float, 0, 1000, 30) var linear_speed_max := 200.0 setget set_linear_speed_max
export (float, 0, 2000, 40) var linear_accel_max := 10.0 setget set_linear_accel_max
export (float) var player_speed := 600.0 setget set_player_speed
var camera_boundaries: Rect2
onready var player: KinematicBody2D = $Player
onready var spawner: Node2D = $Spawner
func _ready() -> void:
camera_boundaries = Rect2(
Vector2.ZERO,
Vector2(
ProjectSettings["display/window/size/width"],
ProjectSettings["display/window/size/height"]
)
)
var rng := RandomNumberGenerator.new()
rng.randomize()
player.speed = player_speed
for i in range(spawner.entity_count):
var new_pos := Vector2(
rng.randf_range(0, camera_boundaries.size.x),
rng.randf_range(0, camera_boundaries.size.y)
)
var entity: KinematicBody2D = spawner.Entity.instance()
entity.global_position = new_pos
entity.player_agent = player.agent
entity.start_speed = linear_speed_max
entity.start_accel = linear_accel_max
entity.use_seek = behavior_mode == Mode.SEEK
spawner.add_child(entity)
func set_behavior_mode(mode: int) -> void:
behavior_mode = mode
if not is_inside_tree():
return
match mode:
Mode.SEEK:
for child in spawner.get_children():
child.use_seek = true
Mode.FLEE:
for child in spawner.get_children():
child.use_seek = false
func set_linear_speed_max(value: float) -> void:
linear_speed_max = value
if not is_inside_tree():
return
for child in spawner.get_children():
child.agent.linear_speed_max = value
func set_linear_accel_max(value: float) -> void:
linear_accel_max = value
if not is_inside_tree():
return
for child in spawner.get_children():
child.agent.linear_acceleration_max = value
func set_player_speed(value: float) -> void:
player_speed = value
if not is_inside_tree():
return
player.speed = player_speed

View File

@ -0,0 +1,81 @@
[gd_scene load_steps=12 format=2]
[ext_resource path="res://Demos/SeekFlee/Player.gd" type="Script" id=2]
[ext_resource path="res://Demos/SeekFlee/SeekFleeDemo.gd" type="Script" id=3]
[ext_resource path="res://Demos/SeekFlee/Spawner.gd" type="Script" id=4]
[ext_resource path="res://Demos/Utils/DemoInterface.tscn" type="PackedScene" id=5]
[ext_resource path="res://Demos/SeekFlee/Seeker.tscn" type="PackedScene" id=6]
[ext_resource path="res://Demos/Utils/CircleDraw.gd" type="Script" id=7]
[ext_resource path="res://Demos/Utils/BackgroudLayer.tscn" type="PackedScene" id=8]
[ext_resource path="res://Demos/SeekFlee/Boundaries.gd" type="Script" id=9]
[sub_resource type="CircleShape2D" id=1]
radius = 32.0
[sub_resource type="RectangleShape2D" id=2]
extents = Vector2( 10, 542 )
[sub_resource type="RectangleShape2D" id=3]
extents = Vector2( 965.654, 10 )
[node name="SeekFleeDemo" type="Node"]
script = ExtResource( 3 )
linear_speed_max = 570.0
linear_accel_max = 1160.0
[node name="BackgroudLayer" parent="." instance=ExtResource( 8 )]
[node name="Player" type="KinematicBody2D" parent="."]
position = Vector2( 960, 540 )
collision_mask = 2
script = ExtResource( 2 )
[node name="CollisionShape2D" type="CollisionShape2D" parent="Player"]
shape = SubResource( 1 )
script = ExtResource( 7 )
inner_color = Color( 0.235294, 0.639216, 0.439216, 1 )
outer_color = Color( 0.560784, 0.870588, 0.364706, 1 )
stroke = 4.0
[node name="Boundaries" type="Node2D" parent="."]
script = ExtResource( 9 )
[node name="LeftBoundary" type="StaticBody2D" parent="Boundaries"]
position = Vector2( 0, 540 )
collision_layer = 2
collision_mask = 5
[node name="CollisionShape2D" type="CollisionShape2D" parent="Boundaries/LeftBoundary"]
shape = SubResource( 2 )
[node name="RightBoundary" type="StaticBody2D" parent="Boundaries"]
position = Vector2( 1920, 540 )
collision_layer = 2
collision_mask = 5
[node name="CollisionShape2D" type="CollisionShape2D" parent="Boundaries/RightBoundary"]
shape = SubResource( 2 )
[node name="TopBoundary" type="StaticBody2D" parent="Boundaries"]
position = Vector2( 960, 0 )
collision_layer = 2
collision_mask = 5
[node name="CollisionShape2D" type="CollisionShape2D" parent="Boundaries/TopBoundary"]
shape = SubResource( 3 )
[node name="BottomBoundary" type="StaticBody2D" parent="Boundaries"]
position = Vector2( 960, 1080 )
collision_layer = 2
collision_mask = 5
[node name="CollisionShape2D" type="CollisionShape2D" parent="Boundaries/BottomBoundary"]
shape = SubResource( 3 )
[node name="Spawner" type="Node2D" parent="."]
script = ExtResource( 4 )
Entity = ExtResource( 6 )
[node name="DemoInterface" parent="." instance=ExtResource( 5 )]
text_bbcode = "Seek & Flee Demo
Move the [color=lime]green \"Player\"[/color] around with WASD and notice the [color=#ffb570]orange \"Enemies\"[/color] try to seek to or flee from the player."

View File

@ -0,0 +1,42 @@
extends KinematicBody2D
var player_agent: GSAIAgentLocation
var velocity := Vector2.ZERO
var start_speed: float
var start_accel: float
var use_seek := true
var agent : GSAIKinematicBody2DAgent = null
var accel : GSAITargetAcceleration = null
var seek : GSAISeek = null
var flee : GSAIFlee = null
func _ready() -> void:
agent = GSAIKinematicBody2DAgent.new()
agent.body = self
accel = GSAITargetAcceleration.new()
seek = GSAISeek.new()
seek.agent = agent
seek.target = player_agent
flee = GSAIFlee.new()
flee.agent = agent
flee.target = player_agent
agent.linear_acceleration_max = start_accel
agent.linear_speed_max = start_speed
func _physics_process(delta: float) -> void:
if not player_agent:
return
if use_seek:
seek.calculate_steering(accel)
else:
flee.calculate_steering(accel)
agent.apply_steering(accel, delta)

View File

@ -0,0 +1,19 @@
[gd_scene load_steps=4 format=2]
[ext_resource path="res://Demos/SeekFlee/Seeker.gd" type="Script" id=1]
[ext_resource path="res://Demos/Utils/CircleDraw.gd" type="Script" id=2]
[sub_resource type="CircleShape2D" id=1]
radius = 16.0
[node name="Seeker" type="KinematicBody2D"]
collision_layer = 4
collision_mask = 2
script = ExtResource( 1 )
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
shape = SubResource( 1 )
script = ExtResource( 2 )
inner_color = Color( 0.890196, 0.411765, 0.337255, 1 )
outer_color = Color( 1, 0.709804, 0.439216, 1 )
stroke = 4.0

View File

@ -0,0 +1,6 @@
extends Node2D
# Holds data to instantiate and configure a number of agent entities.
export (PackedScene) var Entity: PackedScene
export var entity_count := 10
export var entity_color := Color.blue

View File

@ -0,0 +1,17 @@
[gd_scene load_steps=2 format=2]
[ext_resource path="res://assets/sprites/background.png" type="Texture" id=1]
[node name="BackgroudLayer" type="CanvasLayer"]
layer = -1
[node name="Background" type="TextureRect" parent="."]
anchor_right = 1.0
anchor_bottom = 1.0
mouse_filter = 2
texture = ExtResource( 1 )
expand = true
stretch_mode = 7
__meta__ = {
"_edit_use_anchors_": false
}

View File

@ -0,0 +1,26 @@
tool
extends CollisionShape2D
export (Color) var inner_color := Color() setget set_inner_color
export (Color) var outer_color := Color() setget set_outer_color
export (float) var stroke := 0.0 setget set_stroke
func _draw() -> void:
draw_circle(Vector2.ZERO, shape.radius + stroke, outer_color)
draw_circle(Vector2.ZERO, shape.radius, inner_color)
func set_inner_color(val: Color) -> void:
inner_color = val
update()
func set_outer_color(val: Color) -> void:
outer_color = val
update()
func set_stroke(val: float) -> void:
stroke = val
update()

View File

@ -0,0 +1,13 @@
tool
extends PanelContainer
export (String, MULTILINE) var text_bbcode := "" setget set_text_bbcode
onready var rich_text_label: RichTextLabel = $MarginContainer/RichTextLabel
func set_text_bbcode(value: String) -> void:
text_bbcode = value
if not rich_text_label:
yield(self, "ready")
rich_text_label.bbcode_text = text_bbcode

View File

@ -0,0 +1,37 @@
[gd_scene load_steps=4 format=2]
[ext_resource path="res://assets/theme/gdquest.theme" type="Theme" id=1]
[ext_resource path="res://Demos/Utils/DemoInterface.gd" type="Script" id=2]
[sub_resource type="GDScript" id=1]
script/source = "tool
extends RichTextLabel
"
[node name="DemoInterface" type="PanelContainer"]
anchor_right = 1.0
margin_bottom = 140.0
rect_min_size = Vector2( 1024, 0 )
theme = ExtResource( 1 )
script = ExtResource( 2 )
__meta__ = {
"_edit_use_anchors_": false
}
text_bbcode = "Replace this text for the demo."
[node name="MarginContainer" type="MarginContainer" parent="."]
margin_right = 1920.0
margin_bottom = 140.0
[node name="RichTextLabel" type="RichTextLabel" parent="MarginContainer"]
margin_left = 16.0
margin_top = 16.0
margin_right = 1904.0
margin_bottom = 124.0
rect_min_size = Vector2( 0, 55 )
bbcode_enabled = true
bbcode_text = "Replace this text for the demo."
text = "Replace this text for the demo."
scroll_active = false
script = SubResource( 1 )

View File

@ -0,0 +1,13 @@
tool
extends Line2D
export (Color) var inner_color := Color() setget set_inner_color
func _draw() -> void:
draw_colored_polygon(points, inner_color)
func set_inner_color(val: Color) -> void:
inner_color = val
update()

View File

@ -0,0 +1,164 @@
extends GSAISpecializedAgent
class_name GSAIKinematicBody2DAgent
# A specialized steering agent that updates itself every frame so the user does
# not have to using a KinematicBody2D
# @category - Specialized agents
# SLIDE uses `move_and_slide`
# COLLIDE uses `move_and_collide`
# POSITION changes the `global_position` directly
enum MovementType {
SLIDE,
COLLIDE,
POSITION
}
# The KinematicBody2D to keep track of
var body: KinematicBody2D setget _set_body
# The type of movement the body executes
var movement_type : int = MovementType.SLIDE
var _last_position : Vector2
var _body_ref : WeakRef
func _body_ready() -> void:
# warning-ignore:return_value_discarded
body.get_tree().connect("physics_frame", self, "_on_SceneTree_physics_frame")
# Moves the agent's `body` by target `acceleration`.
# @tags - virtual
func _apply_steering(acceleration: GSAITargetAcceleration, delta: float) -> void:
applied_steering = true
if movement_type == MovementType.COLLIDE:
_apply_collide_steering(acceleration.linear, delta)
elif movement_type == MovementType.SLIDE:
_apply_sliding_steering(acceleration.linear, delta)
else:
_apply_position_steering(acceleration.linear, delta)
_apply_orientation_steering(acceleration.angular, delta)
func _apply_sliding_steering(accel: Vector3, delta: float) -> void:
var _body: KinematicBody2D = _body_ref.get_ref()
if !_body:
return
if !_body.is_inside_tree() or _body.get_tree().paused:
return
var velocity : Vector2 = GSAIUtils.to_vector2(linear_velocity + accel * delta).clamped(linear_speed_max)
if apply_linear_drag:
velocity = velocity.linear_interpolate(Vector2.ZERO, linear_drag_percentage)
velocity = _body.move_and_slide(velocity)
if calculate_velocities:
linear_velocity = GSAIUtils.to_vector3(velocity)
func _apply_collide_steering(accel: Vector3, delta: float) -> void:
var _body: KinematicBody2D = _body_ref.get_ref()
if !_body:
return
var velocity : Vector3 = GSAIUtils.clampedv3(linear_velocity + accel * delta, linear_speed_max)
if apply_linear_drag:
velocity = velocity.linear_interpolate(Vector3.ZERO, linear_drag_percentage)
# warning-ignore:return_value_discarded
_body.move_and_collide(GSAIUtils.to_vector2(velocity) * delta)
if calculate_velocities:
linear_velocity = velocity
func _apply_position_steering(accel: Vector3, delta: float) -> void:
var _body: KinematicBody2D = _body_ref.get_ref()
if !_body:
return
var velocity : Vector3 = GSAIUtils.clampedv3(linear_velocity + accel * delta, linear_speed_max)
if apply_linear_drag:
velocity = velocity.linear_interpolate(Vector3.ZERO, linear_drag_percentage)
_body.global_position += GSAIUtils.to_vector2(velocity) * delta
if calculate_velocities:
linear_velocity = velocity
func _apply_orientation_steering(angular_acceleration: float, delta: float) -> void:
var _body: KinematicBody2D = _body_ref.get_ref()
if !_body:
return
var velocity = clamp(angular_velocity + angular_acceleration * delta, -angular_speed_max, angular_speed_max)
if apply_angular_drag:
velocity = lerp(velocity, 0, angular_drag_percentage)
_body.rotation += velocity * delta
if calculate_velocities:
angular_velocity = velocity
func _set_body(value: KinematicBody2D) -> void:
var had_body : bool = false
if body:
had_body = true
body = value
_body_ref = weakref(body)
_last_position = value.global_position
last_orientation = value.rotation
position = GSAIUtils.to_vector3(_last_position)
orientation = last_orientation
if !had_body:
if !body.is_inside_tree():
body.connect("ready", self, "_body_ready")
else:
_body_ready()
func _on_SceneTree_physics_frame() -> void:
var _body: KinematicBody2D = _body_ref.get_ref()
if !_body:
return
var current_position : Vector2 = _body.global_position
var current_orientation : float = _body.rotation
position = GSAIUtils.to_vector3(current_position)
orientation = current_orientation
if calculate_velocities:
if applied_steering:
applied_steering = false
else:
linear_velocity = GSAIUtils.clampedv3(GSAIUtils.to_vector3(current_position - _last_position), linear_speed_max)
if apply_linear_drag:
linear_velocity = linear_velocity.linear_interpolate(Vector3.ZERO, linear_drag_percentage)
angular_velocity = clamp(last_orientation - current_orientation, -angular_speed_max, angular_speed_max)
if apply_angular_drag:
angular_velocity = lerp(angular_velocity, 0, angular_drag_percentage)
_last_position = current_position
last_orientation = current_orientation

View File

@ -0,0 +1,160 @@
extends GSAISpecializedAgent
class_name GSAIKinematicBody3DAgent
# A specialized steering agent that updates itself every frame so the user does
# not have to using a KinematicBody
# @category - Specialized agents
# SLIDE uses `move_and_slide`
# COLLIDE uses `move_and_collide`
# POSITION changes the global_position directly
enum MovementType {
SLIDE,
COLLIDE,
POSITION
}
# The KinematicBody to keep track of
var body : KinematicBody setget _set_body
# The type of movement the body executes
var movement_type : int = 0
var _last_position : Vector3
var _body_ref : WeakRef
func _body_ready() -> void:
# warning-ignore:return_value_discarded
body.get_tree().connect("physics_frame", self, "_on_SceneTree_physics_frame")
# Moves the agent's `body` by target `acceleration`.
# @tags - virtual
func _apply_steering(acceleration: GSAITargetAcceleration, delta: float) -> void:
applied_steering = true
if movement_type == MovementType.COLLIDE:
_apply_collide_steering(acceleration.linear, delta)
elif movement_type == MovementType.SLIDE:
_apply_sliding_steering(acceleration.linear, delta)
else:
_apply_position_steering(acceleration.linear, delta)
_apply_orientation_steering(acceleration.angular, delta)
func _apply_sliding_steering(accel: Vector3, delta: float) -> void:
var _body: KinematicBody = _body_ref.get_ref()
if !_body:
return
var velocity : Vector3 = GSAIUtils.clampedv3(linear_velocity + accel * delta, linear_speed_max)
if apply_linear_drag:
velocity = velocity.linear_interpolate(Vector3.ZERO, linear_drag_percentage)
velocity = _body.move_and_slide(velocity)
if calculate_velocities:
linear_velocity = velocity
func _apply_collide_steering(accel: Vector3, delta: float) -> void:
var _body: KinematicBody = _body_ref.get_ref()
if !_body:
return
var velocity : Vector3 = GSAIUtils.clampedv3(linear_velocity + accel * delta, linear_speed_max)
if apply_linear_drag:
velocity = velocity.linear_interpolate(Vector3.ZERO, linear_drag_percentage)
# warning-ignore:return_value_discarded
_body.move_and_collide(velocity * delta)
if calculate_velocities:
linear_velocity = velocity
func _apply_position_steering(accel: Vector3, delta: float) -> void:
var _body: KinematicBody = _body_ref.get_ref()
if !_body:
return
var velocity : Vector3 = GSAIUtils.clampedv3(linear_velocity + accel * delta, linear_speed_max)
if apply_linear_drag:
velocity = velocity.linear_interpolate(Vector3.ZERO, linear_drag_percentage)
_body.global_transform.origin += velocity * delta
if calculate_velocities:
linear_velocity = velocity
func _apply_orientation_steering(angular_acceleration: float, delta: float) -> void:
var _body: KinematicBody = _body_ref.get_ref()
if !_body:
return
var velocity = clamp(angular_velocity + angular_acceleration * delta,-angular_speed_max,angular_speed_max)
if apply_angular_drag:
velocity = lerp(velocity, 0, angular_drag_percentage)
_body.rotation.y += velocity * delta
if calculate_velocities:
angular_velocity = velocity
func _set_body(value: KinematicBody) -> void:
var had_body : bool = false
if body:
had_body = true
body = value
_body_ref = weakref(value)
_last_position = value.transform.origin
last_orientation = value.rotation.y
position = _last_position
orientation = last_orientation
if !had_body:
if !body.is_inside_tree():
body.connect("ready", self, "_body_ready")
else:
_body_ready()
func _on_SceneTree_physics_frame() -> void:
var _body : KinematicBody = _body_ref.get_ref()
if !_body:
return
if !_body.is_inside_tree() || _body.get_tree().paused:
return
var current_position : Vector3 = _body.transform.origin
var current_orientation : float = _body.rotation.y
position = current_position
orientation = current_orientation
if calculate_velocities:
if applied_steering:
applied_steering = false
else:
linear_velocity = GSAIUtils.clampedv3(current_position - _last_position, linear_speed_max)
if apply_linear_drag:
linear_velocity = linear_velocity.linear_interpolate(Vector3.ZERO, linear_drag_percentage)
angular_velocity = clamp(last_orientation - current_orientation,-angular_speed_max,angular_speed_max)
if apply_angular_drag:
angular_velocity = lerp(angular_velocity, 0, angular_drag_percentage)
_last_position = current_position
last_orientation = current_orientation

View File

@ -0,0 +1,76 @@
extends GSAISpecializedAgent
class_name GSAIRigidBody2DAgent
# A specialized steering agent that updates itself every frame so the user does
# not have to using a RigidBody2D
# @category - Specialized agents
# The RigidBody2D to keep track of
var body : RigidBody2D setget _set_body
var _last_position : Vector2
var _body_ref : WeakRef
func _body_ready() -> void:
# warning-ignore:return_value_discarded
body.get_tree().connect("physics_frame", self, "_on_SceneTree_frame")
# Moves the agent's `body` by target `acceleration`.
# @tags - virtual
func _apply_steering(acceleration : GSAITargetAcceleration, _delta : float) -> void:
var _body: RigidBody2D = _body_ref.get_ref()
if not _body:
return
applied_steering = true
_body.apply_central_impulse(GSAIUtils.to_vector2(acceleration.linear))
_body.apply_torque_impulse(acceleration.angular)
if calculate_velocities:
linear_velocity = GSAIUtils.to_vector3(_body.linear_velocity)
angular_velocity = _body.angular_velocity
func _set_body(value: RigidBody2D) -> void:
var had_body : bool = false
if body:
had_body = true
body = value
_body_ref = weakref(value)
_last_position = value.global_position
last_orientation = value.rotation
position = GSAIUtils.to_vector3(_last_position)
orientation = last_orientation
if !had_body:
if !body.is_inside_tree():
body.connect("ready", self, "_body_ready")
else:
_body_ready()
func _on_SceneTree_frame() -> void:
var _body: RigidBody2D = _body_ref.get_ref()
if !_body:
return
if !_body.is_inside_tree() or _body.get_tree().paused:
return
var current_position : Vector2 = _body.global_position
var current_orientation : float = _body.rotation
position = GSAIUtils.to_vector3(current_position)
orientation = current_orientation
if calculate_velocities:
if applied_steering:
applied_steering = false
else:
linear_velocity = GSAIUtils.to_vector3(_body.linear_velocity)
angular_velocity = _body.angular_velocity

View File

@ -0,0 +1,74 @@
extends GSAISpecializedAgent
class_name GSAIRigidBody3DAgent
# A specialized steering agent that updates itself every frame so the user does
# not have to using a RigidBody
# @category - Specialized agents
# The RigidBody to keep track of
var body: RigidBody setget _set_body
var _last_position: Vector3
var _body_ref: WeakRef
func _body_ready() -> void:
# warning-ignore:return_value_discarded
body.get_tree().connect("physics_frame", self, "_on_SceneTree_frame")
# Moves the agent's `body` by target `acceleration`.
# @tags - virtual
func _apply_steering(acceleration: GSAITargetAcceleration, _delta: float) -> void:
var _body: RigidBody = _body_ref.get_ref()
if !_body:
return
applied_steering = true
_body.apply_central_impulse(acceleration.linear)
_body.apply_torque_impulse(Vector3.UP * acceleration.angular)
if calculate_velocities:
linear_velocity = _body.linear_velocity
angular_velocity = _body.angular_velocity.y
func _set_body(value: RigidBody) -> void:
var had_body : bool = false
if body:
had_body = true
body = value
_body_ref = weakref(value)
_last_position = value.transform.origin
last_orientation = value.rotation.y
position = _last_position
orientation = last_orientation
if !had_body:
if !body.is_inside_tree():
body.connect("ready", self, "_body_ready")
else:
_body_ready()
func _on_SceneTree_frame() -> void:
var _body: RigidBody = _body_ref.get_ref()
if not _body:
return
if not _body.is_inside_tree() or _body.get_tree().paused:
return
var current_position : Vector3 = _body.transform.origin
var current_orientation : float = _body.rotation.y
position = current_position
orientation = current_orientation
if calculate_velocities:
if applied_steering:
applied_steering = false
else:
linear_velocity = _body.linear_velocity
angular_velocity = _body.angular_velocity.y

View File

@ -0,0 +1,42 @@
extends GSAISteeringAgent
class_name GSAISpecializedAgent
# A base class for a specialized steering agent that updates itself every frame
# so the user does not have to. All other specialized agents derive from this.
# @category - Specialized agents
# @tags - abstract
# If `true`, calculates linear and angular velocities based on the previous
# frame. When `false`, the user must keep those values updated.
var calculate_velocities : bool = true
# If `true`, interpolates the current linear velocity towards 0 by the
# `linear_drag_percentage` value.
# Does not apply to `RigidBody` and `RigidBody2D` nodes.
var apply_linear_drag : bool = true
# If `true`, interpolates the current angular velocity towards 0 by the
# `angular_drag_percentage` value.
# Does not apply to `RigidBody` and `RigidBody2D` nodes.
var apply_angular_drag : bool = true
# The percentage between the current linear velocity and 0 to interpolate by if
# `apply_linear_drag` is true.
# Does not apply to `RigidBody` and `RigidBody2D` nodes.
var linear_drag_percentage : float = 0.0
# The percentage between the current angular velocity and 0 to interpolate by if
# `apply_angular_drag` is true.
# Does not apply to `RigidBody` and `RigidBody2D` nodes.
var angular_drag_percentage : float = 0.0
var last_orientation : float = 0.0
var applied_steering : bool = false
func apply_steering(_acceleration : GSAITargetAcceleration, _delta : float) -> void:
call("_apply_steering", _acceleration, _delta)
# Moves the agent's body by target `acceleration`.
# @tags - virtual
func _apply_steering(_acceleration : GSAITargetAcceleration, _delta : float) -> void:
pass

View File

@ -0,0 +1,41 @@
class_name GSAIArrive
extends GSAISteeringBehavior
# Calculates acceleration to take an agent to its target's location. The
# calculation attempts to arrive with zero remaining velocity.
# @category - Individual behaviors
# Target agent to arrive to.
var target : GSAIAgentLocation
# Distance from the target for the agent to be considered successfully
# arrived.
var arrival_tolerance : float = 0.0
# Distance from the target for the agent to begin slowing down.
var deceleration_radius : float = 0.0
# Represents the time it takes to change acceleration.
var time_to_reach : float = 0.1
func arrive(acceleration : GSAITargetAcceleration, target_position : Vector3) -> void:
call("_arrive", acceleration, target_position)
func _arrive(acceleration : GSAITargetAcceleration, target_position : Vector3) -> void:
var to_target : Vector3 = target_position - agent.position
var distance : float = to_target.length()
if distance <= arrival_tolerance:
acceleration.set_zero()
else:
var desired_speed : float = agent.linear_speed_max
if distance <= deceleration_radius:
desired_speed *= distance / deceleration_radius
var desired_velocity : Vector3 = to_target * desired_speed / distance
desired_velocity = ((desired_velocity - agent.linear_velocity) * 1.0 / time_to_reach)
acceleration.linear = GSAIUtils.clampedv3(desired_velocity, agent.linear_acceleration_max)
acceleration.angular = 0
func _calculate_steering(acceleration: GSAITargetAcceleration) -> void:
arrive(acceleration, target.position)

View File

@ -0,0 +1,63 @@
class_name GSAIAvoidCollisions
extends GSAIGroupBehavior
# Steers the agent to avoid obstacles in its path. Approximates obstacles as
# spheres.
# @category - Group behaviors
var _first_neighbor: GSAISteeringAgent
var _shortest_time : float = 0.0
var _first_minimum_separation : float = 0.0
var _first_distance : float = 0.0
var _first_relative_position : Vector3
var _first_relative_velocity : Vector3
func _calculate_steering(acceleration: GSAITargetAcceleration) -> void:
_shortest_time = INF
_first_neighbor = null
_first_minimum_separation = 0
_first_distance = 0
var neighbor_count : int = proximity.find_neighbors(_callback)
if neighbor_count == 0 or not _first_neighbor:
acceleration.set_zero()
else:
if (_first_minimum_separation <= 0 || _first_distance < agent.bounding_radius + _first_neighbor.bounding_radius):
acceleration.linear = _first_neighbor.position - agent.position
else:
acceleration.linear = (_first_relative_position+ (_first_relative_velocity * _shortest_time))
acceleration.linear = (acceleration.linear.normalized() * -agent.linear_acceleration_max)
acceleration.angular = 0
# Callback for the proximity to call when finding neighbors. Keeps track of every `neighbor`
# that was found but only keeps the one the owning agent will most likely collide with.
# @tags - virtual
func _report_neighbor(neighbor: GSAISteeringAgent) -> bool:
var relative_position : Vector3 = neighbor.position - agent.position
var relative_velocity : Vector3 = neighbor.linear_velocity - agent.linear_velocity
var relative_speed_squared : float = relative_velocity.length_squared()
if relative_speed_squared == 0:
return false
else:
var time_to_collision : float = -relative_position.dot(relative_velocity) / relative_speed_squared
if time_to_collision <= 0 || time_to_collision >= _shortest_time:
return false
else:
var distance = relative_position.length()
var minimum_separation: float = (distance - sqrt(relative_speed_squared) * time_to_collision)
if minimum_separation > agent.bounding_radius + neighbor.bounding_radius:
return false
else:
_shortest_time = time_to_collision
_first_neighbor = neighbor
_first_minimum_separation = minimum_separation
_first_distance = distance
_first_relative_position = relative_position
_first_relative_velocity = relative_velocity
return true

View File

@ -0,0 +1,64 @@
class_name GSAIBlend
extends GSAISteeringBehavior
# Blends multiple steering behaviors into one, and returns a weighted
# acceleration from their calculations.
#
# Stores the behaviors internally as dictionaries of the form
# {
# behavior : GSAISteeringBehavior,
# weight : float
# }
# @category - Combination behaviors
var _behaviors : Array = Array()
var _accel : GSAITargetAcceleration = GSAITargetAcceleration.new()
# Appends a behavior to the internal array along with its `weight`.
func add_behavior(behavior : GSAISteeringBehavior, weight : float) -> void:
behavior.agent = agent
var dict : Dictionary = Dictionary()
dict["behavior"] = behavior;
dict["weight"] = weight;
_behaviors.append(dict)
# Returns the behavior at the specified `index`, or an empty `Dictionary` if
# none was found.
func get_behavior(index : int) -> Dictionary:
if _behaviors.size() > index:
return _behaviors[index]
printerr("Tried to get index " + str(index) + " in array of size " + str(_behaviors.size()))
return Dictionary()
func remove_behavior(index : int) -> void:
if _behaviors.size() > index:
_behaviors.remove(index)
return
printerr("Tried to get index " + str(index) + " in array of size " + str(_behaviors.size()))
return
func get_behaviour_count() -> int:
return _behaviors.size()
func get_accel() -> GSAITargetAcceleration:
return _accel
func _calculate_steering(blended_accel: GSAITargetAcceleration) -> void:
blended_accel.set_zero()
for i in range(_behaviors.size()):
var bw : Dictionary = _behaviors[i]
bw.behavior.calculate_steering(_accel)
blended_accel.add_scaled_accel(_accel, bw.weight)
blended_accel.linear = GSAIUtils.clampedv3(blended_accel.linear, agent.linear_acceleration_max)
blended_accel.angular = clamp(blended_accel.angular, -agent.angular_acceleration_max, agent.angular_acceleration_max)

View File

@ -0,0 +1,26 @@
class_name GSAICohesion
extends GSAIGroupBehavior
# Calculates an acceleration that attempts to move the agent towards the center
# of mass of the agents in the area defined by the `GSAIProximity`.
# @category - Group behaviors
var _center_of_mass: Vector3
func _calculate_steering(acceleration: GSAITargetAcceleration) -> void:
acceleration.set_zero()
_center_of_mass = Vector3.ZERO
var neighbor_count = proximity.find_neighbors(_callback)
if neighbor_count > 0:
_center_of_mass *= 1.0 / neighbor_count
acceleration.linear = ((_center_of_mass - agent.position).normalized() * agent.linear_acceleration_max)
# Callback for the proximity to call when finding neighbors. Adds `neighbor`'s position
# to the center of mass of the group.
# @tags - virtual
func _report_neighbor(neighbor: GSAISteeringAgent) -> bool:
_center_of_mass += neighbor.position
return true

View File

@ -0,0 +1,9 @@
class_name GSAIEvade
extends GSAIPursue
# Calculates acceleration to take an agent away from where a target agent is
# moving.
# @category - Individual behaviors
func _get_modified_acceleration() -> float:
return -agent.linear_acceleration_max

View File

@ -0,0 +1,28 @@
class_name GSAIFace
extends GSAIMatchOrientation
# Calculates angular acceleration to rotate a target to face its target's
# position. The behavior attemps to arrive with zero remaining angular velocity.
# @category - Individual behaviors
func face(acceleration: GSAITargetAcceleration, target_position: Vector3) -> void:
call("_face", acceleration, target_position)
func _face(acceleration: GSAITargetAcceleration, target_position: Vector3) -> void:
var to_target : Vector3 = target_position - agent.position
var distance_squared : float = to_target.length_squared()
if distance_squared < agent.zero_linear_speed_threshold:
acceleration.set_zero()
else:
var orientation : float
if use_z:
orientation = GSAIUtils.vector3_to_angle(to_target)
else:
orientation = GSAIUtils.vector2_to_angle(GSAIUtils.to_vector2(to_target))
match_orientation(acceleration, orientation)
func _calculate_steering(acceleration: GSAITargetAcceleration) -> void:
face(acceleration, target.position)

View File

@ -0,0 +1,9 @@
class_name GSAIFlee
extends GSAISeek
# Calculates acceleration to take an agent directly away from a target agent.
# @category - Individual behaviors
func _calculate_steering(acceleration: GSAITargetAcceleration) -> void:
acceleration.linear = ((agent.position - target.position).normalized() * agent.linear_acceleration_max)
acceleration.angular = 0

View File

@ -0,0 +1,48 @@
class_name GSAIFollowPath
extends GSAIArrive
# Produces a linear acceleration that moves the agent along the specified path.
# @category - Individual behaviors
# The path to follow and travel along.
var path : GSAIPath
# The distance along the path to generate the next target position.
var path_offset : float = 0.0
# Whether to use `GSAIArrive` behavior on an open path.
var is_arrive_enabled : bool = true
# The amount of time in the future to predict the owning agent's position along
# the path. Setting it to 0.0 will force non-predictive path following.
var prediction_time : float = 0.0
func _calculate_steering(acceleration: GSAITargetAcceleration) -> void:
var location : Vector3
if prediction_time == 0:
location = agent.position
else:
location = agent.position + (agent.linear_velocity * prediction_time)
var distance : float = path.calculate_distance(location)
var target_distance : float = distance + path_offset
if prediction_time > 0 and path.is_open:
if target_distance < path.calculate_distance(agent.position):
target_distance = path.length
var target_position : Vector3 = path.calculate_target_position(target_distance)
if is_arrive_enabled and path.is_open:
if path_offset >= 0:
if target_distance > path.length - deceleration_radius:
arrive(acceleration, target_position)
return
else:
if target_distance < deceleration_radius:
arrive(acceleration, target_position)
return
acceleration.linear = (target_position - agent.position).normalized()
acceleration.linear *= agent.linear_acceleration_max
acceleration.angular = 0

View File

@ -0,0 +1,19 @@
class_name GSAILookWhereYouGo
extends GSAIMatchOrientation
# Calculates an angular acceleration to match an agent's orientation to its
# direction of travel.
# @category - Individual behaviors
func _calculate_steering(accel: GSAITargetAcceleration) -> void:
if agent.linear_velocity.length_squared() < agent.zero_linear_speed_threshold:
accel.set_zero()
else:
var orientation : float
if use_z:
orientation = GSAIUtils.vector3_to_angle(agent.linear_velocity)
else:
orientation = GSAIUtils.vector2_to_angle(GSAIUtils.to_vector2(agent.linear_velocity))
match_orientation(accel, orientation)

View File

@ -0,0 +1,50 @@
class_name GSAIMatchOrientation
extends GSAISteeringBehavior
# Calculates an angular acceleration to match an agent's orientation to that of
# its target. Attempts to make the agent arrive with zero remaining angular
# velocity.
# @category - Individual behaviors
# The target orientation for the behavior to try and match rotations to.
var target : GSAIAgentLocation
# The amount of distance in radians for the behavior to consider itself close
# enough to be matching the target agent's rotation.
var alignment_tolerance : float = 0.0
# The amount of distance in radians from the goal to start slowing down.
var deceleration_radius : float = 0.0
# The amount of time to reach the target velocity
var time_to_reach : float = 0.1
# Whether to use the X and Z components instead of X and Y components when
# determining angles. X and Z should be used in 3D.
var use_z : bool = false
func match_orientation(acceleration: GSAITargetAcceleration, desired_orientation: float) -> void:
call("_match_orientation", acceleration, desired_orientation)
func _match_orientation(acceleration: GSAITargetAcceleration, desired_orientation: float) -> void:
var rotation : float = wrapf(desired_orientation - agent.orientation, -PI, PI)
var rotation_size : float = abs(rotation)
if rotation_size <= alignment_tolerance:
acceleration.set_zero()
else:
var desired_rotation : float = agent.angular_speed_max
if rotation_size <= deceleration_radius:
desired_rotation *= rotation_size / deceleration_radius
desired_rotation *= rotation / rotation_size
acceleration.angular = ((desired_rotation - agent.angular_velocity) / time_to_reach)
var limited_acceleration : float = abs(acceleration.angular)
if limited_acceleration > agent.angular_acceleration_max:
acceleration.angular *= (agent.angular_acceleration_max / limited_acceleration)
acceleration.linear = Vector3.ZERO
func _calculate_steering(acceleration: GSAITargetAcceleration) -> void:
match_orientation(acceleration, target.orientation)

View File

@ -0,0 +1,60 @@
class_name GSAIPriority
extends GSAISteeringBehavior
# Container for multiple behaviors that returns the result of the first child
# behavior with non-zero acceleration.
# @category - Combination behaviors
# If a behavior's acceleration is lower than this threshold, the container
# considers it has an acceleration of zero.
var zero_threshold : float = 0.0
# The index of the last behavior the container prioritized.
var _last_selected_index : int = 0
var _behaviors : Array = Array()
# Appends a steering behavior as a child of this container.
func add_behavior(behavior: GSAISteeringBehavior) -> void:
_behaviors.append(behavior)
# Returns the behavior at the position in the pool referred to by `index`, or
# `null` if no behavior was found.
func get_behavior(index : int) -> GSAISteeringBehavior:
if _behaviors.size() > index:
return _behaviors[index]
printerr("Tried to get index " + str(index) + " in array of size " + str(_behaviors.size()))
return null
func remove_behavior(index : int) -> void:
if _behaviors.size() > index:
_behaviors.remove(index)
return
printerr("Tried to get index " + str(index) + " in array of size " + str(_behaviors.size()))
return
func get_behaviour_count() -> int:
return _behaviors.size()
func _calculate_steering(accel : GSAITargetAcceleration) -> void:
var threshold_squared : float = zero_threshold * zero_threshold
_last_selected_index = -1
var size : int = _behaviors.size()
if size > 0:
for i in range(size):
_last_selected_index = i
var behavior: GSAISteeringBehavior = _behaviors[i]
behavior.calculate_steering(accel)
if accel.get_magnitude_squared() > threshold_squared:
break
else:
accel.set_zero()

View File

@ -0,0 +1,35 @@
class_name GSAIPursue
extends GSAISteeringBehavior
# Calculates an acceleration to make an agent intercept another based on the
# target agent's movement.
# @category - Individual behaviors
# The target agent that the behavior is trying to intercept.
var target : GSAISteeringAgent
# The maximum amount of time in the future the behavior predicts the target's
# location.
var predict_time_max : float = 1.0
func _calculate_steering(acceleration : GSAITargetAcceleration) -> void:
var target_position : Vector3 = target.position
var distance_squared : float = (target_position - agent.position).length_squared()
var speed_squared : float = agent.linear_velocity.length_squared()
var predict_time : float = predict_time_max
if speed_squared > 0:
var predict_time_squared := distance_squared / speed_squared
if predict_time_squared < predict_time_max * predict_time_max:
predict_time = sqrt(predict_time_squared)
acceleration.linear = ((target_position + (target.linear_velocity * predict_time)) - agent.position).normalized()
acceleration.linear *= get_modified_acceleration()
acceleration.angular = 0
func get_modified_acceleration() -> float:
return call("_get_modified_acceleration")
func _get_modified_acceleration() -> float:
return agent.linear_acceleration_max

View File

@ -0,0 +1,14 @@
class_name GSAISeek
extends GSAISteeringBehavior
# Calculates an acceleration to take an agent to a target agent's position
# directly.
# @category - Individual behaviors
# The target that the behavior aims to move the agent to.
var target : GSAIAgentLocation
func _calculate_steering(acceleration: GSAITargetAcceleration) -> void:
acceleration.linear = ((target.position - agent.position).normalized() * agent.linear_acceleration_max)
acceleration.angular = 0

View File

@ -0,0 +1,40 @@
class_name GSAISeparation
extends GSAIGroupBehavior
# Calculates an acceleration that repels the agent from its neighbors in the
# given `GSAIProximity`.
#
# The acceleration is an average based on all neighbors, multiplied by a
# strength decreasing by the inverse square law in relation to distance, and it
# accumulates.
# @category - Group behaviors
# The coefficient to calculate how fast the separation strength decays with distance.
var decay_coefficient : float = 1.0
var acceleration : GSAITargetAcceleration
func _calculate_steering(_acceleration : GSAITargetAcceleration) -> void:
self.acceleration = _acceleration
acceleration.set_zero()
# warning-ignore:return_value_discarded
proximity.find_neighbors(_callback)
# Callback for the proximity to call when finding neighbors. Determines the amount of
# acceleration that `neighbor` imposes based on its distance from the owner agent.
# @tags - virtual
func _report_neighbor(neighbor : GSAISteeringAgent) -> bool:
var to_agent : Vector3 = agent.position - neighbor.position
var distance_squared : float = to_agent.length_squared()
var acceleration_max : float = agent.linear_acceleration_max
var strength := decay_coefficient / distance_squared
if strength > acceleration_max:
strength = acceleration_max
acceleration.linear += to_agent * (strength / sqrt(distance_squared))
return true

View File

@ -0,0 +1,10 @@
extends Reference
class_name GSAIAgentLocation
# Represents an agent with only a location and an orientation.
# @category - Base types
# The agent's position in space.
var position : Vector3 = Vector3.ZERO
# The agent's orientation on its Y axis rotation.
var orientation : float = 0.0

View File

@ -0,0 +1,19 @@
class_name GSAIGroupBehavior
extends GSAISteeringBehavior
# Base type for group-based steering behaviors.
# @category - Base types
# Container to find neighbors of the agent and calculate group behavior.
var proximity : GSAIProximity
var _callback : FuncRef = funcref(self, "_report_neighbor")
func get_callback() -> FuncRef:
return _callback
# Internal callback for the behavior to define whether or not a member is
# relevant
# @tags - virtual
func _report_neighbor(_neighbor : GSAISteeringAgent) -> bool:
return false

View File

@ -0,0 +1,129 @@
class_name GSAIPath
extends Reference
# Represents a path made up of Vector3 waypoints, split into segments path
# follow behaviors can use.
# @category - Base types
# If `false`, the path loops.
var is_open : bool
# Total length of the path.
var length : float
var _segments : Array
var _nearest_point_on_segment : Vector3
var _nearest_point_on_path : Vector3
func initialize(waypoints : Array, _is_open : bool = false) -> void:
self.is_open = _is_open
create_path(waypoints)
_nearest_point_on_segment = waypoints[0]
_nearest_point_on_path = waypoints[0]
# Creates a path from a list of waypoints.
func create_path(waypoints : Array) -> void:
if not waypoints or waypoints.size() < 2:
printerr("Waypoints cannot be null and must contain at least two (2) waypoints.")
return
_segments = []
length = 0
var current : Vector3 = waypoints.front()
var previous : Vector3
for i in range(1, waypoints.size(), 1):
previous = current
if i < waypoints.size():
current = waypoints[i]
elif is_open:
break
else:
current = waypoints[0]
var segment : GSAISegment = GSAISegment.new(previous, current)
length += segment.length
segment.cumulative_length = length
_segments.append(segment)
# Returns the distance from `agent_current_position` to the next waypoint.
func calculate_distance(agent_current_position : Vector3) -> float:
if _segments.size() == 0:
return 0.0
var smallest_distance_squared : float = INF
var nearest_segment : GSAISegment = null
for i in range(_segments.size()):
var segment: GSAISegment = _segments[i]
var distance_squared : float = _calculate_point_segment_distance_squared(segment.begin, segment.end, agent_current_position)
if distance_squared < smallest_distance_squared:
_nearest_point_on_path = _nearest_point_on_segment
smallest_distance_squared = distance_squared
nearest_segment = segment
var length_on_path : float = nearest_segment.cumulative_length - _nearest_point_on_path.distance_to(nearest_segment.end)
return length_on_path
# Calculates a target position from the path's starting point based on the `target_distance`.
func calculate_target_position(target_distance: float) -> Vector3:
if is_open:
target_distance = clamp(target_distance, 0, length)
else:
if target_distance < 0:
target_distance = length + fmod(target_distance, length)
elif target_distance > length:
target_distance = fmod(target_distance, length)
var desired_segment: GSAISegment
for i in range(_segments.size()):
var segment: GSAISegment = _segments[i]
if segment.cumulative_length >= target_distance:
desired_segment = segment
break
if not desired_segment:
desired_segment = _segments.back()
var distance := desired_segment.cumulative_length - target_distance
return (((desired_segment.begin - desired_segment.end) * (distance / desired_segment.length)) + desired_segment.end)
# Returns the position of the first point on the path.
func get_start_point() -> Vector3:
return _segments.front().begin
# Returns the position of the last point on the path.
func get_end_point() -> Vector3:
return _segments.back().end
func _calculate_point_segment_distance_squared(start: Vector3, end: Vector3, position: Vector3) -> float:
_nearest_point_on_segment = start
var start_end : Vector3 = end - start
var start_end_length_squared : float = start_end.length_squared()
if start_end_length_squared != 0:
var t = (position - start).dot(start_end) / start_end_length_squared
_nearest_point_on_segment += start_end * clamp(t, 0, 1)
return _nearest_point_on_segment.distance_squared_to(position)
# not exposed helper struct
class GSAISegment:
var begin: Vector3
var end: Vector3
var length: float
var cumulative_length: float
func _init(_begin: Vector3, _end: Vector3) -> void:
self.begin = _begin
self.end = _end
length = _begin.distance_to(_end)

View File

@ -0,0 +1,29 @@
extends GSAIAgentLocation
class_name GSAISteeringAgent
# Adds velocity, speed, and size data to `GSAIAgentLocation`.
#
# It is the character's responsibility to keep this information up to date for
# the steering toolkit to work correctly.
# @category - Base types
# The amount of velocity to be considered effectively not moving.
var zero_linear_speed_threshold : float = 0.01
# The maximum speed at which the agent can move.
var linear_speed_max : float = 0.0
# The maximum amount of acceleration that any behavior can apply to the agent.
var linear_acceleration_max : float = 0.0
# The maximum amount of angular speed at which the agent can rotate.
var angular_speed_max : float = 0.0
# The maximum amount of angular acceleration that any behavior can apply to an
# agent.
var angular_acceleration_max : float = 0.0
# Current velocity of the agent.
var linear_velocity : Vector3 = Vector3.ZERO
# Current angular velocity of the agent.
var angular_velocity : float = 0.0
# The radius of the sphere that approximates the agent's size in space.
var bounding_radius : float = 0.0
# Used internally by group behaviors and proximities to mark the agent as already
# considered.
var is_tagged : bool = false

View File

@ -0,0 +1,26 @@
extends Reference
class_name GSAISteeringBehavior
# Base class for all steering behaviors.
#
# Steering behaviors calculate the linear and the angular acceleration to be
# to the agent that owns them.
#
# The `calculate_steering` function is the entry point for all behaviors.
# Individual steering behaviors encapsulate the steering logic.
# @category - Base types
# If `false`, all calculations return zero amounts of acceleration.
var is_enabled : bool = true
# The AI agent on which the steering behavior bases its calculations.
var agent : GSAISteeringAgent
# Sets the `acceleration` with the behavior's desired amount of acceleration.
func calculate_steering(acceleration: GSAITargetAcceleration) -> void:
if is_enabled:
call("_calculate_steering", acceleration)
else:
acceleration.set_zero()
func _calculate_steering(acceleration: GSAITargetAcceleration) -> void:
acceleration.set_zero()

View File

@ -0,0 +1,31 @@
extends Reference
class_name GSAITargetAcceleration
# A desired linear and angular amount of acceleration requested by the steering
# system.
# @category - Base types
# Linear acceleration
var linear : Vector3 = Vector3.ZERO
# Angular acceleration
var angular : float = 0.0
# Sets the linear and angular components to 0.
func set_zero() -> void:
linear.x = 0.0
linear.y = 0.0
linear.z = 0.0
angular = 0.0
# Adds `accel`'s components, multiplied by `scalar`, to this one.
func add_scaled_accel(accel: GSAITargetAcceleration, scalar: float) -> void:
linear += accel.linear * scalar
angular += accel.angular * scalar
# Returns the squared magnitude of the linear and angular components.
func get_magnitude_squared() -> float:
return linear.length_squared() + angular * angular
# Returns the magnitude of the linear and angular components.
func get_magnitude() -> float:
return sqrt(get_magnitude_squared())

View File

@ -0,0 +1,40 @@
class_name GSAIUtils
# Math and vector utility functions.
# @Category - Utilities
# Returns the `vector` with its length capped to `limit`.
static func clampedv3(vector: Vector3, limit: float) -> Vector3:
var length_squared : float = vector.length_squared()
var limit_squared : float = limit * limit
if length_squared > limit_squared:
vector *= sqrt(limit_squared / length_squared)
return vector
# Returns an angle in radians between the positive X axis and the `vector`.
#
# This assumes orientation for 3D agents that are upright and rotate
# around the Y axis.
static func vector3_to_angle(vector: Vector3) -> float:
return atan2(vector.x, vector.z)
# Returns an angle in radians between the positive X axis and the `vector`.
static func vector2_to_angle(vector: Vector2) -> float:
return atan2(vector.x, -vector.y)
# Returns a directional vector from the given orientation angle.
#
# This assumes orientation for 2D agents or 3D agents that are upright and
# rotate around the Y axis.
static func angle_to_vector2(angle: float) -> Vector2:
return Vector2(sin(-angle), cos(angle))
# Returns a vector2 with `vector`'s x and y components.
static func to_vector2(vector: Vector3) -> Vector2:
return Vector2(vector.x, vector.y)
# Returns a vector3 with `vector`'s x and y components and 0 in z.
static func to_vector3(vector: Vector2) -> Vector3:
return Vector3(vector.x, vector.y, 0)

View File

@ -0,0 +1,23 @@
extends GSAIProximity
class_name GSAIInfiniteProximity
# Determines any agent that is in the specified list as being neighbors with the
# owner agent, regardless of distance.
# @category - Proximities
# Returns a number of neighbors based on a `callback` function.
#
# `_find_neighbors` calls `callback` for each agent in the `agents` array and
# adds one to the count if its `callback` returns true.
# @tags - virtual
func _find_neighbors(callback: FuncRef) -> int:
var neighbor_count : int = 0
var agent_count : int = agents.size()
for i in range(agent_count):
var current_agent : GSAISteeringAgent = agents[i] as GSAISteeringAgent
if current_agent != agent:
if callback.call_func(current_agent):
neighbor_count += 1
return neighbor_count

View File

@ -0,0 +1,22 @@
extends Reference
class_name GSAIProximity
# Base container type that stores data to find the neighbors of an agent.
# @category - Proximities
# @tags - abstract
# The owning agent whose neighbors are found in the group
var agent : GSAISteeringAgent
# The agents who are part of this group and could be potential neighbors
var agents : Array = Array()
func find_neighbors(_callback: FuncRef) -> int:
return call("_find_neighbors", _callback)
# Returns a number of neighbors based on a `callback` function.
#
# `_find_neighbors` calls `callback` for each agent in the `agents` array and
# adds one to the count if its `callback` returns true.
# @tags - virtual
func _find_neighbors(_callback: FuncRef) -> int:
return 0

View File

@ -0,0 +1,62 @@
extends GSAIProximity
class_name GSAIRadiusProximity
# Determines any agent that is in the specified list as being neighbors with the owner agent if
# they lie within the specified radius.
# @category - Proximities
# The radius around the owning agent to find neighbors in
var radius : float = 0.0
var _last_frame : int = 0
var _scene_tree : SceneTree
func _init() -> void:
_scene_tree = Engine.get_main_loop()
# Returns a number of neighbors based on a `callback` function.
#
# `_find_neighbors` calls `callback` for each agent in the `agents` array that lie within
# the radius around the owning agent and adds one to the count if its `callback` returns true.
# @tags - virtual
func _find_neighbors(callback : FuncRef) -> int:
var agent_count : int = agents.size()
var neighbor_count : int = 0
var current_frame : int
if _scene_tree:
current_frame = _scene_tree.get_frame()
else:
current_frame = -_last_frame
if current_frame != _last_frame:
_last_frame = current_frame
var owner_position : Vector3 = agent.position
for i in range(agent_count):
var current_agent : GSAISteeringAgent = agents[i] as GSAISteeringAgent
if current_agent != agent:
var distance_squared : float = owner_position.distance_squared_to(current_agent.position)
var range_to : float = radius + current_agent.bounding_radius
if distance_squared < range_to * range_to:
if callback.call_func(current_agent):
current_agent.is_tagged = true
neighbor_count += 1
continue
current_agent.is_tagged = false
else:
for i in range(agent_count):
var current_agent : GSAISteeringAgent = agents[i] as GSAISteeringAgent
if current_agent != agent and current_agent.is_tagged:
if callback.call_func(current_agent):
neighbor_count += 1
return neighbor_count

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -0,0 +1,35 @@
[remap]
importer="texture"
type="StreamTexture"
path="res://.import/icon.png-b6a7fb2db36edd3d95dc42f1dc8c1c5d.stex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://assets/icon.png"
dest_files=[ "res://.import/icon.png-b6a7fb2db36edd3d95dc42f1dc8c1c5d.stex" ]
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_mode=0
compress/bptc_ldr=0
compress/normal_map=0
flags/repeat=0
flags/filter=true
flags/mipmaps=false
flags/anisotropic=false
flags/srgb=2
process/fix_alpha_border=true
process/premult_alpha=false
process/HDR_as_SRGB=false
process/invert_color=false
process/normal_map_invert_y=false
stream=false
size_limit=0
detect_3d=true
svg/scale=1.0

View File

@ -0,0 +1,151 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
sodipodi:docname="icon.svg"
inkscape:version="1.1-dev (83f2ee46ff, 2020-02-13)"
id="svg1340"
version="1.1"
viewBox="0 0 16.933333 16.933334"
height="64"
width="64">
<defs
id="defs1334">
<clipPath
id="clipPath1560"
clipPathUnits="userSpaceOnUse">
<rect
style="font-variation-settings:normal;opacity:1;vector-effect:none;fill:#293252;fill-opacity:1;stroke:none;stroke-width:1.40425;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0;stop-color:#000000;stop-opacity:1"
id="rect1562"
width="16.933332"
height="16.933332"
x="0"
y="9.6346028e-08" />
</clipPath>
</defs>
<sodipodi:namedview
inkscape:snap-global="true"
inkscape:snap-others="false"
inkscape:object-nodes="false"
inkscape:snap-page="true"
inkscape:snap-bbox="true"
inkscape:window-maximized="1"
inkscape:window-y="0"
inkscape:window-x="0"
inkscape:window-height="1043"
inkscape:window-width="1920"
units="px"
showgrid="false"
inkscape:current-layer="layer1"
inkscape:document-units="mm"
inkscape:cy="31.438019"
inkscape:cx="15.072392"
inkscape:zoom="8"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
borderopacity="1.0"
bordercolor="#666666"
pagecolor="#ffffff"
id="base" />
<metadata
id="metadata1337">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="layer1"
inkscape:groupmode="layer"
inkscape:label="Layer 1">
<rect
y="0"
x="0"
height="16.933332"
width="16.933332"
id="rect1504"
style="font-variation-settings:normal;opacity:1;vector-effect:none;fill:#293252;fill-opacity:1;stroke:none;stroke-width:1.40425;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0;stop-color:#000000;stop-opacity:1" />
<path
clip-path="url(#clipPath1560)"
id="path2490"
d="M 2.8526138,-0.0226856 2.9507848,1.274306 C 2.5771927,1.4791664 2.2289224,1.7185349 1.9075218,1.988743 L 0.29690799,1.3221671 -0.40866643,2.1624613 0.56110833,3.5585158 C 0.33791661,3.9257554 0.15316113,4.3166174 0.00975169,4.7250204 l -0.7432386,0.090259 -0.009472,1.7228785 0.40521717,0.191826 c 0,0.2151562 0.0190602,0.4320655 0.0310875,0.6505844 0.0205584,0.2185642 0.0528327,0.4331209 0.0974454,0.6435666 l -0.54416748,0.401985 -0.008756,1.5907221 1.2930922,-0.090536 C 0.75306491,10.299542 1.0115466,10.64731 1.3029499,10.96517 l -0.672761,1.553839 0.858959,0.689584 1.4456413,-0.959917 c 0.3687832,0.203771 0.7600417,0.372343 1.1673979,0.502553 l 0.2153945,1.714295 1.0964166,0.176342 0.7818497,-1.5822 c 0.2037711,0 0.4094501,-0.01685 0.6159876,-0.03895 H 6.812698 L 9.8042599,12.732617 9.3779282,8.4823903 6.3854715,8.7696026 6.5157868,10.092058 C 4.6330774,10.272636 2.9585336,8.9258916 2.7734622,7.0836627 2.5888891,5.2418454 3.9644158,3.6030022 5.8465387,3.4220125 L 8.8381053,3.1348571 8.5438181,0.20619376 5.5531989,0.49334922 C 5.3459823,0.51050176 5.1420605,0.5378074 4.9415586,0.57229759 L 4.4901554,-0.01349219 Z M 10.360111,2.7244603 9.577372,4.3066562 c -0.2035882,0 -0.4084223,0.016659 -0.6150947,0.038946 L 5.969764,4.633705 6.2641051,7.5605285 9.2556723,7.273373 a 3.4244787,3.3513826 0 0 1 3.7431617,3.007448 3.4244787,3.3513826 0 0 1 -3.0739683,3.662595 l -2.9915662,0.28894 0.2652052,2.646411 2.7965066,0.01566 0.2241417,-0.0215 c 0.206675,-0.01714 0.41087,-0.0444 0.610692,-0.07889 l 0.0814,0.105523 1.973596,0.01109 -0.06151,-0.818622 c 0.373232,-0.204827 0.723028,-0.444409 1.044158,-0.714444 l 1.609666,0.667466 0.705575,-0.840294 -0.968773,-1.396053 c 0.222649,-0.367242 0.407573,-0.758822 0.551301,-1.167399 l 1.699205,-0.205645 0.181684,-1.073405 -1.533445,-0.725078 c 0.01889,-0.214756 -0.01899,-0.432247 -0.03098,-0.6505829 -0.02061,-0.2185654 -0.05301,-0.4339146 -0.09935,-0.6444001 L 17.342103,8.3379231 16.950314,7.3203422 15.242313,7.4418002 C 15.020345,7.0685674 14.760954,6.7200461 14.469323,6.4020456 L 15.142137,4.8490976 14.284072,4.1595128 12.836646,5.1194284 C 12.467685,4.9156577 12.078251,4.7477386 11.671033,4.6177136 L 11.454747,2.9034731 Z"
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#2f395f;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3.17682;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
inkscape:connector-curvature="0" />
<g
style="stroke-width:1.24672"
transform="matrix(0.18285727,0,0,0.14154908,150.31973,-187.36868)"
id="g2450">
<path
inkscape:connector-curvature="0"
style="opacity:1;vector-effect:none;fill:#5e315b;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.989584;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.97917, 1.97917;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:normal"
d="m -822.06042,1434.5708 h 9.26041 v 9.2604 h -9.26041 z"
id="path2430" />
<path
id="path2432"
d="m -812.80001,1434.5708 h 9.26041 v 9.2604 h -9.26041 z"
style="opacity:1;vector-effect:none;fill:#8c3f5d;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.989584;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.97917, 1.97917;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:normal"
inkscape:connector-curvature="0" />
<path
inkscape:connector-curvature="0"
style="opacity:1;vector-effect:none;fill:#ba6156;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.989584;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.97917, 1.97917;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:normal"
d="m -803.5396,1434.5708 h 9.26041 v 9.2604 h -9.26041 z"
id="path2434" />
<path
id="path2436"
d="m -794.27919,1434.5708 h 9.26041 v 9.2604 h -9.26041 z"
style="opacity:1;vector-effect:none;fill:#f2a65e;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.989584;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.97917, 1.97917;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:normal"
inkscape:connector-curvature="0" />
<path
inkscape:connector-curvature="0"
style="opacity:1;vector-effect:none;fill:#ffe478;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.989584;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.97917, 1.97917;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:normal"
d="m -785.01878,1434.5708 h 9.26041 v 9.2604 h -9.26041 z"
id="path2438" />
<path
id="path2440"
d="m -775.75837,1434.5708 h 9.26041 v 9.2604 h -9.26041 z"
style="opacity:1;vector-effect:none;fill:#cfff70;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.989584;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.97917, 1.97917;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:normal"
inkscape:connector-curvature="0" />
<path
inkscape:connector-curvature="0"
style="opacity:1;vector-effect:none;fill:#8fde5d;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.989584;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.97917, 1.97917;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:normal"
d="m -766.49796,1434.5708 h 9.26041 v 9.2604 h -9.26041 z"
id="path2442" />
<path
id="path2444"
d="m -757.23755,1434.5708 h 9.26041 v 9.2604 h -9.26041 z"
style="opacity:1;vector-effect:none;fill:#3ca370;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.989584;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.97917, 1.97917;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:normal"
inkscape:connector-curvature="0" />
<path
inkscape:connector-curvature="0"
style="opacity:1;vector-effect:none;fill:#3d6e70;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.989584;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.97917, 1.97917;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:normal"
d="m -747.97714,1434.5708 h 9.26041 v 9.2604 h -9.26041 z"
id="path2446" />
<path
id="path2448"
d="m -738.71673,1434.5708 h 9.26041 v 9.2604 h -9.26041 z"
style="opacity:1;vector-effect:none;fill:#323e4f;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.989584;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.97917, 1.97917;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:normal"
inkscape:connector-curvature="0" />
</g>
<path
sodipodi:nodetypes="cccc"
d="M 1.7415414,13.864431 C 2.8091455,12.301646 3.8954985,10.918152 4.8840406,10.004274 M 12.44693,3.6341761 c 0,0 1.658558,-1.2568206 2.909891,-2.065662"
style="fill:none;stroke:#a0a9ce;stroke-width:1.087;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:1.6;stroke-dasharray:2.174, 2.174;stroke-dashoffset:1.087;stroke-opacity:1"
id="path2283"
inkscape:connector-curvature="0" />
<path
inkscape:connector-curvature="0"
id="path2351"
d="M 4.6789148,3.8638581 6.5936823,6.200326 4.9999674,7.512026 7.0448877,10.007103 8.6388709,8.6952013 10.553866,11.032019 11.16251,4.529541 Z"
style="vector-effect:none;fill:#293252;fill-opacity:1;fill-rule:nonzero;stroke:#7786bb;stroke-width:0.827338;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:normal" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -0,0 +1,35 @@
[remap]
importer="texture"
type="StreamTexture"
path="res://.import/icon.svg-56083ea2a1f1a4f1e49773bdc6d7826c.stex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://assets/icon.svg"
dest_files=[ "res://.import/icon.svg-56083ea2a1f1a4f1e49773bdc6d7826c.stex" ]
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_mode=0
compress/bptc_ldr=0
compress/normal_map=0
flags/repeat=0
flags/filter=true
flags/mipmaps=false
flags/anisotropic=false
flags/srgb=2
process/fix_alpha_border=true
process/premult_alpha=false
process/HDR_as_SRGB=false
process/invert_color=false
process/normal_map_invert_y=false
stream=false
size_limit=0
detect_3d=true
svg/scale=1.0

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View File

@ -0,0 +1,35 @@
[remap]
importer="texture"
type="StreamTexture"
path="res://.import/background.png-dde469fb1f19281f3784b52d4bea96cd.stex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://assets/sprites/background.png"
dest_files=[ "res://.import/background.png-dde469fb1f19281f3784b52d4bea96cd.stex" ]
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_mode=0
compress/bptc_ldr=0
compress/normal_map=0
flags/repeat=0
flags/filter=true
flags/mipmaps=false
flags/anisotropic=false
flags/srgb=2
process/fix_alpha_border=true
process/premult_alpha=false
process/HDR_as_SRGB=false
process/invert_color=false
process/normal_map_invert_y=false
stream=false
size_limit=0
detect_3d=true
svg/scale=1.0

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,8 @@
[gd_resource type="DynamicFont" load_steps=2 format=2]
[ext_resource path="res://assets/theme/fonts/montserrat/Montserrat-Medium.ttf" type="DynamicFontData" id=1]
[resource]
size = 26
use_filter = true
font_data = ExtResource( 1 )

View File

@ -0,0 +1,8 @@
[gd_resource type="DynamicFont" load_steps=2 format=2]
[ext_resource path="res://assets/theme/fonts/montserrat/Montserrat-Bold.ttf" type="DynamicFontData" id=1]
[resource]
size = 26
use_filter = true
font_data = ExtResource( 1 )

View File

@ -0,0 +1,8 @@
[gd_resource type="DynamicFont" load_steps=2 format=2]
[ext_resource path="res://assets/theme/fonts/source_code_pro/SourceCodePro-Medium.otf" type="DynamicFontData" id=1]
[resource]
size = 26
use_filter = true
font_data = ExtResource( 1 )

View File

@ -0,0 +1,8 @@
[gd_resource type="DynamicFont" load_steps=2 format=2]
[ext_resource path="res://assets/theme/fonts/montserrat/Montserrat-Black.ttf" type="DynamicFontData" id=1]
[resource]
size = 34
use_filter = true
font_data = ExtResource( 1 )

Some files were not shown because too many files have changed in this diff Show More