mirror of
https://github.com/Relintai/godot-steering-ai-framework.git
synced 2024-11-14 04:57:19 +01:00
Merge pull request #2 from GDQuest/features/mvp-impl
Features/mvp implementation
This commit is contained in:
commit
fa91e6bca7
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
.import/
|
@ -8,6 +8,7 @@
|
||||
- Arrive
|
||||
- Pursue/Evade
|
||||
- Face
|
||||
- LookWhereYouGo
|
||||
- FollowPath
|
||||
- Separation
|
||||
- AvoidCollisions
|
BIN
project/assets/sprites/large_circle.png
Normal file
BIN
project/assets/sprites/large_circle.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.3 KiB |
34
project/assets/sprites/large_circle.png.import
Normal file
34
project/assets/sprites/large_circle.png.import
Normal file
@ -0,0 +1,34 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="StreamTexture"
|
||||
path="res://.import/large_circle.png-31c2e25548cad683b9cdbdea4df32e13.stex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/sprites/large_circle.png"
|
||||
dest_files=[ "res://.import/large_circle.png-31c2e25548cad683b9cdbdea4df32e13.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
|
||||
stream=false
|
||||
size_limit=0
|
||||
detect_3d=true
|
||||
svg/scale=1.0
|
BIN
project/assets/sprites/small_circle.png
Normal file
BIN
project/assets/sprites/small_circle.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.9 KiB |
34
project/assets/sprites/small_circle.png.import
Normal file
34
project/assets/sprites/small_circle.png.import
Normal file
@ -0,0 +1,34 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="StreamTexture"
|
||||
path="res://.import/small_circle.png-e9ef462acf0465fde3767e7b0877ff44.stex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/sprites/small_circle.png"
|
||||
dest_files=[ "res://.import/small_circle.png-e9ef462acf0465fde3767e7b0877ff44.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
|
||||
stream=false
|
||||
size_limit=0
|
||||
detect_3d=true
|
||||
svg/scale=1.0
|
BIN
project/assets/sprites/triangle.png
Normal file
BIN
project/assets/sprites/triangle.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.7 KiB |
34
project/assets/sprites/triangle.png.import
Normal file
34
project/assets/sprites/triangle.png.import
Normal file
@ -0,0 +1,34 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="StreamTexture"
|
||||
path="res://.import/triangle.png-fa05d9e46946b626c9973edf66af1138.stex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/sprites/triangle.png"
|
||||
dest_files=[ "res://.import/triangle.png-fa05d9e46946b626c9973edf66af1138.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
|
||||
stream=false
|
||||
size_limit=0
|
||||
detect_3d=true
|
||||
svg/scale=1.0
|
BIN
project/assets/theme/button/disabled.stylebox
Normal file
BIN
project/assets/theme/button/disabled.stylebox
Normal file
Binary file not shown.
BIN
project/assets/theme/button/focused.stylebox
Normal file
BIN
project/assets/theme/button/focused.stylebox
Normal file
Binary file not shown.
BIN
project/assets/theme/button/hover.stylebox
Normal file
BIN
project/assets/theme/button/hover.stylebox
Normal file
Binary file not shown.
BIN
project/assets/theme/button/normal.stylebox
Normal file
BIN
project/assets/theme/button/normal.stylebox
Normal file
Binary file not shown.
BIN
project/assets/theme/button/pressed.stylebox
Normal file
BIN
project/assets/theme/button/pressed.stylebox
Normal file
Binary file not shown.
BIN
project/assets/theme/empty.stylebox
Normal file
BIN
project/assets/theme/empty.stylebox
Normal file
Binary file not shown.
11
project/assets/theme/fonts/default_font.tres
Normal file
11
project/assets/theme/fonts/default_font.tres
Normal file
@ -0,0 +1,11 @@
|
||||
[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 = 20
|
||||
use_filter = true
|
||||
font_data = ExtResource( 1 )
|
||||
|
11
project/assets/theme/fonts/default_font_bold.tres
Normal file
11
project/assets/theme/fonts/default_font_bold.tres
Normal file
@ -0,0 +1,11 @@
|
||||
[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 = 20
|
||||
use_filter = true
|
||||
font_data = ExtResource( 1 )
|
||||
|
11
project/assets/theme/fonts/default_font_code.tres
Normal file
11
project/assets/theme/fonts/default_font_code.tres
Normal file
@ -0,0 +1,11 @@
|
||||
[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 = 20
|
||||
use_filter = true
|
||||
font_data = ExtResource( 1 )
|
||||
|
11
project/assets/theme/fonts/font_title.tres
Normal file
11
project/assets/theme/fonts/font_title.tres
Normal file
@ -0,0 +1,11 @@
|
||||
[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 = 28
|
||||
use_filter = true
|
||||
font_data = ExtResource( 1 )
|
||||
|
BIN
project/assets/theme/fonts/montserrat/Montserrat-Black.ttf
Normal file
BIN
project/assets/theme/fonts/montserrat/Montserrat-Black.ttf
Normal file
Binary file not shown.
BIN
project/assets/theme/fonts/montserrat/Montserrat-Bold.ttf
Normal file
BIN
project/assets/theme/fonts/montserrat/Montserrat-Bold.ttf
Normal file
Binary file not shown.
BIN
project/assets/theme/fonts/montserrat/Montserrat-Medium.ttf
Normal file
BIN
project/assets/theme/fonts/montserrat/Montserrat-Medium.ttf
Normal file
Binary file not shown.
Binary file not shown.
BIN
project/assets/theme/gdquest.theme
Normal file
BIN
project/assets/theme/gdquest.theme
Normal file
Binary file not shown.
1
project/assets/theme/icons/chevron-right.svg
Normal file
1
project/assets/theme/icons/chevron-right.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-chevron-right"><polyline points="9 18 15 12 9 6"></polyline></svg>
|
After Width: | Height: | Size: 270 B |
34
project/assets/theme/icons/chevron-right.svg.import
Normal file
34
project/assets/theme/icons/chevron-right.svg.import
Normal file
@ -0,0 +1,34 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="StreamTexture"
|
||||
path="res://.import/chevron-right.svg-f77dee7a088177a2ac1d467f4c7cd3e1.stex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/theme/icons/chevron-right.svg"
|
||||
dest_files=[ "res://.import/chevron-right.svg-f77dee7a088177a2ac1d467f4c7cd3e1.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
|
||||
stream=false
|
||||
size_limit=0
|
||||
detect_3d=true
|
||||
svg/scale=1.0
|
1
project/assets/theme/icons/chevron-up.svg
Normal file
1
project/assets/theme/icons/chevron-up.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-chevron-up"><polyline points="18 15 12 9 6 15"></polyline></svg>
|
After Width: | Height: | Size: 268 B |
34
project/assets/theme/icons/chevron-up.svg.import
Normal file
34
project/assets/theme/icons/chevron-up.svg.import
Normal file
@ -0,0 +1,34 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="StreamTexture"
|
||||
path="res://.import/chevron-up.svg-48b5b69265734774d0a7516dcc6f0863.stex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/theme/icons/chevron-up.svg"
|
||||
dest_files=[ "res://.import/chevron-up.svg-48b5b69265734774d0a7516dcc6f0863.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
|
||||
stream=false
|
||||
size_limit=0
|
||||
detect_3d=true
|
||||
svg/scale=1.0
|
BIN
project/assets/theme/panel/panel.stylebox
Normal file
BIN
project/assets/theme/panel/panel.stylebox
Normal file
Binary file not shown.
7
project/assets/theme/separator/line.tres
Normal file
7
project/assets/theme/separator/line.tres
Normal file
@ -0,0 +1,7 @@
|
||||
[gd_resource type="StyleBoxLine" format=2]
|
||||
|
||||
[resource]
|
||||
|
||||
color = Color( 1, 1, 1, 0.196078 )
|
||||
thickness = 2
|
||||
|
BIN
project/assets/theme/slider/grabber_area.stylebox
Normal file
BIN
project/assets/theme/slider/grabber_area.stylebox
Normal file
Binary file not shown.
BIN
project/assets/theme/slider/slider.stylebox
Normal file
BIN
project/assets/theme/slider/slider.stylebox
Normal file
Binary file not shown.
7
project/default_env.tres
Normal file
7
project/default_env.tres
Normal file
@ -0,0 +1,7 @@
|
||||
[gd_resource type="Environment" load_steps=2 format=2]
|
||||
|
||||
[sub_resource type="ProceduralSky" id=1]
|
||||
|
||||
[resource]
|
||||
background_mode = 2
|
||||
background_sky = SubResource( 1 )
|
37
project/demos/arrive/ArriveDemo.gd
Normal file
37
project/demos/arrive/ArriveDemo.gd
Normal file
@ -0,0 +1,37 @@
|
||||
extends Node2D
|
||||
|
||||
|
||||
onready var target: = $Target
|
||||
onready var arriver: = $Arriver
|
||||
onready var gui: = $GUI
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
gui.connect("align_tolerance_changed", self, "_on_GUI_align_tolerance_changed")
|
||||
gui.connect("decel_radius_changed", self, "_on_GUI_decel_radius_changed")
|
||||
gui.connect("max_speed_changed", self, "_on_GUI_max_speed_changed")
|
||||
gui.connect("max_accel_changed", self, "_on_GUI_max_accel_changed")
|
||||
gui.max_speed.text = str(arriver._agent.max_linear_speed)
|
||||
gui.max_accel.text = str(arriver._agent.max_linear_acceleration)
|
||||
gui.arrival_tolerance.text = str(arriver._arrive.arrival_tolerance)
|
||||
gui.deceleration_radius.text = str(arriver._arrive.deceleration_radius)
|
||||
|
||||
|
||||
func draw(location: Vector2) -> void:
|
||||
target.draw(location)
|
||||
|
||||
|
||||
func _on_GUI_align_tolerance_changed(value: int) -> void:
|
||||
arriver._arrive.arrival_tolerance = value
|
||||
|
||||
|
||||
func _on_GUI_decel_radius_changed(value: int) -> void:
|
||||
arriver._arrive.deceleration_radius = value
|
||||
|
||||
|
||||
func _on_GUI_max_speed_changed(value: int) -> void:
|
||||
arriver._agent.max_linear_speed = value
|
||||
|
||||
|
||||
func _on_GUI_max_accel_changed(value: int) -> void:
|
||||
arriver._agent.max_linear_acceleration = value
|
25
project/demos/arrive/ArriveDemo.tscn
Normal file
25
project/demos/arrive/ArriveDemo.tscn
Normal file
@ -0,0 +1,25 @@
|
||||
[gd_scene load_steps=6 format=2]
|
||||
|
||||
[ext_resource path="res://demos/arrive/Arriver.gd" type="Script" id=1]
|
||||
[ext_resource path="res://demos/arrive/ArriveDemo.gd" type="Script" id=2]
|
||||
[ext_resource path="res://demos/arrive/Target.gd" type="Script" id=3]
|
||||
[ext_resource path="res://demos/arrive/GUI.tscn" type="PackedScene" id=4]
|
||||
|
||||
[sub_resource type="CircleShape2D" id=1]
|
||||
radius = 15.0
|
||||
|
||||
[node name="ArriveDemo" type="Node2D"]
|
||||
script = ExtResource( 2 )
|
||||
|
||||
[node name="Arriver" type="KinematicBody2D" parent="."]
|
||||
script = ExtResource( 1 )
|
||||
|
||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="Arriver"]
|
||||
shape = SubResource( 1 )
|
||||
|
||||
[node name="Target" type="Node2D" parent="."]
|
||||
script = ExtResource( 3 )
|
||||
|
||||
[node name="GUI" parent="." instance=ExtResource( 4 )]
|
||||
margin_right = 227.0
|
||||
margin_bottom = 184.0
|
46
project/demos/arrive/Arriver.gd
Normal file
46
project/demos/arrive/Arriver.gd
Normal file
@ -0,0 +1,46 @@
|
||||
extends KinematicBody2D
|
||||
|
||||
|
||||
onready var collision_shape: = $CollisionShape2D
|
||||
|
||||
var _radius: = 0.0
|
||||
var _agent: = GSTSteeringAgent.new()
|
||||
var _target: = GSTAgentLocation.new()
|
||||
var _arrive: = GSTArrive.new(_agent, _target)
|
||||
var _accel: = GSTTargetAcceleration.new()
|
||||
|
||||
var _velocity: = Vector2()
|
||||
var _drag: = 1.0
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
_radius = collision_shape.shape.radius
|
||||
_agent.max_linear_acceleration = 10
|
||||
_agent.max_linear_speed = 200
|
||||
_arrive.arrival_tolerance = 25
|
||||
_arrive.deceleration_radius = 225
|
||||
|
||||
|
||||
func _draw() -> void:
|
||||
draw_circle(Vector2.ZERO, _radius, Color.red)
|
||||
|
||||
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
if event is InputEventMouseButton:
|
||||
var mb: InputEventMouseButton = event
|
||||
if mb.button_index == BUTTON_LEFT and mb.pressed:
|
||||
_target.position = Vector3(mb.position.x, mb.position.y, 0)
|
||||
owner.draw(Vector2(_target.position.x, _target.position.y))
|
||||
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
_accel = _arrive.calculate_steering(_accel)
|
||||
_velocity += Vector2(_accel.linear.x, _accel.linear.y)
|
||||
_velocity -= _velocity * _drag * delta
|
||||
_velocity = move_and_slide(_velocity)
|
||||
_update_agent()
|
||||
|
||||
|
||||
func _update_agent() -> void:
|
||||
_agent.position = Vector3(global_position.x, global_position.y, 0)
|
||||
_agent.linear_velocity = Vector3(_velocity.x, _velocity.y, 0)
|
47
project/demos/arrive/GUI.gd
Normal file
47
project/demos/arrive/GUI.gd
Normal file
@ -0,0 +1,47 @@
|
||||
extends MarginContainer
|
||||
|
||||
|
||||
signal max_speed_changed(value)
|
||||
signal max_accel_changed(value)
|
||||
signal align_tolerance_changed(value)
|
||||
signal decel_radius_changed(value)
|
||||
|
||||
onready var max_speed: = $Controls/MaxSpeed/LineEdit
|
||||
onready var max_accel: = $Controls/MaxAccel/LineEdit
|
||||
onready var arrival_tolerance: = $Controls/ArrivalTolerance/LineEdit
|
||||
onready var deceleration_radius: = $Controls/DecelRadius/LineEdit
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
max_speed.connect("text_changed", self, "_on_MaxSpeed_text_changed")
|
||||
max_accel.connect("text_changed", self, "_on_MaxAccel_text_changed")
|
||||
arrival_tolerance.connect("text_changed", self, "_on_ArrivalTolerance_text_changed")
|
||||
deceleration_radius.connect("text_changed", self, "_on_DecelerationRadius_text_changed")
|
||||
|
||||
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
if event is InputEventMouseButton:
|
||||
max_speed.release_focus()
|
||||
max_accel.release_focus()
|
||||
arrival_tolerance.release_focus()
|
||||
deceleration_radius.release_focus()
|
||||
|
||||
|
||||
func _on_MaxSpeed_text_changed(new_text: String) -> void:
|
||||
if new_text.is_valid_integer():
|
||||
emit_signal("max_speed_changed", int(float(new_text)))
|
||||
|
||||
|
||||
func _on_MaxAccel_text_changed(new_text: String) -> void:
|
||||
if new_text.is_valid_integer():
|
||||
emit_signal("max_accel_changed", int(float(new_text)))
|
||||
|
||||
|
||||
func _on_ArrivalTolerance_text_changed(new_text: String) -> void:
|
||||
if new_text.is_valid_integer():
|
||||
emit_signal("align_tolerance_changed", int(float(new_text)))
|
||||
|
||||
|
||||
func _on_DecelerationRadius_text_changed(new_text: String) -> void:
|
||||
if new_text.is_valid_integer():
|
||||
emit_signal("decel_radius_changed", int(float(new_text)))
|
108
project/demos/arrive/GUI.tscn
Normal file
108
project/demos/arrive/GUI.tscn
Normal file
@ -0,0 +1,108 @@
|
||||
[gd_scene load_steps=2 format=2]
|
||||
|
||||
[ext_resource path="res://demos/arrive/GUI.gd" type="Script" id=1]
|
||||
|
||||
[node name="GUI" type="MarginContainer"]
|
||||
anchor_bottom = 1.0
|
||||
margin_right = 40.0
|
||||
custom_constants/margin_right = 20
|
||||
custom_constants/margin_top = 20
|
||||
custom_constants/margin_left = 20
|
||||
custom_constants/margin_bottom = 20
|
||||
script = ExtResource( 1 )
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[node name="Controls" type="VBoxContainer" parent="."]
|
||||
margin_left = 20.0
|
||||
margin_top = 20.0
|
||||
margin_right = 207.0
|
||||
margin_bottom = 580.0
|
||||
|
||||
[node name="MaxSpeed" type="HBoxContainer" parent="Controls"]
|
||||
margin_right = 187.0
|
||||
margin_bottom = 24.0
|
||||
|
||||
[node name="Label" type="Label" parent="Controls/MaxSpeed"]
|
||||
margin_top = 4.0
|
||||
margin_right = 125.0
|
||||
margin_bottom = 19.0
|
||||
rect_min_size = Vector2( 125, 15 )
|
||||
text = "Max speed"
|
||||
|
||||
[node name="LineEdit" type="LineEdit" parent="Controls/MaxSpeed"]
|
||||
margin_left = 129.0
|
||||
margin_right = 187.0
|
||||
margin_bottom = 24.0
|
||||
focus_mode = 1
|
||||
|
||||
[node name="MaxAccel" type="HBoxContainer" parent="Controls"]
|
||||
margin_top = 28.0
|
||||
margin_right = 187.0
|
||||
margin_bottom = 52.0
|
||||
|
||||
[node name="Label" type="Label" parent="Controls/MaxAccel"]
|
||||
margin_top = 4.0
|
||||
margin_right = 125.0
|
||||
margin_bottom = 19.0
|
||||
rect_min_size = Vector2( 125, 15 )
|
||||
text = "Max acceleration"
|
||||
|
||||
[node name="LineEdit" type="LineEdit" parent="Controls/MaxAccel"]
|
||||
margin_left = 129.0
|
||||
margin_right = 187.0
|
||||
margin_bottom = 24.0
|
||||
focus_mode = 1
|
||||
|
||||
[node name="ArrivalTolerance" type="HBoxContainer" parent="Controls"]
|
||||
margin_top = 56.0
|
||||
margin_right = 187.0
|
||||
margin_bottom = 80.0
|
||||
|
||||
[node name="Label" type="Label" parent="Controls/ArrivalTolerance"]
|
||||
margin_top = 4.0
|
||||
margin_right = 125.0
|
||||
margin_bottom = 19.0
|
||||
rect_min_size = Vector2( 125, 15 )
|
||||
text = "Arrival tolerance"
|
||||
|
||||
[node name="LineEdit" type="LineEdit" parent="Controls/ArrivalTolerance"]
|
||||
margin_left = 129.0
|
||||
margin_right = 187.0
|
||||
margin_bottom = 24.0
|
||||
focus_mode = 1
|
||||
|
||||
[node name="DecelRadius" type="HBoxContainer" parent="Controls"]
|
||||
margin_top = 84.0
|
||||
margin_right = 187.0
|
||||
margin_bottom = 108.0
|
||||
|
||||
[node name="Label" type="Label" parent="Controls/DecelRadius"]
|
||||
margin_top = 4.0
|
||||
margin_right = 125.0
|
||||
margin_bottom = 19.0
|
||||
rect_min_size = Vector2( 125, 15 )
|
||||
text = "Deceleration radius"
|
||||
|
||||
[node name="LineEdit" type="LineEdit" parent="Controls/DecelRadius"]
|
||||
margin_left = 129.0
|
||||
margin_right = 187.0
|
||||
margin_bottom = 24.0
|
||||
focus_mode = 1
|
||||
|
||||
[node name="Help" type="VBoxContainer" parent="Controls"]
|
||||
margin_top = 112.0
|
||||
margin_right = 187.0
|
||||
margin_bottom = 144.0
|
||||
|
||||
[node name="Controls" type="Label" parent="Controls/Help"]
|
||||
margin_right = 187.0
|
||||
margin_bottom = 14.0
|
||||
text = "Controls"
|
||||
|
||||
[node name="Label" type="Label" parent="Controls/Help"]
|
||||
margin_top = 18.0
|
||||
margin_right = 187.0
|
||||
margin_bottom = 32.0
|
||||
text = "Mouse click"
|
14
project/demos/arrive/Target.gd
Normal file
14
project/demos/arrive/Target.gd
Normal file
@ -0,0 +1,14 @@
|
||||
extends Node2D
|
||||
|
||||
|
||||
var target: = Vector2.ZERO
|
||||
|
||||
|
||||
func _draw() -> void:
|
||||
draw_circle(target, 20, Color(1, 1, 0, 0.25))
|
||||
draw_circle(target, 5, Color.yellow)
|
||||
|
||||
|
||||
func draw(location: Vector2) -> void:
|
||||
target = location
|
||||
update()
|
34
project/demos/face/FaceDemo.gd
Normal file
34
project/demos/face/FaceDemo.gd
Normal file
@ -0,0 +1,34 @@
|
||||
extends Node2D
|
||||
|
||||
|
||||
onready var player: = $Player
|
||||
onready var gui: = $GUI
|
||||
onready var turret: = $Turret
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
gui.connect("align_tolerance_changed", self, "_on_GUI_align_tolerance_changed")
|
||||
gui.connect("decel_radius_changed", self, "_on_GUI_decel_radius_changed")
|
||||
gui.connect("max_accel_changed", self, "_on_GUI_max_accel_changed")
|
||||
gui.connect("max_speed_changed", self, "_on_GUI_max_speed_changed")
|
||||
turret.setup()
|
||||
gui.align_tolerance.text = str(int(rad2deg(turret._face.alignment_tolerance)))
|
||||
gui.decel_radius.text = str(int(rad2deg(turret._face.deceleration_radius)))
|
||||
gui.max_speed.text = str(int(rad2deg(turret._agent.max_angular_speed)))
|
||||
gui.max_accel.text = str(int(rad2deg(turret._agent.max_angular_acceleration)))
|
||||
|
||||
|
||||
func _on_GUI_align_tolerance_changed(value: int) -> void:
|
||||
turret._face.alignment_tolerance = deg2rad(value)
|
||||
|
||||
|
||||
func _on_GUI_decel_radius_changed(value: int) -> void:
|
||||
turret._face.deceleration_radius = deg2rad(value)
|
||||
|
||||
|
||||
func _on_GUI_max_accel_changed(value: int) -> void:
|
||||
turret._agent.max_angular_acceleration = deg2rad(value)
|
||||
|
||||
|
||||
func _on_GUI_max_speed_changed(value: int) -> void:
|
||||
turret._agent.max_angular_speed = deg2rad(value)
|
36
project/demos/face/FaceDemo.tscn
Normal file
36
project/demos/face/FaceDemo.tscn
Normal file
@ -0,0 +1,36 @@
|
||||
[gd_scene load_steps=7 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/face/GUI.tscn" type="PackedScene" id=4]
|
||||
|
||||
[sub_resource type="CircleShape2D" id=1]
|
||||
radius = 20.0
|
||||
|
||||
[sub_resource type="CircleShape2D" id=2]
|
||||
radius = 30.0
|
||||
|
||||
[node name="FaceDemo" type="Node2D"]
|
||||
script = ExtResource( 2 )
|
||||
__meta__ = {
|
||||
"_editor_description_": "A demo showing the usage of the Face steering behavior."
|
||||
}
|
||||
|
||||
[node name="Player" type="KinematicBody2D" parent="."]
|
||||
position = Vector2( 512, 450 )
|
||||
script = ExtResource( 3 )
|
||||
|
||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="Player"]
|
||||
shape = SubResource( 1 )
|
||||
|
||||
[node name="Turret" type="KinematicBody2D" parent="."]
|
||||
position = Vector2( 512, 150 )
|
||||
script = ExtResource( 1 )
|
||||
|
||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="Turret"]
|
||||
shape = SubResource( 2 )
|
||||
|
||||
[node name="GUI" parent="." instance=ExtResource( 4 )]
|
||||
margin_right = 232.0
|
||||
margin_bottom = 204.0
|
47
project/demos/face/GUI.gd
Normal file
47
project/demos/face/GUI.gd
Normal file
@ -0,0 +1,47 @@
|
||||
extends MarginContainer
|
||||
|
||||
|
||||
signal max_speed_changed(value)
|
||||
signal max_accel_changed(value)
|
||||
signal align_tolerance_changed(value)
|
||||
signal decel_radius_changed(value)
|
||||
|
||||
onready var max_speed: = $Controls/MaxSpeed/LineEdit
|
||||
onready var max_accel: = $Controls/MaxAccel/LineEdit
|
||||
onready var align_tolerance: = $Controls/AlignmentTolerance/LineEdit
|
||||
onready var decel_radius: = $Controls/DecelerationRadius/LineEdit
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
max_speed.connect("text_changed", self, "_on_MaxSpeed_text_changed")
|
||||
max_accel.connect("text_changed", self, "_on_MaxAccel_text_changed")
|
||||
align_tolerance.connect("text_changed", self, "_on_AlignTolerance_text_changed")
|
||||
decel_radius.connect("text_changed", self, "_on_DecelRadius_text_changed")
|
||||
|
||||
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
if event is InputEventMouseButton:
|
||||
max_speed.release_focus()
|
||||
max_accel.release_focus()
|
||||
align_tolerance.release_focus()
|
||||
decel_radius.release_focus()
|
||||
|
||||
|
||||
func _on_MaxSpeed_text_changed(new_text: String) -> void:
|
||||
if new_text.is_valid_integer():
|
||||
emit_signal("max_speed_changed", int(float(new_text)))
|
||||
|
||||
|
||||
func _on_MaxAccel_text_changed(new_text: String) -> void:
|
||||
if new_text.is_valid_integer():
|
||||
emit_signal("max_accel_changed", int(float(new_text)))
|
||||
|
||||
|
||||
func _on_AlignTolerance_text_changed(new_text: String) -> void:
|
||||
if new_text.is_valid_integer():
|
||||
emit_signal("align_tolerance_changed", int(float(new_text)))
|
||||
|
||||
|
||||
func _on_DecelRadius_text_changed(new_text: String) -> void:
|
||||
if new_text.is_valid_integer():
|
||||
emit_signal("decel_radius_changed", int(float(new_text)))
|
153
project/demos/face/GUI.tscn
Normal file
153
project/demos/face/GUI.tscn
Normal file
@ -0,0 +1,153 @@
|
||||
[gd_scene load_steps=2 format=2]
|
||||
|
||||
[ext_resource path="res://demos/face/GUI.gd" type="Script" id=1]
|
||||
|
||||
[node name="GUI" type="MarginContainer"]
|
||||
anchor_bottom = 1.0
|
||||
margin_right = 40.0
|
||||
custom_constants/margin_right = 20
|
||||
custom_constants/margin_top = 20
|
||||
custom_constants/margin_left = 20
|
||||
custom_constants/margin_bottom = 20
|
||||
script = ExtResource( 1 )
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[node name="Controls" type="VBoxContainer" parent="."]
|
||||
margin_left = 20.0
|
||||
margin_top = 20.0
|
||||
margin_right = 212.0
|
||||
margin_bottom = 580.0
|
||||
|
||||
[node name="MaxSpeed" type="HBoxContainer" parent="Controls"]
|
||||
margin_right = 192.0
|
||||
margin_bottom = 24.0
|
||||
|
||||
[node name="Label" type="Label" parent="Controls/MaxSpeed"]
|
||||
margin_top = 4.0
|
||||
margin_right = 130.0
|
||||
margin_bottom = 19.0
|
||||
rect_min_size = Vector2( 130, 15 )
|
||||
text = "Max speed"
|
||||
|
||||
[node name="LineEdit" type="LineEdit" parent="Controls/MaxSpeed"]
|
||||
margin_left = 134.0
|
||||
margin_right = 192.0
|
||||
margin_bottom = 24.0
|
||||
focus_mode = 1
|
||||
|
||||
[node name="MaxAccel" type="HBoxContainer" parent="Controls"]
|
||||
margin_top = 28.0
|
||||
margin_right = 192.0
|
||||
margin_bottom = 52.0
|
||||
|
||||
[node name="Label" type="Label" parent="Controls/MaxAccel"]
|
||||
margin_top = 4.0
|
||||
margin_right = 130.0
|
||||
margin_bottom = 19.0
|
||||
rect_min_size = Vector2( 130, 15 )
|
||||
text = "Max acceleration"
|
||||
|
||||
[node name="LineEdit" type="LineEdit" parent="Controls/MaxAccel"]
|
||||
margin_left = 134.0
|
||||
margin_right = 192.0
|
||||
margin_bottom = 24.0
|
||||
focus_mode = 1
|
||||
|
||||
[node name="AlignmentTolerance" type="HBoxContainer" parent="Controls"]
|
||||
margin_top = 56.0
|
||||
margin_right = 192.0
|
||||
margin_bottom = 80.0
|
||||
|
||||
[node name="Label" type="Label" parent="Controls/AlignmentTolerance"]
|
||||
margin_top = 4.0
|
||||
margin_right = 130.0
|
||||
margin_bottom = 19.0
|
||||
rect_min_size = Vector2( 130, 15 )
|
||||
text = "Alignment tolerance"
|
||||
|
||||
[node name="LineEdit" type="LineEdit" parent="Controls/AlignmentTolerance"]
|
||||
margin_left = 134.0
|
||||
margin_right = 192.0
|
||||
margin_bottom = 24.0
|
||||
focus_mode = 1
|
||||
|
||||
[node name="DecelerationRadius" type="HBoxContainer" parent="Controls"]
|
||||
margin_top = 84.0
|
||||
margin_right = 192.0
|
||||
margin_bottom = 108.0
|
||||
|
||||
[node name="Label" type="Label" parent="Controls/DecelerationRadius"]
|
||||
margin_top = 4.0
|
||||
margin_right = 130.0
|
||||
margin_bottom = 19.0
|
||||
rect_min_size = Vector2( 130, 15 )
|
||||
text = "Deceleration radius"
|
||||
|
||||
[node name="LineEdit" type="LineEdit" parent="Controls/DecelerationRadius"]
|
||||
margin_left = 134.0
|
||||
margin_right = 192.0
|
||||
margin_bottom = 24.0
|
||||
focus_mode = 1
|
||||
|
||||
[node name="Help" type="VBoxContainer" parent="Controls"]
|
||||
margin_top = 112.0
|
||||
margin_right = 192.0
|
||||
margin_bottom = 164.0
|
||||
|
||||
[node name="Controls" type="Label" parent="Controls/Help"]
|
||||
margin_right = 192.0
|
||||
margin_bottom = 14.0
|
||||
text = "Controls"
|
||||
|
||||
[node name="GridContainer" type="GridContainer" parent="Controls/Help"]
|
||||
margin_top = 18.0
|
||||
margin_right = 192.0
|
||||
margin_bottom = 52.0
|
||||
columns = 3
|
||||
|
||||
[node name="Sep" type="Control" parent="Controls/Help/GridContainer"]
|
||||
margin_right = 15.0
|
||||
margin_bottom = 15.0
|
||||
rect_min_size = Vector2( 15, 15 )
|
||||
|
||||
[node name="W" type="Label" parent="Controls/Help/GridContainer"]
|
||||
margin_left = 19.0
|
||||
margin_right = 34.0
|
||||
margin_bottom = 15.0
|
||||
rect_min_size = Vector2( 15, 15 )
|
||||
text = "W"
|
||||
align = 1
|
||||
|
||||
[node name="Sep2" type="Control" parent="Controls/Help/GridContainer"]
|
||||
margin_left = 38.0
|
||||
margin_right = 53.0
|
||||
margin_bottom = 15.0
|
||||
rect_min_size = Vector2( 15, 15 )
|
||||
|
||||
[node name="A" type="Label" parent="Controls/Help/GridContainer"]
|
||||
margin_top = 19.0
|
||||
margin_right = 15.0
|
||||
margin_bottom = 34.0
|
||||
rect_min_size = Vector2( 15, 15 )
|
||||
text = "A"
|
||||
align = 1
|
||||
|
||||
[node name="S" type="Label" parent="Controls/Help/GridContainer"]
|
||||
margin_left = 19.0
|
||||
margin_top = 19.0
|
||||
margin_right = 34.0
|
||||
margin_bottom = 34.0
|
||||
rect_min_size = Vector2( 15, 15 )
|
||||
text = "S"
|
||||
align = 1
|
||||
|
||||
[node name="D" type="Label" parent="Controls/Help/GridContainer"]
|
||||
margin_left = 38.0
|
||||
margin_top = 19.0
|
||||
margin_right = 53.0
|
||||
margin_bottom = 34.0
|
||||
rect_min_size = Vector2( 15, 15 )
|
||||
text = "D"
|
||||
align = 1
|
32
project/demos/face/Player.gd
Normal file
32
project/demos/face/Player.gd
Normal file
@ -0,0 +1,32 @@
|
||||
extends KinematicBody2D
|
||||
|
||||
|
||||
onready var collision_shape: = $CollisionShape2D
|
||||
onready var agent: = GSTAgentLocation.new()
|
||||
|
||||
export var speed: = 125.0
|
||||
|
||||
var _radius: = 0.0
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
_radius = collision_shape.shape.radius
|
||||
|
||||
|
||||
func _draw() -> void:
|
||||
draw_circle(Vector2.ZERO, _radius, Color.red)
|
||||
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
var movement: = _get_movement()
|
||||
move_and_slide(movement * speed)
|
||||
_update_agent()
|
||||
|
||||
|
||||
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"))
|
||||
|
||||
|
||||
func _update_agent() -> void:
|
||||
agent.position = Vector3(global_position.x, global_position.y, 0)
|
59
project/demos/face/Turret.gd
Normal file
59
project/demos/face/Turret.gd
Normal file
@ -0,0 +1,59 @@
|
||||
extends KinematicBody2D
|
||||
|
||||
|
||||
onready var collision_shape: = $CollisionShape2D
|
||||
|
||||
var _radius: = 0.0
|
||||
var _cannon: Rect2
|
||||
|
||||
var _agent: = GSTSteeringAgent.new()
|
||||
var _accel: = GSTTargetAcceleration.new()
|
||||
|
||||
var _angular_velocity: = 0.0
|
||||
var _angular_drag: = 1.0
|
||||
var _face: GSTFace
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
_radius = collision_shape.shape.radius
|
||||
_cannon = Rect2(Vector2(-5, 0), Vector2(10, -_radius*2))
|
||||
|
||||
|
||||
func _draw() -> void:
|
||||
draw_rect(_cannon, Color.blue)
|
||||
draw_circle(Vector2.ZERO, _radius, Color.teal)
|
||||
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
if not _face:
|
||||
return
|
||||
|
||||
_accel = _face.calculate_steering(_accel)
|
||||
_angular_velocity += _accel.angular
|
||||
|
||||
if _angular_velocity < 0:
|
||||
_angular_velocity += _angular_drag * delta
|
||||
elif _angular_velocity > 0:
|
||||
_angular_velocity -= _angular_drag * delta
|
||||
|
||||
rotation += _angular_velocity * delta
|
||||
|
||||
_update_agent()
|
||||
|
||||
|
||||
func setup() -> void:
|
||||
_face = GSTFace.new(_agent, owner.player.agent)
|
||||
|
||||
_face.alignment_tolerance = 0.1
|
||||
_face.deceleration_radius = PI/2
|
||||
|
||||
_agent.max_angular_acceleration = 0.5
|
||||
_agent.max_angular_speed = 5
|
||||
_agent.position = Vector3(global_position.x, global_position.y, 0)
|
||||
|
||||
_update_agent()
|
||||
|
||||
|
||||
func _update_agent() -> void:
|
||||
_agent.angular_velocity = _angular_velocity
|
||||
_agent.orientation = rotation
|
3
project/demos/follow_path/FollowPathDemo.tscn
Normal file
3
project/demos/follow_path/FollowPathDemo.tscn
Normal file
@ -0,0 +1,3 @@
|
||||
[gd_scene format=2]
|
||||
|
||||
[node name="FollowPathDemo" type="Node2D"]
|
11
project/demos/group_behaviors/GroupBehaviorsDemo.tscn
Normal file
11
project/demos/group_behaviors/GroupBehaviorsDemo.tscn
Normal file
@ -0,0 +1,11 @@
|
||||
[gd_scene load_steps=3 format=2]
|
||||
|
||||
[ext_resource path="res://demos/group_behaviors/Member.tscn" type="PackedScene" id=1]
|
||||
[ext_resource path="res://demos/group_behaviors/Spawner.gd" type="Script" id=2]
|
||||
|
||||
[node name="GroupBehaviorsDemo" type="Node2D"]
|
||||
|
||||
[node name="Spawner" type="Node2D" parent="."]
|
||||
position = Vector2( 397, 207 )
|
||||
script = ExtResource( 2 )
|
||||
member = ExtResource( 1 )
|
51
project/demos/group_behaviors/Member.gd
Normal file
51
project/demos/group_behaviors/Member.gd
Normal file
@ -0,0 +1,51 @@
|
||||
extends KinematicBody2D
|
||||
|
||||
|
||||
var agent: = GSTSteeringAgent.new()
|
||||
var separation: GSTSeparation
|
||||
var cohesion: GSTCohesion
|
||||
var proximity: GSTRadiusProximity
|
||||
var blend: = GSTBlend.new(agent)
|
||||
var acceleration: = GSTTargetAcceleration.new()
|
||||
|
||||
var _radius: float
|
||||
var _color: = Color.red
|
||||
var _velocity: = Vector2()
|
||||
|
||||
onready var shape: = $CollisionShape2D
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
_radius = shape.shape.radius
|
||||
_color = Color(rand_range(0.5, 1), rand_range(0.25, 1), rand_range(0, 1))
|
||||
agent.max_linear_acceleration = 7
|
||||
agent.max_linear_speed = 70
|
||||
|
||||
proximity = GSTRadiusProximity.new(agent, [], 140)
|
||||
separation = GSTSeparation.new(agent, proximity)
|
||||
separation.decay_coefficient = 2000
|
||||
cohesion = GSTCohesion.new(agent, proximity)
|
||||
blend.add(separation, 1.5)
|
||||
blend.add(cohesion, 0.3)
|
||||
|
||||
|
||||
func _draw() -> void:
|
||||
draw_circle(Vector2.ZERO, _radius, _color)
|
||||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
update_agent()
|
||||
if blend:
|
||||
acceleration = blend.calculate_steering(acceleration)
|
||||
_velocity = (_velocity + Vector2(acceleration.linear.x, acceleration.linear.y)).clamped(agent.max_linear_speed)
|
||||
move_and_slide(_velocity)
|
||||
|
||||
|
||||
func set_neighbors(neighbor: Array) -> void:
|
||||
proximity.agents = neighbor
|
||||
|
||||
|
||||
func update_agent() -> void:
|
||||
var current_position: = global_position
|
||||
agent.position.x = current_position.x
|
||||
agent.position.y = current_position.y
|
12
project/demos/group_behaviors/Member.tscn
Normal file
12
project/demos/group_behaviors/Member.tscn
Normal file
@ -0,0 +1,12 @@
|
||||
[gd_scene load_steps=3 format=2]
|
||||
|
||||
[ext_resource path="res://demos/group_behaviors/Member.gd" type="Script" id=1]
|
||||
|
||||
[sub_resource type="CircleShape2D" id=1]
|
||||
radius = 15.0
|
||||
|
||||
[node name="Member" type="KinematicBody2D"]
|
||||
script = ExtResource( 1 )
|
||||
|
||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
|
||||
shape = SubResource( 1 )
|
18
project/demos/group_behaviors/Spawner.gd
Normal file
18
project/demos/group_behaviors/Spawner.gd
Normal file
@ -0,0 +1,18 @@
|
||||
extends Node2D
|
||||
|
||||
|
||||
export var member: PackedScene
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
var followers: = []
|
||||
for i in range(19):
|
||||
var follower: = member.instance()
|
||||
add_child(follower)
|
||||
follower.position += Vector2(rand_range(-60, 60), rand_range(-60, 60))
|
||||
followers.append(follower)
|
||||
var agents: = []
|
||||
for i in followers:
|
||||
agents.append(i.agent)
|
||||
for i in followers:
|
||||
i.proximity.agents = agents
|
67
project/demos/pursue_vs_seek/BoundaryManager.gd
Normal file
67
project/demos/pursue_vs_seek/BoundaryManager.gd
Normal file
@ -0,0 +1,67 @@
|
||||
extends Node2D
|
||||
# Wraps the ships' positions around the world border, and controls their rendering clones.
|
||||
|
||||
|
||||
onready var ShipType: = preload("res://demos/pursue_vs_seek/Ship.gd")
|
||||
onready var ships: = [$Player, $Pursuer, $Seeker]
|
||||
|
||||
var _clones: = {}
|
||||
var _world_bounds: Vector2
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
_world_bounds = Vector2(
|
||||
ProjectSettings["display/window/size/width"],
|
||||
ProjectSettings["display/window/size/height"]
|
||||
)
|
||||
|
||||
for i in range(ships.size()):
|
||||
var ship: Node2D = ships[i]
|
||||
var world_pos: = ship.position
|
||||
|
||||
for i in range(3):
|
||||
var ship_clone: = ShipType.new()
|
||||
|
||||
ship_clone.position.x = world_pos.x if i == 1 else (world_pos.x - _world_bounds.x)
|
||||
ship_clone.position.y = world_pos.y if i == 0 else (world_pos.y - _world_bounds.y)
|
||||
ship_clone.rotation = ship.rotation
|
||||
ship_clone.tag = i
|
||||
ship_clone.name = ship.name + "Clone"
|
||||
|
||||
add_child(ship_clone)
|
||||
ship_clone.generate_sprite(ship.get_node("Sprite"))
|
||||
_clones[ship_clone] = ship
|
||||
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
for clone in _clones.keys():
|
||||
var original: Node2D = _clones[clone]
|
||||
var world_pos: Vector2 = original.position
|
||||
|
||||
if world_pos.y < 0:
|
||||
original.position.y = _world_bounds.y + world_pos.y
|
||||
elif world_pos.y > _world_bounds.y:
|
||||
original.position.y = (world_pos.y - _world_bounds.y)
|
||||
|
||||
if world_pos.x < 0:
|
||||
original.position.x = _world_bounds.x + world_pos.x
|
||||
elif world_pos.x > _world_bounds.x:
|
||||
original.position.x = (world_pos.x - _world_bounds.x)
|
||||
|
||||
var tag: int = clone.tag
|
||||
if tag != 2:
|
||||
if world_pos.x < _world_bounds.x/2:
|
||||
clone.position.x = world_pos.x + _world_bounds.x
|
||||
else:
|
||||
clone.position.x = world_pos.x - _world_bounds.x
|
||||
else:
|
||||
clone.position.x = world_pos.x
|
||||
|
||||
if tag != 0:
|
||||
if world_pos.y < _world_bounds.y/2:
|
||||
clone.position.y = world_pos.y + _world_bounds.y
|
||||
else:
|
||||
clone.position.y = world_pos.y - _world_bounds.y
|
||||
else:
|
||||
clone.position.y = world_pos.y
|
||||
clone.rotation = original.rotation
|
38
project/demos/pursue_vs_seek/GUI.gd
Normal file
38
project/demos/pursue_vs_seek/GUI.gd
Normal file
@ -0,0 +1,38 @@
|
||||
extends PanelContainer
|
||||
|
||||
|
||||
signal linear_speed_changed(value)
|
||||
signal linear_accel_changed(value)
|
||||
signal angular_speed_changed(value)
|
||||
signal angular_accel_changed(value)
|
||||
signal decel_radius_changed(value)
|
||||
signal predict_time_changed(value)
|
||||
|
||||
|
||||
onready var linear_speed: = $GUI/Controls/LinSpeedBox/MaxLinSpeed
|
||||
onready var lin_speed_label: = $GUI/Controls/LinSpeedBox/Label
|
||||
onready var linear_accel: = $GUI/Controls/LinAccelBox/MaxLinAccel
|
||||
onready var lin_accel_label: = $GUI/Controls/LinAccelBox/Label
|
||||
onready var predict_time: = $GUI/Controls/PredictTime/PredictTime
|
||||
onready var predict_time_label: = $GUI/Controls/PredictTime/Label
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
linear_speed.connect("value_changed", self, "_on_Slider_linear_speed_changed")
|
||||
linear_accel.connect("value_changed", self, "_on_Slider_linear_accel_changed")
|
||||
predict_time.connect("value_changed", self, "_on_Slider_predict_time_changed")
|
||||
|
||||
|
||||
func _on_Slider_linear_speed_changed(value: float) -> void:
|
||||
lin_speed_label.text = "Max linear speed (" + str(value) + ")"
|
||||
emit_signal("linear_speed_changed", value)
|
||||
|
||||
|
||||
func _on_Slider_linear_accel_changed(value: float) -> void:
|
||||
lin_accel_label.text = "Max linear accel (" + str(value) + ")"
|
||||
emit_signal("linear_accel_changed", value)
|
||||
|
||||
|
||||
func _on_Slider_predict_time_changed(value: float) -> void:
|
||||
predict_time_label.text = "Predict time (" + str(value) + " sec)"
|
||||
emit_signal("predict_time_changed", value)
|
139
project/demos/pursue_vs_seek/GUI.tscn
Normal file
139
project/demos/pursue_vs_seek/GUI.tscn
Normal file
@ -0,0 +1,139 @@
|
||||
[gd_scene load_steps=3 format=2]
|
||||
|
||||
[ext_resource path="res://demos/pursue_vs_seek/GUI.gd" type="Script" id=1]
|
||||
[ext_resource path="res://assets/theme/gdquest.theme" type="Theme" id=2]
|
||||
|
||||
[node name="PanelContainer" type="PanelContainer"]
|
||||
anchor_bottom = 1.0
|
||||
margin_right = 364.0
|
||||
theme = ExtResource( 2 )
|
||||
script = ExtResource( 1 )
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[node name="GUI" type="MarginContainer" parent="."]
|
||||
margin_right = 364.0
|
||||
margin_bottom = 600.0
|
||||
custom_constants/margin_right = 20
|
||||
custom_constants/margin_top = 20
|
||||
custom_constants/margin_left = 20
|
||||
custom_constants/margin_bottom = 20
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[node name="Controls" type="VBoxContainer" parent="GUI"]
|
||||
margin_left = 20.0
|
||||
margin_top = 20.0
|
||||
margin_right = 344.0
|
||||
margin_bottom = 580.0
|
||||
|
||||
[node name="LinSpeedBox" type="VBoxContainer" parent="GUI/Controls"]
|
||||
margin_right = 324.0
|
||||
margin_bottom = 50.0
|
||||
|
||||
[node name="Label" type="Label" parent="GUI/Controls/LinSpeedBox"]
|
||||
margin_right = 324.0
|
||||
margin_bottom = 26.0
|
||||
text = "Max linear speed (2000)"
|
||||
|
||||
[node name="MaxLinSpeed" type="HSlider" parent="GUI/Controls/LinSpeedBox"]
|
||||
margin_top = 34.0
|
||||
margin_right = 324.0
|
||||
margin_bottom = 50.0
|
||||
max_value = 500.0
|
||||
|
||||
[node name="LinAccelBox" type="VBoxContainer" parent="GUI/Controls"]
|
||||
margin_top = 58.0
|
||||
margin_right = 324.0
|
||||
margin_bottom = 108.0
|
||||
|
||||
[node name="Label" type="Label" parent="GUI/Controls/LinAccelBox"]
|
||||
margin_right = 324.0
|
||||
margin_bottom = 26.0
|
||||
text = "Max linear accel (2000)"
|
||||
|
||||
[node name="MaxLinAccel" type="HSlider" parent="GUI/Controls/LinAccelBox"]
|
||||
margin_top = 34.0
|
||||
margin_right = 324.0
|
||||
margin_bottom = 50.0
|
||||
step = 0.5
|
||||
|
||||
[node name="PredictTime" type="VBoxContainer" parent="GUI/Controls"]
|
||||
margin_top = 116.0
|
||||
margin_right = 324.0
|
||||
margin_bottom = 166.0
|
||||
|
||||
[node name="Label" type="Label" parent="GUI/Controls/PredictTime"]
|
||||
margin_right = 324.0
|
||||
margin_bottom = 26.0
|
||||
text = "Predict time (2000)"
|
||||
|
||||
[node name="PredictTime" type="HSlider" parent="GUI/Controls/PredictTime"]
|
||||
margin_top = 34.0
|
||||
margin_right = 324.0
|
||||
margin_bottom = 50.0
|
||||
max_value = 5.0
|
||||
step = 0.1
|
||||
|
||||
[node name="Help" type="VBoxContainer" parent="GUI/Controls"]
|
||||
margin_top = 174.0
|
||||
margin_right = 324.0
|
||||
margin_bottom = 264.0
|
||||
|
||||
[node name="Controls" type="Label" parent="GUI/Controls/Help"]
|
||||
margin_right = 324.0
|
||||
margin_bottom = 26.0
|
||||
text = "Controls"
|
||||
|
||||
[node name="GridContainer" type="GridContainer" parent="GUI/Controls/Help"]
|
||||
margin_top = 34.0
|
||||
margin_right = 324.0
|
||||
margin_bottom = 90.0
|
||||
columns = 3
|
||||
|
||||
[node name="Sep" type="Control" parent="GUI/Controls/Help/GridContainer"]
|
||||
margin_right = 15.0
|
||||
margin_bottom = 26.0
|
||||
rect_min_size = Vector2( 15, 15 )
|
||||
|
||||
[node name="W" type="Label" parent="GUI/Controls/Help/GridContainer"]
|
||||
margin_left = 19.0
|
||||
margin_right = 42.0
|
||||
margin_bottom = 26.0
|
||||
rect_min_size = Vector2( 15, 15 )
|
||||
text = "W"
|
||||
align = 1
|
||||
|
||||
[node name="Sep2" type="Control" parent="GUI/Controls/Help/GridContainer"]
|
||||
margin_left = 46.0
|
||||
margin_right = 63.0
|
||||
margin_bottom = 26.0
|
||||
rect_min_size = Vector2( 15, 15 )
|
||||
|
||||
[node name="A" type="Label" parent="GUI/Controls/Help/GridContainer"]
|
||||
margin_top = 30.0
|
||||
margin_right = 15.0
|
||||
margin_bottom = 56.0
|
||||
rect_min_size = Vector2( 15, 15 )
|
||||
text = "A"
|
||||
align = 1
|
||||
|
||||
[node name="S" type="Label" parent="GUI/Controls/Help/GridContainer"]
|
||||
margin_left = 19.0
|
||||
margin_top = 30.0
|
||||
margin_right = 42.0
|
||||
margin_bottom = 56.0
|
||||
rect_min_size = Vector2( 15, 15 )
|
||||
text = "S"
|
||||
align = 1
|
||||
|
||||
[node name="D" type="Label" parent="GUI/Controls/Help/GridContainer"]
|
||||
margin_left = 46.0
|
||||
margin_top = 30.0
|
||||
margin_right = 63.0
|
||||
margin_bottom = 56.0
|
||||
rect_min_size = Vector2( 15, 15 )
|
||||
text = "D"
|
||||
align = 1
|
101
project/demos/pursue_vs_seek/Player.gd
Normal file
101
project/demos/pursue_vs_seek/Player.gd
Normal file
@ -0,0 +1,101 @@
|
||||
extends "res://demos/pursue_vs_seek/Ship.gd"
|
||||
# Controls the player ship's movements based on player input.
|
||||
|
||||
|
||||
onready var agent: = GSTSteeringAgent.new()
|
||||
|
||||
export var thruster_strength: = 150.0
|
||||
export var side_thruster_strength: = 10.0
|
||||
export var max_velocity: = 150.0
|
||||
export var max_angular_velocity: = 2.0
|
||||
export var angular_drag: = 5.0
|
||||
export var linear_drag: = 100.0
|
||||
|
||||
var _linear_velocity: = Vector2()
|
||||
var _angular_velocity: = 0.0
|
||||
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
var movement: = _get_movement()
|
||||
_angular_velocity = _calculate_angular_velocity(
|
||||
movement.x,
|
||||
_angular_velocity,
|
||||
side_thruster_strength,
|
||||
max_angular_velocity,
|
||||
angular_drag,
|
||||
delta
|
||||
)
|
||||
rotation += (_angular_velocity * delta)
|
||||
|
||||
_linear_velocity = _calculate_linear_velocity(
|
||||
movement.y,
|
||||
_linear_velocity,
|
||||
Vector2.UP.rotated(rotation),
|
||||
linear_drag,
|
||||
thruster_strength,
|
||||
max_velocity,
|
||||
delta
|
||||
)
|
||||
|
||||
_linear_velocity = move_and_slide(_linear_velocity)
|
||||
|
||||
_update_agent(_linear_velocity, rotation)
|
||||
|
||||
|
||||
func _calculate_angular_velocity(
|
||||
horizontal_movement: float,
|
||||
current_velocity: float,
|
||||
thruster_strength: float,
|
||||
max_velocity: float,
|
||||
ship_drag: float,
|
||||
delta: float) -> float:
|
||||
|
||||
var velocity: = clamp(
|
||||
current_velocity + thruster_strength * horizontal_movement * delta,
|
||||
-max_velocity,
|
||||
max_velocity
|
||||
)
|
||||
|
||||
if velocity > 0:
|
||||
velocity -= ship_drag * delta
|
||||
elif velocity < 0:
|
||||
velocity += ship_drag * delta
|
||||
if abs(velocity) < 0.01:
|
||||
velocity = 0
|
||||
|
||||
return velocity
|
||||
|
||||
|
||||
func _calculate_linear_velocity(
|
||||
vertical_movement: float,
|
||||
current_velocity: Vector2,
|
||||
facing_direction: Vector2,
|
||||
ship_drag: float,
|
||||
strength: float,
|
||||
max_speed: 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 -= current_velocity.normalized() * (ship_drag * delta)
|
||||
|
||||
return velocity.clamped(max_speed)
|
||||
|
||||
|
||||
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(velocity: Vector2, orientation: float) -> void:
|
||||
agent.position.x = global_position.x
|
||||
agent.position.y = global_position.y
|
||||
agent.linear_velocity.x = velocity.x
|
||||
agent.linear_velocity.y = velocity.y
|
||||
agent.angular_velocity = _angular_velocity
|
||||
agent.orientation = orientation
|
34
project/demos/pursue_vs_seek/PursueVSSeekDemo.gd
Normal file
34
project/demos/pursue_vs_seek/PursueVSSeekDemo.gd
Normal file
@ -0,0 +1,34 @@
|
||||
extends Node2D
|
||||
|
||||
|
||||
onready var gui: = $GUI
|
||||
onready var pursuer: = $BoundaryManager/Pursuer
|
||||
onready var seeker: = $BoundaryManager/Seeker
|
||||
|
||||
export var start_linear_speed: = 200.0
|
||||
export var start_linear_accel: = 25.0
|
||||
export var start_predict_time: = 0.3
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
gui.connect("linear_accel_changed", self, "_on_GUI_linear_accel_changed")
|
||||
gui.connect("linear_speed_changed", self, "_on_GUI_linear_speed_changed")
|
||||
gui.connect("predict_time_changed", self, "_on_GUI_predict_time_changed")
|
||||
yield(get_tree(), "idle_frame")
|
||||
gui.linear_speed.value = start_linear_speed
|
||||
gui.linear_accel.value = start_linear_accel
|
||||
gui.predict_time.value = start_predict_time
|
||||
|
||||
|
||||
func _on_GUI_linear_accel_changed(value: int) -> void:
|
||||
pursuer.agent.max_linear_acceleration = float(value)
|
||||
seeker.agent.max_linear_acceleration = float(value)
|
||||
|
||||
|
||||
func _on_GUI_linear_speed_changed(value: int) -> void:
|
||||
pursuer.agent.max_linear_speed = float(value)
|
||||
seeker.agent.max_linear_speed = float(value)
|
||||
|
||||
|
||||
func _on_GUI_predict_time_changed(value: int) -> void:
|
||||
pursuer._behavior.max_predict_time = float(value)
|
61
project/demos/pursue_vs_seek/PursueVSSeekDemo.tscn
Normal file
61
project/demos/pursue_vs_seek/PursueVSSeekDemo.tscn
Normal file
@ -0,0 +1,61 @@
|
||||
[gd_scene load_steps=7 format=2]
|
||||
|
||||
[ext_resource path="res://demos/pursue_vs_seek/Pursuer.gd" type="Script" id=1]
|
||||
[ext_resource path="res://demos/pursue_vs_seek/Player.gd" type="Script" id=2]
|
||||
[ext_resource path="res://demos/pursue_vs_seek/BoundaryManager.gd" type="Script" id=3]
|
||||
[ext_resource path="res://demos/pursue_vs_seek/PursueVSSeekDemo.gd" type="Script" id=4]
|
||||
[ext_resource path="res://demos/pursue_vs_seek/GUI.tscn" type="PackedScene" id=5]
|
||||
[ext_resource path="res://assets/sprites/triangle.png" type="Texture" id=6]
|
||||
|
||||
[node name="PursueVSSeekDemo" type="Node2D"]
|
||||
script = ExtResource( 4 )
|
||||
__meta__ = {
|
||||
"_editor_description_": "Toy demo to demonstrate the use of the Pursue contrasted to the more naive Seek steering behavior."
|
||||
}
|
||||
start_linear_speed = 150.0
|
||||
start_linear_accel = 75.0
|
||||
start_predict_time = 2.0
|
||||
|
||||
[node name="BoundaryManager" type="Node2D" parent="."]
|
||||
script = ExtResource( 3 )
|
||||
|
||||
[node name="Player" type="KinematicBody2D" parent="BoundaryManager"]
|
||||
position = Vector2( 49.2031, 556.936 )
|
||||
rotation = 1.5708
|
||||
collision_mask = 2
|
||||
script = ExtResource( 2 )
|
||||
|
||||
[node name="CollisionPolygon2D" type="CollisionPolygon2D" parent="BoundaryManager/Player"]
|
||||
polygon = PoolVector2Array( 0, -32, -24, 32, 24, 32 )
|
||||
|
||||
[node name="Sprite" type="Sprite" parent="BoundaryManager/Player"]
|
||||
modulate = Color( 0.968627, 0.188235, 0.0352941, 1 )
|
||||
texture = ExtResource( 6 )
|
||||
|
||||
[node name="Pursuer" type="KinematicBody2D" parent="BoundaryManager"]
|
||||
position = Vector2( 868.495, 87.9043 )
|
||||
collision_layer = 2
|
||||
script = ExtResource( 1 )
|
||||
|
||||
[node name="CollisionPolygon2D" type="CollisionPolygon2D" parent="BoundaryManager/Pursuer"]
|
||||
polygon = PoolVector2Array( 0, -32, -24, 32, 24, 32 )
|
||||
|
||||
[node name="Sprite" type="Sprite" parent="BoundaryManager/Pursuer"]
|
||||
modulate = Color( 0.756863, 0.952941, 0.054902, 1 )
|
||||
texture = ExtResource( 6 )
|
||||
|
||||
[node name="Seeker" type="KinematicBody2D" parent="BoundaryManager"]
|
||||
position = Vector2( 821.24, 87.9043 )
|
||||
collision_layer = 2
|
||||
script = ExtResource( 1 )
|
||||
use_seek = true
|
||||
|
||||
[node name="CollisionPolygon2D" type="CollisionPolygon2D" parent="BoundaryManager/Seeker"]
|
||||
polygon = PoolVector2Array( 0, -32, -24, 32, 24, 32 )
|
||||
|
||||
[node name="Sprite" type="Sprite" parent="BoundaryManager/Seeker"]
|
||||
modulate = Color( 0.278431, 0.815686, 0.14902, 1 )
|
||||
texture = ExtResource( 6 )
|
||||
|
||||
[node name="GUI" parent="." instance=ExtResource( 5 )]
|
||||
margin_right = 309.0
|
66
project/demos/pursue_vs_seek/Pursuer.gd
Normal file
66
project/demos/pursue_vs_seek/Pursuer.gd
Normal file
@ -0,0 +1,66 @@
|
||||
extends "res://demos/pursue_vs_seek/Ship.gd"
|
||||
# Represents a ship that chases after the player.
|
||||
|
||||
|
||||
onready var agent: = GSTSteeringAgent.new()
|
||||
onready var accel: = GSTTargetAcceleration.new()
|
||||
onready var player_agent: GSTSteeringAgent = owner.find_node("Player", true, false).agent
|
||||
|
||||
export var use_seek: bool = false
|
||||
|
||||
var _orient_behavior: GSTSteeringBehavior
|
||||
var _behavior: GSTSteeringBehavior
|
||||
|
||||
var _linear_velocity: = Vector2()
|
||||
var _angular_velocity: = 0.0
|
||||
var _angular_drag: = 1.0
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
_setup()
|
||||
|
||||
|
||||
func _setup() -> void:
|
||||
if use_seek:
|
||||
_behavior = GSTSeek.new(agent, player_agent)
|
||||
else:
|
||||
_behavior = GSTPursue.new(agent, player_agent, 2)
|
||||
|
||||
_orient_behavior = GSTLookWhereYouGo.new(agent)
|
||||
_orient_behavior.alignment_tolerance = 0.001
|
||||
_orient_behavior.deceleration_radius = PI/2
|
||||
|
||||
agent.max_angular_acceleration = 2
|
||||
agent.max_angular_speed = 5
|
||||
agent.max_linear_acceleration = 75
|
||||
agent.max_linear_speed = 125
|
||||
_update_agent()
|
||||
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
accel = _orient_behavior.calculate_steering(accel)
|
||||
_angular_velocity += accel.angular
|
||||
|
||||
if _angular_velocity < 0:
|
||||
_angular_velocity += _angular_drag * delta
|
||||
elif _angular_velocity > 0:
|
||||
_angular_velocity -= _angular_drag * delta
|
||||
|
||||
rotation = rotation + _angular_velocity * delta
|
||||
|
||||
accel = _behavior.calculate_steering(accel)
|
||||
_linear_velocity += Vector2(accel.linear.x, accel.linear.y) * delta
|
||||
_linear_velocity -= _linear_velocity.normalized() * 10 * delta
|
||||
_linear_velocity = _linear_velocity.clamped(agent.max_linear_speed)
|
||||
_linear_velocity = move_and_slide(_linear_velocity)
|
||||
|
||||
_update_agent()
|
||||
|
||||
|
||||
func _update_agent() -> void:
|
||||
agent.position.x = global_position.x
|
||||
agent.position.y = global_position.y
|
||||
agent.orientation = rotation
|
||||
agent.linear_velocity.x = _linear_velocity.x
|
||||
agent.linear_velocity.y = _linear_velocity.y
|
||||
agent.angular_velocity = _angular_velocity
|
13
project/demos/pursue_vs_seek/Ship.gd
Normal file
13
project/demos/pursue_vs_seek/Ship.gd
Normal file
@ -0,0 +1,13 @@
|
||||
extends KinematicBody2D
|
||||
# Represents a basic ship
|
||||
|
||||
|
||||
var tag: int = 0
|
||||
|
||||
|
||||
func generate_sprite(sprite: Sprite) -> void:
|
||||
var new_sprite = Sprite.new()
|
||||
new_sprite.texture = sprite.texture
|
||||
new_sprite.modulate = sprite.modulate
|
||||
new_sprite.name = "Sprite"
|
||||
add_child(new_sprite)
|
14
project/demos/seek_and_flee/Boundary.gd
Normal file
14
project/demos/seek_and_flee/Boundary.gd
Normal file
@ -0,0 +1,14 @@
|
||||
extends StaticBody2D
|
||||
# Draws the bounding box of the static body wall.
|
||||
|
||||
|
||||
var rect: Rect2
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
var extents: = ($CollisionShape2D.shape as RectangleShape2D).extents
|
||||
rect = Rect2(-extents, extents*2)
|
||||
|
||||
|
||||
func _draw() -> void:
|
||||
draw_rect(rect, Color.yellowgreen)
|
48
project/demos/seek_and_flee/GUI.gd
Normal file
48
project/demos/seek_and_flee/GUI.gd
Normal file
@ -0,0 +1,48 @@
|
||||
extends PanelContainer
|
||||
|
||||
|
||||
enum BehaviorMode { SEEK, FLEE }
|
||||
|
||||
signal mode_changed(behavior_mode)
|
||||
signal accel_changed(value)
|
||||
signal speed_changed(value)
|
||||
|
||||
onready var seek: = $MarginContainer/BehaviorControls/Seek
|
||||
onready var flee: = $MarginContainer/BehaviorControls/Flee
|
||||
onready var max_accel: = $MarginContainer/BehaviorControls/MaxAccelValue
|
||||
onready var max_speed: = $MarginContainer/BehaviorControls/MaxSpeedValue
|
||||
onready var max_accel_label: = $MarginContainer/BehaviorControls/MaxAccel
|
||||
onready var max_speed_label: = $MarginContainer/BehaviorControls/MaxSpeed
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
seek.connect("pressed", self, "_on_Seek_pressed")
|
||||
flee.connect("pressed", self, "_on_Flee_pressed")
|
||||
max_accel.connect("value_changed", self, "_on_Accel_changed")
|
||||
max_speed.connect("value_changed", self, "_on_Speed_changed")
|
||||
max_accel_label.text = "Max accel (" + str(max_accel.value) + ")"
|
||||
max_speed_label.text = "Max speed (" + str(max_speed.value) + ")"
|
||||
|
||||
|
||||
func _on_Seek_pressed() -> void:
|
||||
flee.pressed = false
|
||||
flee.button_mask = BUTTON_MASK_LEFT
|
||||
seek.button_mask = 0
|
||||
emit_signal("mode_changed", BehaviorMode.SEEK)
|
||||
|
||||
|
||||
func _on_Flee_pressed() -> void:
|
||||
seek.pressed = false
|
||||
seek.button_mask = BUTTON_MASK_LEFT
|
||||
flee.button_mask = 0
|
||||
emit_signal("mode_changed", BehaviorMode.FLEE)
|
||||
|
||||
|
||||
func _on_Accel_changed(value: float) -> void:
|
||||
max_accel_label.text = "Max accel (" + str(value) + ")"
|
||||
emit_signal("accel_changed", value)
|
||||
|
||||
|
||||
func _on_Speed_changed(value: float) -> void:
|
||||
max_speed_label.text = "Max speed (" + str(value) + ")"
|
||||
emit_signal("speed_changed", value)
|
142
project/demos/seek_and_flee/GUI.tscn
Normal file
142
project/demos/seek_and_flee/GUI.tscn
Normal file
@ -0,0 +1,142 @@
|
||||
[gd_scene load_steps=3 format=2]
|
||||
|
||||
[ext_resource path="res://assets/theme/gdquest.theme" type="Theme" id=1]
|
||||
[ext_resource path="res://demos/seek_and_flee/GUI.gd" type="Script" id=3]
|
||||
|
||||
[node name="GUI" type="PanelContainer"]
|
||||
anchor_bottom = 1.0
|
||||
margin_right = 118.0
|
||||
theme = ExtResource( 1 )
|
||||
script = ExtResource( 3 )
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[node name="MarginContainer" type="MarginContainer" parent="."]
|
||||
margin_right = 218.0
|
||||
margin_bottom = 600.0
|
||||
custom_constants/margin_right = 20
|
||||
custom_constants/margin_top = 20
|
||||
custom_constants/margin_left = 20
|
||||
custom_constants/margin_bottom = 20
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[node name="BehaviorControls" type="VBoxContainer" parent="MarginContainer"]
|
||||
margin_left = 20.0
|
||||
margin_top = 20.0
|
||||
margin_right = 198.0
|
||||
margin_bottom = 580.0
|
||||
|
||||
[node name="Seek" type="CheckBox" parent="MarginContainer/BehaviorControls"]
|
||||
margin_right = 178.0
|
||||
margin_bottom = 26.0
|
||||
focus_mode = 0
|
||||
pressed = true
|
||||
enabled_focus_mode = 0
|
||||
text = "Seek"
|
||||
|
||||
[node name="Flee" type="CheckBox" parent="MarginContainer/BehaviorControls"]
|
||||
margin_top = 34.0
|
||||
margin_right = 178.0
|
||||
margin_bottom = 60.0
|
||||
focus_mode = 0
|
||||
enabled_focus_mode = 0
|
||||
text = "Flee"
|
||||
|
||||
[node name="MaxAccel" type="Label" parent="MarginContainer/BehaviorControls"]
|
||||
margin_top = 68.0
|
||||
margin_right = 178.0
|
||||
margin_bottom = 94.0
|
||||
text = "Max accel (2000)"
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[node name="MaxAccelValue" type="HSlider" parent="MarginContainer/BehaviorControls"]
|
||||
margin_top = 102.0
|
||||
margin_right = 178.0
|
||||
margin_bottom = 118.0
|
||||
max_value = 250.0
|
||||
step = 0.5
|
||||
value = 20.0
|
||||
|
||||
[node name="MaxSpeed" type="Label" parent="MarginContainer/BehaviorControls"]
|
||||
margin_top = 126.0
|
||||
margin_right = 178.0
|
||||
margin_bottom = 152.0
|
||||
text = "Max speed (2000)"
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[node name="MaxSpeedValue" type="HSlider" parent="MarginContainer/BehaviorControls"]
|
||||
margin_top = 160.0
|
||||
margin_right = 178.0
|
||||
margin_bottom = 176.0
|
||||
max_value = 450.0
|
||||
value = 100.0
|
||||
|
||||
[node name="Help" type="VBoxContainer" parent="MarginContainer/BehaviorControls"]
|
||||
margin_top = 184.0
|
||||
margin_right = 178.0
|
||||
margin_bottom = 274.0
|
||||
|
||||
[node name="Controls" type="Label" parent="MarginContainer/BehaviorControls/Help"]
|
||||
margin_right = 178.0
|
||||
margin_bottom = 26.0
|
||||
text = "Controls"
|
||||
|
||||
[node name="GridContainer" type="GridContainer" parent="MarginContainer/BehaviorControls/Help"]
|
||||
margin_left = 57.0
|
||||
margin_top = 34.0
|
||||
margin_right = 120.0
|
||||
margin_bottom = 90.0
|
||||
size_flags_horizontal = 4
|
||||
columns = 3
|
||||
|
||||
[node name="Sep" type="Control" parent="MarginContainer/BehaviorControls/Help/GridContainer"]
|
||||
margin_right = 15.0
|
||||
margin_bottom = 26.0
|
||||
rect_min_size = Vector2( 15, 15 )
|
||||
|
||||
[node name="W" type="Label" parent="MarginContainer/BehaviorControls/Help/GridContainer"]
|
||||
margin_left = 19.0
|
||||
margin_right = 42.0
|
||||
margin_bottom = 26.0
|
||||
rect_min_size = Vector2( 15, 15 )
|
||||
text = "W"
|
||||
align = 1
|
||||
|
||||
[node name="Sep2" type="Control" parent="MarginContainer/BehaviorControls/Help/GridContainer"]
|
||||
margin_left = 46.0
|
||||
margin_right = 63.0
|
||||
margin_bottom = 26.0
|
||||
rect_min_size = Vector2( 15, 15 )
|
||||
|
||||
[node name="A" type="Label" parent="MarginContainer/BehaviorControls/Help/GridContainer"]
|
||||
margin_top = 30.0
|
||||
margin_right = 15.0
|
||||
margin_bottom = 56.0
|
||||
rect_min_size = Vector2( 15, 15 )
|
||||
text = "A"
|
||||
align = 1
|
||||
|
||||
[node name="S" type="Label" parent="MarginContainer/BehaviorControls/Help/GridContainer"]
|
||||
margin_left = 19.0
|
||||
margin_top = 30.0
|
||||
margin_right = 42.0
|
||||
margin_bottom = 56.0
|
||||
rect_min_size = Vector2( 15, 15 )
|
||||
text = "S"
|
||||
align = 1
|
||||
|
||||
[node name="D" type="Label" parent="MarginContainer/BehaviorControls/Help/GridContainer"]
|
||||
margin_left = 46.0
|
||||
margin_top = 30.0
|
||||
margin_right = 63.0
|
||||
margin_bottom = 56.0
|
||||
rect_min_size = Vector2( 15, 15 )
|
||||
text = "D"
|
||||
align = 1
|
21
project/demos/seek_and_flee/Player.gd
Normal file
21
project/demos/seek_and_flee/Player.gd
Normal file
@ -0,0 +1,21 @@
|
||||
extends KinematicBody2D
|
||||
# Class to control the player in basic left/right up/down movement.
|
||||
|
||||
|
||||
onready var agent: = GSTAgentLocation.new()
|
||||
|
||||
export var speed: = 150.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"))
|
||||
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
var movement: = _get_movement()
|
||||
if movement.length_squared() < 0.01:
|
||||
return
|
||||
|
||||
move_and_slide(movement * speed)
|
||||
agent.position = Vector3(global_position.x, global_position.y, 0)
|
42
project/demos/seek_and_flee/SeekFleeDemo.gd
Normal file
42
project/demos/seek_and_flee/SeekFleeDemo.gd
Normal file
@ -0,0 +1,42 @@
|
||||
extends Node2D
|
||||
# Access helper class for children to access window boundaries.
|
||||
|
||||
|
||||
onready var player: KinematicBody2D = $Player
|
||||
onready var spawner: Node2D = $Spawner
|
||||
onready var gui: = $GUI
|
||||
|
||||
var camera_boundaries: Rect2
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
camera_boundaries = Rect2(
|
||||
Vector2.ZERO,
|
||||
Vector2(
|
||||
ProjectSettings["display/window/size/width"],
|
||||
ProjectSettings["display/window/size/height"]
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
var rng: = RandomNumberGenerator.new()
|
||||
rng.randomize()
|
||||
|
||||
gui.max_accel.value = spawner.max_accel
|
||||
gui.max_speed.value = spawner.max_speed
|
||||
|
||||
for i in range(spawner.entity_count):
|
||||
var new_pos: = Vector2(
|
||||
rng.randf_range(-camera_boundaries.size.x/2, camera_boundaries.size.x/2),
|
||||
rng.randf_range(-camera_boundaries.size.y/2, camera_boundaries.size.y/2)
|
||||
)
|
||||
var entity: KinematicBody2D = spawner.Entity.instance()
|
||||
entity.global_position = new_pos
|
||||
entity.player_agent = player.agent
|
||||
entity.start_speed = spawner.max_speed
|
||||
entity.start_accel = spawner.max_accel
|
||||
gui.connect("mode_changed", entity, "_on_GUI_mode_changed")
|
||||
gui.connect("accel_changed", entity, "_on_GUI_accel_changed")
|
||||
gui.connect("speed_changed", entity, "_on_GUI_speed_changed")
|
||||
spawner.add_child(entity)
|
86
project/demos/seek_and_flee/SeekFleeDemo.tscn
Normal file
86
project/demos/seek_and_flee/SeekFleeDemo.tscn
Normal file
@ -0,0 +1,86 @@
|
||||
[gd_scene load_steps=11 format=2]
|
||||
|
||||
[ext_resource path="res://demos/seek_and_flee/Boundary.gd" type="Script" id=1]
|
||||
[ext_resource path="res://demos/seek_and_flee/Player.gd" type="Script" id=2]
|
||||
[ext_resource path="res://demos/seek_and_flee/SeekFleeDemo.gd" type="Script" id=3]
|
||||
[ext_resource path="res://demos/seek_and_flee/Spawner.gd" type="Script" id=4]
|
||||
[ext_resource path="res://demos/seek_and_flee/GUI.tscn" type="PackedScene" id=5]
|
||||
[ext_resource path="res://demos/seek_and_flee/Seeker.tscn" type="PackedScene" id=6]
|
||||
[ext_resource path="res://assets/sprites/large_circle.png" type="Texture" id=7]
|
||||
|
||||
[sub_resource type="CircleShape2D" id=1]
|
||||
radius = 32.0
|
||||
|
||||
[sub_resource type="RectangleShape2D" id=2]
|
||||
extents = Vector2( 10, 300 )
|
||||
|
||||
[sub_resource type="RectangleShape2D" id=3]
|
||||
extents = Vector2( 512, 10 )
|
||||
|
||||
[node name="SeekFleeDemo" type="Node2D"]
|
||||
script = ExtResource( 3 )
|
||||
__meta__ = {
|
||||
"_editor_description_": "A toy demo to demonstrate the usage for the Seek and Flee steering behaviors."
|
||||
}
|
||||
|
||||
[node name="Camera2D" type="Camera2D" parent="."]
|
||||
current = true
|
||||
|
||||
[node name="Player" type="KinematicBody2D" parent="."]
|
||||
collision_mask = 2
|
||||
script = ExtResource( 2 )
|
||||
|
||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="Player"]
|
||||
shape = SubResource( 1 )
|
||||
|
||||
[node name="Sprite" type="Sprite" parent="Player"]
|
||||
modulate = Color( 0.952941, 0.290196, 0.0588235, 1 )
|
||||
texture = ExtResource( 7 )
|
||||
|
||||
[node name="LeftBoundary" type="StaticBody2D" parent="."]
|
||||
position = Vector2( -512, 0 )
|
||||
collision_layer = 2
|
||||
collision_mask = 5
|
||||
script = ExtResource( 1 )
|
||||
|
||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="LeftBoundary"]
|
||||
shape = SubResource( 2 )
|
||||
|
||||
[node name="RightBoundary" type="StaticBody2D" parent="."]
|
||||
position = Vector2( 512, 0 )
|
||||
collision_layer = 2
|
||||
collision_mask = 5
|
||||
script = ExtResource( 1 )
|
||||
|
||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="RightBoundary"]
|
||||
shape = SubResource( 2 )
|
||||
|
||||
[node name="TopBoundary" type="StaticBody2D" parent="."]
|
||||
position = Vector2( 0, -300 )
|
||||
collision_layer = 2
|
||||
collision_mask = 5
|
||||
script = ExtResource( 1 )
|
||||
|
||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="TopBoundary"]
|
||||
shape = SubResource( 3 )
|
||||
|
||||
[node name="BottomBoundary" type="StaticBody2D" parent="."]
|
||||
position = Vector2( 0, 300 )
|
||||
collision_layer = 2
|
||||
collision_mask = 5
|
||||
script = ExtResource( 1 )
|
||||
|
||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="BottomBoundary"]
|
||||
shape = SubResource( 3 )
|
||||
|
||||
[node name="Spawner" type="Node2D" parent="."]
|
||||
script = ExtResource( 4 )
|
||||
Entity = ExtResource( 6 )
|
||||
max_speed = 150.0
|
||||
|
||||
[node name="GUI" parent="." instance=ExtResource( 5 )]
|
||||
anchor_bottom = 0.0
|
||||
margin_left = -512.0
|
||||
margin_top = -300.0
|
||||
margin_right = -294.0
|
||||
margin_bottom = 14.0
|
52
project/demos/seek_and_flee/Seeker.gd
Normal file
52
project/demos/seek_and_flee/Seeker.gd
Normal file
@ -0,0 +1,52 @@
|
||||
extends KinematicBody2D
|
||||
# AI agent that uses the Seek behavior to hone in on the player's location as directly as possible.
|
||||
|
||||
|
||||
onready var agent: = GSTSteeringAgent.new()
|
||||
onready var accel: = GSTTargetAcceleration.new()
|
||||
onready var seek: = GSTSeek.new(agent, player_agent)
|
||||
onready var flee: = GSTFlee.new(agent, player_agent)
|
||||
onready var _active_behavior: = seek
|
||||
|
||||
var player_agent: GSTAgentLocation
|
||||
var velocity: = Vector2.ZERO
|
||||
var start_speed: float
|
||||
var start_accel: float
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
agent.max_linear_acceleration = start_accel
|
||||
agent.max_linear_speed = start_speed
|
||||
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
if not player_agent:
|
||||
return
|
||||
|
||||
_update_agent()
|
||||
accel = _active_behavior.calculate_steering(accel)
|
||||
|
||||
velocity = (velocity + Vector2(accel.linear.x, accel.linear.y)).clamped(agent.max_linear_speed)
|
||||
velocity = move_and_slide(velocity)
|
||||
|
||||
|
||||
func _update_agent() -> void:
|
||||
agent.position.x = global_position.x
|
||||
agent.position.y = global_position.y
|
||||
agent.linear_velocity.x = velocity.x
|
||||
agent.linear_velocity.y = velocity.y
|
||||
|
||||
|
||||
func _on_GUI_mode_changed(mode: int) -> void:
|
||||
if mode == 0:
|
||||
_active_behavior = seek
|
||||
else:
|
||||
_active_behavior = flee
|
||||
|
||||
|
||||
func _on_GUI_accel_changed(value: float) -> void:
|
||||
agent.max_linear_acceleration = value
|
||||
|
||||
|
||||
func _on_GUI_speed_changed(value: float) -> void:
|
||||
agent.max_linear_speed = value
|
19
project/demos/seek_and_flee/Seeker.tscn
Normal file
19
project/demos/seek_and_flee/Seeker.tscn
Normal file
@ -0,0 +1,19 @@
|
||||
[gd_scene load_steps=4 format=2]
|
||||
|
||||
[ext_resource path="res://demos/seek_and_flee/Seeker.gd" type="Script" id=1]
|
||||
[ext_resource path="res://assets/sprites/small_circle.png" type="Texture" 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 )
|
||||
|
||||
[node name="Sprite" type="Sprite" parent="."]
|
||||
modulate = Color( 0.113725, 0.635294, 0.94902, 1 )
|
||||
texture = ExtResource( 2 )
|
9
project/demos/seek_and_flee/Spawner.gd
Normal file
9
project/demos/seek_and_flee/Spawner.gd
Normal file
@ -0,0 +1,9 @@
|
||||
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
|
||||
export var max_speed: = 100.0
|
||||
export var max_accel: = 20.0
|
BIN
project/icon.png
Normal file
BIN
project/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.4 KiB |
34
project/icon.png.import
Normal file
34
project/icon.png.import
Normal file
@ -0,0 +1,34 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="StreamTexture"
|
||||
path="res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://icon.png"
|
||||
dest_files=[ "res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.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
|
||||
stream=false
|
||||
size_limit=0
|
||||
detect_3d=true
|
||||
svg/scale=1.0
|
193
project/project.godot
Normal file
193
project/project.godot
Normal file
@ -0,0 +1,193 @@
|
||||
; Engine configuration file.
|
||||
; It's best edited using the editor UI and not directly,
|
||||
; since the parameters that go here are not all obvious.
|
||||
;
|
||||
; Format:
|
||||
; [section] ; section goes between []
|
||||
; param=value ; assign values to parameters
|
||||
|
||||
config_version=4
|
||||
|
||||
_global_script_classes=[ {
|
||||
"base": "Reference",
|
||||
"class": "GSTAgentLocation",
|
||||
"language": "GDScript",
|
||||
"path": "res://src/GSTAgentLocation.gd"
|
||||
}, {
|
||||
"base": "GSTSteeringBehavior",
|
||||
"class": "GSTArrive",
|
||||
"language": "GDScript",
|
||||
"path": "res://src/behaviors/GSTArrive.gd"
|
||||
}, {
|
||||
"base": "GSTGroupBehavior",
|
||||
"class": "GSTAvoidCollisions",
|
||||
"language": "GDScript",
|
||||
"path": "res://src/behaviors/GSTAvoidCollisions.gd"
|
||||
}, {
|
||||
"base": "GSTSteeringBehavior",
|
||||
"class": "GSTBlend",
|
||||
"language": "GDScript",
|
||||
"path": "res://src/behaviors/GSTBlend.gd"
|
||||
}, {
|
||||
"base": "GSTGroupBehavior",
|
||||
"class": "GSTCohesion",
|
||||
"language": "GDScript",
|
||||
"path": "res://src/behaviors/GSTCohesion.gd"
|
||||
}, {
|
||||
"base": "GSTPursue",
|
||||
"class": "GSTEvade",
|
||||
"language": "GDScript",
|
||||
"path": "res://src/behaviors/GSTEvade.gd"
|
||||
}, {
|
||||
"base": "GSTMatchOrientation",
|
||||
"class": "GSTFace",
|
||||
"language": "GDScript",
|
||||
"path": "res://src/behaviors/GSTFace.gd"
|
||||
}, {
|
||||
"base": "GSTSeek",
|
||||
"class": "GSTFlee",
|
||||
"language": "GDScript",
|
||||
"path": "res://src/behaviors/GSTFlee.gd"
|
||||
}, {
|
||||
"base": "GSTArrive",
|
||||
"class": "GSTFollowPath",
|
||||
"language": "GDScript",
|
||||
"path": "res://src/behaviors/GSTFollowPath.gd"
|
||||
}, {
|
||||
"base": "GSTSteeringBehavior",
|
||||
"class": "GSTGroupBehavior",
|
||||
"language": "GDScript",
|
||||
"path": "res://src/GSTGroupBehavior.gd"
|
||||
}, {
|
||||
"base": "GSTProximity",
|
||||
"class": "GSTInfiniteProximity",
|
||||
"language": "GDScript",
|
||||
"path": "res://src/proximities/GSTInfiniteProximity.gd"
|
||||
}, {
|
||||
"base": "GSTMatchOrientation",
|
||||
"class": "GSTLookWhereYouGo",
|
||||
"language": "GDScript",
|
||||
"path": "res://src/behaviors/GSTLookWhereYouGo.gd"
|
||||
}, {
|
||||
"base": "GSTSteeringBehavior",
|
||||
"class": "GSTMatchOrientation",
|
||||
"language": "GDScript",
|
||||
"path": "res://src/behaviors/GSTMatchOrientation.gd"
|
||||
}, {
|
||||
"base": "Reference",
|
||||
"class": "GSTPath",
|
||||
"language": "GDScript",
|
||||
"path": "res://src/GSTPath.gd"
|
||||
}, {
|
||||
"base": "GSTSteeringBehavior",
|
||||
"class": "GSTPriority",
|
||||
"language": "GDScript",
|
||||
"path": "res://src/behaviors/GSTPriority.gd"
|
||||
}, {
|
||||
"base": "Reference",
|
||||
"class": "GSTProximity",
|
||||
"language": "GDScript",
|
||||
"path": "res://src/proximities/GSTProximity.gd"
|
||||
}, {
|
||||
"base": "GSTSteeringBehavior",
|
||||
"class": "GSTPursue",
|
||||
"language": "GDScript",
|
||||
"path": "res://src/behaviors/GSTPursue.gd"
|
||||
}, {
|
||||
"base": "GSTProximity",
|
||||
"class": "GSTRadiusProximity",
|
||||
"language": "GDScript",
|
||||
"path": "res://src/proximities/GSTRadiusProximity.gd"
|
||||
}, {
|
||||
"base": "GSTSteeringBehavior",
|
||||
"class": "GSTSeek",
|
||||
"language": "GDScript",
|
||||
"path": "res://src/behaviors/GSTSeek.gd"
|
||||
}, {
|
||||
"base": "GSTGroupBehavior",
|
||||
"class": "GSTSeparation",
|
||||
"language": "GDScript",
|
||||
"path": "res://src/behaviors/GSTSeparation.gd"
|
||||
}, {
|
||||
"base": "GSTAgentLocation",
|
||||
"class": "GSTSteeringAgent",
|
||||
"language": "GDScript",
|
||||
"path": "res://src/GSTSteeringAgent.gd"
|
||||
}, {
|
||||
"base": "Reference",
|
||||
"class": "GSTSteeringBehavior",
|
||||
"language": "GDScript",
|
||||
"path": "res://src/GSTSteeringBehavior.gd"
|
||||
}, {
|
||||
"base": "Reference",
|
||||
"class": "GSTTargetAcceleration",
|
||||
"language": "GDScript",
|
||||
"path": "res://src/GSTTargetAcceleration.gd"
|
||||
}, {
|
||||
"base": "Reference",
|
||||
"class": "GSTUtils",
|
||||
"language": "GDScript",
|
||||
"path": "res://src/GSTUtils.gd"
|
||||
} ]
|
||||
_global_script_class_icons={
|
||||
"GSTAgentLocation": "",
|
||||
"GSTArrive": "",
|
||||
"GSTAvoidCollisions": "",
|
||||
"GSTBlend": "",
|
||||
"GSTCohesion": "",
|
||||
"GSTEvade": "",
|
||||
"GSTFace": "",
|
||||
"GSTFlee": "",
|
||||
"GSTFollowPath": "",
|
||||
"GSTGroupBehavior": "",
|
||||
"GSTInfiniteProximity": "",
|
||||
"GSTLookWhereYouGo": "",
|
||||
"GSTMatchOrientation": "",
|
||||
"GSTPath": "",
|
||||
"GSTPriority": "",
|
||||
"GSTProximity": "",
|
||||
"GSTPursue": "",
|
||||
"GSTRadiusProximity": "",
|
||||
"GSTSeek": "",
|
||||
"GSTSeparation": "",
|
||||
"GSTSteeringAgent": "",
|
||||
"GSTSteeringBehavior": "",
|
||||
"GSTTargetAcceleration": "",
|
||||
"GSTUtils": ""
|
||||
}
|
||||
|
||||
[application]
|
||||
|
||||
config/name="SteeringToolkit"
|
||||
config/icon="res://icon.png"
|
||||
|
||||
[input]
|
||||
|
||||
sf_left={
|
||||
"deadzone": 0.5,
|
||||
"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":65,"unicode":0,"echo":false,"script":null)
|
||||
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":0,"axis_value":-1.0,"script":null)
|
||||
]
|
||||
}
|
||||
sf_right={
|
||||
"deadzone": 0.5,
|
||||
"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":68,"unicode":0,"echo":false,"script":null)
|
||||
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":0,"axis_value":1.0,"script":null)
|
||||
]
|
||||
}
|
||||
sf_up={
|
||||
"deadzone": 0.5,
|
||||
"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":87,"unicode":0,"echo":false,"script":null)
|
||||
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":1,"axis_value":-1.0,"script":null)
|
||||
]
|
||||
}
|
||||
sf_down={
|
||||
"deadzone": 0.5,
|
||||
"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":83,"unicode":0,"echo":false,"script":null)
|
||||
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":1,"axis_value":1.0,"script":null)
|
||||
]
|
||||
}
|
||||
|
||||
[rendering]
|
||||
|
||||
environment/default_environment="res://default_env.tres"
|
6
project/src/GSTAgentLocation.gd
Normal file
6
project/src/GSTAgentLocation.gd
Normal file
@ -0,0 +1,6 @@
|
||||
class_name GSTAgentLocation
|
||||
# Data type to represent an agent with a location and an orientation
|
||||
|
||||
|
||||
var position: = Vector3.ZERO
|
||||
var orientation: = 0.0
|
16
project/src/GSTGroupBehavior.gd
Normal file
16
project/src/GSTGroupBehavior.gd
Normal file
@ -0,0 +1,16 @@
|
||||
extends GSTSteeringBehavior
|
||||
class_name GSTGroupBehavior
|
||||
# Extended behavior that features a Proximity group for group-based behaviors.
|
||||
|
||||
|
||||
var proximity: GSTProximity
|
||||
|
||||
var _callback: = funcref(self, "report_neighbor")
|
||||
|
||||
|
||||
func _init(agent: GSTSteeringAgent, proximity: GSTProximity).(agent) -> void:
|
||||
self.proximity = proximity
|
||||
|
||||
|
||||
func report_neighbor(neighbor: GSTSteeringAgent) -> bool:
|
||||
return false
|
125
project/src/GSTPath.gd
Normal file
125
project/src/GSTPath.gd
Normal file
@ -0,0 +1,125 @@
|
||||
extends Reference
|
||||
class_name GSTPath
|
||||
# Represents a path made up of Vector3 waypoints, split into path segments for use by path
|
||||
# following algorithms.
|
||||
|
||||
# # Keeping it updated requires calling `create_path` to update the path.
|
||||
|
||||
|
||||
var open: bool
|
||||
var path_length: float
|
||||
|
||||
var _segments: Array
|
||||
|
||||
var _nearest_point_on_segment: Vector3
|
||||
var _nearest_point_on_path: Vector3
|
||||
|
||||
|
||||
func _init(waypoints: Array, is_open: = false) -> void:
|
||||
self.is_open = is_open
|
||||
create_path(waypoints)
|
||||
_nearest_point_on_segment = waypoints[0]
|
||||
_nearest_point_on_path = waypoints[0]
|
||||
|
||||
|
||||
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.")
|
||||
|
||||
_segments = []
|
||||
path_length = 0
|
||||
var current: Vector3 = _segments[0]
|
||||
var previous: Vector3
|
||||
|
||||
for i in range(1, waypoints.size(), 1):
|
||||
previous = current
|
||||
if i < waypoints.size():
|
||||
current = waypoints[i]
|
||||
elif open:
|
||||
break
|
||||
else:
|
||||
current = waypoints[0]
|
||||
var segment: = GSTSegment.new(previous, current)
|
||||
path_length += segment.length
|
||||
segment.cumulative_length = path_length
|
||||
_segments.append(segment)
|
||||
|
||||
|
||||
func calculate_distance(agent_current_position: Vector3, path_parameter: Dictionary) -> float:
|
||||
var smallest_distance_squared: float = INF
|
||||
var nearest_segment: GSTSegment
|
||||
for i in range(_segments.size()):
|
||||
var segment: GSTSegment = _segments[i]
|
||||
var distance_squared: = _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
|
||||
path_parameter.segment_index = i
|
||||
|
||||
var length_on_path: = (
|
||||
nearest_segment.cumulative_length -
|
||||
_nearest_point_on_path.distance_to(nearest_segment.end))
|
||||
|
||||
path_parameter.distance = length_on_path
|
||||
|
||||
return length_on_path
|
||||
|
||||
|
||||
func calculate_target_position(param: Dictionary, target_distance: float) -> Vector3:
|
||||
if open:
|
||||
target_distance = clamp(target_distance, 0, path_length)
|
||||
else:
|
||||
if target_distance < 0:
|
||||
target_distance = path_length + fmod(target_distance, path_length)
|
||||
elif target_distance > path_length:
|
||||
target_distance = fmod(target_distance, path_length)
|
||||
|
||||
var desired_segment: GSTSegment
|
||||
for i in range(_segments.size()):
|
||||
var segment: GSTSegment = _segments[i]
|
||||
if segment.cumulative_length >= target_distance:
|
||||
desired_segment = segment
|
||||
break
|
||||
|
||||
var distance: = desired_segment.cumulative_length - target_distance
|
||||
|
||||
return (
|
||||
(desired_segment.begin - desired_segment.end) *
|
||||
(distance / desired_segment.length) + desired_segment.end)
|
||||
|
||||
|
||||
func get_start_point() -> Vector3:
|
||||
return _segments.front().begin
|
||||
|
||||
|
||||
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: = end - start
|
||||
var start_end_length_squared: = 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)
|
||||
|
||||
|
||||
class GSTSegment:
|
||||
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)
|
14
project/src/GSTSteeringAgent.gd
Normal file
14
project/src/GSTSteeringAgent.gd
Normal file
@ -0,0 +1,14 @@
|
||||
extends GSTAgentLocation
|
||||
class_name GSTSteeringAgent
|
||||
# Extended agent data type that adds velocity, speed, and size data
|
||||
|
||||
|
||||
var zero_linear_speed_threshold: = 0.01
|
||||
var max_linear_speed: = 0.0
|
||||
var max_linear_acceleration: = 0.0
|
||||
var max_angular_speed: = 0.0
|
||||
var max_angular_acceleration: = 0.0
|
||||
var linear_velocity: = Vector3.ZERO
|
||||
var angular_velocity: = 0.0
|
||||
var bounding_radius: = 0.0
|
||||
var tagged: = false
|
23
project/src/GSTSteeringBehavior.gd
Normal file
23
project/src/GSTSteeringBehavior.gd
Normal file
@ -0,0 +1,23 @@
|
||||
class_name GSTSteeringBehavior
|
||||
# Base class to calculate how an AI agent steers itself.
|
||||
|
||||
|
||||
var enabled: = true
|
||||
var agent: GSTSteeringAgent
|
||||
|
||||
|
||||
func _init(agent: GSTSteeringAgent) -> void:
|
||||
self.agent = agent
|
||||
|
||||
|
||||
func calculate_steering(acceleration: GSTTargetAcceleration) -> GSTTargetAcceleration:
|
||||
if enabled:
|
||||
return _calculate_steering(acceleration)
|
||||
else:
|
||||
acceleration.set_zero()
|
||||
return acceleration
|
||||
|
||||
|
||||
func _calculate_steering(acceleration: GSTTargetAcceleration) -> GSTTargetAcceleration:
|
||||
acceleration.set_zero()
|
||||
return acceleration
|
26
project/src/GSTTargetAcceleration.gd
Normal file
26
project/src/GSTTargetAcceleration.gd
Normal file
@ -0,0 +1,26 @@
|
||||
class_name GSTTargetAcceleration
|
||||
# A linear and angular amount of acceleration.
|
||||
|
||||
|
||||
var linear: = Vector3.ZERO
|
||||
var angular: = 0.0
|
||||
|
||||
|
||||
func set_zero() -> void:
|
||||
linear.x = 0.0
|
||||
linear.y = 0.0
|
||||
linear.z = 0.0
|
||||
angular = 0.0
|
||||
|
||||
|
||||
func add_scaled_accel(accel: GSTTargetAcceleration, scalar: float) -> void:
|
||||
linear += accel.linear * scalar
|
||||
angular += accel.angular * scalar
|
||||
|
||||
|
||||
func get_magnitude_squared() -> float:
|
||||
return linear.length_squared() + angular * angular
|
||||
|
||||
|
||||
func get_magnitude() -> float:
|
||||
return sqrt(get_magnitude_squared())
|
10
project/src/GSTUtils.gd
Normal file
10
project/src/GSTUtils.gd
Normal file
@ -0,0 +1,10 @@
|
||||
class_name GSTUtils
|
||||
# Useful math and utility functions to complement Godot's own.
|
||||
|
||||
|
||||
static func clampedv3(vector: Vector3, limit: float) -> Vector3:
|
||||
var length_squared: = vector.length_squared()
|
||||
var limit_squared: = limit * limit
|
||||
if length_squared > limit_squared:
|
||||
vector *= sqrt(limit_squared / length_squared)
|
||||
return vector
|
40
project/src/behaviors/GSTArrive.gd
Normal file
40
project/src/behaviors/GSTArrive.gd
Normal file
@ -0,0 +1,40 @@
|
||||
extends GSTSteeringBehavior
|
||||
class_name GSTArrive
|
||||
# Calculates acceleration to take an agent to its target's location.
|
||||
# The calculation will attempt to arrive with zero remaining velocity.
|
||||
|
||||
|
||||
var target: GSTAgentLocation
|
||||
var arrival_tolerance: float
|
||||
var deceleration_radius: float
|
||||
var time_to_reach: = 0.1
|
||||
|
||||
|
||||
func _init(agent: GSTSteeringAgent, target: GSTAgentLocation).(agent) -> void:
|
||||
self.target = target
|
||||
|
||||
|
||||
func _arrive(acceleration: GSTTargetAcceleration, target_position: Vector3) -> GSTTargetAcceleration:
|
||||
var to_target: = target_position - agent.position
|
||||
var distance: = to_target.length()
|
||||
|
||||
if distance <= arrival_tolerance:
|
||||
acceleration.set_zero()
|
||||
else:
|
||||
var desired_speed: = agent.max_linear_speed
|
||||
|
||||
if distance <= deceleration_radius:
|
||||
desired_speed *= distance / deceleration_radius
|
||||
|
||||
var desired_velocity: = to_target * desired_speed/distance
|
||||
|
||||
desired_velocity = (desired_velocity - agent.linear_velocity) * 1.0 / time_to_reach
|
||||
|
||||
acceleration.linear = GSTUtils.clampedv3(desired_velocity, agent.max_linear_acceleration)
|
||||
acceleration.angular = 0
|
||||
|
||||
return acceleration
|
||||
|
||||
|
||||
func _calculate_steering(acceleration: GSTTargetAcceleration) -> GSTTargetAcceleration:
|
||||
return _arrive(acceleration, target.position)
|
65
project/src/behaviors/GSTAvoidCollisions.gd
Normal file
65
project/src/behaviors/GSTAvoidCollisions.gd
Normal file
@ -0,0 +1,65 @@
|
||||
extends GSTGroupBehavior
|
||||
class_name GSTAvoidCollisions
|
||||
# Behavior that steers the agent to avoid obstacles lying in its path, approximated by a sphere.
|
||||
|
||||
|
||||
var first_neighbor: GSTSteeringAgent
|
||||
var shortest_time: float
|
||||
var first_minimum_separation: float
|
||||
var first_distance: float
|
||||
var first_relative_position: Vector3
|
||||
var first_relative_velocity: Vector3
|
||||
|
||||
|
||||
func _init(agent: GSTSteeringAgent, proximity: GSTProximity).(agent, proximity) -> void:
|
||||
pass
|
||||
|
||||
|
||||
func _calculate_steering(acceleration: GSTTargetAcceleration) -> GSTTargetAcceleration:
|
||||
shortest_time = INF
|
||||
first_neighbor = null
|
||||
first_minimum_separation = 0
|
||||
first_distance = 0
|
||||
|
||||
var neighbor_count: = proximity.find_neighbors(_callback)
|
||||
|
||||
if neighbor_count == 0 or not first_neighbor:
|
||||
acceleration.set_zero()
|
||||
else:
|
||||
if(
|
||||
first_minimum_separation <= 0 or
|
||||
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.max_linear_acceleration
|
||||
acceleration.angular = 0
|
||||
|
||||
return acceleration
|
||||
|
||||
|
||||
func report_neighbor(neighbor: GSTSteeringAgent) -> bool:
|
||||
var relative_position: = neighbor.position - agent.position
|
||||
var relative_velocity: = neighbor.linear_velocity - agent.linear_velocity
|
||||
var relative_speed_squared: = relative_velocity.length_squared()
|
||||
|
||||
if relative_speed_squared == 0:
|
||||
return false
|
||||
else:
|
||||
var time_to_collision = -relative_position.dot(relative_velocity) / relative_speed_squared
|
||||
|
||||
if time_to_collision <= 0 or 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
|
44
project/src/behaviors/GSTBlend.gd
Normal file
44
project/src/behaviors/GSTBlend.gd
Normal file
@ -0,0 +1,44 @@
|
||||
extends GSTSteeringBehavior
|
||||
class_name GSTBlend
|
||||
# Blends multiple steering behaviors into one, and returns acceleration combining all of them.
|
||||
|
||||
# # Each behavior is associated with a weight - a modifier by which the result will be multiplied by,
|
||||
# then added to a total acceleration.
|
||||
|
||||
# # Each behavior is stored internally as a `Dictionary` with a `behavior` key with a value of type
|
||||
# `GSTSteeringBehavior` and a `weight` key with a value of type float.
|
||||
|
||||
|
||||
var _behaviors: = []
|
||||
var _accel: = GSTTargetAcceleration.new()
|
||||
|
||||
|
||||
func _init(agent: GSTSteeringAgent).(agent) -> void:
|
||||
pass
|
||||
|
||||
|
||||
func add(behavior: GSTSteeringBehavior, weight: float) -> void:
|
||||
behavior.agent = agent
|
||||
_behaviors.append({behavior = behavior, weight = weight})
|
||||
|
||||
|
||||
func get_behavior_at(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 {}
|
||||
|
||||
|
||||
func _calculate_steering(blended_accel: GSTTargetAcceleration) -> GSTTargetAcceleration:
|
||||
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 = GSTUtils.clampedv3(blended_accel.linear, agent.max_linear_acceleration)
|
||||
blended_accel.angular = min(blended_accel.angular, agent.max_angular_acceleration)
|
||||
|
||||
return blended_accel
|
26
project/src/behaviors/GSTCohesion.gd
Normal file
26
project/src/behaviors/GSTCohesion.gd
Normal file
@ -0,0 +1,26 @@
|
||||
extends GSTGroupBehavior
|
||||
class_name GSTCohesion
|
||||
# Group behavior that produces linear acceleration that attempts to move the agent towards the
|
||||
# center of mass of the agents in the area defined by the defined Proximity.
|
||||
|
||||
|
||||
var center_of_mass: Vector3
|
||||
|
||||
|
||||
func _init(agent: GSTSteeringAgent, proximity: GSTProximity).(agent, proximity) -> void:
|
||||
pass
|
||||
|
||||
|
||||
func _calculate_steering(acceleration: GSTTargetAcceleration) -> GSTTargetAcceleration:
|
||||
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.max_linear_acceleration
|
||||
return acceleration
|
||||
|
||||
|
||||
func report_neighbor(neighbor: GSTSteeringAgent) -> bool:
|
||||
center_of_mass += neighbor.position
|
||||
return true
|
16
project/src/behaviors/GSTEvade.gd
Normal file
16
project/src/behaviors/GSTEvade.gd
Normal file
@ -0,0 +1,16 @@
|
||||
extends GSTPursue
|
||||
class_name GSTEvade
|
||||
# Calculates acceleration to take an agent away from where a target agent will be.
|
||||
|
||||
# # The `max_predict_time` variable represents how far ahead to calculate the intersection point.
|
||||
|
||||
|
||||
func _init(
|
||||
agent: GSTSteeringAgent,
|
||||
target: GSTSteeringAgent,
|
||||
max_predict_time: = 1.0).(agent, target, max_predict_time):
|
||||
pass
|
||||
|
||||
|
||||
func _get_modified_acceleration() -> float:
|
||||
return -agent.max_linear_acceleration
|
24
project/src/behaviors/GSTFace.gd
Normal file
24
project/src/behaviors/GSTFace.gd
Normal file
@ -0,0 +1,24 @@
|
||||
extends GSTMatchOrientation
|
||||
class_name GSTFace
|
||||
# Calculates angular acceleration to rotate a target to face its target's position.
|
||||
# The acceleration will attempt to arrive with zero remaining angular velocity.
|
||||
|
||||
|
||||
func _init(agent: GSTSteeringAgent, target: GSTAgentLocation).(agent, target) -> void:
|
||||
pass
|
||||
|
||||
|
||||
func _face(acceleration: GSTTargetAcceleration, target_position: Vector3) -> GSTTargetAcceleration:
|
||||
var to_target: = target_position - agent.position
|
||||
var distance_squared: = to_target.length_squared()
|
||||
|
||||
if distance_squared < agent.zero_linear_speed_threshold:
|
||||
acceleration.set_zero()
|
||||
return acceleration
|
||||
else:
|
||||
var orientation = atan2(to_target.x, -to_target.y)
|
||||
return _match_orientation(acceleration, orientation)
|
||||
|
||||
|
||||
func _calculate_steering(acceleration: GSTTargetAcceleration) -> GSTTargetAcceleration:
|
||||
return _face(acceleration, target.position)
|
15
project/src/behaviors/GSTFlee.gd
Normal file
15
project/src/behaviors/GSTFlee.gd
Normal file
@ -0,0 +1,15 @@
|
||||
extends GSTSeek
|
||||
class_name GSTFlee
|
||||
# Calculates acceleration to take an agent directly away from a target agent.
|
||||
|
||||
|
||||
func _init(agent: GSTSteeringAgent, target: GSTAgentLocation).(agent, target) -> void:
|
||||
pass
|
||||
|
||||
|
||||
func _calculate_steering(acceleration: GSTTargetAcceleration) -> GSTTargetAcceleration:
|
||||
acceleration.linear = (
|
||||
(agent.position - target.position).normalized() * agent.max_linear_acceleration)
|
||||
acceleration.angular = 0
|
||||
|
||||
return acceleration
|
47
project/src/behaviors/GSTFollowPath.gd
Normal file
47
project/src/behaviors/GSTFollowPath.gd
Normal file
@ -0,0 +1,47 @@
|
||||
extends GSTArrive
|
||||
class_name GSTFollowPath
|
||||
# Produces a linear acceleration that moves the agent along the specified path.
|
||||
|
||||
|
||||
var path: GSTPath
|
||||
var path_offset: = 0.0
|
||||
|
||||
var path_param: = {}
|
||||
|
||||
var arrive_enabled: = true
|
||||
var prediction_time: = 0.0
|
||||
|
||||
|
||||
func _init(
|
||||
agent: GSTSteeringAgent,
|
||||
path: GSTPath,
|
||||
path_offset: = 0.0,
|
||||
prediction_time: = 0.0).(agent, null) -> void:
|
||||
self.path = path
|
||||
self.path_offset = path_offset
|
||||
self.prediction_time = prediction_time
|
||||
|
||||
|
||||
func _calculate_steering(acceleration: GSTTargetAcceleration) -> GSTTargetAcceleration:
|
||||
var location: = (
|
||||
agent.position if prediction_time == 0
|
||||
else agent.position + (agent.linear_velocity * prediction_time))
|
||||
|
||||
var distance: = path.calculate_distance(location, path_param)
|
||||
var target_distance: = distance + path_offset
|
||||
|
||||
var target_position: = path.calculate_target_position(path_param, target_distance)
|
||||
|
||||
if arrive_enabled and path.is_open:
|
||||
if path_offset >= 0:
|
||||
if target_distance > path.length - deceleration_radius:
|
||||
return _arrive(acceleration, target_position)
|
||||
else:
|
||||
if target_distance < deceleration_radius:
|
||||
return _arrive(acceleration, target_position)
|
||||
|
||||
acceleration.linear = (target_position - agent.position).normalized()
|
||||
acceleration.linear *= agent.max_linear_acceleration
|
||||
acceleration.angular = 0
|
||||
|
||||
return acceleration
|
16
project/src/behaviors/GSTLookWhereYouGo.gd
Normal file
16
project/src/behaviors/GSTLookWhereYouGo.gd
Normal file
@ -0,0 +1,16 @@
|
||||
extends GSTMatchOrientation
|
||||
class_name GSTLookWhereYouGo
|
||||
# Calculates an angular acceleration to match an agent's orientation to its direction of travel.
|
||||
|
||||
|
||||
func _init(agent: GSTSteeringAgent).(agent, null) -> void:
|
||||
pass
|
||||
|
||||
|
||||
func _calculate_steering(accel: GSTTargetAcceleration) -> GSTTargetAcceleration:
|
||||
if agent.linear_velocity.length_squared() < agent.zero_linear_speed_threshold:
|
||||
accel.set_zero()
|
||||
return accel
|
||||
else:
|
||||
var orientation: = atan2(agent.linear_velocity.x, -agent.linear_velocity.y)
|
||||
return _match_orientation(accel, orientation)
|
44
project/src/behaviors/GSTMatchOrientation.gd
Normal file
44
project/src/behaviors/GSTMatchOrientation.gd
Normal file
@ -0,0 +1,44 @@
|
||||
extends GSTSteeringBehavior
|
||||
class_name GSTMatchOrientation
|
||||
# Calculates an angular acceleration to match an agent's orientation to its target's.
|
||||
# The calculation will attempt to arrive with zero remaining angular velocity.
|
||||
|
||||
|
||||
var target: GSTAgentLocation
|
||||
var alignment_tolerance: float
|
||||
var deceleration_radius: float
|
||||
var time_to_reach: float = 0.1
|
||||
|
||||
|
||||
func _init(agent: GSTSteeringAgent, target: GSTAgentLocation).(agent) -> void:
|
||||
self.target = target
|
||||
|
||||
|
||||
func _match_orientation(acceleration: GSTTargetAcceleration, desired_orientation: float) -> GSTTargetAcceleration:
|
||||
var rotation: = wrapf(desired_orientation - agent.orientation, -PI, PI)
|
||||
|
||||
var rotation_size: = abs(rotation)
|
||||
|
||||
if rotation_size <= alignment_tolerance:
|
||||
acceleration.set_zero()
|
||||
else:
|
||||
var desired_rotation: = agent.max_angular_speed
|
||||
|
||||
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: = abs(acceleration.angular)
|
||||
if limited_acceleration > agent.max_angular_acceleration:
|
||||
acceleration.angular *= agent.max_angular_acceleration / limited_acceleration
|
||||
|
||||
acceleration.linear = Vector3.ZERO
|
||||
|
||||
return acceleration
|
||||
|
||||
|
||||
func _calculate_steering(acceleration: GSTTargetAcceleration) -> GSTTargetAcceleration:
|
||||
return _match_orientation(acceleration, target.orientation)
|
46
project/src/behaviors/GSTPriority.gd
Normal file
46
project/src/behaviors/GSTPriority.gd
Normal file
@ -0,0 +1,46 @@
|
||||
extends GSTSteeringBehavior
|
||||
class_name GSTPriority
|
||||
# Contains multiple steering behaviors and returns only the result of the first that has a non-zero
|
||||
# acceleration.
|
||||
|
||||
|
||||
var _behaviors: = []
|
||||
|
||||
var last_selected_index: int
|
||||
var threshold_for_zero: float
|
||||
|
||||
|
||||
func _init(agent: GSTSteeringAgent, threshold_for_zero: = 0.001).(agent) -> void:
|
||||
self.threshold_for_zero = threshold_for_zero
|
||||
|
||||
|
||||
func add(behavior: GSTSteeringBehavior) -> void:
|
||||
_behaviors.append(behavior)
|
||||
|
||||
|
||||
func get_behavior_at(index: int) -> GSTSteeringBehavior:
|
||||
if _behaviors.size() > index:
|
||||
return _behaviors[index]
|
||||
printerr("Tried to get index " + str(index) + " in array of size " + str(_behaviors.size()))
|
||||
return null
|
||||
|
||||
|
||||
func _calculate_steering(accel: GSTTargetAcceleration) -> GSTTargetAcceleration:
|
||||
var threshold_squared: = threshold_for_zero * threshold_for_zero
|
||||
|
||||
last_selected_index = -1
|
||||
|
||||
var size: = _behaviors.size()
|
||||
|
||||
if size > 0:
|
||||
for i in range(size):
|
||||
last_selected_index = i
|
||||
var behavior: GSTSteeringBehavior = _behaviors[i]
|
||||
behavior.calculate_steering(accel)
|
||||
|
||||
if accel.get_magnitude_squared() > threshold_squared:
|
||||
break
|
||||
else:
|
||||
accel.set_zero()
|
||||
|
||||
return accel
|
42
project/src/behaviors/GSTPursue.gd
Normal file
42
project/src/behaviors/GSTPursue.gd
Normal file
@ -0,0 +1,42 @@
|
||||
extends GSTSteeringBehavior
|
||||
class_name GSTPursue
|
||||
# Calculates acceleration to take an agent to intersect with where a target agent will be.
|
||||
|
||||
# # The `max_predict_time` variable represents how far ahead to calculate the intersection point.
|
||||
|
||||
|
||||
var target: GSTSteeringAgent
|
||||
var max_predict_time: float
|
||||
|
||||
|
||||
func _init(
|
||||
agent: GSTSteeringAgent,
|
||||
target: GSTSteeringAgent,
|
||||
max_predict_time: = 1.0).(agent) -> void:
|
||||
self.target = target
|
||||
self.max_predict_time = max_predict_time
|
||||
|
||||
|
||||
func _calculate_steering(acceleration: GSTTargetAcceleration) -> GSTTargetAcceleration:
|
||||
var target_position: = target.position
|
||||
var distance_squared: = (target_position - agent.position).length_squared()
|
||||
|
||||
var speed_squared: = agent.linear_velocity.length_squared()
|
||||
var predict_time: = max_predict_time
|
||||
|
||||
if speed_squared > 0:
|
||||
var predict_time_squared: = distance_squared / speed_squared
|
||||
if predict_time_squared < max_predict_time * max_predict_time:
|
||||
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
|
||||
|
||||
return acceleration
|
||||
|
||||
|
||||
func _get_modified_acceleration() -> float:
|
||||
return agent.max_linear_acceleration
|
18
project/src/behaviors/GSTSeek.gd
Normal file
18
project/src/behaviors/GSTSeek.gd
Normal file
@ -0,0 +1,18 @@
|
||||
extends GSTSteeringBehavior
|
||||
class_name GSTSeek
|
||||
# Calculates acceleration to take an agent to a target agent's position as directly as possible
|
||||
|
||||
|
||||
var target: GSTAgentLocation
|
||||
|
||||
|
||||
func _init(agent: GSTSteeringAgent, target: GSTAgentLocation).(agent) -> void:
|
||||
self.target = target
|
||||
|
||||
|
||||
func _calculate_steering(acceleration: GSTTargetAcceleration) -> GSTTargetAcceleration:
|
||||
acceleration.linear = (
|
||||
(target.position - agent.position).normalized() * agent.max_linear_acceleration)
|
||||
acceleration.angular = 0
|
||||
|
||||
return acceleration
|
39
project/src/behaviors/GSTSeparation.gd
Normal file
39
project/src/behaviors/GSTSeparation.gd
Normal file
@ -0,0 +1,39 @@
|
||||
extends GSTGroupBehavior
|
||||
class_name GSTSeparation
|
||||
# Group behavior that produces acceleration repelling from the other neighbors that are in the
|
||||
# immediate area defined by the given `GSTProximity`.
|
||||
|
||||
# # The produced acceleration is an average of all agents under consideration, multiplied by a
|
||||
# # strength decreasing by the inverse square law in relation to distance, and accumulated.
|
||||
# # In effect, all neighbors produce a single repelling force.
|
||||
|
||||
|
||||
var decay_coefficient: = 1.0
|
||||
|
||||
var acceleration: GSTTargetAcceleration
|
||||
|
||||
|
||||
func _init(agent: GSTSteeringAgent, proximity: GSTProximity).(agent, proximity) -> void:
|
||||
pass
|
||||
|
||||
|
||||
func _calculate_steering(acceleration: GSTTargetAcceleration) -> GSTTargetAcceleration:
|
||||
acceleration.set_zero()
|
||||
self.acceleration = acceleration
|
||||
proximity.find_neighbors(_callback)
|
||||
return acceleration
|
||||
|
||||
|
||||
func report_neighbor(neighbor: GSTSteeringAgent) -> bool:
|
||||
var to_agent: = agent.position - neighbor.position
|
||||
|
||||
var distance_squared: = to_agent.length_squared()
|
||||
var max_acceleration: = agent.max_linear_acceleration
|
||||
|
||||
var strength: = decay_coefficient / distance_squared
|
||||
if strength > max_acceleration:
|
||||
strength = max_acceleration
|
||||
|
||||
acceleration.linear += to_agent * (strength / sqrt(distance_squared))
|
||||
|
||||
return true
|
20
project/src/proximities/GSTInfiniteProximity.gd
Normal file
20
project/src/proximities/GSTInfiniteProximity.gd
Normal file
@ -0,0 +1,20 @@
|
||||
extends GSTProximity
|
||||
class_name GSTInfiniteProximity
|
||||
# Determines any agent that is in the specified list as being neighbors with the owner agent.
|
||||
|
||||
|
||||
func _init(agent: GSTSteeringAgent, agents: Array).(agent, agents) -> void:
|
||||
pass
|
||||
|
||||
|
||||
func find_neighbors(callback: FuncRef) -> int:
|
||||
var neighbor_count: = 0
|
||||
var agent_count: = agents.size()
|
||||
for i in range(agent_count):
|
||||
var current_agent: = agents[i] as GSTSteeringAgent
|
||||
|
||||
if current_agent != agent:
|
||||
if callback.call_func(current_agent):
|
||||
neighbor_count += 1
|
||||
|
||||
return neighbor_count
|
17
project/src/proximities/GSTProximity.gd
Normal file
17
project/src/proximities/GSTProximity.gd
Normal file
@ -0,0 +1,17 @@
|
||||
extends Reference
|
||||
class_name GSTProximity
|
||||
# Defines a way to determine any agent that is in the specified list as being neighbors with the
|
||||
# owner agent.
|
||||
|
||||
|
||||
var agent: GSTSteeringAgent
|
||||
var agents: = []
|
||||
|
||||
|
||||
func _init(agent: GSTSteeringAgent, agents: Array) -> void:
|
||||
self.agent = agent
|
||||
self.agents = agents
|
||||
|
||||
|
||||
func find_neighbors(callback: FuncRef) -> int:
|
||||
return 0
|
51
project/src/proximities/GSTRadiusProximity.gd
Normal file
51
project/src/proximities/GSTRadiusProximity.gd
Normal file
@ -0,0 +1,51 @@
|
||||
extends GSTProximity
|
||||
class_name GSTRadiusProximity
|
||||
# Determines any agent that is in the specified list as being neighbors with the owner agent if
|
||||
# they lie within the specified radius.
|
||||
|
||||
|
||||
var radius: = 0.0
|
||||
|
||||
var _last_frame: = 0
|
||||
var _scene_tree: SceneTree
|
||||
|
||||
|
||||
func _init(agent: GSTSteeringAgent, agents: Array, radius: float).(agent, agents) -> void:
|
||||
self.radius = radius
|
||||
_scene_tree = Engine.get_main_loop()
|
||||
|
||||
|
||||
func find_neighbors(callback: FuncRef) -> int:
|
||||
var agent_count: = agents.size()
|
||||
var neighbor_count: = 0
|
||||
|
||||
var current_frame: = _scene_tree.get_frame() if _scene_tree else -_last_frame
|
||||
if current_frame != _last_frame:
|
||||
_last_frame = current_frame
|
||||
|
||||
var owner_position: = agent.position
|
||||
|
||||
for i in range(agent_count):
|
||||
var current_agent: = agents[i] as GSTSteeringAgent
|
||||
|
||||
if current_agent != agent:
|
||||
var distance_squared: = owner_position.distance_squared_to(current_agent.position)
|
||||
|
||||
var range_to: = radius + current_agent.bounding_radius
|
||||
|
||||
if distance_squared < range_to * range_to:
|
||||
if callback.call_func(current_agent):
|
||||
current_agent.tagged = true
|
||||
neighbor_count += 1
|
||||
continue
|
||||
|
||||
current_agent.tagged = false
|
||||
else:
|
||||
for i in range(agent_count):
|
||||
var current_agent = agents[i] as GSTSteeringAgent
|
||||
|
||||
if current_agent != agent and current_agent.tagged:
|
||||
if callback.call_func(current_agent):
|
||||
neighbor_count += 1
|
||||
|
||||
return neighbor_count
|
Loading…
Reference in New Issue
Block a user