├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── assets
└── banner.svg
├── godot
├── Demos
│ ├── Arrive
│ │ ├── ArriveDemo.gd
│ │ ├── ArriveDemo.tscn
│ │ ├── Arriver.gd
│ │ └── TargetDrawer.gd
│ ├── Arrive3d
│ │ ├── Arrive3dDemo.tscn
│ │ ├── Camera.gd
│ │ ├── PadMat.tres
│ │ ├── Seek3dDemo.gd
│ │ ├── Seeker.gd
│ │ ├── SeekerMat.tres
│ │ └── WorldMat.tres
│ ├── AvoidCollisions
│ │ ├── AvoidCollisionsDemo.gd
│ │ ├── AvoidCollisionsDemo.tscn
│ │ ├── Avoider.gd
│ │ ├── Avoider.tscn
│ │ └── Spawner.gd
│ ├── DemoPickerUI.gd
│ ├── DemoPlayer.gd
│ ├── DemoSelector.tscn
│ ├── Demos.gd
│ ├── Face
│ │ ├── FaceDemo.gd
│ │ ├── FaceDemo.tscn
│ │ ├── Player.gd
│ │ └── Turret.gd
│ ├── FollowPath
│ │ ├── Drawer.gd
│ │ ├── FollowPathDemo.gd
│ │ ├── FollowPathDemo.tscn
│ │ └── PathFollower.gd
│ ├── GroupBehaviors
│ │ ├── GroupBehaviorsDemo.gd
│ │ ├── GroupBehaviorsDemo.tscn
│ │ ├── Member.gd
│ │ ├── Member.tscn
│ │ └── Spawner.gd
│ ├── PopulateItemList.gd
│ ├── PursueSeek
│ │ ├── BoundaryManager.gd
│ │ ├── Player.gd
│ │ ├── PursueAndSeekDemo.gd
│ │ ├── PursueAndSeekDemo.tscn
│ │ └── Pursuer.gd
│ ├── Quickstart
│ │ ├── Agent.gd
│ │ ├── Bullet.gd
│ │ ├── Bullet.tscn
│ │ ├── Player.gd
│ │ ├── QuickStartDemo.gd
│ │ └── QuickStartDemo.tscn
│ ├── SeekFlee
│ │ ├── Boundaries.gd
│ │ ├── Player.gd
│ │ ├── SeekFleeDemo.gd
│ │ ├── SeekFleeDemo.tscn
│ │ ├── Seeker.gd
│ │ ├── Seeker.tscn
│ │ └── Spawner.gd
│ └── Utils
│ │ ├── BackgroundLayer.tscn
│ │ ├── CircleDraw.gd
│ │ ├── DemoInterface.gd
│ │ ├── DemoInterface.tscn
│ │ └── Line2DDraw.gd
├── addons
│ └── com.gdquest.godot-steering-ai-framework
│ │ ├── Agents
│ │ ├── GSAICharacterBody2DAgent.gd
│ │ ├── GSAICharacterBody3DAgent.gd
│ │ ├── GSAIRigidBody2DAgent.gd
│ │ ├── GSAIRigidBody3DAgent.gd
│ │ └── GSAISpecializedAgent.gd
│ │ ├── Behaviors
│ │ ├── GSAIArrive.gd
│ │ ├── GSAIAvoidCollisions.gd
│ │ ├── GSAIBlend.gd
│ │ ├── GSAICohesion.gd
│ │ ├── GSAIEvade.gd
│ │ ├── GSAIFace.gd
│ │ ├── GSAIFlee.gd
│ │ ├── GSAIFollowPath.gd
│ │ ├── GSAILookWhereYouGo.gd
│ │ ├── GSAIMatchOrientation.gd
│ │ ├── GSAIPriority.gd
│ │ ├── GSAIPursue.gd
│ │ ├── GSAISeek.gd
│ │ └── GSAISeparation.gd
│ │ ├── GSAIAgentLocation.gd
│ │ ├── GSAIGroupBehavior.gd
│ │ ├── GSAIPath.gd
│ │ ├── GSAISteeringAgent.gd
│ │ ├── GSAISteeringBehavior.gd
│ │ ├── GSAITargetAcceleration.gd
│ │ ├── GSAIUtils.gd
│ │ └── Proximities
│ │ ├── GSAIInfiniteProximity.gd
│ │ ├── GSAIProximity.gd
│ │ └── GSAIRadiusProximity.gd
├── assets
│ ├── icon.png
│ ├── icon.png.import
│ ├── icon.svg
│ ├── icon.svg.import
│ ├── sprites
│ │ ├── background.png
│ │ └── background.png.import
│ └── theme
│ │ ├── button
│ │ ├── disabled.stylebox
│ │ ├── focused.stylebox
│ │ ├── hover.stylebox
│ │ ├── normal.stylebox
│ │ └── pressed.stylebox
│ │ ├── empty.stylebox
│ │ ├── fonts
│ │ ├── default_font.tres
│ │ ├── default_font_bold.tres
│ │ ├── default_font_code.tres
│ │ ├── font_title.tres
│ │ ├── montserrat
│ │ │ ├── Montserrat-Black.ttf
│ │ │ ├── Montserrat-Black.ttf.import
│ │ │ ├── Montserrat-Bold.ttf
│ │ │ ├── Montserrat-Bold.ttf.import
│ │ │ ├── Montserrat-Medium.ttf
│ │ │ └── Montserrat-Medium.ttf.import
│ │ └── source_code_pro
│ │ │ ├── SourceCodePro-Medium.otf
│ │ │ └── SourceCodePro-Medium.otf.import
│ │ ├── gdquest.theme
│ │ ├── icons
│ │ ├── chevron-right.svg
│ │ ├── chevron-right.svg.import
│ │ ├── chevron-up.svg
│ │ └── chevron-up.svg.import
│ │ ├── panel
│ │ └── panel.stylebox
│ │ ├── separator
│ │ └── line.tres
│ │ └── slider
│ │ ├── grabber_area.stylebox
│ │ └── slider.stylebox
└── project.godot
└── reference.json
/.gitignore:
--------------------------------------------------------------------------------
1 | .godot/
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | This document lists new features, improvements, changes, and bug fixes in every release of the add-on.
4 |
5 | ## Master
6 |
7 | - Removed the demos repository and brought demos back into main repo
8 | - Added a repository with just the files for [submodule](https://github.com/GDQuest/godot-steering-ai-framework-submodule) usage.
9 | - Fix a bug in GSAIKinematicBody2DAgent and GSAIKinematicBody3DAgent where angular velocity was clamped using max acceleration instead of max speed.
10 |
11 | ## Godot Steering AI Framework 3.0.0
12 |
13 | ### Changes
14 |
15 | - The structure of the project has been overhauled in order to make it possible to import as an add-on in Godot directly.
16 | - Acceleration for agents are now multiplied by delta in order to make acceleration be per second instead of instant. The demos' values have been increased significantly to better fit with reality.
17 |
18 | ### Fixes
19 |
20 | - KinematicBody2DAgents and KinematicBody3DAgents that moved fast enough no longer reverse velocity suddenly during a frame where no acceleration is applied.
21 | - Specialized Agents like RigidBody2DAgent should no longer crash due to a missing reference.
22 | - Specialized physics agents no longer believe they are at 0,0,0 on the first frame.
23 |
24 | ## Godot Steering AI Framework 2.1.0
25 |
26 | ### Features
27 |
28 | - There is now an `Arrive3d` demo to showcase 3D movement.
29 |
30 | ### Improvements
31 |
32 | - All the demos got a bit of attention to improve their feel.
33 |
34 | ### Changes
35 |
36 | - `GSAIUtils.vector3_to_angle` now uses the vector's X and Z components to determine angle. Use `GSAIUtils.vector2_to_angle` for 2D use cases.
37 | - `GSAIMatchOrientation` and its subclasses like `GSAIFace` and `GSAILookWhereYouGo` now include a `use_z` property. It should be `true` when using 3D so that facing will be done with the X and Z components.
38 | - The README now mentions a simple way to install the framework.
39 | - Exposed `agent_count` inside the `AvoidCollisionsDemo`.
40 | - Unused and undocumented variable `_body_type` has been removed from `SpecializedAgent`
41 |
42 | ### Bug fixes
43 |
44 | - Fixed `GSAIKinematicBody3DAgent` and `GSAIRigidBody3DAgent` trying to use `global_position` instead of `transform.origin`.
45 | - The `SeekFleeDemo`'s boundaries will now match the size of the screen.
46 | - Fixed error when double clicking an item in the DemoPicker.
47 | - Fixed the background sometimes not covering the entire viewport in demos.
48 | - The specialized agents now use WeakRef internally to prevent crashes when their `body` is freed.
49 | - `RigidBody2DAgent` now properly connects to physics updates.
50 |
51 | ## Godot Steering AI Framework 2.0.0
52 |
53 | This release brings one new feature and bug fix, and breaking changes to the framework as we renamed all the classes.
54 |
55 | **Important**: we renamed all classes from GST\* to GSAI\* (Godot Steering AI). When you upgrade the framework in your project, use the project search and replace feature in Godot (Ctrl Shift F) to find and replace `GST` with `GSAI`.
56 |
57 | If you were using `GSTKinematicBodyAgent` or `GSTRigidBodyAgent`, search and replace them respectively with `GSAIKinematicBody3DAgent` and `GSAIRigidBody3DAgent`.
58 |
59 | We decided to make this change as soon as possible, as the framework was released a few days ago.
60 |
61 | ### Features
62 |
63 | - There is now a main scene with a demo picker, so you can select and play any demo on the fly.
64 | - The demo projects now support resizing and toggling fullscreen with F11.
65 |
66 | ### Improvements
67 |
68 | - We handled all warnings in the framework, so using it won't add warnings to your projects.
69 |
70 | ### Changes
71 |
72 | - Renamed all classes from `GST*` (Godot Steering Toolkit) to `GSAI*` (Godot Steering AI).
73 | - Removed `GSTNode2DAgent`, `GSTNodeAgent`, and `GSTSpatialAgent` classes.
74 | - For specialized steering agents, `GSAIKinematicBody2DAgent`, `GSAIRigidBody2DAgent`, or their 3D equivalent.
75 | - If you intend to write your own movement system instead of using Godot's, the base class `GSTSpecializedAgent` is there to help you.
76 | - Renamed `GSAIRigidBodyAgent` and `GSAIRigidBodyAgent` to `GSAIRigidBody3DAgent` and `GSAIRigidBody3DAgent` respectively.
77 | - 3D nodes like `Sprite`, `KinematicBody`, etc. are being renamed to `Sprite3D`, `KinematicBody3D`, etc. in the upcoming Godot 4.0 release, to be consistent with 2D nodes. We decided to rename them now instead of breaking compatibility in a future release.
78 |
79 | ### Bug fixes
80 |
81 | - GSTFollowPath no longer loops back around itself on open paths when `predict_time` is non-zero.
82 |
83 | ## Godot Steering AI Framework 1.0.0
84 |
85 | This is the first major release of the framework. It comes with:
86 |
87 | - All the essential steering behaviors: `Arrive`, `AvoidCollisions`, `Blend`, `Cohesion`, `Evade`, `Face`, `Flee`, `FollowPath`, `LookWhereYouGo`, `MatchOrientation`, `Priority`, `Pursue`, `Seek`, `Separation`.
88 | - Group behaviors and detecting neighbors.
89 | - Blending and prioritized behaviors.
90 | - Specialized types to code agents based on physics bodies:
91 | - For 2D games, `KinematicBody2DAgent` and `RigidBody2DAgent`.
92 | - For 3D games, `KinematicBody3DAgent` and `RigidBody3DAgent`.
93 | - 9 Godot demos to learn straight from the code.
94 |
95 | ### Manual
96 |
97 | To get started, check out the framework's [manual](https://www.gdquest.com/docs/godot-steering-toolkit/).
98 |
99 | There, you can also find the full [code reference](https://www.gdquest.com/docs/godot-steering-toolkit/reference/).
100 |
101 | *Note*: we generate the code reference from docstrings in the source code with [GDScript Docs Maker](https://github.com/GDQuest/gdscript-docs-maker).
102 |
103 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 GDQuest
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Godot Steering AI Framework
2 |
3 | 
4 |
5 | This project is a framework to code complex and smooth AI movement in the [Godot game engine](https://godotengine.org/), in GDScript, using steering behaviors. It works in **both 2D and 3D games**.
6 |
7 | ➡ Follow us on [Twitter](https://twitter.com/NathanGDQuest) and [YouTube](https://www.youtube.com/c/gdquest/) for free game creation tutorials, tips, and news! Get one of our [Godot game creation courses](https://gdquest.mavenseed.com/) to support our work on Free Software.
8 |
9 | It supports all essential steering behaviors like flee, follow, look at, but also blended behaviors, group behaviors, avoiding neighbors, following a path, following the leader, and much more.
10 |
11 | - [Godot Steering AI Framework](#godot-steering-ai-framework)
12 | - [Getting the framework](#getting-the-framework)
13 | - [Introduction](#introduction)
14 | - [The framework](#the-framework)
15 | - [How it works](#how-it-works)
16 | - [Documentation](#documentation)
17 | - [Contributing](#contributing)
18 | - [Support us](#support-us)
19 | - [Join the community](#join-the-community)
20 |
21 | ## Getting the framework
22 |
23 | This repository contains the framework and some demos for learning purposes. You can download a copy by clicking the green _Code_ button and clicking _Download ZIP_. Then, copy and paste the `addons/` directory into your project.
24 |
25 | ## Introduction
26 |
27 | In the 1990s, [Craig Reynolds](http://www.red3d.com/cwr/) developed algorithms for common AI behaviors. They allowed AI agents to seek out or flee from a target, follow a pre-defined path, or face in a particular direction. They were simple, repeatable tasks that could be broken down into programming algorithms, which made them easy to reuse, maintain, combine, and extend.
28 |
29 | While an AI agent's next action is based on decision making and planning algorithms, steering behaviors dictate how it will move from one frame to the next. They use available information and calculate where to move at that moment.
30 |
31 | Joining these systems together can give sophisticated and graceful movement while also being more efficient than complex pathfinding algorithms like A\*.
32 |
33 | ## The framework
34 |
35 | This project is a framework for the [Godot game engine](https://godotengine.org/). It takes inspiration from the excellent [GDX-AI](https://github.com/libgdx/gdx-ai) framework for the [LibGDX](https://libgdx.badlogicgames.com/) java-based framework.
36 |
37 | Every class in the framework extends Godot's [Reference](https://docs.godotengine.org/en/latest/classes/class_reference.html) type. There is no need to have a complex scene tree; you can contain that has to do with the AI's movement inside GDScript classes.
38 |
39 | ### How it works
40 |
41 | In GSAI, a steering agent represents a character or a vehicle. The agent stores its position, orientation, maximum speeds, and current velocity. The agent stores a steering behavior that calculates a linear or angular change in velocity based on its information.
42 |
43 | The coder then applies that acceleration in whatever ways is appropriate to the character to change its velocities, like RigidBody's `apply_impulse`, or a KinematicBody's `move_and_slide`.
44 |
45 | ## Documentation
46 |
47 | The framework's documentation and code reference are available here: [Godot steering AI framework documentation](https://gdquest.gitbook.io/godot-3-steering-ai-framework-reference/)
48 |
49 | ## Contributing
50 |
51 | If you encounter a bug or you have an idea to improve the tool, please [open an issue](https://github.com/GDQuest/gdscript-docs-maker/issues/new).
52 |
53 | If you want to contribute to the project, for instance by fixing a bug or adding a feature, check out our:
54 |
55 | 1. [Contributor's guidelines](https://www.gdquest.com/docs/guidelines/contributing-to/gdquest-projects/).
56 | 1. [GDScript style guide](https://www.gdquest.com/docs/guidelines/best-practices/godot-gdscript/)
57 |
58 | ## Support us
59 |
60 | Our work on Free Software is sponsored by our [Godot game creation courses](https://gdquest.mavenseed.com/). Consider getting one to support us!
61 |
62 | _If you like our work, please star the repository! This helps more people find it._
63 |
64 | ## Join the community
65 |
66 | - You can join the GDQuest community and come chat with us on [Discord](https://discord.gg/CHYVgar)
67 | - For quick news, follow us on [Twitter](https://twitter.com/nathangdquest)
68 | - We release video tutorials and major updates on [YouTube](https://youtube.com/c/gdquest)
69 |
--------------------------------------------------------------------------------
/godot/Demos/Arrive/ArriveDemo.gd:
--------------------------------------------------------------------------------
1 | extends Node
2 |
3 | @export_range(0, 3200, 100) var linear_speed_max := 800.0: set = set_linear_speed_max
4 | @export_range(0, 10000, 100) var linear_acceleration_max := 80.0: set = set_linear_acceleration_max
5 | @export_range(0, 100, 0.1) var arrival_tolerance := 25.0: set = set_arrival_tolerance
6 | @export_range(0, 500, 10) var deceleration_radius := 125.0: set = set_deceleration_radius
7 |
8 | @onready var arriver := $Arriver
9 | @onready var target_drawer := $TargetDrawer
10 |
11 |
12 | func _ready() -> void:
13 | get_tree().root.content_scale_mode = Window.CONTENT_SCALE_MODE_CANVAS_ITEMS
14 | get_tree().root.content_scale_aspect = Window.CONTENT_SCALE_ASPECT_EXPAND
15 |
16 | arriver.setup(linear_speed_max, linear_acceleration_max, arrival_tolerance, deceleration_radius)
17 |
18 |
19 | func _unhandled_input(event: InputEvent) -> void:
20 | if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT and event.is_pressed():
21 | arriver.target.position = Vector3(event.position.x, event.position.y, 0)
22 | target_drawer.queue_redraw()
23 |
24 |
25 | func set_arrival_tolerance(value: float) -> void:
26 | arrival_tolerance = value
27 | if not is_inside_tree():
28 | return
29 |
30 | arriver.arrive.arrival_tolerance = value
31 |
32 |
33 | func set_deceleration_radius(value: float) -> void:
34 | deceleration_radius = value
35 | if not is_inside_tree():
36 | return
37 |
38 | arriver.arrive.deceleration_radius = value
39 |
40 |
41 | func set_linear_speed_max(value: float) -> void:
42 | linear_speed_max = value
43 | if not is_inside_tree():
44 | return
45 |
46 | arriver.agent.linear_speed_max = value
47 |
48 |
49 | func set_linear_acceleration_max(value: float) -> void:
50 | linear_acceleration_max = value
51 | if not is_inside_tree():
52 | return
53 |
54 | arriver.agent.linear_acceleration_max = value
55 |
--------------------------------------------------------------------------------
/godot/Demos/Arrive/ArriveDemo.tscn:
--------------------------------------------------------------------------------
1 | [gd_scene load_steps=8 format=3 uid="uid://g1dlf61suo34"]
2 |
3 | [ext_resource type="Script" path="res://Demos/Arrive/Arriver.gd" id="1"]
4 | [ext_resource type="PackedScene" uid="uid://b1iy8iir6k8tr" path="res://Demos/Utils/DemoInterface.tscn" id="2"]
5 | [ext_resource type="Script" path="res://Demos/Arrive/ArriveDemo.gd" id="3"]
6 | [ext_resource type="PackedScene" uid="uid://ctke36x8wxrla" path="res://Demos/Utils/BackgroundLayer.tscn" id="4"]
7 | [ext_resource type="Script" path="res://Demos/Arrive/TargetDrawer.gd" id="5"]
8 | [ext_resource type="Script" path="res://Demos/Utils/CircleDraw.gd" id="6"]
9 |
10 | [sub_resource type="CircleShape2D" id="1"]
11 | radius = 23.2163
12 |
13 | [node name="ArriveDemo" type="Node"]
14 | script = ExtResource("3")
15 | linear_speed_max = 1600.0
16 | linear_acceleration_max = 5000.0
17 | arrival_tolerance = 35.0
18 | deceleration_radius = 180.0
19 |
20 | [node name="BackgroundLayer" parent="." instance=ExtResource("4")]
21 |
22 | [node name="TargetDrawer" type="Node2D" parent="."]
23 | script = ExtResource("5")
24 |
25 | [node name="Arriver" type="CharacterBody2D" parent="."]
26 | show_behind_parent = true
27 | position = Vector2(640, 360)
28 | script = ExtResource("1")
29 |
30 | [node name="CollisionShape2D" type="CollisionShape2D" parent="Arriver"]
31 | shape = SubResource("1")
32 | script = ExtResource("6")
33 | inner_color = Color(0.235294, 0.639216, 0.439216, 1)
34 | outer_color = Color(0.560784, 0.870588, 0.364706, 1)
35 | stroke = 6.0
36 |
37 | [node name="DemoInterface" parent="." instance=ExtResource("2")]
38 | text_bbcode = "Arrive Demo
39 | Mouse click to make the [color=lime]green \"Player\"[/color] move to the [color=fuchsia]purple target[/color]"
40 |
--------------------------------------------------------------------------------
/godot/Demos/Arrive/Arriver.gd:
--------------------------------------------------------------------------------
1 | extends CharacterBody2D
2 |
3 | var agent := await GSAICharacterBody2DAgent.new(self)
4 | var target := GSAIAgentLocation.new()
5 | var arrive := GSAIArrive.new(agent, target)
6 | var _accel := GSAITargetAcceleration.new()
7 |
8 | var _velocity := Vector2()
9 | var _drag := 0.1
10 |
11 | func _physics_process(delta: float) -> void:
12 | arrive.calculate_steering(_accel)
13 | agent._apply_steering(_accel, delta)
14 |
15 |
16 | func setup(
17 | linear_speed_max: float,
18 | linear_acceleration_max: float,
19 | arrival_tolerance: float,
20 | deceleration_radius: float
21 | ) -> void:
22 | agent.linear_speed_max = linear_speed_max
23 | agent.linear_acceleration_max = linear_acceleration_max
24 | agent.linear_drag_percentage = _drag
25 | arrive.deceleration_radius = deceleration_radius
26 | arrive.arrival_tolerance = arrival_tolerance
27 | target.position = agent.position
28 |
--------------------------------------------------------------------------------
/godot/Demos/Arrive/TargetDrawer.gd:
--------------------------------------------------------------------------------
1 | extends Node2D
2 |
3 | const COLORS := {
4 | deceleration_radius = Color(1.0, 0.419, 0.592, 0.5),
5 | arrival_tolerance = Color(0.278, 0.231, 0.47, 0.3)
6 | }
7 |
8 | var arriver: Node2D
9 |
10 |
11 | func _ready() -> void:
12 | await owner.ready
13 | arriver = owner.arriver
14 |
15 |
16 | func _draw():
17 | var target_position := GSAIUtils.to_vector2(arriver.target.position)
18 | draw_circle(target_position, owner.deceleration_radius, COLORS.deceleration_radius)
19 | draw_circle(target_position, owner.arrival_tolerance, COLORS.arrival_tolerance)
20 |
21 |
--------------------------------------------------------------------------------
/godot/Demos/Arrive3d/Arrive3dDemo.tscn:
--------------------------------------------------------------------------------
1 | [gd_scene load_steps=18 format=3 uid="uid://ivd5evryupnn"]
2 |
3 | [ext_resource type="PackedScene" uid="uid://b1iy8iir6k8tr" path="res://Demos/Utils/DemoInterface.tscn" id="1"]
4 | [ext_resource type="Script" path="res://Demos/Arrive3d/Camera.gd" id="2"]
5 | [ext_resource type="Script" path="res://Demos/Arrive3d/Seek3dDemo.gd" id="3"]
6 | [ext_resource type="Script" path="res://Demos/Arrive3d/Seeker.gd" id="4"]
7 | [ext_resource type="Material" path="res://Demos/Arrive3d/SeekerMat.tres" id="5"]
8 | [ext_resource type="Material" uid="uid://mf2wt4b6p2nq" path="res://Demos/Arrive3d/PadMat.tres" id="5_gbb8u"]
9 | [ext_resource type="Material" uid="uid://bqm7h6xksp1f5" path="res://Demos/Arrive3d/WorldMat.tres" id="6_p2gy2"]
10 |
11 | [sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_6j8j1"]
12 | sky_horizon_color = Color(0.64625, 0.65575, 0.67075, 1)
13 | ground_horizon_color = Color(0.64625, 0.65575, 0.67075, 1)
14 |
15 | [sub_resource type="Sky" id="Sky_5eou5"]
16 | sky_material = SubResource("ProceduralSkyMaterial_6j8j1")
17 |
18 | [sub_resource type="Environment" id="Environment_uyrx5"]
19 | background_mode = 2
20 | sky = SubResource("Sky_5eou5")
21 | tonemap_mode = 2
22 | glow_enabled = true
23 |
24 | [sub_resource type="CapsuleShape3D" id="1"]
25 | height = 1.0
26 |
27 | [sub_resource type="CapsuleMesh" id="2"]
28 | material = ExtResource("5")
29 | radius = 1.0
30 | height = 3.0
31 |
32 | [sub_resource type="BoxMesh" id="3"]
33 | material = ExtResource("5")
34 | size = Vector3(0.5, 0.5, 1)
35 |
36 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_wnjpm"]
37 | albedo_color = Color(0.945098, 0.85098, 0.0745098, 1)
38 |
39 | [sub_resource type="CylinderMesh" id="4"]
40 | material = SubResource("StandardMaterial3D_wnjpm")
41 | top_radius = 2.0
42 | bottom_radius = 2.0
43 | height = 0.2
44 |
45 | [sub_resource type="BoxShape3D" id="6"]
46 | size = Vector3(1000, 0.1, 1000)
47 |
48 | [sub_resource type="PlaneMesh" id="7"]
49 | size = Vector2(250, 250)
50 |
51 | [node name="Arrive3dDemo" type="Node"]
52 | script = ExtResource("3")
53 | linear_speed_max = 50.0
54 | linear_acceleration_max = 53.2
55 | deceleration_radius = 10.8
56 | angular_speed_max = 550
57 | angular_accel_max = 910
58 |
59 | [node name="WorldEnvironment" type="WorldEnvironment" parent="."]
60 | environment = SubResource("Environment_uyrx5")
61 |
62 | [node name="Arriver" type="CharacterBody3D" parent="."]
63 | script = ExtResource("4")
64 |
65 | [node name="CollisionShape3D" type="CollisionShape3D" parent="Arriver"]
66 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.5, 0)
67 | shape = SubResource("1")
68 |
69 | [node name="Capsule" type="MeshInstance3D" parent="Arriver"]
70 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.5, 0)
71 | mesh = SubResource("2")
72 |
73 | [node name="Nose" type="MeshInstance3D" parent="Arriver"]
74 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2, 1.25)
75 | mesh = SubResource("3")
76 |
77 | [node name="Camera3D" type="Camera3D" parent="."]
78 | transform = Transform3D(0.989952, 0.0720094, -0.121693, 0.0339305, 0.714503, 0.69881, 0.137271, -0.695917, 0.70488, -7.68317, 14.1265, 25.616)
79 | current = true
80 | script = ExtResource("2")
81 |
82 | [node name="RayCast3D" type="RayCast3D" parent="Camera3D"]
83 | target_position = Vector3(-627, 200, -777)
84 | collision_mask = 2
85 |
86 | [node name="MouseTarget" type="Node3D" parent="."]
87 | transform = Transform3D(1, 0, 7.45058e-09, 0, 1, 0, 7.45058e-09, 0, 1, -4.76837e-07, 9.53674e-07, 1.90735e-06)
88 |
89 | [node name="MeshInstance3D" type="MeshInstance3D" parent="MouseTarget"]
90 | material_override = ExtResource("5_gbb8u")
91 | mesh = SubResource("4")
92 |
93 | [node name="StaticBody3D" type="StaticBody3D" parent="."]
94 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.1, 0)
95 | collision_layer = 2
96 | collision_mask = 2
97 |
98 | [node name="CollisionShape3D" type="CollisionShape3D" parent="StaticBody3D"]
99 | shape = SubResource("6")
100 |
101 | [node name="Ground" type="MeshInstance3D" parent="."]
102 | material_override = ExtResource("6_p2gy2")
103 | mesh = SubResource("7")
104 |
105 | [node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
106 | transform = Transform3D(-0.588165, 0.462179, -0.663666, -0.804031, -0.245728, 0.541436, 0.087159, 0.852061, 0.516134, -17.6076, 12.1748, 0)
107 | shadow_enabled = true
108 |
109 | [node name="DemoInterface" parent="." instance=ExtResource("1")]
110 | mouse_filter = 2
111 | text_bbcode = "3D Arrive Demo
112 | Move the mouse about the field to have the agent turn towards and smoothly arrive at the target marker."
113 |
--------------------------------------------------------------------------------
/godot/Demos/Arrive3d/Camera.gd:
--------------------------------------------------------------------------------
1 | extends Camera3D
2 |
3 | var target: Node3D
4 |
5 | @onready var ray : RayCast3D = $RayCast3D
6 |
7 |
8 | func _unhandled_input(event: InputEvent) -> void:
9 | if event is InputEventMouseMotion:
10 | _set_target_position(event.position)
11 |
12 |
13 | func setup(_target: Node3D) -> void:
14 | self.target = _target
15 | _set_target_position(get_viewport().get_mouse_position())
16 |
17 |
18 | func _set_target_position(pos: Vector2) -> void:
19 | var to = project_local_ray_normal(pos) * 10000
20 | ray.target_position = to
21 | ray.force_raycast_update()
22 | if ray.is_colliding():
23 | var point = ray.get_collision_point()
24 | target.global_position = point
25 |
--------------------------------------------------------------------------------
/godot/Demos/Arrive3d/PadMat.tres:
--------------------------------------------------------------------------------
1 | [gd_resource type="StandardMaterial3D" format=3 uid="uid://mf2wt4b6p2nq"]
2 |
3 | [resource]
4 | albedo_color = Color(0.945098, 0.85098, 0.0745098, 1)
5 |
--------------------------------------------------------------------------------
/godot/Demos/Arrive3d/Seek3dDemo.gd:
--------------------------------------------------------------------------------
1 | extends Node
2 |
3 | @export_range(0, 100, 5) var linear_speed_max := 10.0: set = set_linear_speed_max
4 | @export_range(0, 100, 0.1) var linear_acceleration_max := 1.0: set = set_linear_acceleration_max
5 | @export_range(0, 50, 0.1) var arrival_tolerance := 0.5: set = set_arrival_tolerance
6 | @export_range(0, 50, 0.1) var deceleration_radius := 5.0: set = set_deceleration_radius
7 | @export_range(0, 1080, 10) var angular_speed_max := 270: set = set_angular_speed_max
8 | @export_range(0, 2048, 10) var angular_accel_max := 45: set = set_angular_accel_max
9 | @export_range(0, 178, 2) var align_tolerance := 5: set = set_align_tolerance
10 | @export_range(0, 180, 2) var angular_deceleration_radius := 45: set = set_angular_deceleration_radius
11 |
12 | @onready var target := $MouseTarget
13 | @onready var arriver := $Arriver
14 |
15 |
16 | func _ready() -> void:
17 | get_tree().root.content_scale_mode = Window.CONTENT_SCALE_MODE_CANVAS_ITEMS
18 | get_tree().root.content_scale_aspect = Window.CONTENT_SCALE_ASPECT_EXPAND
19 | arriver.setup(
20 | deg_to_rad(align_tolerance),
21 | deg_to_rad(angular_deceleration_radius),
22 | deg_to_rad(angular_accel_max),
23 | deg_to_rad(angular_speed_max),
24 | deceleration_radius,
25 | arrival_tolerance,
26 | linear_acceleration_max,
27 | linear_speed_max,
28 | target
29 | )
30 | $Camera3D.setup(target)
31 |
32 |
33 | func set_align_tolerance(value: int) -> void:
34 | align_tolerance = value
35 | if not is_inside_tree():
36 | return
37 |
38 | arriver.face.alignment_tolerance = deg_to_rad(value)
39 |
40 |
41 | func set_angular_deceleration_radius(value: int) -> void:
42 | deceleration_radius = value
43 | if not is_inside_tree():
44 | return
45 |
46 | arriver.face.deceleration_radius = deg_to_rad(value)
47 |
48 |
49 | func set_angular_accel_max(value: int) -> void:
50 | angular_accel_max = value
51 | if not is_inside_tree():
52 | return
53 |
54 | arriver.agent.angular_acceleration_max = deg_to_rad(value)
55 |
56 |
57 | func set_angular_speed_max(value: int) -> void:
58 | angular_speed_max = value
59 | if not is_inside_tree():
60 | return
61 |
62 | arriver.agent.angular_speed_max = deg_to_rad(value)
63 |
64 |
65 | func set_arrival_tolerance(value: float) -> void:
66 | arrival_tolerance = value
67 | if not is_inside_tree():
68 | return
69 |
70 | arriver.arrive.arrival_tolerance = value
71 |
72 |
73 | func set_deceleration_radius(value: float) -> void:
74 | deceleration_radius = value
75 | if not is_inside_tree():
76 | return
77 |
78 | arriver.arrive.deceleration_radius = value
79 |
80 |
81 | func set_linear_speed_max(value: float) -> void:
82 | linear_speed_max = value
83 | if not is_inside_tree():
84 | return
85 |
86 | arriver.agent.linear_speed_max = value
87 |
88 |
89 | func set_linear_acceleration_max(value: float) -> void:
90 | linear_acceleration_max = value
91 | if not is_inside_tree():
92 | return
93 |
94 | arriver.agent.linear_acceleration_max = value
95 |
--------------------------------------------------------------------------------
/godot/Demos/Arrive3d/Seeker.gd:
--------------------------------------------------------------------------------
1 | extends CharacterBody3D
2 |
3 | var target_node: Node3D
4 |
5 | @onready var agent := await GSAICharacterBody3DAgent.new(self)
6 | @onready var target := GSAIAgentLocation.new()
7 | @onready var accel := GSAITargetAcceleration.new()
8 | @onready var blend := GSAIBlend.new(agent)
9 | @onready var face := GSAIFace.new(agent, target, true)
10 | @onready var arrive := GSAIArrive.new(agent, target)
11 |
12 |
13 | func _physics_process(delta: float) -> void:
14 | target.position = target_node.transform.origin
15 | target.position.y = transform.origin.y
16 | blend.calculate_steering(accel)
17 | agent._apply_steering(accel, delta)
18 |
19 |
20 | func setup(
21 | align_tolerance: float,
22 | angular_deceleration_radius: float,
23 | angular_accel_max: float,
24 | angular_speed_max: float,
25 | deceleration_radius: float,
26 | arrival_tolerance: float,
27 | linear_acceleration_max: float,
28 | linear_speed_max: float,
29 | _target: Node3D
30 | ) -> void:
31 | agent.linear_speed_max = linear_speed_max
32 | agent.linear_acceleration_max = linear_acceleration_max
33 | agent.linear_drag_percentage = 0.05
34 | agent.angular_acceleration_max = angular_accel_max
35 | agent.angular_speed_max = angular_speed_max
36 | agent.angular_drag_percentage = 0.1
37 |
38 | arrive.arrival_tolerance = arrival_tolerance
39 | arrive.deceleration_radius = deceleration_radius
40 |
41 | face.alignment_tolerance = align_tolerance
42 | face.deceleration_radius = angular_deceleration_radius
43 |
44 | target_node = _target
45 | self.target.position = target_node.transform.origin
46 | blend.add(arrive, 1)
47 | blend.add(face, 1)
48 |
--------------------------------------------------------------------------------
/godot/Demos/Arrive3d/SeekerMat.tres:
--------------------------------------------------------------------------------
1 | [gd_resource type="StandardMaterial3D" format=2]
2 |
3 | [resource]
4 | albedo_color = Color( 0.152941, 0.764706, 0.247059, 1 )
5 |
--------------------------------------------------------------------------------
/godot/Demos/Arrive3d/WorldMat.tres:
--------------------------------------------------------------------------------
1 | [gd_resource type="StandardMaterial3D" format=3 uid="uid://bqm7h6xksp1f5"]
2 |
3 | [resource]
4 | albedo_color = Color(0.0941176, 0.235294, 0.486275, 1)
5 |
--------------------------------------------------------------------------------
/godot/Demos/AvoidCollisions/AvoidCollisionsDemo.gd:
--------------------------------------------------------------------------------
1 | extends Node
2 |
3 | @export_range(0, 1000, 40) var linear_speed_max := 350.0: set = set_linear_speed_max
4 | @export_range(0, 4000, 2) var linear_acceleration_max := 40.0: set = set_linear_accel_max
5 | @export_range(0, 500, 10) var proximity_radius := 140.0: set = set_proximity_radius
6 | @export var draw_proximity := true: set = set_draw_proximity
7 |
8 | @onready var spawner := $Spawner
9 |
10 |
11 | func _ready():
12 | get_tree().root.content_scale_mode = Window.CONTENT_SCALE_MODE_CANVAS_ITEMS
13 | get_tree().root.content_scale_aspect = Window.CONTENT_SCALE_ASPECT_EXPAND
14 |
15 | func set_linear_speed_max(value: float) -> void:
16 | linear_speed_max = value
17 | if not is_inside_tree():
18 | return
19 |
20 | spawner.set_linear_speed_max(value)
21 |
22 |
23 | func set_linear_accel_max(value: float) -> void:
24 | linear_acceleration_max = value
25 | if not is_inside_tree():
26 | return
27 |
28 | spawner.set_linear_accel_max(value)
29 |
30 |
31 | func set_proximity_radius(value: float) -> void:
32 | proximity_radius = value
33 | if not is_inside_tree():
34 | return
35 |
36 | spawner.set_proximity_radius(value)
37 |
38 |
39 | func set_draw_proximity(value: bool) -> void:
40 | draw_proximity = value
41 | if not is_inside_tree():
42 | return
43 |
44 | spawner.set_draw_proximity(value)
45 |
--------------------------------------------------------------------------------
/godot/Demos/AvoidCollisions/AvoidCollisionsDemo.tscn:
--------------------------------------------------------------------------------
1 | [gd_scene load_steps=6 format=3 uid="uid://1aothxx5rbm6"]
2 |
3 | [ext_resource type="Script" path="res://Demos/AvoidCollisions/Spawner.gd" id="1"]
4 | [ext_resource type="Script" path="res://Demos/AvoidCollisions/AvoidCollisionsDemo.gd" id="2"]
5 | [ext_resource type="PackedScene" path="res://Demos/AvoidCollisions/Avoider.tscn" id="3"]
6 | [ext_resource type="PackedScene" uid="uid://b1iy8iir6k8tr" path="res://Demos/Utils/DemoInterface.tscn" id="4"]
7 | [ext_resource type="PackedScene" uid="uid://ctke36x8wxrla" path="res://Demos/Utils/BackgroundLayer.tscn" id="5"]
8 |
9 | [node name="AvoidCollisionsDemo" type="Node"]
10 | script = ExtResource("2")
11 | linear_speed_max = 520.0
12 | linear_acceleration_max = 2250.0
13 | proximity_radius = 100.0
14 |
15 | [node name="BackgroudLayer" parent="." instance=ExtResource("5")]
16 |
17 | [node name="Spawner" type="Node2D" parent="."]
18 | script = ExtResource("1")
19 | avoider_template = ExtResource("3")
20 | inner_color = Color(0.235294, 0.639216, 0.439216, 1)
21 | outer_color = Color(0.560784, 0.870588, 0.364706, 1)
22 | agent_count = 80
23 |
24 | [node name="DemoInterface" parent="." instance=ExtResource("4")]
25 | text_bbcode = "Avoid Collisions Demo
26 | Watch each agent try to keep traveling in a particular direction, but prioritize avoiding collisions with other agents."
27 |
--------------------------------------------------------------------------------
/godot/Demos/AvoidCollisions/Avoider.gd:
--------------------------------------------------------------------------------
1 | extends CharacterBody2D
2 |
3 | var draw_proximity: bool
4 |
5 | var _boundary_right: float
6 | var _boundary_bottom: float
7 | var _radius: float
8 | var _accel := GSAITargetAcceleration.new()
9 | var _velocity := Vector2.ZERO
10 | var _direction := Vector2()
11 | var _drag := 0.1
12 | var _color := Color(0.4, 1.0, 0.89, 0.3)
13 |
14 | @onready var collision := $CollisionShape2D
15 | @onready var agent := await GSAICharacterBody2DAgent.new(self)
16 | @onready var proximity := GSAIRadiusProximity.new(agent, [], 140)
17 | @onready var avoid := GSAIAvoidCollisions.new(agent, proximity)
18 | @onready var target := GSAIAgentLocation.new()
19 | @onready var seek := GSAISeek.new(agent, target)
20 | @onready var priority := GSAIPriority.new(agent, 0.0001)
21 |
22 |
23 | func _draw() -> void:
24 | if draw_proximity:
25 | draw_circle(Vector2.ZERO, proximity.radius, _color)
26 |
27 |
28 | func _physics_process(delta: float) -> void:
29 | target.position.x = agent.position.x + _direction.x * _radius
30 | target.position.y = agent.position.y + _direction.y * _radius
31 |
32 | priority.calculate_steering(_accel)
33 | agent._apply_steering(_accel, delta)
34 |
35 |
36 | func setup(
37 | linear_speed_max: float,
38 | linear_accel_max: float,
39 | proximity_radius: float,
40 | boundary_right: float,
41 | boundary_bottom: float,
42 | _draw_proximity: bool,
43 | rng: RandomNumberGenerator
44 | ) -> void:
45 | rng.randomize()
46 | _direction = Vector2(rng.randf_range(-1, 1), rng.randf_range(-1, 1)).normalized()
47 |
48 | agent.linear_speed_max = linear_speed_max
49 | agent.linear_acceleration_max = linear_accel_max
50 |
51 | proximity.radius = proximity_radius
52 | _boundary_bottom = boundary_bottom
53 | _boundary_right = boundary_right
54 |
55 | _radius = collision.shape.radius
56 | agent.bounding_radius = _radius
57 |
58 | agent.linear_drag_percentage = _drag
59 |
60 | self.draw_proximity = _draw_proximity
61 |
62 | priority.add(avoid)
63 | priority.add(seek)
64 |
65 |
66 | func set_proximity_agents(agents: Array) -> void:
67 | proximity.agents = agents
68 |
69 |
70 | func set_random_nonoverlapping_position(others: Array, distance_from_boundary_min: float) -> void:
71 | var rng := RandomNumberGenerator.new()
72 | rng.randomize()
73 | var tries_max : int = max(100, others.size() * others.size())
74 | while tries_max > 0:
75 | tries_max -= 1
76 | global_position.x = rng.randf_range(
77 | distance_from_boundary_min, _boundary_right - distance_from_boundary_min
78 | )
79 | global_position.y = rng.randf_range(
80 | distance_from_boundary_min, _boundary_bottom - distance_from_boundary_min
81 | )
82 | var done := true
83 | for i in range(others.size()):
84 | var other: Node2D = others[i]
85 | if (
86 | other.global_position.distance_to(position)
87 | <= _radius * 2 + distance_from_boundary_min
88 | ):
89 | done = false
90 | if done:
91 | break
92 |
--------------------------------------------------------------------------------
/godot/Demos/AvoidCollisions/Avoider.tscn:
--------------------------------------------------------------------------------
1 | [gd_scene load_steps=4 format=2]
2 |
3 | [ext_resource path="res://Demos/Utils/CircleDraw.gd" type="Script" id=1]
4 | [ext_resource path="res://Demos/AvoidCollisions/Avoider.gd" type="Script" id=2]
5 |
6 | [sub_resource type="CircleShape2D" id=1]
7 | radius = 21.3503
8 |
9 | [node name="Avoider" type="CharacterBody2D"]
10 | script = ExtResource( 2 )
11 |
12 | [node name="CollisionShape2D" type="CollisionShape2D" parent="."]
13 | shape = SubResource( 1 )
14 | script = ExtResource( 1 )
15 | inner_color = Color( 0.890196, 0.411765, 0.337255, 1 )
16 | outer_color = Color( 1, 0.709804, 0.439216, 1 )
17 | stroke = 5.0
18 |
--------------------------------------------------------------------------------
/godot/Demos/AvoidCollisions/Spawner.gd:
--------------------------------------------------------------------------------
1 | extends Node2D
2 |
3 | @export var avoider_template: PackedScene
4 | @export var inner_color := Color()
5 | @export var outer_color := Color()
6 | @export var agent_count := 60
7 |
8 | var boundaries: Vector2
9 |
10 |
11 | func _ready() -> void:
12 | boundaries = Vector2(
13 | ProjectSettings["display/window/size/viewport_width"], ProjectSettings["display/window/size/viewport_height"]
14 | )
15 | var rng := RandomNumberGenerator.new()
16 | var avoiders := []
17 | var avoider_agents := []
18 | for i in range(agent_count):
19 | var avoider := avoider_template.instantiate()
20 | add_child(avoider)
21 | avoider.setup(
22 | owner.linear_speed_max,
23 | owner.linear_acceleration_max,
24 | owner.proximity_radius,
25 | boundaries.x,
26 | boundaries.y,
27 | true if i == 0 and owner.draw_proximity else false,
28 | rng
29 | )
30 | avoider_agents.append(avoider.agent)
31 | avoider.set_random_nonoverlapping_position(avoiders, 16)
32 | if i == 0:
33 | avoider.collision.inner_color = inner_color
34 | avoider.collision.outer_color = outer_color
35 | avoiders.append(avoider)
36 | if i % 10 == 0:
37 | await get_tree().process_frame
38 | for child in get_children():
39 | child.set_proximity_agents(avoider_agents)
40 |
41 |
42 | func _physics_process(_delta: float) -> void:
43 | for child in get_children():
44 | child.global_position = child.global_position.posmodv(boundaries)
45 |
46 |
47 | func set_linear_speed_max(value: float) -> void:
48 | for child in get_children():
49 | child.agent.linear_speed_max = value
50 |
51 |
52 | func set_linear_accel_max(value: float) -> void:
53 | for child in get_children():
54 | child.agent.linear_acceleration_max = value
55 |
56 |
57 | func set_proximity_radius(value: float) -> void:
58 | for child in get_children():
59 | child.proximity.radius = value
60 | get_child(0).update()
61 |
62 |
63 | func set_draw_proximity(value: bool) -> void:
64 | var child := get_child(0)
65 | child.draw_proximity = value
66 | child.update()
67 |
--------------------------------------------------------------------------------
/godot/Demos/DemoPickerUI.gd:
--------------------------------------------------------------------------------
1 | class_name DemoPickerUI
2 | extends Control
3 |
4 | # warning-ignore:unused_signal
5 | signal demo_requested
6 |
7 | var demo_path := "": set = set_demo_path
8 |
9 | @onready var list: ItemList = $VBoxContainer/ItemList
10 | @onready var button: Button = $VBoxContainer/Button
11 |
12 |
13 | func _ready() -> void:
14 | # warning-ignore:return_value_discarded
15 | list.connect("demo_selected", Callable(self, "set_demo_path"))
16 | # warning-ignore:return_value_discarded
17 | list.connect("item_activated", Callable(self, "_on_ItemList_item_activated"))
18 | # warning-ignore:return_value_discarded
19 | button.connect("pressed", Callable(self, "emit_signal").bind("demo_requested"))
20 | demo_path = list.file_paths[0]
21 |
22 |
23 | func set_demo_path(value: String) -> void:
24 | demo_path = value
25 |
26 |
27 | func _on_ItemList_item_activated(_index: int) -> void:
28 | emit_signal("demo_requested")
29 |
--------------------------------------------------------------------------------
/godot/Demos/DemoPlayer.gd:
--------------------------------------------------------------------------------
1 | extends Node2D
2 |
3 |
4 | func load_demo(scene_path: String) -> void:
5 | if not scene_path:
6 | return
7 |
8 | var demo = load(scene_path)
9 | if demo:
10 | add_child(demo.instantiate())
11 |
12 |
13 | func unload() -> void:
14 | for node in get_children():
15 | call_deferred("remove_child", node)
16 | node.queue_free()
17 |
--------------------------------------------------------------------------------
/godot/Demos/DemoSelector.tscn:
--------------------------------------------------------------------------------
1 | [gd_scene load_steps=7 format=3 uid="uid://jgqvqwajjasm"]
2 |
3 | [ext_resource type="Script" path="res://Demos/PopulateItemList.gd" id="1"]
4 | [ext_resource type="Theme" uid="uid://c2l0v31q141pv" path="res://assets/theme/gdquest.theme" id="2"]
5 | [ext_resource type="Texture2D" uid="uid://chqsknldl55hp" path="res://assets/sprites/background.png" id="3"]
6 | [ext_resource type="Script" path="res://Demos/DemoPickerUI.gd" id="4"]
7 | [ext_resource type="Script" path="res://Demos/DemoPlayer.gd" id="5"]
8 | [ext_resource type="Script" path="res://Demos/Demos.gd" id="6"]
9 |
10 | [node name="Demos" type="Node"]
11 | script = ExtResource("6")
12 |
13 | [node name="DemoPlayer" type="Node2D" parent="."]
14 | script = ExtResource("5")
15 |
16 | [node name="DemoPickerUI" type="Control" parent="."]
17 | layout_mode = 3
18 | anchors_preset = 15
19 | anchor_right = 1.0
20 | anchor_bottom = 1.0
21 | grow_horizontal = 2
22 | grow_vertical = 2
23 | theme = ExtResource("2")
24 | script = ExtResource("4")
25 |
26 | [node name="TextureRect" type="TextureRect" parent="DemoPickerUI"]
27 | custom_minimum_size = Vector2(1024, 600)
28 | layout_mode = 0
29 | anchor_right = 1.0
30 | anchor_bottom = 1.0
31 | size_flags_horizontal = 3
32 | size_flags_vertical = 3
33 | texture = ExtResource("3")
34 |
35 | [node name="VBoxContainer" type="VBoxContainer" parent="DemoPickerUI"]
36 | custom_minimum_size = Vector2(682, 0)
37 | layout_mode = 1
38 | anchors_preset = 8
39 | anchor_left = 0.5
40 | anchor_top = 0.5
41 | anchor_right = 0.5
42 | anchor_bottom = 0.5
43 | offset_left = -341.0
44 | offset_top = -290.0
45 | offset_right = 341.0
46 | offset_bottom = 290.0
47 | grow_horizontal = 2
48 | grow_vertical = 2
49 | size_flags_horizontal = 3
50 | size_flags_vertical = 3
51 | alignment = 1
52 |
53 | [node name="ItemList" type="ItemList" parent="DemoPickerUI/VBoxContainer"]
54 | layout_mode = 2
55 | auto_height = true
56 | script = ExtResource("1")
57 |
58 | [node name="Button" type="Button" parent="DemoPickerUI/VBoxContainer"]
59 | custom_minimum_size = Vector2(280, 100)
60 | layout_mode = 2
61 | size_flags_horizontal = 4
62 | size_flags_vertical = 13
63 | text = "Load scene"
64 |
65 | [node name="ButtonGoBack" type="Button" parent="."]
66 | custom_minimum_size = Vector2(280, 100)
67 | anchors_preset = 2
68 | anchor_top = 1.0
69 | anchor_bottom = 1.0
70 | offset_left = 48.0
71 | offset_top = -156.0
72 | offset_right = 328.0
73 | offset_bottom = -56.0
74 | size_flags_horizontal = 4
75 | size_flags_vertical = 13
76 | theme = ExtResource("2")
77 | text = "Go back"
78 |
--------------------------------------------------------------------------------
/godot/Demos/Demos.gd:
--------------------------------------------------------------------------------
1 | extends Node
2 |
3 | @onready var demo_picker: DemoPickerUI = $DemoPickerUI
4 | @onready var demo_player := $DemoPlayer
5 | @onready var button_go_back: Button = $ButtonGoBack
6 |
7 |
8 | func _ready() -> void:
9 | # warning-ignore:return_value_discarded
10 | demo_picker.connect("demo_requested", Callable(self, "_on_DemoPickerUI_demo_requested"))
11 | # warning-ignore:return_value_discarded
12 | button_go_back.connect("pressed", Callable(self, "_on_ButtonGoBack_pressed"))
13 |
14 |
15 | func _input(event: InputEvent) -> void:
16 | if event.is_action_pressed("toggle_fullscreen"):
17 | get_window().mode = Window.MODE_EXCLUSIVE_FULLSCREEN if (not ((get_window().mode == Window.MODE_EXCLUSIVE_FULLSCREEN) or (get_window().mode == Window.MODE_FULLSCREEN))) else Window.MODE_WINDOWED
18 | get_viewport().set_input_as_handled()
19 |
20 |
21 | func _on_DemoPickerUI_demo_requested() -> void:
22 | demo_player.load_demo(demo_picker.demo_path)
23 | demo_picker.hide()
24 | button_go_back.show()
25 |
26 |
27 | func _on_ButtonGoBack_pressed() -> void:
28 | demo_player.unload()
29 | button_go_back.hide()
30 | demo_picker.show()
31 |
--------------------------------------------------------------------------------
/godot/Demos/Face/FaceDemo.gd:
--------------------------------------------------------------------------------
1 | extends Node
2 |
3 | @export_range(0, 1080, 2) var angular_speed_max := 120: set = set_angular_speed_max
4 | @export_range(0, 2048, 2) var angular_accel_max := 10: set = set_angular_accel_max
5 | @export_range(0, 180, 2) var align_tolerance := 5: set = set_align_tolerance
6 | @export_range(0, 359, 2) var deceleration_radius := 45: set = set_deceleration_radius
7 | @export_range(0, 1000, 40) var player_speed := 600.0: set = set_player_speed
8 |
9 | @onready var player := $Player
10 | @onready var turret := $Turret
11 |
12 |
13 | func _ready() -> void:
14 | get_tree().root.content_scale_mode = Window.CONTENT_SCALE_MODE_CANVAS_ITEMS
15 | get_tree().root.content_scale_aspect = Window.CONTENT_SCALE_ASPECT_EXPAND
16 |
17 | player.speed = player_speed
18 | turret.setup(
19 | player.agent,
20 | deg_to_rad(align_tolerance),
21 | deg_to_rad(deceleration_radius),
22 | deg_to_rad(angular_accel_max),
23 | deg_to_rad(angular_speed_max)
24 | )
25 |
26 |
27 | func set_align_tolerance(value: int) -> void:
28 | align_tolerance = value
29 | if not is_inside_tree():
30 | return
31 |
32 | turret.face.alignment_tolerance = deg_to_rad(value)
33 |
34 |
35 | func set_deceleration_radius(value: int) -> void:
36 | deceleration_radius = value
37 | if not is_inside_tree():
38 | return
39 |
40 | turret.face.deceleration_radius = deg_to_rad(value)
41 |
42 |
43 | func set_angular_accel_max(value: int) -> void:
44 | angular_accel_max = value
45 | if not is_inside_tree():
46 | return
47 |
48 | turret.agent.angular_acceleration_max = deg_to_rad(value)
49 |
50 |
51 | func set_angular_speed_max(value: int) -> void:
52 | angular_speed_max = value
53 | if not is_inside_tree():
54 | return
55 |
56 | turret.agent.angular_speed_max = deg_to_rad(value)
57 |
58 |
59 | func set_player_speed(value: float) -> void:
60 | player_speed = value
61 | if not is_inside_tree():
62 | return
63 |
64 | player.speed = player_speed
65 |
--------------------------------------------------------------------------------
/godot/Demos/Face/FaceDemo.tscn:
--------------------------------------------------------------------------------
1 | [gd_scene load_steps=9 format=3 uid="uid://da11eth7mspym"]
2 |
3 | [ext_resource type="Script" path="res://Demos/Face/Turret.gd" id="1"]
4 | [ext_resource type="Script" path="res://Demos/Face/FaceDemo.gd" id="2"]
5 | [ext_resource type="Script" path="res://Demos/Face/Player.gd" id="3"]
6 | [ext_resource type="PackedScene" uid="uid://b1iy8iir6k8tr" path="res://Demos/Utils/DemoInterface.tscn" id="4"]
7 | [ext_resource type="PackedScene" uid="uid://ctke36x8wxrla" path="res://Demos/Utils/BackgroundLayer.tscn" id="5"]
8 | [ext_resource type="Script" path="res://Demos/Utils/CircleDraw.gd" id="8"]
9 |
10 | [sub_resource type="CircleShape2D" id="1"]
11 | radius = 20.2633
12 |
13 | [sub_resource type="CircleShape2D" id="2"]
14 | radius = 37.1052
15 |
16 | [node name="FaceDemo" type="Node"]
17 | script = ExtResource("2")
18 | angular_speed_max = 662
19 | angular_accel_max = 924
20 | deceleration_radius = 136
21 |
22 | [node name="BackgroudLayer" parent="." instance=ExtResource("5")]
23 |
24 | [node name="Player" type="CharacterBody2D" parent="."]
25 | position = Vector2(687.363, 351.005)
26 | script = ExtResource("3")
27 |
28 | [node name="CollisionShape2D" type="CollisionShape2D" parent="Player"]
29 | shape = SubResource("1")
30 | script = ExtResource("8")
31 | inner_color = Color(0.235294, 0.639216, 0.439216, 1)
32 | outer_color = Color(0.560784, 0.870588, 0.364706, 1)
33 | stroke = 6.0
34 |
35 | [node name="Turret" type="CharacterBody2D" parent="."]
36 | position = Vector2(984.348, 571.959)
37 | script = ExtResource("1")
38 |
39 | [node name="CollisionShape2D" type="CollisionShape2D" parent="Turret"]
40 | shape = SubResource("2")
41 | script = ExtResource("8")
42 | inner_color = Color(0.890196, 0.411765, 0.337255, 1)
43 | outer_color = Color(1, 0.709804, 0.439216, 1)
44 | stroke = 8.0
45 |
46 | [node name="DemoInterface" parent="." instance=ExtResource("4")]
47 | text_bbcode = "Face Demo
48 | Move the [color=lime]green player[/color] around with WASD and notice the [color=#ffb570]orange turret[/color] orient itself"
49 |
--------------------------------------------------------------------------------
/godot/Demos/Face/Player.gd:
--------------------------------------------------------------------------------
1 | extends CharacterBody2D
2 |
3 | var speed: float
4 |
5 | @onready var agent := GSAIAgentLocation.new()
6 |
7 |
8 | func _physics_process(_delta: float) -> void:
9 | var movement := _get_movement()
10 | set_velocity(movement * speed)
11 | move_and_slide()
12 | agent.position = Vector3(global_position.x, global_position.y, 0)
13 |
14 |
15 | func _get_movement() -> Vector2:
16 | return Vector2(
17 | Input.get_action_strength("sf_right") - Input.get_action_strength("sf_left"),
18 | Input.get_action_strength("sf_down") - Input.get_action_strength("sf_up")
19 | )
20 |
--------------------------------------------------------------------------------
/godot/Demos/Face/Turret.gd:
--------------------------------------------------------------------------------
1 | extends CharacterBody2D
2 |
3 | var face: GSAIFace
4 | var agent := await GSAICharacterBody2DAgent.new(self)
5 |
6 | var _accel := GSAITargetAcceleration.new()
7 | var _angular_drag := 0.1
8 | var _cannon: Rect2
9 | var _color: Color
10 |
11 | @onready var collision_shape := $CollisionShape2D
12 |
13 |
14 | func _ready() -> void:
15 | var radius = collision_shape.shape.radius
16 | _cannon = Rect2(Vector2(-5, 0), Vector2(10, -radius * 2))
17 | _color = collision_shape.outer_color
18 |
19 |
20 | func _physics_process(delta: float) -> void:
21 | face.calculate_steering(_accel)
22 | agent._apply_steering(_accel, delta)
23 |
24 |
25 | func _draw() -> void:
26 | draw_rect(_cannon, _color)
27 |
28 |
29 | func setup(
30 | player_agent: GSAIAgentLocation,
31 | align_tolerance: float,
32 | deceleration_radius: float,
33 | angular_accel_max: float,
34 | angular_speed_max: float
35 | ) -> void:
36 | face = GSAIFace.new(agent, player_agent)
37 |
38 | face.alignment_tolerance = align_tolerance
39 | face.deceleration_radius = deceleration_radius
40 |
41 | agent.angular_acceleration_max = angular_accel_max
42 | agent.angular_speed_max = angular_speed_max
43 | agent.angular_drag_percentage = _angular_drag
44 |
--------------------------------------------------------------------------------
/godot/Demos/FollowPath/Drawer.gd:
--------------------------------------------------------------------------------
1 | extends Node2D
2 |
3 | signal path_established(points)
4 |
5 | var active_points := []
6 | var is_drawing := false
7 | var distance_threshold := 100.0
8 |
9 |
10 | func _unhandled_input(event: InputEvent) -> void:
11 | if event is InputEventMouseMotion:
12 | if is_drawing:
13 | active_points.append(event.position)
14 | queue_redraw()
15 | elif event is InputEventMouseButton:
16 | if event.pressed and event.button_index == MOUSE_BUTTON_LEFT:
17 | active_points.clear()
18 | active_points.append(event.position)
19 | is_drawing = true
20 | queue_redraw()
21 | elif not event.pressed:
22 | is_drawing = false
23 | if active_points.size() >= 2:
24 | _simplify()
25 |
26 |
27 | func _draw() -> void:
28 | if is_drawing:
29 | for point in active_points:
30 | draw_circle(point, 2, Color.RED)
31 | else:
32 | if active_points.size() > 0:
33 | draw_circle(active_points.front(), 2, Color.RED)
34 | draw_circle(active_points.back(), 2, Color.YELLOW)
35 | draw_polyline(active_points, Color.SKY_BLUE, 1.0)
36 |
37 |
38 | func _simplify() -> void:
39 | var first: Vector2 = active_points.front()
40 | var last: Vector2 = active_points.back()
41 | var key := first
42 | var simplified_path := [first]
43 | for i in range(1, active_points.size()):
44 | var point: Vector2 = active_points[i]
45 | var distance := point.distance_to(key)
46 | if distance > distance_threshold:
47 | key = point
48 | simplified_path.append(key)
49 | active_points = simplified_path
50 | if active_points.back() != last:
51 | active_points.append(last)
52 | queue_redraw()
53 | emit_signal("path_established", active_points)
54 |
--------------------------------------------------------------------------------
/godot/Demos/FollowPath/FollowPathDemo.gd:
--------------------------------------------------------------------------------
1 | extends Node
2 |
3 | @export_range(0, 2000, 40) var linear_speed_max := 600.0: set = set_linear_speed_max
4 | @export_range(0, 9000, 10.0) var linear_acceleration_max := 40.0: set = set_linear_acceleration_max
5 | @export_range(0, 100, 0.1) var arrival_tolerance := 10.0: set = set_arrival_tolerance
6 | @export_range(0, 500, 10) var deceleration_radius := 100.0: set = set_deceleration_radius
7 | @export_range(0, 5, 0.1) var predict_time := 0.3: set = set_predict_time
8 | @export_range(0, 200, 10.0) var path_offset := 20.0: set = set_path_offset
9 |
10 | @onready var drawer := $Drawer
11 | @onready var follower := $PathFollower
12 |
13 |
14 | func _ready() -> void:
15 | get_tree().root.content_scale_mode = Window.CONTENT_SCALE_MODE_CANVAS_ITEMS
16 | get_tree().root.content_scale_aspect = Window.CONTENT_SCALE_ASPECT_EXPAND
17 |
18 | follower.setup(
19 | path_offset,
20 | predict_time,
21 | linear_acceleration_max,
22 | linear_speed_max,
23 | deceleration_radius,
24 | arrival_tolerance
25 | )
26 |
27 |
28 | func set_linear_speed_max(value: float) -> void:
29 | linear_speed_max = value
30 | if not is_inside_tree():
31 | return
32 |
33 | follower.agent.linear_speed_max = value
34 |
35 |
36 | func set_linear_acceleration_max(value: float) -> void:
37 | linear_acceleration_max = value
38 | if not is_inside_tree():
39 | return
40 |
41 | follower.agent.linear_acceleration_max = value
42 |
43 |
44 | func set_arrival_tolerance(value: float) -> void:
45 | arrival_tolerance = value
46 | if not is_inside_tree():
47 | return
48 |
49 | follower.follow.arrival_tolerance = value
50 |
51 |
52 | func set_deceleration_radius(value: float) -> void:
53 | deceleration_radius = value
54 | if not is_inside_tree():
55 | return
56 |
57 | follower.follow.deceleration_radius = value
58 |
59 |
60 | func set_predict_time(value: float) -> void:
61 | predict_time = value
62 | if not is_inside_tree():
63 | return
64 |
65 | follower.follow.prediction_time = value
66 |
67 |
68 | func set_path_offset(value: float) -> void:
69 | path_offset = value
70 | if not is_inside_tree():
71 | return
72 |
73 | follower.follow.path_offset = value
74 |
--------------------------------------------------------------------------------
/godot/Demos/FollowPath/FollowPathDemo.tscn:
--------------------------------------------------------------------------------
1 | [gd_scene load_steps=8 format=3 uid="uid://c8f672bbiqr2d"]
2 |
3 | [ext_resource type="Script" path="res://Demos/FollowPath/Drawer.gd" id="1"]
4 | [ext_resource type="PackedScene" uid="uid://b1iy8iir6k8tr" path="res://Demos/Utils/DemoInterface.tscn" id="2"]
5 | [ext_resource type="Script" path="res://Demos/FollowPath/PathFollower.gd" id="3"]
6 | [ext_resource type="Script" path="res://Demos/FollowPath/FollowPathDemo.gd" id="4"]
7 | [ext_resource type="PackedScene" uid="uid://ctke36x8wxrla" path="res://Demos/Utils/BackgroundLayer.tscn" id="5"]
8 | [ext_resource type="Script" path="res://Demos/Utils/CircleDraw.gd" id="6"]
9 |
10 | [sub_resource type="CircleShape2D" id="1"]
11 | radius = 24.1954
12 |
13 | [node name="FollowPathDemo" type="Node"]
14 | script = ExtResource("4")
15 | linear_speed_max = 920.0
16 | linear_acceleration_max = 3740.0
17 | deceleration_radius = 200.0
18 |
19 | [node name="BackgroudLayer" parent="." instance=ExtResource("5")]
20 |
21 | [node name="Drawer" type="Node2D" parent="."]
22 | script = ExtResource("1")
23 |
24 | [node name="PathFollower" type="CharacterBody2D" parent="."]
25 | position = Vector2(640, 360)
26 | script = ExtResource("3")
27 |
28 | [node name="CollisionShape2D" type="CollisionShape2D" parent="PathFollower"]
29 | shape = SubResource("1")
30 | script = ExtResource("6")
31 | inner_color = Color(0.235294, 0.639216, 0.439216, 1)
32 | outer_color = Color(0.560784, 0.870588, 0.364706, 1)
33 | stroke = 6.0
34 |
35 | [node name="DemoInterface" parent="." instance=ExtResource("2")]
36 | text_bbcode = "Follow Path3D Demo
37 | Use the mouse to draw a path on screen and watch the [color=lime]green \"Agent\"[/color] follow it to the end."
38 |
--------------------------------------------------------------------------------
/godot/Demos/FollowPath/PathFollower.gd:
--------------------------------------------------------------------------------
1 | extends CharacterBody2D
2 |
3 | var _velocity := Vector2.ZERO
4 | var _accel := GSAITargetAcceleration.new()
5 | var _valid := false
6 | var _drag := 0.1
7 |
8 | @onready var agent := await GSAICharacterBody2DAgent.new(self)
9 | @onready var path := GSAIPath.new(
10 | [
11 | Vector3(global_position.x, global_position.y, 0),
12 | Vector3(global_position.x, global_position.y, 0)
13 | ],
14 | true
15 | )
16 | @onready var follow := GSAIFollowPath.new(agent, path, 0.0, 0)
17 |
18 |
19 | func setup(
20 | path_offset: float,
21 | predict_time: float,
22 | accel_max: float,
23 | speed_max: float,
24 | decel_radius: float,
25 | arrival_tolerance: float
26 | ) -> void:
27 | owner.drawer.connect("path_established", Callable(self, "_on_Drawer_path_established"))
28 | follow.path_offset = path_offset
29 | follow.prediction_time = predict_time
30 | follow.deceleration_radius = decel_radius
31 | follow.arrival_tolerance = arrival_tolerance
32 |
33 | agent.linear_acceleration_max = accel_max
34 | agent.linear_speed_max = speed_max
35 | agent.linear_drag_percentage = _drag
36 |
37 |
38 | func _physics_process(delta: float) -> void:
39 | if _valid:
40 | follow.calculate_steering(_accel)
41 | agent._apply_steering(_accel, delta)
42 |
43 |
44 | func _on_Drawer_path_established(points: Array) -> void:
45 | var positions := PackedVector3Array()
46 | for p in points:
47 | positions.append(Vector3(p.x, p.y, 0))
48 | path.create_path(positions)
49 | _valid = true
50 |
--------------------------------------------------------------------------------
/godot/Demos/GroupBehaviors/GroupBehaviorsDemo.gd:
--------------------------------------------------------------------------------
1 | extends Node
2 |
3 | @onready var spawner := $Spawner
4 |
5 | @export_range(0, 2000, 40.0) var linear_speed_max := 600.0: set = set_linear_speed_max
6 | @export_range(0, 9000, 2.0) var linear_accel_max := 40.0: set = set_linear_accel_max
7 | @export_range(0, 300, 2.0) var proximity_radius := 140.0: set = set_proximity_radius
8 | @export_range(0, 200000, 250) var separation_decay_coefficient := 2000.0: set = set_separation_decay_coef
9 | @export_range(0, 2, 0.1) var cohesion_strength := 0.1: set = set_cohesion_strength
10 | @export_range(0, 10, 0.2) var separation_strength := 1.5: set = set_separation_strength
11 | @export var show_proximity_radius := true: set = set_show_proximity_radius
12 |
13 |
14 | func _ready() -> void:
15 | get_tree().root.content_scale_mode = Window.CONTENT_SCALE_MODE_CANVAS_ITEMS
16 | get_tree().root.content_scale_aspect = Window.CONTENT_SCALE_ASPECT_EXPAND
17 |
18 | spawner.setup(
19 | linear_speed_max,
20 | linear_accel_max,
21 | proximity_radius,
22 | separation_decay_coefficient,
23 | cohesion_strength,
24 | separation_strength,
25 | show_proximity_radius
26 | )
27 |
28 |
29 | func set_linear_speed_max(value: float) -> void:
30 | linear_speed_max = value
31 | if not is_inside_tree():
32 | return
33 |
34 | spawner.set_linear_speed_max(value)
35 |
36 |
37 | func set_linear_accel_max(value: float) -> void:
38 | linear_accel_max = value
39 | if not is_inside_tree():
40 | return
41 |
42 | spawner.set_linear_accel_max(value)
43 |
44 |
45 | func set_proximity_radius(value: float) -> void:
46 | proximity_radius = value
47 | if not is_inside_tree():
48 | return
49 |
50 | spawner.set_proximity_radius(value)
51 |
52 |
53 | func set_show_proximity_radius(value: bool) -> void:
54 | show_proximity_radius = value
55 | if not is_inside_tree():
56 | return
57 |
58 | spawner.set_show_proximity_radius(value)
59 |
60 |
61 | func set_separation_decay_coef(value: float) -> void:
62 | separation_decay_coefficient = value
63 | if not is_inside_tree():
64 | return
65 |
66 | spawner.set_separation_decay_coef(value)
67 |
68 |
69 | func set_cohesion_strength(value: float) -> void:
70 | cohesion_strength = value
71 | if not is_inside_tree():
72 | return
73 |
74 | spawner.set_cohesion_strength(value)
75 |
76 |
77 | func set_separation_strength(value: float) -> void:
78 | separation_strength = value
79 | if not is_inside_tree():
80 | return
81 |
82 | spawner.set_separation_strength(value)
83 |
--------------------------------------------------------------------------------
/godot/Demos/GroupBehaviors/GroupBehaviorsDemo.tscn:
--------------------------------------------------------------------------------
1 | [gd_scene load_steps=6 format=3 uid="uid://4tjcn5vtctiy"]
2 |
3 | [ext_resource type="PackedScene" path="res://Demos/GroupBehaviors/Member.tscn" id="1"]
4 | [ext_resource type="Script" path="res://Demos/GroupBehaviors/Spawner.gd" id="2"]
5 | [ext_resource type="Script" path="res://Demos/GroupBehaviors/GroupBehaviorsDemo.gd" id="3"]
6 | [ext_resource type="PackedScene" uid="uid://b1iy8iir6k8tr" path="res://Demos/Utils/DemoInterface.tscn" id="4"]
7 | [ext_resource type="PackedScene" uid="uid://ctke36x8wxrla" path="res://Demos/Utils/BackgroundLayer.tscn" id="5"]
8 |
9 | [node name="GroupBehaviorsDemo" type="Node"]
10 | script = ExtResource("3")
11 | linear_accel_max = 4234.0
12 | proximity_radius = 158.0
13 | separation_decay_coefficient = 121500.0
14 | cohesion_strength = 0.2
15 | separation_strength = 8.8
16 |
17 | [node name="BackgroudLayer" parent="." instance=ExtResource("5")]
18 |
19 | [node name="Spawner" type="Node2D" parent="."]
20 | position = Vector2(640, 360)
21 | script = ExtResource("2")
22 | member = ExtResource("1")
23 |
24 | [node name="DemoInterface" parent="." instance=ExtResource("4")]
25 | mouse_filter = 2
26 | text_bbcode = "Group Behavior Demo
27 | Each of the \"Agents\" are both attempting to stay separated from each other but within reach of their nearest group's center of mass.
28 | Click on agent to see it's proximity."
29 |
--------------------------------------------------------------------------------
/godot/Demos/GroupBehaviors/Member.gd:
--------------------------------------------------------------------------------
1 | extends CharacterBody2D
2 |
3 | var separation: GSAISeparation
4 | var cohesion: GSAICohesion
5 | var proximity: GSAIRadiusProximity
6 | var agent := await GSAICharacterBody2DAgent.new(self)
7 | var blend := GSAIBlend.new(agent)
8 | var acceleration := GSAITargetAcceleration.new()
9 | var draw_proximity := false
10 |
11 | var _color := Color.RED
12 | var _velocity := Vector2()
13 |
14 | @onready var collision_shape := $CollisionShape2D
15 |
16 |
17 | func setup(
18 | linear_speed_max: float,
19 | linear_accel_max: float,
20 | proximity_radius: float,
21 | separation_decay_coefficient: float,
22 | cohesion_strength: float,
23 | separation_strength: float
24 | ) -> void:
25 | _color = Color(randf_range(0.5, 1), randf_range(0.25, 1), randf_range(0, 1))
26 | collision_shape.inner_color = _color
27 |
28 | agent.linear_acceleration_max = linear_accel_max
29 | agent.linear_speed_max = linear_speed_max
30 | agent.linear_drag_percentage = 0.1
31 |
32 | proximity = GSAIRadiusProximity.new(agent, [], proximity_radius)
33 | separation = GSAISeparation.new(agent, proximity)
34 | separation.decay_coefficient = separation_decay_coefficient
35 | cohesion = GSAICohesion.new(agent, proximity)
36 | blend.add(separation, separation_strength)
37 | blend.add(cohesion, cohesion_strength)
38 |
39 |
40 | func _draw() -> void:
41 | if draw_proximity:
42 | draw_circle(Vector2.ZERO, proximity.radius, Color(0.4, 1.0, 0.89, 0.3))
43 |
44 |
45 | func _physics_process(delta: float) -> void:
46 | if blend:
47 | blend.calculate_steering(acceleration)
48 | agent._apply_steering(acceleration, delta)
49 |
50 |
51 | func set_neighbors(neighbor: Array) -> void:
52 | proximity.agents = neighbor
53 |
--------------------------------------------------------------------------------
/godot/Demos/GroupBehaviors/Member.tscn:
--------------------------------------------------------------------------------
1 | [gd_scene load_steps=4 format=2]
2 |
3 | [ext_resource path="res://Demos/GroupBehaviors/Member.gd" type="Script" id=1]
4 | [ext_resource path="res://Demos/Utils/CircleDraw.gd" type="Script" id=3]
5 |
6 |
7 | [sub_resource type="CircleShape2D" id=1]
8 | radius = 16.0
9 |
10 | [node name="Member" type="CharacterBody2D"]
11 | input_pickable = true
12 | script = ExtResource( 1 )
13 |
14 | [node name="CollisionShape2D" type="CollisionShape2D" parent="."]
15 | shape = SubResource( 1 )
16 | script = ExtResource( 3 )
17 | outer_color = Color( 0.301961, 0.65098, 1, 1 )
18 | stroke = 4.0
19 |
--------------------------------------------------------------------------------
/godot/Demos/GroupBehaviors/Spawner.gd:
--------------------------------------------------------------------------------
1 | extends Node2D
2 |
3 | @export var member: PackedScene
4 |
5 |
6 | func follower_input_event(
7 | viewport: Node,
8 | event: InputEvent,
9 | shape_idx: int,
10 | follower: CharacterBody2D
11 | ) -> void:
12 | if event.is_action_pressed("click"):
13 | for other in get_children():
14 | if other.draw_proximity:
15 | other.draw_proximity = false
16 | other.queue_redraw()
17 | follower.draw_proximity = true
18 | follower.queue_redraw()
19 | move_child(follower, get_child_count())
20 |
21 |
22 | func setup(
23 | linear_speed_max: float,
24 | linear_accel_max: float,
25 | proximity_radius: float,
26 | separation_decay_coefficient: float,
27 | cohesion_strength: float,
28 | separation_strength: float,
29 | show_proximity_radius: bool
30 | ) -> void:
31 | var followers := []
32 | for i in range(19):
33 | var follower : CharacterBody2D = member.instantiate()
34 | add_child(follower)
35 | follower.position += Vector2(randf_range(-60, 60), randf_range(-60, 60))
36 | followers.append(follower)
37 | follower.setup(
38 | linear_speed_max,
39 | linear_accel_max,
40 | proximity_radius,
41 | separation_decay_coefficient,
42 | cohesion_strength,
43 | separation_strength
44 | )
45 |
46 | if i == 0 and show_proximity_radius:
47 | follower.draw_proximity = true
48 | follower.queue_redraw()
49 | follower.connect("input_event", Callable(self, "follower_input_event").bind(follower))
50 |
51 | var agents := []
52 | for i in followers:
53 | agents.append(i.agent)
54 | for i in followers:
55 | i.proximity.agents = agents
56 |
57 |
58 | func set_linear_speed_max(value: float) -> void:
59 | for child in get_children():
60 | child.agent.linear_speed_max = value
61 |
62 |
63 | func set_linear_accel_max(value: float) -> void:
64 | for child in get_children():
65 | child.agent.linear_acceleration_max = value
66 |
67 |
68 | func set_proximity_radius(value: float) -> void:
69 | for child in get_children():
70 | child.proximity.radius = value
71 | if child == get_child(0):
72 | child.update()
73 |
74 |
75 | func set_show_proximity_radius(value: bool) -> void:
76 | get_child(0).draw_proximity = value
77 | get_child(0).update()
78 |
79 |
80 | func set_separation_decay_coef(value: float) -> void:
81 | for child in get_children():
82 | child.separation.decay_coefficient = value
83 |
84 |
85 | func set_cohesion_strength(value: float) -> void:
86 | for child in get_children():
87 | child.blend.get_behavior_at(1).weight = value
88 |
89 |
90 | func set_separation_strength(value: float) -> void:
91 | for child in get_children():
92 | child.blend.get_behavior_at(0).weight = value
93 |
--------------------------------------------------------------------------------
/godot/Demos/PopulateItemList.gd:
--------------------------------------------------------------------------------
1 | extends ItemList
2 |
3 | signal demo_selected(scene_path)
4 |
5 | var file_paths := PackedStringArray()
6 |
7 |
8 | func _ready() -> void:
9 | # warning-ignore:return_value_discarded
10 | self.connect("item_selected", Callable(self, "_on_item_selected"))
11 |
12 | var this_directory: String = get_tree().current_scene.scene_file_path.rsplit("/", false, 1)[0]
13 | file_paths = _find_files(this_directory, ["*Demo.tscn"], true)
14 | populate(file_paths)
15 | select(0)
16 |
17 |
18 | func populate(demos: PackedStringArray) -> void:
19 | for path in demos:
20 | var demo_name: String = path.rsplit("/", true, 1)[-1]
21 | demo_name = demo_name.rsplit("Demo", true, 1)[0]
22 | demo_name = sentencify(demo_name)
23 | add_item(demo_name)
24 |
25 |
26 | func sentencify(line: String) -> String:
27 | var regex := RegEx.new()
28 | # warning-ignore:return_value_discarded
29 | regex.compile("[A-Z]")
30 |
31 | line = line.split(".", true, 1)[0]
32 | line = regex.sub(line, " $0", true)
33 | return line
34 |
35 |
36 | func _find_files(
37 | dirpath := "", patterns := PackedStringArray(), is_recursive := false, _do_skip_hidden := true
38 | ) -> PackedStringArray:
39 | var paths := PackedStringArray()
40 | var directory := DirAccess.open(dirpath)
41 |
42 | if not directory.dir_exists(dirpath):
43 | printerr("The directory does not exist: %s" % dirpath)
44 | return paths
45 | if directory == null:
46 | printerr("Could not open the following dirpath: %s" % dirpath)
47 | return paths
48 |
49 | # warning-ignore:return_value_discarded
50 | directory.list_dir_begin() # TODOConverter3To4 fill missing arguments https://github.com/godotengine/godot/pull/40547
51 | var file_name := directory.get_next()
52 | while file_name != "":
53 | if directory.current_is_dir() and is_recursive:
54 | var subdirectory := dirpath.path_join(file_name)
55 | paths.append_array(_find_files(subdirectory, patterns, is_recursive))
56 | else:
57 | for pattern in patterns:
58 | if file_name.match(pattern):
59 | paths.append(dirpath.path_join(file_name))
60 | file_name = directory.get_next()
61 |
62 | directory.list_dir_end()
63 | return paths
64 |
65 |
66 | func _on_item_selected(index: int) -> void:
67 | var demo_path := file_paths[index]
68 | emit_signal("demo_selected", demo_path)
69 |
--------------------------------------------------------------------------------
/godot/Demos/PursueSeek/BoundaryManager.gd:
--------------------------------------------------------------------------------
1 | extends Node2D
2 | # Wraps the ships' positions around the world border.
3 |
4 | var _world_bounds: Vector2
5 |
6 |
7 | func _ready() -> void:
8 | _world_bounds = Vector2(
9 | ProjectSettings["display/window/size/viewport_width"], ProjectSettings["display/window/size/viewport_height"]
10 | )
11 |
12 |
13 | func _physics_process(_delta: float) -> void:
14 | for ship in get_children():
15 | ship.position = ship.position.posmodv(_world_bounds)
16 |
--------------------------------------------------------------------------------
/godot/Demos/PursueSeek/Player.gd:
--------------------------------------------------------------------------------
1 | extends CharacterBody2D
2 | # Controls the player ship's movements based on player input.
3 |
4 | @export var thruster_strength := 175.0
5 | @export var side_thruster_strength := 10.0
6 | @export var velocity_max := 300.0
7 | @export var angular_velocity_max := 2.0
8 | @export var angular_drag := 0.025
9 | @export var linear_drag := 0.025
10 |
11 | var _linear_velocity := Vector2()
12 | var _angular_velocity := 0.0
13 |
14 | @onready var agent := GSAISteeringAgent.new()
15 |
16 |
17 | func _physics_process(delta: float) -> void:
18 | var movement := _get_movement()
19 | _angular_velocity = _calculate_angular_velocity(
20 | movement.x,
21 | _angular_velocity,
22 | side_thruster_strength,
23 | angular_velocity_max,
24 | angular_drag,
25 | delta
26 | )
27 | rotation += _angular_velocity * delta
28 |
29 | _linear_velocity = _calculate_linear_velocity(
30 | movement.y,
31 | _linear_velocity,
32 | Vector2.UP.rotated(rotation),
33 | linear_drag,
34 | thruster_strength,
35 | velocity_max,
36 | delta
37 | )
38 |
39 | set_velocity(_linear_velocity)
40 | move_and_slide()
41 | _linear_velocity = velocity
42 | _update_agent()
43 |
44 |
45 | func _calculate_angular_velocity(
46 | horizontal_movement: float,
47 | current_velocity: float,
48 | _thruster_strength: float,
49 | _velocity_max: float,
50 | ship_drag: float,
51 | delta: float
52 | ) -> float:
53 | var velocity : float = clamp(
54 | current_velocity + _thruster_strength * horizontal_movement * delta,
55 | -_velocity_max,
56 | _velocity_max
57 | )
58 |
59 | velocity = lerp(velocity, 0.0, ship_drag)
60 |
61 | return velocity
62 |
63 |
64 | func _calculate_linear_velocity(
65 | vertical_movement: float,
66 | current_velocity: Vector2,
67 | facing_direction: Vector2,
68 | ship_drag_coefficient: float,
69 | strength: float,
70 | speed_max: float,
71 | delta: float
72 | ) -> Vector2:
73 | var actual_strength := 0.0
74 | if vertical_movement > 0:
75 | actual_strength = strength
76 | elif vertical_movement < 0:
77 | actual_strength = -strength / 1.5
78 |
79 | var velocity := current_velocity + facing_direction * actual_strength * delta
80 | velocity = velocity.lerp(Vector2.ZERO, ship_drag_coefficient)
81 |
82 | return velocity.limit_length(speed_max)
83 |
84 |
85 | func _get_movement() -> Vector2:
86 | return Vector2(
87 | Input.get_action_strength("sf_right") - Input.get_action_strength("sf_left"),
88 | Input.get_action_strength("sf_up") - Input.get_action_strength("sf_down")
89 | )
90 |
91 |
92 | func _update_agent() -> void:
93 | agent.position.x = global_position.x
94 | agent.position.y = global_position.y
95 | agent.linear_velocity.x = _linear_velocity.x
96 | agent.linear_velocity.y = _linear_velocity.y
97 | agent.angular_velocity = _angular_velocity
98 | agent.orientation = rotation
99 |
--------------------------------------------------------------------------------
/godot/Demos/PursueSeek/PursueAndSeekDemo.gd:
--------------------------------------------------------------------------------
1 | extends Node
2 |
3 | @export_range(0, 2000, 40) var linear_speed_max := 120.0: set = set_linear_speed_max
4 | @export_range(0, 2000, 20) var linear_accel_max := 10.0: set = set_linear_accel_max
5 | @export_range(0, 5, 0.1) var predict_time := 1.0: set = set_predict_time
6 |
7 | @onready var pursuer := $BoundaryManager/Pursuer
8 | @onready var seeker := $BoundaryManager/Seeker
9 |
10 |
11 | func _ready() -> void:
12 | get_tree().root.content_scale_mode = Window.CONTENT_SCALE_MODE_CANVAS_ITEMS
13 | get_tree().root.content_scale_aspect = Window.CONTENT_SCALE_ASPECT_EXPAND
14 |
15 | pursuer.setup(predict_time, linear_speed_max, linear_accel_max)
16 | seeker.setup(predict_time, linear_speed_max, linear_accel_max)
17 |
18 |
19 | func set_linear_speed_max(value: float) -> void:
20 | linear_speed_max = value
21 | if not is_inside_tree():
22 | return
23 |
24 | pursuer.agent.linear_speed_max = value
25 | seeker.agent.linear_speed_max = value
26 |
27 |
28 | func set_linear_accel_max(value: float) -> void:
29 | linear_accel_max = value
30 | if not is_inside_tree():
31 | return
32 |
33 | pursuer.agent.linear_acceleration_max = value
34 | seeker.agent.linear_acceleration_max = value
35 |
36 |
37 | func set_predict_time(value: float) -> void:
38 | predict_time = value
39 | if not is_inside_tree():
40 | return
41 |
42 | pursuer._behavior.predict_time_max = value
43 |
--------------------------------------------------------------------------------
/godot/Demos/PursueSeek/PursueAndSeekDemo.tscn:
--------------------------------------------------------------------------------
1 | [gd_scene load_steps=8 format=3 uid="uid://bgqvru6qtewu8"]
2 |
3 | [ext_resource type="Script" path="res://Demos/PursueSeek/Pursuer.gd" id="1"]
4 | [ext_resource type="Script" path="res://Demos/PursueSeek/Player.gd" id="2"]
5 | [ext_resource type="Script" path="res://Demos/PursueSeek/BoundaryManager.gd" id="3"]
6 | [ext_resource type="Script" path="res://Demos/PursueSeek/PursueAndSeekDemo.gd" id="4"]
7 | [ext_resource type="PackedScene" uid="uid://b1iy8iir6k8tr" path="res://Demos/Utils/DemoInterface.tscn" id="5"]
8 | [ext_resource type="Script" path="res://Demos/Utils/Line2DDraw.gd" id="6"]
9 | [ext_resource type="PackedScene" uid="uid://ctke36x8wxrla" path="res://Demos/Utils/BackgroundLayer.tscn" id="7"]
10 |
11 | [node name="PursueVSSeekDemo" type="Node"]
12 | script = ExtResource("4")
13 | linear_speed_max = 1280.0
14 | linear_accel_max = 1040.0
15 | predict_time = 1.1
16 |
17 | [node name="BackgroudLayer" parent="." instance=ExtResource("7")]
18 |
19 | [node name="BoundaryManager" type="Node2D" parent="."]
20 | script = ExtResource("3")
21 |
22 | [node name="Player" type="CharacterBody2D" parent="BoundaryManager"]
23 | position = Vector2(200, 360)
24 | rotation = 1.5708
25 | collision_mask = 2
26 | script = ExtResource("2")
27 | thruster_strength = 1000.0
28 | side_thruster_strength = 40.0
29 | velocity_max = 450.0
30 | angular_velocity_max = 3.0
31 |
32 | [node name="CollisionPolygon2D" type="CollisionPolygon2D" parent="BoundaryManager/Player"]
33 | polygon = PackedVector2Array(0, -32, -24, 32, 24, 32)
34 |
35 | [node name="Line2D" type="Line2D" parent="BoundaryManager/Player"]
36 | points = PackedVector2Array(0, 32, 24, 32, 0, -32, -24, 32, 0, 32)
37 | width = 8.0
38 | default_color = Color(0.560784, 0.870588, 0.364706, 1)
39 | joint_mode = 2
40 | antialiased = true
41 | script = ExtResource("6")
42 | inner_color = Color(0.235294, 0.639216, 0.439216, 1)
43 |
44 | [node name="Pursuer" type="CharacterBody2D" parent="BoundaryManager"]
45 | position = Vector2(768, 440)
46 | collision_layer = 2
47 | script = ExtResource("1")
48 |
49 | [node name="CollisionPolygon2D" type="CollisionPolygon2D" parent="BoundaryManager/Pursuer"]
50 | polygon = PackedVector2Array(0, -32, -24, 32, 24, 32)
51 |
52 | [node name="Line2D" type="Line2D" parent="BoundaryManager/Pursuer"]
53 | points = PackedVector2Array(0, 32, 24, 32, 0, -32, -24, 32, 0, 32)
54 | width = 8.0
55 | default_color = Color(1, 0.709804, 0.439216, 1)
56 | joint_mode = 2
57 | antialiased = true
58 | script = ExtResource("6")
59 | inner_color = Color(0.890196, 0.411765, 0.337255, 1)
60 |
61 | [node name="Seeker" type="CharacterBody2D" parent="BoundaryManager"]
62 | position = Vector2(768, 280)
63 | rotation = 3.14159
64 | collision_layer = 2
65 | script = ExtResource("1")
66 | use_seek = true
67 |
68 | [node name="CollisionPolygon2D" type="CollisionPolygon2D" parent="BoundaryManager/Seeker"]
69 | polygon = PackedVector2Array(0, -32, -24, 32, 24, 32)
70 |
71 | [node name="Line2D" type="Line2D" parent="BoundaryManager/Seeker"]
72 | points = PackedVector2Array(0, 32, 24, 32, 0, -32, -24, 32, 0, 32)
73 | width = 8.0
74 | default_color = Color(0.301961, 0.65098, 1, 1)
75 | joint_mode = 2
76 | antialiased = true
77 | script = ExtResource("6")
78 | inner_color = Color(0.294118, 0.356863, 0.670588, 1)
79 |
80 | [node name="DemoInterface" parent="." instance=ExtResource("5")]
81 | text_bbcode = "Pursue vs. Seek Demo
82 | Move the player around with WASD and notice the [color=#ffb570]orange Pursuer[/color] and the [color=aqua]blue Seeker[/color] follow
83 | the [color=lime]green \"Ship\"[/color] around"
84 |
--------------------------------------------------------------------------------
/godot/Demos/PursueSeek/Pursuer.gd:
--------------------------------------------------------------------------------
1 | extends CharacterBody2D
2 | # Represents a ship that chases after the player.
3 |
4 | @export var use_seek: bool = false
5 |
6 | var _blend: GSAIBlend
7 |
8 | var _linear_drag_coefficient := 0.025
9 | var _angular_drag := 0.1
10 | var _direction_face := GSAIAgentLocation.new()
11 |
12 | @onready var agent := await GSAICharacterBody2DAgent.new(self)
13 | @onready var accel := GSAITargetAcceleration.new()
14 | @onready var player_agent: GSAISteeringAgent = owner.find_child("Player", true, false).agent
15 |
16 |
17 | func _ready() -> void:
18 | agent.calculate_velocities = false
19 | set_physics_process(false)
20 |
21 |
22 | func _physics_process(delta: float) -> void:
23 | _direction_face.position = agent.position + accel.linear.normalized()
24 |
25 | _blend.calculate_steering(accel)
26 |
27 | agent.angular_velocity = clamp(
28 | agent.angular_velocity + accel.angular * delta, -agent.angular_speed_max, agent.angular_speed_max
29 | )
30 | agent.angular_velocity = lerp(agent.angular_velocity, 0.0, _angular_drag)
31 |
32 | rotation += agent.angular_velocity * delta
33 |
34 | var linear_velocity := (
35 | GSAIUtils.to_vector2(agent.linear_velocity)
36 | + (GSAIUtils.angle_to_vector2(rotation) * -agent.linear_acceleration_max * delta)
37 | )
38 | linear_velocity = linear_velocity.limit_length(agent.linear_speed_max)
39 | linear_velocity = linear_velocity.lerp(Vector2.ZERO, _linear_drag_coefficient)
40 |
41 | set_velocity(linear_velocity)
42 | move_and_slide()
43 | linear_velocity = velocity
44 | agent.linear_velocity = GSAIUtils.to_vector3(linear_velocity)
45 |
46 |
47 | func setup(predict_time: float, linear_speed_max: float, linear_accel_max: float) -> void:
48 | var behavior: GSAISteeringBehavior
49 | if use_seek:
50 | behavior = GSAISeek.new(agent, player_agent)
51 | else:
52 | behavior = GSAIPursue.new(agent, player_agent, predict_time)
53 |
54 | var orient_behavior := GSAIFace.new(agent, _direction_face)
55 | orient_behavior.alignment_tolerance = deg_to_rad(5)
56 | orient_behavior.deceleration_radius = deg_to_rad(30)
57 |
58 | _blend = GSAIBlend.new(agent)
59 | _blend.add(behavior, 1)
60 | _blend.add(orient_behavior, 1)
61 |
62 | agent.angular_acceleration_max = deg_to_rad(1080)
63 | agent.angular_speed_max = deg_to_rad(360)
64 | agent.linear_acceleration_max = linear_accel_max
65 | agent.linear_speed_max = linear_speed_max
66 |
67 | set_physics_process(true)
68 |
--------------------------------------------------------------------------------
/godot/Demos/Quickstart/Agent.gd:
--------------------------------------------------------------------------------
1 | extends CharacterBody2D
2 |
3 | # Maximum possible linear velocity
4 | @export var speed_max := 450.0
5 | # Maximum change in linear velocity
6 | @export var acceleration_max := 50.0
7 | # Maximum rotation velocity represented in degrees
8 | @export var angular_speed_max := 240
9 | # Maximum change in rotation velocity represented in degrees
10 | @export var angular_acceleration_max := 40
11 |
12 | @export var health_max := 100
13 | @export var flee_health_threshold := 20
14 |
15 | var angular_velocity := 0.0
16 | var linear_drag := 0.1
17 | var angular_drag := 0.1
18 |
19 | # Holds the linear and angular components calculated by our steering behaviors.
20 | var acceleration := GSAITargetAcceleration.new()
21 |
22 | @onready var current_health := health_max
23 |
24 | # GSAISteeringAgent holds our agent's position, orientation, maximum speed and acceleration.
25 | @onready var agent := GSAISteeringAgent.new()
26 |
27 | @onready var player: Node = get_tree().get_nodes_in_group("Player")[0]
28 | # This assumes that our player class will keep its own agent updated.
29 | @onready var player_agent: GSAISteeringAgent = player.agent
30 |
31 | # Proximities represent an area with which an agent can identify where neighbors in its relevant
32 | # group are. In our case, the group will feature the player, which will be used to avoid a
33 | # collision with them. We use a radius proximity so the player is only relevant inside 100 pixels.
34 | @onready var proximity := GSAIRadiusProximity.new(agent, [player_agent], 100)
35 |
36 | # GSAIBlend combines behaviors together, calculating all of their acceleration together and adding
37 | # them together, multiplied by a strength. We will have one for fleeing, and one for pursuing,
38 | # toggling them depending on the agent's health. Since we want the agent to rotate AND move, then
39 | # we aim to blend them together.
40 | @onready var flee_blend := GSAIBlend.new(agent)
41 | @onready var pursue_blend := GSAIBlend.new(agent)
42 |
43 | # GSAIPriority will be the main steering behavior we use. It holds sub-behaviors and will pick the
44 | # first one that returns non-zero acceleration, ignoring any afterwards.
45 | @onready var priority := GSAIPriority.new(agent)
46 |
47 |
48 | func _ready() -> void:
49 | # ---------- Configuration for our agent ----------
50 | agent.linear_speed_max = speed_max
51 | agent.linear_acceleration_max = acceleration_max
52 | agent.angular_speed_max = deg_to_rad(angular_speed_max)
53 | agent.angular_acceleration_max = deg_to_rad(angular_acceleration_max)
54 | agent.bounding_radius = calculate_radius($CollisionPolygon2D.polygon)
55 | update_agent()
56 |
57 | # ---------- Configuration for our behaviors ----------
58 | # Pursue will happen while the agent is in good health. It produces acceleration that takes
59 | # the agent on an intercept course with the target, predicting its position in the future.
60 | var pursue := GSAIPursue.new(agent, player_agent)
61 | pursue.predict_time_max = 1.5
62 |
63 | # Flee will happen while the agent is in bad health, so will start disabled. It produces
64 | # acceleration that takes the agent directly away from the target with no prediction.
65 | var flee := GSAIFlee.new(agent, player_agent)
66 |
67 | # AvoidCollision tries to keep the agent from running into any of the neighbors found in its
68 | # proximity group. In our case, this will be the player, if they are close enough.
69 | var avoid := GSAIAvoidCollisions.new(agent, proximity)
70 |
71 | # Face turns the agent to keep looking towards its target. It will be enabled while the agent
72 | # is not fleeing due to low health. It tries to arrive 'on alignment' with 0 remaining velocity.
73 | var face := GSAIFace.new(agent, player_agent)
74 |
75 | # We use deg2rad because the math in the toolkit assumes radians.
76 | # How close for the agent to be 'aligned', if not exact.
77 | face.alignment_tolerance = deg_to_rad(5)
78 | # When to start slowing down
79 | face.deceleration_radius = deg_to_rad(60)
80 |
81 | # LookWhereYouGo turns the agent to keep looking towards its direction of travel. It will only
82 | # be enabled while the agent is at low health.
83 | var look := GSAILookWhereYouGo.new(agent)
84 | # How close for the agent to be 'aligned', if not exact
85 | look.alignment_tolerance = deg_to_rad(5)
86 | # When to start slowing down.
87 | look.deceleration_radius = deg_to_rad(60)
88 |
89 | # Behaviors that are not enabled produce 0 acceleration.
90 | # Adding our fleeing behaviors to a blend. The order does not matter.
91 | flee_blend.is_enabled = false
92 | flee_blend.add(look, 1)
93 | flee_blend.add(flee, 1)
94 |
95 | # Adding our pursuit behaviors to a blend. The order does not matter.
96 | pursue_blend.add(face, 1)
97 | pursue_blend.add(pursue, 1)
98 |
99 | # Adding our final behaviors to the main priority behavior. The order does matter here.
100 | # We want to avoid collision with the player first, flee from the player second when enabled,
101 | # and pursue the player last when enabled.
102 | priority.add(avoid)
103 | priority.add(flee_blend)
104 | priority.add(pursue_blend)
105 |
106 |
107 | func _physics_process(delta: float) -> void:
108 | # Make sure any change in position and speed has been recorded.
109 | update_agent()
110 |
111 | if current_health <= flee_health_threshold:
112 | pursue_blend.is_enabled = false
113 | flee_blend.is_enabled = true
114 |
115 | # Calculate the desired acceleration.
116 | priority.calculate_steering(acceleration)
117 |
118 | # We add the discovered acceleration to our linear velocity. The toolkit does not limit
119 | # velocity, just acceleration, so we clamp the result ourselves here.
120 | velocity = (velocity + Vector2(acceleration.linear.x, acceleration.linear.y) * delta).limit_length(
121 | agent.linear_speed_max
122 | )
123 |
124 | # This applies drag on the agent's motion, helping it to slow down naturally.
125 | velocity = velocity.lerp(Vector2.ZERO, linear_drag)
126 |
127 | # And since we're using a KinematicBody2D, we use Godot's excellent move_and_slide to actually
128 | # apply the final movement, and record any change in velocity the physics engine discovered.
129 | move_and_slide()
130 |
131 | # We then do something similar to apply our agent's rotational speed.
132 | angular_velocity = clamp(
133 | angular_velocity + acceleration.angular * delta, -agent.angular_speed_max, agent.angular_speed_max
134 | )
135 | # This applies drag on the agent's rotation, helping it slow down naturally.
136 | angular_velocity = lerp(angular_velocity, 0.0, angular_drag)
137 | rotation += angular_velocity * delta
138 |
139 |
140 | # In order to support both 2D and 3D, the toolkit uses Vector3, so the conversion is required
141 | # when using 2D nodes. The Z component can be left to 0 safely.
142 | func update_agent() -> void:
143 | agent.position.x = global_position.x
144 | agent.position.y = global_position.y
145 | agent.orientation = rotation
146 | agent.linear_velocity.x = velocity.x
147 | agent.linear_velocity.y = velocity.y
148 | agent.angular_velocity = angular_velocity
149 |
150 |
151 | # We calculate the radius from the collision shape - this will approximate the agent's size in the
152 | # game world, to avoid collisions with the player.
153 | func calculate_radius(polygon: PackedVector2Array) -> float:
154 | var furthest_point := Vector2(-INF, -INF)
155 | for p in polygon:
156 | if abs(p.x) > furthest_point.x:
157 | furthest_point.x = p.x
158 | if abs(p.y) > furthest_point.y:
159 | furthest_point.y = p.y
160 | return furthest_point.length()
161 |
162 |
163 | func damage(amount: int) -> void:
164 | current_health -= amount
165 | if current_health <= 0:
166 | queue_free()
167 |
--------------------------------------------------------------------------------
/godot/Demos/Quickstart/Bullet.gd:
--------------------------------------------------------------------------------
1 | extends CharacterBody2D
2 |
3 | @export var speed := 1500.0
4 |
5 | var player: Node
6 |
7 | @onready var timer := $Lifetime
8 |
9 |
10 | func _ready() -> void:
11 | timer.connect("timeout", Callable(self, "_on_Lifetime_timeout"))
12 | timer.start()
13 |
14 |
15 | func _physics_process(delta: float) -> void:
16 | var collision := move_and_collide(velocity * delta)
17 | if collision:
18 | timer.stop()
19 | clear()
20 | collision.get_collider().damage(10)
21 |
22 |
23 | func start(direction: Vector2) -> void:
24 | velocity = direction * speed
25 |
26 |
27 | func clear() -> void:
28 | queue_free()
29 |
30 |
31 | func _on_Lifetime_timeout() -> void:
32 | clear()
33 |
--------------------------------------------------------------------------------
/godot/Demos/Quickstart/Bullet.tscn:
--------------------------------------------------------------------------------
1 | [gd_scene load_steps=4 format=2]
2 |
3 | [ext_resource path="res://Demos/Utils/CircleDraw.gd" type="Script" id=1]
4 | [ext_resource path="res://Demos/Quickstart/Bullet.gd" type="Script" id=2]
5 |
6 | [sub_resource type="CircleShape2D" id=1]
7 | radius = 4.0
8 |
9 | [node name="Bullet" type="CharacterBody2D"]
10 | collision_layer = 4
11 | collision_mask = 2
12 | script = ExtResource( 2 )
13 |
14 | [node name="CollisionShape2D" type="CollisionShape2D" parent="."]
15 | shape = SubResource( 1 )
16 | script = ExtResource( 1 )
17 | inner_color = Color( 0.235294, 0.639216, 0.439216, 1 )
18 | outer_color = Color( 0.560784, 0.870588, 0.364706, 1 )
19 | stroke = 2.0
20 |
21 | [node name="Lifetime" type="Timer" parent="."]
22 | process_mode = 0
23 | wait_time = 3.0
24 |
--------------------------------------------------------------------------------
/godot/Demos/Quickstart/Player.gd:
--------------------------------------------------------------------------------
1 | extends CharacterBody2D
2 |
3 | @export var speed_max := 650.0
4 | @export var acceleration_max := 70.0
5 | @export var rotation_speed_max := 240
6 | @export var rotation_accel_max := 40
7 | @export var bullet: PackedScene
8 |
9 | var angular_velocity := 0.0
10 | var direction := Vector2.RIGHT
11 |
12 | @onready var agent := GSAISteeringAgent.new()
13 | @onready var proxy_target := GSAIAgentLocation.new()
14 | @onready var face := GSAIFace.new(agent, proxy_target)
15 | @onready var accel := GSAITargetAcceleration.new()
16 | @onready var bullets := owner.get_node("Bullets")
17 |
18 |
19 | func _ready() -> void:
20 | agent.linear_speed_max = speed_max
21 | agent.linear_acceleration_max = acceleration_max
22 | agent.angular_speed_max = deg_to_rad(rotation_speed_max)
23 | agent.angular_acceleration_max = deg_to_rad(rotation_accel_max)
24 | agent.bounding_radius = calculate_radius($CollisionPolygon2D.polygon)
25 | update_agent()
26 |
27 | var mouse_pos := get_global_mouse_position()
28 | proxy_target.position.x = mouse_pos.x
29 | proxy_target.position.y = mouse_pos.y
30 |
31 | face.alignment_tolerance = deg_to_rad(5)
32 | face.deceleration_radius = deg_to_rad(45)
33 |
34 |
35 | func _physics_process(delta: float) -> void:
36 | update_agent()
37 |
38 | var movement := get_movement()
39 |
40 | direction = GSAIUtils.angle_to_vector2(rotation)
41 |
42 | velocity += direction * acceleration_max * movement * delta
43 | velocity = velocity.limit_length(speed_max)
44 | velocity = velocity.lerp(Vector2.ZERO, 0.1)
45 | move_and_slide()
46 |
47 | face.calculate_steering(accel)
48 | angular_velocity += accel.angular * delta
49 | angular_velocity = clamp(angular_velocity, -agent.angular_speed_max, agent.angular_speed_max)
50 | angular_velocity = lerp(angular_velocity, 0.0, 0.1)
51 | rotation += angular_velocity * delta
52 |
53 |
54 | func _unhandled_input(event: InputEvent) -> void:
55 | if event is InputEventMouseMotion:
56 | var mouse_pos: Vector2 = event.position
57 | proxy_target.position.x = mouse_pos.x
58 | proxy_target.position.y = mouse_pos.y
59 | elif event is InputEventMouseButton:
60 | if event.button_index == MOUSE_BUTTON_LEFT and event.pressed:
61 | var next_bullet := bullet.instantiate()
62 | next_bullet.global_position = (
63 | global_position
64 | - direction * (agent.bounding_radius - 5)
65 | )
66 | next_bullet.player = self
67 | next_bullet.start(-direction)
68 | bullets.add_child(next_bullet)
69 |
70 |
71 | func get_movement() -> float:
72 | return Input.get_action_strength("sf_down") - Input.get_action_strength("sf_up")
73 |
74 |
75 | func update_agent() -> void:
76 | agent.position.x = global_position.x
77 | agent.position.y = global_position.y
78 | agent.orientation = rotation
79 | agent.linear_velocity.x = velocity.x
80 | agent.linear_velocity.y = velocity.y
81 | agent.angular_velocity = angular_velocity
82 |
83 |
84 | func calculate_radius(polygon: PackedVector2Array) -> float:
85 | var furthest_point := Vector2(-INF, -INF)
86 | for p in polygon:
87 | if abs(p.x) > furthest_point.x:
88 | furthest_point.x = p.x
89 | if abs(p.y) > furthest_point.y:
90 | furthest_point.y = p.y
91 | return furthest_point.length()
92 |
--------------------------------------------------------------------------------
/godot/Demos/Quickstart/QuickStartDemo.gd:
--------------------------------------------------------------------------------
1 | extends Node
2 |
3 | func _ready():
4 | get_tree().root.content_scale_mode = Window.CONTENT_SCALE_MODE_CANVAS_ITEMS
5 | get_tree().root.content_scale_aspect = Window.CONTENT_SCALE_ASPECT_EXPAND
6 |
--------------------------------------------------------------------------------
/godot/Demos/Quickstart/QuickStartDemo.tscn:
--------------------------------------------------------------------------------
1 | [gd_scene load_steps=7 format=3 uid="uid://5bnnsdfs5edt"]
2 |
3 | [ext_resource type="Script" path="res://Demos/Utils/Line2DDraw.gd" id="1"]
4 | [ext_resource type="Script" path="res://Demos/Quickstart/QuickStartDemo.gd" id="1_b5qi2"]
5 | [ext_resource type="Script" path="res://Demos/Quickstart/Agent.gd" id="2"]
6 | [ext_resource type="Script" path="res://Demos/Quickstart/Player.gd" id="3"]
7 | [ext_resource type="PackedScene" path="res://Demos/Quickstart/Bullet.tscn" id="4"]
8 | [ext_resource type="PackedScene" uid="uid://ctke36x8wxrla" path="res://Demos/Utils/BackgroundLayer.tscn" id="5"]
9 |
10 | [node name="QuickStartDemo" type="Node"]
11 | script = ExtResource("1_b5qi2")
12 |
13 | [node name="BackgroudLayer" parent="." instance=ExtResource("5")]
14 |
15 | [node name="Player" type="CharacterBody2D" parent="." groups=["Player"]]
16 | position = Vector2(266.667, 480)
17 | rotation = 1.5708
18 | collision_mask = 2
19 | script = ExtResource("3")
20 | speed_max = 900.0
21 | acceleration_max = 4200.0
22 | rotation_speed_max = 360
23 | rotation_accel_max = 1280
24 | bullet = ExtResource("4")
25 |
26 | [node name="CollisionPolygon2D" type="CollisionPolygon2D" parent="Player"]
27 | polygon = PackedVector2Array(0, -32, -24, 32, 24, 32)
28 |
29 | [node name="Line2D" type="Line2D" parent="Player"]
30 | points = PackedVector2Array(0, 32, 24, 32, 0, -32, -24, 32, 0, 32)
31 | width = 8.0
32 | default_color = Color(0.560784, 0.870588, 0.364706, 1)
33 | joint_mode = 2
34 | antialiased = true
35 | script = ExtResource("1")
36 | inner_color = Color(0.235294, 0.639216, 0.439216, 1)
37 |
38 | [node name="Agent" type="CharacterBody2D" parent="."]
39 | position = Vector2(640, 180)
40 | rotation = 1.5708
41 | collision_layer = 2
42 | collision_mask = 5
43 | script = ExtResource("2")
44 | speed_max = 600.0
45 | acceleration_max = 2800.0
46 | angular_speed_max = 360
47 | angular_acceleration_max = 1280
48 |
49 | [node name="CollisionPolygon2D" type="CollisionPolygon2D" parent="Agent"]
50 | polygon = PackedVector2Array(0, -32, -24, 32, 24, 32)
51 |
52 | [node name="Line2D" type="Line2D" parent="Agent"]
53 | points = PackedVector2Array(0, 32, 24, 32, 0, -32, -24, 32, 0, 32)
54 | width = 8.0
55 | default_color = Color(1, 0.709804, 0.439216, 1)
56 | joint_mode = 2
57 | antialiased = true
58 | script = ExtResource("1")
59 | inner_color = Color(0.890196, 0.411765, 0.337255, 1)
60 |
61 | [node name="Bullets" type="Node2D" parent="."]
62 |
--------------------------------------------------------------------------------
/godot/Demos/SeekFlee/Boundaries.gd:
--------------------------------------------------------------------------------
1 | extends Node2D
2 |
3 | const COLOR := Color("8fde5d")
4 |
5 |
6 | func _ready() -> void:
7 | get_tree().root.connect("size_changed", Callable(self, "_on_SceneTree_size_changed"))
8 | _on_SceneTree_size_changed()
9 |
10 |
11 | func _draw() -> void:
12 | for b in get_children():
13 | var size: Vector2 = b.get_node("CollisionShape2D").shape.size
14 | draw_rect(Rect2(b.global_position - size, size * 2), COLOR)
15 |
16 |
17 | func _on_SceneTree_size_changed() -> void:
18 | var size := Vector2(
19 | ProjectSettings["display/window/size/viewport_width"], ProjectSettings["display/window/size/viewport_height"]
20 | )
21 | for b in get_children():
22 | var boundary: String = b.name.rsplit("Boundary")[0]
23 | match boundary:
24 | "Left":
25 | b.global_position = Vector2(0, size.y / 2)
26 | "Right":
27 | b.global_position = Vector2(size.x, size.y / 2)
28 | "Top":
29 | b.global_position = Vector2(size.x / 2, 0)
30 | "Bottom":
31 | b.global_position = Vector2(size.x / 2, size.y)
32 | queue_redraw()
33 |
--------------------------------------------------------------------------------
/godot/Demos/SeekFlee/Player.gd:
--------------------------------------------------------------------------------
1 | extends CharacterBody2D
2 | # Class to control the player in basic left/right up/down movement.
3 |
4 | var speed: float
5 | @onready var agent := GSAIAgentLocation.new()
6 |
7 |
8 | func _ready() -> void:
9 | agent.position = GSAIUtils.to_vector3(global_position)
10 |
11 |
12 | func _physics_process(_delta: float) -> void:
13 | var movement := _get_movement()
14 | if movement.length_squared() < 0.01:
15 | return
16 |
17 | # warning-ignore:return_value_discarded
18 | set_velocity(movement * speed)
19 | move_and_slide()
20 | agent.position = GSAIUtils.to_vector3(global_position)
21 |
22 |
23 | func _get_movement() -> Vector2:
24 | return Vector2(
25 | Input.get_action_strength("sf_right") - Input.get_action_strength("sf_left"),
26 | Input.get_action_strength("sf_down") - Input.get_action_strength("sf_up")
27 | )
28 |
--------------------------------------------------------------------------------
/godot/Demos/SeekFlee/SeekFleeDemo.gd:
--------------------------------------------------------------------------------
1 | extends Node
2 | # Access helper class for children to access window boundaries.
3 |
4 | enum Mode { FLEE, SEEK }
5 |
6 | @export var behavior_mode := Mode.SEEK: set = set_behavior_mode
7 | @export_range(0, 1000, 30) var linear_speed_max := 200.0: set = set_linear_speed_max
8 | @export_range(0, 2000, 40) var linear_accel_max := 10.0: set = set_linear_accel_max
9 | @export var player_speed := 600.0: set = set_player_speed
10 |
11 | var camera_boundaries: Rect2
12 |
13 | @onready var player: CharacterBody2D = $Player
14 | @onready var spawner: Node2D = $Spawner
15 |
16 |
17 | func _ready() -> void:
18 | get_tree().root.content_scale_mode = Window.CONTENT_SCALE_MODE_CANVAS_ITEMS
19 | get_tree().root.content_scale_aspect = Window.CONTENT_SCALE_ASPECT_EXPAND
20 |
21 | camera_boundaries = Rect2(
22 | Vector2.ZERO,
23 | Vector2(
24 | ProjectSettings["display/window/size/viewport_width"],
25 | ProjectSettings["display/window/size/viewport_height"]
26 | )
27 | )
28 |
29 | var rng := RandomNumberGenerator.new()
30 | rng.randomize()
31 |
32 | player.speed = player_speed
33 |
34 | for i in range(spawner.entity_count):
35 | var new_pos := Vector2(
36 | rng.randf_range(0, camera_boundaries.size.x),
37 | rng.randf_range(0, camera_boundaries.size.y)
38 | )
39 | var entity: CharacterBody2D = spawner.Entity.instantiate()
40 | entity.global_position = new_pos
41 | entity.player_agent = player.agent
42 | entity.start_speed = linear_speed_max
43 | entity.start_accel = linear_accel_max
44 | entity.use_seek = behavior_mode == Mode.SEEK
45 | spawner.add_child(entity)
46 |
47 |
48 | func set_behavior_mode(mode: int) -> void:
49 | behavior_mode = mode
50 | if not is_inside_tree():
51 | return
52 |
53 | match mode:
54 | Mode.SEEK:
55 | for child in spawner.get_children():
56 | child.use_seek = true
57 | Mode.FLEE:
58 | for child in spawner.get_children():
59 | child.use_seek = false
60 |
61 |
62 | func set_linear_speed_max(value: float) -> void:
63 | linear_speed_max = value
64 | if not is_inside_tree():
65 | return
66 |
67 | for child in spawner.get_children():
68 | child.agent.linear_speed_max = value
69 |
70 |
71 | func set_linear_accel_max(value: float) -> void:
72 | linear_accel_max = value
73 | if not is_inside_tree():
74 | return
75 |
76 | for child in spawner.get_children():
77 | child.agent.linear_acceleration_max = value
78 |
79 |
80 | func set_player_speed(value: float) -> void:
81 | player_speed = value
82 | if not is_inside_tree():
83 | return
84 |
85 | player.speed = player_speed
86 |
--------------------------------------------------------------------------------
/godot/Demos/SeekFlee/SeekFleeDemo.tscn:
--------------------------------------------------------------------------------
1 | [gd_scene load_steps=12 format=3 uid="uid://by2w4nmb55dd8"]
2 |
3 | [ext_resource type="Script" path="res://Demos/SeekFlee/Player.gd" id="2"]
4 | [ext_resource type="Script" path="res://Demos/SeekFlee/SeekFleeDemo.gd" id="3"]
5 | [ext_resource type="Script" path="res://Demos/SeekFlee/Spawner.gd" id="4"]
6 | [ext_resource type="PackedScene" uid="uid://b1iy8iir6k8tr" path="res://Demos/Utils/DemoInterface.tscn" id="5"]
7 | [ext_resource type="PackedScene" uid="uid://bgi7o0njly5g4" path="res://Demos/SeekFlee/Seeker.tscn" id="6"]
8 | [ext_resource type="Script" path="res://Demos/Utils/CircleDraw.gd" id="7"]
9 | [ext_resource type="PackedScene" uid="uid://ctke36x8wxrla" path="res://Demos/Utils/BackgroundLayer.tscn" id="8"]
10 | [ext_resource type="Script" path="res://Demos/SeekFlee/Boundaries.gd" id="9"]
11 |
12 | [sub_resource type="CircleShape2D" id="1"]
13 | radius = 32.0
14 |
15 | [sub_resource type="RectangleShape2D" id="2"]
16 | size = Vector2(10, 1100)
17 |
18 | [sub_resource type="RectangleShape2D" id="3"]
19 | size = Vector2(1950, 10)
20 |
21 | [node name="SeekFleeDemo" type="Node"]
22 | script = ExtResource("3")
23 | linear_speed_max = 570.0
24 | linear_accel_max = 1160.0
25 |
26 | [node name="BackgroudLayer" parent="." instance=ExtResource("8")]
27 |
28 | [node name="Player" type="CharacterBody2D" parent="."]
29 | position = Vector2(640, 360)
30 | collision_mask = 2
31 | script = ExtResource("2")
32 |
33 | [node name="CollisionShape2D" type="CollisionShape2D" parent="Player"]
34 | shape = SubResource("1")
35 | script = ExtResource("7")
36 | inner_color = Color(0.235294, 0.639216, 0.439216, 1)
37 | outer_color = Color(0.560784, 0.870588, 0.364706, 1)
38 | stroke = 4.0
39 |
40 | [node name="Boundaries" type="Node2D" parent="."]
41 | script = ExtResource("9")
42 |
43 | [node name="LeftBoundary" type="StaticBody2D" parent="Boundaries"]
44 | position = Vector2(0, 540)
45 | collision_layer = 2
46 | collision_mask = 5
47 |
48 | [node name="CollisionShape2D" type="CollisionShape2D" parent="Boundaries/LeftBoundary"]
49 | shape = SubResource("2")
50 |
51 | [node name="RightBoundary" type="StaticBody2D" parent="Boundaries"]
52 | position = Vector2(1920, 540)
53 | collision_layer = 2
54 | collision_mask = 5
55 |
56 | [node name="CollisionShape2D" type="CollisionShape2D" parent="Boundaries/RightBoundary"]
57 | shape = SubResource("2")
58 |
59 | [node name="TopBoundary" type="StaticBody2D" parent="Boundaries"]
60 | position = Vector2(960, 0)
61 | collision_layer = 2
62 | collision_mask = 5
63 |
64 | [node name="CollisionShape2D" type="CollisionShape2D" parent="Boundaries/TopBoundary"]
65 | shape = SubResource("3")
66 |
67 | [node name="BottomBoundary" type="StaticBody2D" parent="Boundaries"]
68 | position = Vector2(960, 1080)
69 | collision_layer = 2
70 | collision_mask = 5
71 |
72 | [node name="CollisionShape2D" type="CollisionShape2D" parent="Boundaries/BottomBoundary"]
73 | shape = SubResource("3")
74 |
75 | [node name="Spawner" type="Node2D" parent="."]
76 | script = ExtResource("4")
77 | Entity = ExtResource("6")
78 |
79 | [node name="DemoInterface" parent="." instance=ExtResource("5")]
80 | text_bbcode = "Seek & Flee Demo
81 | Move the [color=lime]green \"Player\"[/color] around with WASD and notice the [color=#ffb570]orange \"Enemies\"[/color] try to seek to or flee from the player. Press [Space] to toggle between seek and flee."
82 |
--------------------------------------------------------------------------------
/godot/Demos/SeekFlee/Seeker.gd:
--------------------------------------------------------------------------------
1 | extends CharacterBody2D
2 |
3 | var player_agent: GSAIAgentLocation
4 | var start_speed: float
5 | var start_accel: float
6 | var use_seek := true
7 |
8 | @onready var agent := await GSAICharacterBody2DAgent.new(self)
9 | @onready var accel := GSAITargetAcceleration.new()
10 | @onready var seek := GSAISeek.new(agent, player_agent)
11 | @onready var flee := GSAIFlee.new(agent, player_agent)
12 |
13 |
14 | func _ready() -> void:
15 | agent.linear_acceleration_max = start_accel
16 | agent.linear_speed_max = start_speed
17 |
18 |
19 | func _physics_process(delta: float) -> void:
20 | if not player_agent:
21 | return
22 |
23 | if use_seek:
24 | seek.calculate_steering(accel)
25 | else:
26 | flee.calculate_steering(accel)
27 |
28 | agent._apply_steering(accel, delta)
29 |
--------------------------------------------------------------------------------
/godot/Demos/SeekFlee/Seeker.tscn:
--------------------------------------------------------------------------------
1 | [gd_scene load_steps=4 format=3 uid="uid://bgi7o0njly5g4"]
2 |
3 | [ext_resource type="Script" path="res://Demos/SeekFlee/Seeker.gd" id="1"]
4 | [ext_resource type="Script" path="res://Demos/Utils/CircleDraw.gd" id="2"]
5 |
6 | [sub_resource type="CircleShape2D" id="1"]
7 | radius = 16.0
8 |
9 | [node name="Seeker" type="CharacterBody2D"]
10 | collision_layer = 4
11 | collision_mask = 2
12 | script = ExtResource("1")
13 |
14 | [node name="CollisionShape2D" type="CollisionShape2D" parent="."]
15 | shape = SubResource("1")
16 | script = ExtResource("2")
17 | inner_color = Color(0.890196, 0.411765, 0.337255, 1)
18 | outer_color = Color(1, 0.709804, 0.439216, 1)
19 | stroke = 4.0
20 |
--------------------------------------------------------------------------------
/godot/Demos/SeekFlee/Spawner.gd:
--------------------------------------------------------------------------------
1 | extends Node2D
2 | # Holds data to instantiate and configure a number of agent entities.
3 |
4 | @export var Entity: PackedScene
5 | @export var entity_count := 10
6 | @export var entity_color := Color.BLUE
7 |
--------------------------------------------------------------------------------
/godot/Demos/Utils/BackgroundLayer.tscn:
--------------------------------------------------------------------------------
1 | [gd_scene load_steps=2 format=3 uid="uid://ctke36x8wxrla"]
2 |
3 | [ext_resource type="Texture2D" uid="uid://chqsknldl55hp" path="res://assets/sprites/background.png" id="1"]
4 |
5 | [node name="BackgroundLayer" type="CanvasLayer"]
6 | layer = -1
7 |
8 | [node name="Background" type="TextureRect" parent="."]
9 | anchors_preset = 15
10 | anchor_right = 1.0
11 | anchor_bottom = 1.0
12 | grow_horizontal = 2
13 | grow_vertical = 2
14 | texture = ExtResource("1")
15 |
--------------------------------------------------------------------------------
/godot/Demos/Utils/CircleDraw.gd:
--------------------------------------------------------------------------------
1 | @tool
2 | extends CollisionShape2D
3 |
4 | @export var inner_color := Color(): set = set_inner_color
5 | @export var outer_color := Color(): set = set_outer_color
6 | @export var stroke := 0.0: set = set_stroke
7 |
8 |
9 | func _draw() -> void:
10 | draw_circle(Vector2.ZERO, shape.radius + stroke, outer_color)
11 | draw_circle(Vector2.ZERO, shape.radius, inner_color)
12 |
13 |
14 | func set_inner_color(val: Color) -> void:
15 | inner_color = val
16 | queue_redraw()
17 |
18 |
19 | func set_outer_color(val: Color) -> void:
20 | outer_color = val
21 | queue_redraw()
22 |
23 |
24 | func set_stroke(val: float) -> void:
25 | stroke = val
26 | queue_redraw()
27 |
--------------------------------------------------------------------------------
/godot/Demos/Utils/DemoInterface.gd:
--------------------------------------------------------------------------------
1 | @tool
2 | extends PanelContainer
3 |
4 | @export_multiline var text_bbcode := "": set = set_text_bbcode
5 |
6 | @onready var rich_text_label: RichTextLabel = $MarginContainer/RichTextLabel
7 |
8 |
9 | func _ready():
10 | get_tree().root.content_scale_mode = Window.CONTENT_SCALE_MODE_CANVAS_ITEMS
11 | get_tree().root.content_scale_aspect = Window.CONTENT_SCALE_ASPECT_EXPAND
12 |
13 | func set_text_bbcode(value: String) -> void:
14 | text_bbcode = value
15 | if not rich_text_label:
16 | await self.ready
17 | rich_text_label.text = text_bbcode
18 |
--------------------------------------------------------------------------------
/godot/Demos/Utils/DemoInterface.tscn:
--------------------------------------------------------------------------------
1 | [gd_scene load_steps=4 format=3 uid="uid://b1iy8iir6k8tr"]
2 |
3 | [ext_resource type="Theme" uid="uid://c2l0v31q141pv" path="res://assets/theme/gdquest.theme" id="1"]
4 | [ext_resource type="Script" path="res://Demos/Utils/DemoInterface.gd" id="2"]
5 |
6 | [sub_resource type="GDScript" id="1"]
7 | script/source = "@tool
8 | extends RichTextLabel
9 |
10 | "
11 |
12 | [node name="DemoInterface" type="PanelContainer"]
13 | custom_minimum_size = Vector2(1024, 0)
14 | anchors_preset = 10
15 | anchor_right = 1.0
16 | offset_bottom = 140.0
17 | theme = ExtResource("1")
18 | script = ExtResource("2")
19 | text_bbcode = "Replace this text for the demo."
20 |
21 | [node name="MarginContainer" type="MarginContainer" parent="."]
22 | layout_mode = 2
23 |
24 | [node name="RichTextLabel" type="RichTextLabel" parent="MarginContainer"]
25 | custom_minimum_size = Vector2(0, 55)
26 | layout_mode = 2
27 | bbcode_enabled = true
28 | text = "Replace this text for the demo."
29 | scroll_active = false
30 | script = SubResource("1")
31 |
--------------------------------------------------------------------------------
/godot/Demos/Utils/Line2DDraw.gd:
--------------------------------------------------------------------------------
1 | @tool
2 | extends Line2D
3 |
4 | @export var inner_color := Color(): set = set_inner_color
5 |
6 |
7 | func _draw() -> void:
8 | draw_colored_polygon(points, inner_color)
9 |
10 |
11 | func set_inner_color(val: Color) -> void:
12 | inner_color = val
13 | queue_redraw()
14 |
--------------------------------------------------------------------------------
/godot/addons/com.gdquest.godot-steering-ai-framework/Agents/GSAICharacterBody2DAgent.gd:
--------------------------------------------------------------------------------
1 | # A specialized steering agent that updates itself every frame so the user does
2 | # not have to using a CharacterBody2D
3 | # @category - Specialized agents
4 | class_name GSAICharacterBody2DAgent
5 | extends GSAISpecializedAgent
6 |
7 | # SLIDE uses `move_and_slide`
8 | # COLLIDE uses `move_and_collide`
9 | # POSITION changes the `global_position` directly
10 | enum MovementType { SLIDE, COLLIDE, POSITION }
11 |
12 | # The CharacterBody2D to keep track of
13 | var body: CharacterBody2D: set = _set_body
14 |
15 | # The type of movement the body executes
16 | var movement_type: int
17 |
18 | var _last_position: Vector2
19 | var _body_ref: WeakRef
20 |
21 |
22 | func _init(_body: CharacterBody2D, _movement_type: int = MovementType.SLIDE) -> void:
23 | if not _body.is_inside_tree():
24 | await(_body).ready
25 |
26 | self.body = _body
27 | self.movement_type = _movement_type
28 |
29 | # warning-ignore:return_value_discarded
30 | _body.get_tree().physics_frame.connect(_on_SceneTree_physics_frame)
31 |
32 |
33 | # Moves the agent's `body` by target `acceleration`.
34 | # @tags - virtual
35 | func _apply_steering(acceleration: GSAITargetAcceleration, delta: float) -> void:
36 | _applied_steering = true
37 | match movement_type:
38 | MovementType.COLLIDE:
39 | _apply_collide_steering(acceleration.linear, delta)
40 | MovementType.SLIDE:
41 | _apply_sliding_steering(acceleration.linear, delta)
42 | _:
43 | _apply_position_steering(acceleration.linear, delta)
44 |
45 | _apply_orientation_steering(acceleration.angular, delta)
46 |
47 |
48 | func _apply_sliding_steering(accel: Vector3, delta: float) -> void:
49 | var _body: CharacterBody2D = _body_ref.get_ref()
50 | if not _body:
51 | return
52 |
53 | if not _body.is_inside_tree():
54 | return
55 |
56 | _body.velocity = GSAIUtils.to_vector2(linear_velocity + accel * delta).limit_length(linear_speed_max)
57 | if apply_linear_drag:
58 | _body.velocity = _body.velocity.lerp(Vector2.ZERO, linear_drag_percentage)
59 |
60 | _body.move_and_slide()
61 |
62 | if calculate_velocities:
63 | linear_velocity = GSAIUtils.to_vector3(_body.velocity)
64 |
65 |
66 | func _apply_collide_steering(accel: Vector3, delta: float) -> void:
67 | var _body: CharacterBody2D = _body_ref.get_ref()
68 | if not _body:
69 | return
70 |
71 | _body.velocity = GSAIUtils.to_vector2(
72 | GSAIUtils.clampedv3(linear_velocity + accel * delta, linear_speed_max)
73 | )
74 |
75 | if apply_linear_drag:
76 | _body.velocity = _body.velocity.lerp(Vector2.ZERO, linear_drag_percentage)
77 |
78 | # warning-ignore:return_value_discarded
79 | _body.move_and_collide(_body.velocity * delta)
80 |
81 | if calculate_velocities:
82 | linear_velocity = GSAIUtils.to_vector3(_body.velocity)
83 |
84 |
85 | func _apply_position_steering(accel: Vector3, delta: float) -> void:
86 | var _body: CharacterBody2D = _body_ref.get_ref()
87 | if not _body:
88 | return
89 |
90 | var velocity := GSAIUtils.clampedv3(linear_velocity + accel * delta, linear_speed_max)
91 | if apply_linear_drag:
92 | velocity = velocity.lerp(Vector3.ZERO, linear_drag_percentage)
93 | _body.global_position += GSAIUtils.to_vector2(velocity) * delta
94 | if calculate_velocities:
95 | linear_velocity = velocity
96 |
97 |
98 | func _apply_orientation_steering(angular_acceleration: float, delta: float) -> void:
99 | var _body: CharacterBody2D = _body_ref.get_ref()
100 | if not _body:
101 | return
102 |
103 | var velocity = clamp(
104 | angular_velocity + angular_acceleration * delta,
105 | -angular_speed_max,
106 | angular_speed_max
107 | )
108 | if apply_angular_drag:
109 | velocity = lerp(velocity, 0.0, angular_drag_percentage)
110 | _body.rotation += velocity * delta
111 | if calculate_velocities:
112 | angular_velocity = velocity
113 |
114 |
115 | func _set_body(value: CharacterBody2D) -> void:
116 | body = value
117 | _body_ref = weakref(body)
118 |
119 | _last_position = value.global_position
120 | _last_orientation = value.rotation
121 |
122 | position = GSAIUtils.to_vector3(_last_position)
123 | orientation = _last_orientation
124 |
125 |
126 | func _on_SceneTree_physics_frame() -> void:
127 | var _body: CharacterBody2D = _body_ref.get_ref()
128 | if not _body:
129 | return
130 |
131 | var current_position := _body.global_position
132 | var current_orientation := _body.rotation
133 |
134 | position = GSAIUtils.to_vector3(current_position)
135 | orientation = current_orientation
136 |
137 | if calculate_velocities:
138 | if _applied_steering:
139 | _applied_steering = false
140 | else:
141 | linear_velocity = GSAIUtils.clampedv3(
142 | GSAIUtils.to_vector3(current_position - _last_position), linear_speed_max
143 | )
144 | if apply_linear_drag:
145 | linear_velocity = linear_velocity.lerp(
146 | Vector3.ZERO, linear_drag_percentage
147 | )
148 |
149 | angular_velocity = clamp(
150 | _last_orientation - current_orientation, -angular_speed_max, angular_speed_max
151 | )
152 |
153 | if apply_angular_drag:
154 | angular_velocity = lerp(angular_velocity, 0.0, angular_drag_percentage)
155 |
156 | _last_position = current_position
157 | _last_orientation = current_orientation
158 |
--------------------------------------------------------------------------------
/godot/addons/com.gdquest.godot-steering-ai-framework/Agents/GSAICharacterBody3DAgent.gd:
--------------------------------------------------------------------------------
1 | # A specialized steering agent that updates itself every frame so the user does
2 | # not have to using a CharacterBody3D
3 | # @category - Specialized agents
4 | class_name GSAICharacterBody3DAgent
5 | extends GSAISpecializedAgent
6 |
7 | # SLIDE uses `move_and_slide`
8 | # COLLIDE uses `move_and_collide`
9 | # POSITION changes the global_position directly
10 | enum MovementType { SLIDE, COLLIDE, POSITION }
11 |
12 | # The CharacterBody3D to keep track of
13 | var body: CharacterBody3D: set = _set_body
14 |
15 | # The type of movement the body executes
16 | var movement_type: int
17 |
18 | var _last_position: Vector3
19 | var _body_ref: WeakRef
20 |
21 |
22 | func _init(_body: CharacterBody3D, _movement_type: int = MovementType.SLIDE) -> void:
23 | if not _body.is_inside_tree():
24 | await(_body).ready
25 |
26 | self.body = _body
27 | self.movement_type = _movement_type
28 |
29 | # warning-ignore:return_value_discarded
30 | _body.get_tree().physics_frame.connect(_on_SceneTree_physics_frame)
31 |
32 |
33 | # Moves the agent's `body` by target `acceleration`.
34 | # @tags - virtual
35 | func _apply_steering(acceleration: GSAITargetAcceleration, delta: float) -> void:
36 | _applied_steering = true
37 | match movement_type:
38 | MovementType.COLLIDE:
39 | _apply_collide_steering(acceleration.linear, delta)
40 | MovementType.SLIDE:
41 | _apply_sliding_steering(acceleration.linear, delta)
42 | _:
43 | _apply_position_steering(acceleration.linear, delta)
44 |
45 | _apply_orientation_steering(acceleration.angular, delta)
46 |
47 |
48 | func _apply_sliding_steering(accel: Vector3, delta: float) -> void:
49 | var _body: CharacterBody3D = _body_ref.get_ref()
50 | if not _body:
51 | return
52 |
53 | _body.velocity = GSAIUtils.clampedv3(
54 | linear_velocity + accel * delta, linear_speed_max
55 | )
56 | if apply_linear_drag:
57 | _body.velocity = _body.velocity.lerp(Vector3.ZERO, linear_drag_percentage)
58 |
59 | _body.move_and_slide()
60 |
61 | if calculate_velocities:
62 | linear_velocity = _body.velocity
63 |
64 |
65 | func _apply_collide_steering(accel: Vector3, delta: float) -> void:
66 | var _body: CharacterBody3D = _body_ref.get_ref()
67 | if not _body:
68 | return
69 |
70 | _body.velocity = GSAIUtils.clampedv3(
71 | linear_velocity + accel * delta, linear_speed_max
72 | )
73 | if apply_linear_drag:
74 | _body.velocity = _body.velocity.lerp(Vector3.ZERO, linear_drag_percentage)
75 |
76 | # warning-ignore:return_value_discarded
77 | _body.move_and_collide(_body.velocity * delta)
78 |
79 | if calculate_velocities:
80 | linear_velocity = _body.velocity
81 |
82 |
83 | func _apply_position_steering(accel: Vector3, delta: float) -> void:
84 | var _body: CharacterBody3D = _body_ref.get_ref()
85 | if not _body:
86 | return
87 |
88 | var velocity := GSAIUtils.clampedv3(
89 | linear_velocity + accel * delta, linear_speed_max
90 | )
91 | if apply_linear_drag:
92 | velocity = velocity.lerp(Vector3.ZERO, linear_drag_percentage)
93 |
94 | _body.global_transform.origin += velocity * delta
95 | if calculate_velocities:
96 | linear_velocity = velocity
97 |
98 |
99 | func _apply_orientation_steering(angular_acceleration: float, delta: float) -> void:
100 | var _body: CharacterBody3D = _body_ref.get_ref()
101 | if not _body:
102 | return
103 |
104 | var velocity = clamp(
105 | angular_velocity + angular_acceleration * delta,
106 | -angular_speed_max,
107 | angular_speed_max
108 | )
109 | if apply_angular_drag:
110 | velocity = lerp(velocity, 0.0, angular_drag_percentage)
111 | _body.rotation.y += velocity * delta
112 | if calculate_velocities:
113 | angular_velocity = velocity
114 |
115 |
116 | func _set_body(value: CharacterBody3D) -> void:
117 | body = value
118 | _body_ref = weakref(value)
119 |
120 | _last_position = value.transform.origin
121 | _last_orientation = value.rotation.y
122 |
123 | position = _last_position
124 | orientation = _last_orientation
125 |
126 |
127 | func _on_SceneTree_physics_frame() -> void:
128 | var _body: CharacterBody3D = _body_ref.get_ref()
129 | if not _body:
130 | return
131 |
132 | if not _body.is_inside_tree():
133 | return
134 |
135 | var current_position := _body.transform.origin
136 | var current_orientation := _body.rotation.y
137 |
138 | position = current_position
139 | orientation = current_orientation
140 |
141 | if calculate_velocities:
142 | if _applied_steering:
143 | _applied_steering = false
144 | else:
145 | linear_velocity = GSAIUtils.clampedv3(
146 | current_position - _last_position, linear_speed_max
147 | )
148 | if apply_linear_drag:
149 | linear_velocity = linear_velocity.lerp(Vector3.ZERO, linear_drag_percentage)
150 |
151 | angular_velocity = clamp(
152 | _last_orientation - current_orientation,
153 | -angular_speed_max,
154 | angular_speed_max
155 | )
156 |
157 | if apply_angular_drag:
158 | angular_velocity = lerp(angular_velocity, 0.0, angular_drag_percentage)
159 |
160 | _last_position = current_position
161 | _last_orientation = current_orientation
162 |
--------------------------------------------------------------------------------
/godot/addons/com.gdquest.godot-steering-ai-framework/Agents/GSAIRigidBody2DAgent.gd:
--------------------------------------------------------------------------------
1 | # A specialized steering agent that updates itself every frame so the user does
2 | # not have to using a RigidBody2D
3 | # @category - Specialized agents
4 | class_name GSAIRigidBody2DAgent
5 | extends GSAISpecializedAgent
6 |
7 | # The RigidBody2D to keep track of
8 | var body: RigidBody2D: set = _set_body
9 |
10 | var _last_position: Vector2
11 | var _body_ref: WeakRef
12 |
13 |
14 | func _init(_body: RigidBody2D) -> void:
15 | if not _body.is_inside_tree():
16 | await(_body).ready
17 | self.body = _body
18 |
19 | # warning-ignore:return_value_discarded
20 | _body.get_tree().physics_frame.connect(_on_SceneTree_frame)
21 |
22 |
23 | # Moves the agent's `body` by target `acceleration`.
24 | # @tags - virtual
25 | func _apply_steering(acceleration: GSAITargetAcceleration, _delta: float) -> void:
26 | var _body: RigidBody2D = _body_ref.get_ref()
27 | if not _body:
28 | return
29 |
30 | _applied_steering = true
31 | _body.apply_central_impulse(GSAIUtils.to_vector2(acceleration.linear))
32 | _body.apply_torque_impulse(acceleration.angular)
33 | if calculate_velocities:
34 | linear_velocity = GSAIUtils.to_vector3(_body.linear_velocity)
35 | angular_velocity = _body.angular_velocity
36 |
37 |
38 | func _set_body(value: RigidBody2D) -> void:
39 | body = value
40 | _body_ref = weakref(value)
41 |
42 | _last_position = value.global_position
43 | _last_orientation = value.rotation
44 |
45 | position = GSAIUtils.to_vector3(_last_position)
46 | orientation = _last_orientation
47 |
48 |
49 | func _on_SceneTree_frame() -> void:
50 | var _body: RigidBody2D = _body_ref.get_ref()
51 | if not _body:
52 | return
53 |
54 | if not _body.is_inside_tree():
55 | return
56 |
57 | var current_position := _body.global_position
58 | var current_orientation := _body.rotation
59 |
60 | position = GSAIUtils.to_vector3(current_position)
61 | orientation = current_orientation
62 |
63 | if calculate_velocities:
64 | if _applied_steering:
65 | _applied_steering = false
66 | else:
67 | linear_velocity = GSAIUtils.to_vector3(_body.linear_velocity)
68 | angular_velocity = _body.angular_velocity
69 |
--------------------------------------------------------------------------------
/godot/addons/com.gdquest.godot-steering-ai-framework/Agents/GSAIRigidBody3DAgent.gd:
--------------------------------------------------------------------------------
1 | # A specialized steering agent that updates itself every frame so the user does
2 | # not have to using a RigidBody3D
3 | # @category - Specialized agents
4 | class_name GSAIRigidBody3DAgent
5 | extends GSAISpecializedAgent
6 |
7 | # The RigidBody3D to keep track of
8 | var body: RigidBody3D: set = _set_body
9 |
10 | var _last_position: Vector3
11 | var _body_ref: WeakRef
12 |
13 | func _init(_body: RigidBody3D) -> void:
14 | if not _body.is_inside_tree():
15 | await(_body).ready
16 | self.body = _body
17 |
18 | # warning-ignore:return_value_discarded
19 | _body.get_tree().physics_frame.connect(_on_SceneTree_frame)
20 |
21 |
22 | # Moves the agent's `body` by target `acceleration`.
23 | # @tags - virtual
24 | func _apply_steering(acceleration: GSAITargetAcceleration, _delta: float) -> void:
25 | var _body: RigidBody3D = _body_ref.get_ref()
26 | if not _body:
27 | return
28 |
29 | _applied_steering = true
30 | _body.apply_central_impulse(acceleration.linear)
31 | _body.apply_torque_impulse(Vector3.UP * acceleration.angular)
32 | if calculate_velocities:
33 | linear_velocity = _body.linear_velocity
34 | angular_velocity = _body.angular_velocity.y
35 |
36 |
37 | func _set_body(value: RigidBody3D) -> void:
38 | body = value
39 | _body_ref = weakref(value)
40 |
41 | _last_position = value.transform.origin
42 | _last_orientation = value.rotation.y
43 |
44 | position = _last_position
45 | orientation = _last_orientation
46 |
47 |
48 | func _on_SceneTree_frame() -> void:
49 | var _body: RigidBody3D = _body_ref.get_ref()
50 | if not _body:
51 | return
52 |
53 | if not _body.is_inside_tree():
54 | return
55 |
56 | var current_position := _body.transform.origin
57 | var current_orientation := _body.rotation.y
58 |
59 | position = current_position
60 | orientation = current_orientation
61 |
62 | if calculate_velocities:
63 | if _applied_steering:
64 | _applied_steering = false
65 | else:
66 | linear_velocity = _body.linear_velocity
67 | angular_velocity = _body.angular_velocity.y
68 |
--------------------------------------------------------------------------------
/godot/addons/com.gdquest.godot-steering-ai-framework/Agents/GSAISpecializedAgent.gd:
--------------------------------------------------------------------------------
1 | # A base class for a specialized steering agent that updates itself every frame
2 | # so the user does not have to. All other specialized agents derive from this.
3 | # @category - Specialized agents
4 | # @tags - abstract
5 | class_name GSAISpecializedAgent
6 | extends GSAISteeringAgent
7 |
8 | # If `true`, calculates linear and angular velocities based on the previous
9 | # frame. When `false`, the user must keep those values updated.
10 | var calculate_velocities := true
11 |
12 | # If `true`, interpolates the current linear velocity towards 0 by the
13 | # `linear_drag_percentage` value.
14 | # Does not apply to `RigidBody` and `RigidBody2D` nodes.
15 | var apply_linear_drag := true
16 |
17 | # If `true`, interpolates the current angular velocity towards 0 by the
18 | # `angular_drag_percentage` value.
19 | # Does not apply to `RigidBody` and `RigidBody2D` nodes.
20 | var apply_angular_drag := true
21 |
22 | # The percentage between the current linear velocity and 0 to interpolate by if
23 | # `apply_linear_drag` is true.
24 | # Does not apply to `RigidBody` and `RigidBody2D` nodes.
25 | var linear_drag_percentage := 0.0
26 |
27 | # The percentage between the current angular velocity and 0 to interpolate by if
28 | # `apply_angular_drag` is true.
29 | # Does not apply to `RigidBody` and `RigidBody2D` nodes.
30 | var angular_drag_percentage := 0.0
31 |
32 | var _last_orientation: float
33 | var _applied_steering := false
34 |
35 |
36 | # Moves the agent's body by target `acceleration`.
37 | # @tags - virtual
38 | func _apply_steering(_acceleration: GSAITargetAcceleration, _delta: float) -> void:
39 | pass
40 |
--------------------------------------------------------------------------------
/godot/addons/com.gdquest.godot-steering-ai-framework/Behaviors/GSAIArrive.gd:
--------------------------------------------------------------------------------
1 | # Calculates acceleration to take an agent to its target's location. The
2 | # calculation attempts to arrive with zero remaining velocity.
3 | # @category - Individual behaviors
4 | class_name GSAIArrive
5 | extends GSAISteeringBehavior
6 |
7 | # Target agent to arrive to.
8 | var target: GSAIAgentLocation
9 | # Distance from the target for the agent to be considered successfully
10 | # arrived.
11 | var arrival_tolerance: float
12 | # Distance from the target for the agent to begin slowing down.
13 | var deceleration_radius: float
14 | # Represents the time it takes to change acceleration.
15 | var time_to_reach := 0.1
16 |
17 |
18 | func _init(agent: GSAISteeringAgent, _target: GSAIAgentLocation) -> void:
19 | super._init(agent)
20 | self.target = _target
21 |
22 |
23 | func _arrive(acceleration: GSAITargetAcceleration, target_position: Vector3) -> void:
24 | var to_target := target_position - agent.position
25 | var distance := to_target.length()
26 |
27 | if distance <= arrival_tolerance:
28 | acceleration.set_zero()
29 | else:
30 | var desired_speed := agent.linear_speed_max
31 |
32 | if distance <= deceleration_radius:
33 | desired_speed *= distance / deceleration_radius
34 |
35 | var desired_velocity := to_target * desired_speed / distance
36 |
37 | desired_velocity = ((desired_velocity - agent.linear_velocity) * 1.0 / time_to_reach)
38 |
39 | acceleration.linear = GSAIUtils.clampedv3(desired_velocity, agent.linear_acceleration_max)
40 | acceleration.angular = 0
41 |
42 |
43 | func _calculate_steering(acceleration: GSAITargetAcceleration) -> void:
44 | _arrive(acceleration, target.position)
45 |
--------------------------------------------------------------------------------
/godot/addons/com.gdquest.godot-steering-ai-framework/Behaviors/GSAIAvoidCollisions.gd:
--------------------------------------------------------------------------------
1 | # Steers the agent to avoid obstacles in its path. Approximates obstacles as
2 | # spheres.
3 | # @category - Group behaviors
4 | class_name GSAIAvoidCollisions
5 | extends GSAIGroupBehavior
6 |
7 | var _first_neighbor: GSAISteeringAgent
8 | var _shortest_time: float
9 | var _first_minimum_separation: float
10 | var _first_distance: float
11 | var _first_relative_position: Vector3
12 | var _first_relative_velocity: Vector3
13 |
14 |
15 | func _init(agent: GSAISteeringAgent, proximity: GSAIProximity) -> void:
16 | super._init(agent, proximity)
17 |
18 |
19 | func _calculate_steering(acceleration: GSAITargetAcceleration) -> void:
20 | _shortest_time = INF
21 | _first_neighbor = null
22 | _first_minimum_separation = 0
23 | _first_distance = 0
24 |
25 | var neighbor_count := proximity._find_neighbors(_callback)
26 |
27 | if neighbor_count == 0 or not _first_neighbor:
28 | acceleration.set_zero()
29 | else:
30 | if (
31 | _first_minimum_separation <= 0
32 | or _first_distance < agent.bounding_radius + _first_neighbor.bounding_radius
33 | ):
34 | acceleration.linear = _first_neighbor.position - agent.position
35 | else:
36 | acceleration.linear = (
37 | _first_relative_position
38 | + (_first_relative_velocity * _shortest_time)
39 | )
40 |
41 | acceleration.linear = (acceleration.linear.normalized() * -agent.linear_acceleration_max)
42 | acceleration.angular = 0
43 |
44 |
45 | # Callback for the proximity to call when finding neighbors. Keeps track of every `neighbor`
46 | # that was found but only keeps the one the owning agent will most likely collide with.
47 | # @tags - virtual
48 | func _report_neighbor(neighbor: GSAISteeringAgent) -> bool:
49 | var relative_position := neighbor.position - agent.position
50 | var relative_velocity := neighbor.linear_velocity - agent.linear_velocity
51 | var relative_speed_squared := relative_velocity.length_squared()
52 |
53 | if relative_speed_squared == 0:
54 | return false
55 | else:
56 | var time_to_collision = -relative_position.dot(relative_velocity) / relative_speed_squared
57 |
58 | if time_to_collision <= 0 or time_to_collision >= _shortest_time:
59 | return false
60 | else:
61 | var distance = relative_position.length()
62 | var minimum_separation: float = (
63 | distance
64 | - sqrt(relative_speed_squared) * time_to_collision
65 | )
66 | if minimum_separation > agent.bounding_radius + neighbor.bounding_radius:
67 | return false
68 | else:
69 | _shortest_time = time_to_collision
70 | _first_neighbor = neighbor
71 | _first_minimum_separation = minimum_separation
72 | _first_distance = distance
73 | _first_relative_position = relative_position
74 | _first_relative_velocity = relative_velocity
75 | return true
76 |
--------------------------------------------------------------------------------
/godot/addons/com.gdquest.godot-steering-ai-framework/Behaviors/GSAIBlend.gd:
--------------------------------------------------------------------------------
1 | # Blends multiple steering behaviors into one, and returns a weighted
2 | # acceleration from their calculations.
3 | #
4 | # Stores the behaviors internally as dictionaries of the form
5 | # {
6 | # behavior : GSAISteeringBehavior,
7 | # weight : float
8 | # }
9 | # @category - Combination behaviors
10 | class_name GSAIBlend
11 | extends GSAISteeringBehavior
12 |
13 | var _behaviors := []
14 | var _accel := GSAITargetAcceleration.new()
15 |
16 |
17 | func _init(agent: GSAISteeringAgent) -> void:
18 | super._init(agent)
19 |
20 |
21 | # Appends a behavior to the internal array along with its `weight`.
22 | func add(behavior: GSAISteeringBehavior, weight: float) -> void:
23 | behavior.agent = agent
24 | _behaviors.append({behavior = behavior, weight = weight})
25 |
26 |
27 | # Returns the behavior at the specified `index`, or an empty `Dictionary` if
28 | # none was found.
29 | func get_behavior_at(index: int) -> Dictionary:
30 | if _behaviors.size() > index:
31 | return _behaviors[index]
32 | printerr("Tried to get index " + str(index) + " in array of size " + str(_behaviors.size()))
33 | return {}
34 |
35 |
36 | func _calculate_steering(blended_accel: GSAITargetAcceleration) -> void:
37 | blended_accel.set_zero()
38 |
39 | for i in range(_behaviors.size()):
40 | var bw: Dictionary = _behaviors[i]
41 | bw.behavior.calculate_steering(_accel)
42 |
43 | blended_accel.add_scaled_accel(_accel, bw.weight)
44 |
45 | blended_accel.linear = GSAIUtils.clampedv3(blended_accel.linear, agent.linear_acceleration_max)
46 | blended_accel.angular = clamp(
47 | blended_accel.angular, -agent.angular_acceleration_max, agent.angular_acceleration_max
48 | )
49 |
--------------------------------------------------------------------------------
/godot/addons/com.gdquest.godot-steering-ai-framework/Behaviors/GSAICohesion.gd:
--------------------------------------------------------------------------------
1 | # Calculates an acceleration that attempts to move the agent towards the center
2 | # of mass of the agents in the area defined by the `GSAIProximity`.
3 | # @category - Group behaviors
4 | class_name GSAICohesion
5 | extends GSAIGroupBehavior
6 |
7 | var _center_of_mass: Vector3
8 |
9 |
10 | func _init(agent: GSAISteeringAgent, proximity: GSAIProximity) -> void:
11 | super._init(agent, proximity)
12 |
13 |
14 | func _calculate_steering(acceleration: GSAITargetAcceleration) -> void:
15 | acceleration.set_zero()
16 | _center_of_mass = Vector3.ZERO
17 | var neighbor_count = proximity._find_neighbors(_callback)
18 | if neighbor_count > 0:
19 | _center_of_mass *= 1.0 / neighbor_count
20 | acceleration.linear = (
21 | (_center_of_mass - agent.position).normalized()
22 | * agent.linear_acceleration_max
23 | )
24 |
25 |
26 | # Callback for the proximity to call when finding neighbors. Adds `neighbor`'s position
27 | # to the center of mass of the group.
28 | # @tags - virtual
29 | func _report_neighbor(neighbor: GSAISteeringAgent) -> bool:
30 | _center_of_mass += neighbor.position
31 | return true
32 |
--------------------------------------------------------------------------------
/godot/addons/com.gdquest.godot-steering-ai-framework/Behaviors/GSAIEvade.gd:
--------------------------------------------------------------------------------
1 | # Calculates acceleration to take an agent away from where a target agent is
2 | # moving.
3 | # @category - Individual behaviors
4 | class_name GSAIEvade
5 | extends GSAIPursue
6 |
7 |
8 | func _init(agent: GSAISteeringAgent, target: GSAISteeringAgent, predict_time_max := 1.0):
9 | super._init(agent, target, predict_time_max)
10 |
11 |
12 | func _get_modified_acceleration() -> float:
13 | return -agent.linear_acceleration_max
14 |
--------------------------------------------------------------------------------
/godot/addons/com.gdquest.godot-steering-ai-framework/Behaviors/GSAIFace.gd:
--------------------------------------------------------------------------------
1 | # Calculates angular acceleration to rotate a target to face its target's
2 | # position. The behavior attemps to arrive with zero remaining angular velocity.
3 | # @category - Individual behaviors
4 | class_name GSAIFace
5 | extends GSAIMatchOrientation
6 |
7 |
8 | func _init(agent: GSAISteeringAgent, target: GSAIAgentLocation, use_z := false) -> void:
9 | super._init(agent, target, use_z)
10 |
11 |
12 | func _face(acceleration: GSAITargetAcceleration, target_position: Vector3) -> void:
13 | var to_target := target_position - agent.position
14 | var distance_squared := to_target.length_squared()
15 |
16 | if distance_squared < agent.zero_linear_speed_threshold:
17 | acceleration.set_zero()
18 | else:
19 | var orientation = (
20 | GSAIUtils.vector3_to_angle(to_target)
21 | if use_z
22 | else GSAIUtils.vector2_to_angle(GSAIUtils.to_vector2(to_target))
23 | )
24 | _match_orientation(acceleration, orientation)
25 |
26 |
27 | func _calculate_steering(acceleration: GSAITargetAcceleration) -> void:
28 | _face(acceleration, target.position)
29 |
--------------------------------------------------------------------------------
/godot/addons/com.gdquest.godot-steering-ai-framework/Behaviors/GSAIFlee.gd:
--------------------------------------------------------------------------------
1 | # Calculates acceleration to take an agent directly away from a target agent.
2 | # @category - Individual behaviors
3 | class_name GSAIFlee
4 | extends GSAISeek
5 |
6 |
7 | func _init(agent: GSAISteeringAgent, target: GSAIAgentLocation) -> void:
8 | super._init(agent, target)
9 |
10 |
11 | func _calculate_steering(acceleration: GSAITargetAcceleration) -> void:
12 | acceleration.linear = (
13 | (agent.position - target.position).normalized()
14 | * agent.linear_acceleration_max
15 | )
16 | acceleration.angular = 0
17 |
--------------------------------------------------------------------------------
/godot/addons/com.gdquest.godot-steering-ai-framework/Behaviors/GSAIFollowPath.gd:
--------------------------------------------------------------------------------
1 | # Produces a linear acceleration that moves the agent along the specified path.
2 | # @category - Individual behaviors
3 | class_name GSAIFollowPath
4 | extends GSAIArrive
5 |
6 | # The path to follow and travel along.
7 | var path: GSAIPath
8 | # The distance along the path to generate the next target position.
9 | var path_offset := 0.0
10 |
11 | # Whether to use `GSAIArrive` behavior on an open path.
12 | var is_arrive_enabled := true
13 | # The amount of time in the future to predict the owning agent's position along
14 | # the path. Setting it to 0.0 will force non-predictive path following.
15 | var prediction_time := 0.0
16 |
17 |
18 | func _init(agent: GSAISteeringAgent, _path: GSAIPath, _path_offset := 0.0, _prediction_time := 0.0) -> void:
19 | super._init(agent, null)
20 | self.path = _path
21 | self.path_offset = _path_offset
22 | self.prediction_time = _prediction_time
23 |
24 |
25 | func _calculate_steering(acceleration: GSAITargetAcceleration) -> void:
26 | var location := (
27 | agent.position
28 | if prediction_time == 0
29 | else agent.position + (agent.linear_velocity * prediction_time)
30 | )
31 |
32 | var distance := path.calculate_distance(location)
33 | var target_distance := distance + path_offset
34 |
35 | if prediction_time > 0 and path.is_open:
36 | if target_distance < path.calculate_distance(agent.position):
37 | target_distance = path.length
38 |
39 | var target_position := path.calculate_target_position(target_distance)
40 |
41 | if is_arrive_enabled and path.is_open:
42 | if path_offset >= 0:
43 | if target_distance > path.length - deceleration_radius:
44 | _arrive(acceleration, target_position)
45 | return
46 | else:
47 | if target_distance < deceleration_radius:
48 | _arrive(acceleration, target_position)
49 | return
50 |
51 | acceleration.linear = (target_position - agent.position).normalized()
52 | acceleration.linear *= agent.linear_acceleration_max
53 | acceleration.angular = 0
54 |
--------------------------------------------------------------------------------
/godot/addons/com.gdquest.godot-steering-ai-framework/Behaviors/GSAILookWhereYouGo.gd:
--------------------------------------------------------------------------------
1 | # Calculates an angular acceleration to match an agent's orientation to its
2 | # direction of travel.
3 | # @category - Individual behaviors
4 | class_name GSAILookWhereYouGo
5 | extends GSAIMatchOrientation
6 |
7 |
8 | func _init(agent: GSAISteeringAgent, use_z := false) -> void:
9 | super._init(agent, null, use_z)
10 |
11 |
12 | func _calculate_steering(accel: GSAITargetAcceleration) -> void:
13 | if agent.linear_velocity.length_squared() < agent.zero_linear_speed_threshold:
14 | accel.set_zero()
15 | else:
16 | var orientation := (
17 | GSAIUtils.vector3_to_angle(agent.linear_velocity)
18 | if use_z
19 | else GSAIUtils.vector2_to_angle(GSAIUtils.to_vector2(agent.linear_velocity))
20 | )
21 | _match_orientation(accel, orientation)
22 |
--------------------------------------------------------------------------------
/godot/addons/com.gdquest.godot-steering-ai-framework/Behaviors/GSAIMatchOrientation.gd:
--------------------------------------------------------------------------------
1 | # Calculates an angular acceleration to match an agent's orientation to that of
2 | # its target. Attempts to make the agent arrive with zero remaining angular
3 | # velocity.
4 | # @category - Individual behaviors
5 | class_name GSAIMatchOrientation
6 | extends GSAISteeringBehavior
7 |
8 | # The target orientation for the behavior to try and match rotations to.
9 | var target: GSAIAgentLocation
10 | # The amount of distance in radians for the behavior to consider itself close
11 | # enough to be matching the target agent's rotation.
12 | var alignment_tolerance: float
13 | # The amount of distance in radians from the goal to start slowing down.
14 | var deceleration_radius: float
15 | # The amount of time to reach the target velocity
16 | var time_to_reach: float = 0.1
17 | # Whether to use the X and Z components instead of X and Y components when
18 | # determining angles. X and Z should be used in 3D.
19 | var use_z: bool
20 |
21 |
22 | func _init(agent: GSAISteeringAgent, _target: GSAIAgentLocation, _use_z := false) -> void:
23 | super._init(agent)
24 | self.use_z = _use_z
25 | self.target = _target
26 |
27 |
28 | func _match_orientation(acceleration: GSAITargetAcceleration, desired_orientation: float) -> void:
29 | var rotation := wrapf(desired_orientation - agent.orientation, -PI, PI)
30 |
31 | var rotation_size := absf(rotation)
32 |
33 | if rotation_size <= alignment_tolerance:
34 | acceleration.set_zero()
35 | else:
36 | var desired_rotation := agent.angular_speed_max
37 |
38 | if rotation_size <= deceleration_radius:
39 | desired_rotation *= rotation_size / deceleration_radius
40 |
41 | desired_rotation *= rotation / rotation_size
42 |
43 | acceleration.angular = ((desired_rotation - agent.angular_velocity) / time_to_reach)
44 |
45 | var limited_acceleration := absf(acceleration.angular)
46 | if limited_acceleration > agent.angular_acceleration_max:
47 | acceleration.angular *= (agent.angular_acceleration_max / limited_acceleration)
48 |
49 | acceleration.linear = Vector3.ZERO
50 |
51 |
52 | func _calculate_steering(acceleration: GSAITargetAcceleration) -> void:
53 | _match_orientation(acceleration, target.orientation)
54 |
--------------------------------------------------------------------------------
/godot/addons/com.gdquest.godot-steering-ai-framework/Behaviors/GSAIPriority.gd:
--------------------------------------------------------------------------------
1 | # Container for multiple behaviors that returns the result of the first child
2 | # behavior with non-zero acceleration.
3 | # @category - Combination behaviors
4 | class_name GSAIPriority
5 | extends GSAISteeringBehavior
6 |
7 | var _behaviors := []
8 |
9 | # The index of the last behavior the container prioritized.
10 | var last_selected_index: int
11 | # If a behavior's acceleration is lower than this threshold, the container
12 | # considers it has an acceleration of zero.
13 | var zero_threshold: float
14 |
15 |
16 | func _init(agent: GSAISteeringAgent, _zero_threshold := 0.001) -> void:
17 | super._init(agent)
18 | self.zero_threshold = _zero_threshold
19 |
20 |
21 | # Appends a steering behavior as a child of this container.
22 | func add(behavior: GSAISteeringBehavior) -> void:
23 | _behaviors.append(behavior)
24 |
25 |
26 | # Returns the behavior at the position in the pool referred to by `index`, or
27 | # `null` if no behavior was found.
28 | func get_behavior_at(index: int) -> GSAISteeringBehavior:
29 | if _behaviors.size() > index:
30 | return _behaviors[index]
31 | printerr("Tried to get index " + str(index) + " in array of size " + str(_behaviors.size()))
32 | return null
33 |
34 |
35 | func _calculate_steering(accel: GSAITargetAcceleration) -> void:
36 | var threshold_squared := zero_threshold * zero_threshold
37 |
38 | last_selected_index = -1
39 |
40 | var size := _behaviors.size()
41 |
42 | if size > 0:
43 | for i in range(size):
44 | last_selected_index = i
45 | var behavior: GSAISteeringBehavior = _behaviors[i]
46 | behavior.calculate_steering(accel)
47 |
48 | if accel.get_magnitude_squared() > threshold_squared:
49 | break
50 | else:
51 | accel.set_zero()
52 |
--------------------------------------------------------------------------------
/godot/addons/com.gdquest.godot-steering-ai-framework/Behaviors/GSAIPursue.gd:
--------------------------------------------------------------------------------
1 | # Calculates an acceleration to make an agent intercept another based on the
2 | # target agent's movement.
3 | # @category - Individual behaviors
4 | class_name GSAIPursue
5 | extends GSAISteeringBehavior
6 |
7 | # The target agent that the behavior is trying to intercept.
8 | var target: GSAISteeringAgent
9 | # The maximum amount of time in the future the behavior predicts the target's
10 | # location.
11 | var predict_time_max: float
12 |
13 |
14 | func _init(agent: GSAISteeringAgent, _target: GSAISteeringAgent, _predict_time_max := 1.0) -> void:
15 | super._init(agent)
16 | self.target = _target
17 | self.predict_time_max = _predict_time_max
18 |
19 |
20 | func _calculate_steering(acceleration: GSAITargetAcceleration) -> void:
21 | var target_position := target.position
22 | var distance_squared := (target_position - agent.position).length_squared()
23 |
24 | var speed_squared := agent.linear_velocity.length_squared()
25 | var predict_time := predict_time_max
26 |
27 | if speed_squared > 0:
28 | var predict_time_squared := distance_squared / speed_squared
29 | if predict_time_squared < predict_time_max * predict_time_max:
30 | predict_time = sqrt(predict_time_squared)
31 |
32 | acceleration.linear = ((target_position + (target.linear_velocity * predict_time)) - agent.position).normalized()
33 | acceleration.linear *= _get_modified_acceleration()
34 |
35 | acceleration.angular = 0
36 |
37 |
38 | func _get_modified_acceleration() -> float:
39 | return agent.linear_acceleration_max
40 |
--------------------------------------------------------------------------------
/godot/addons/com.gdquest.godot-steering-ai-framework/Behaviors/GSAISeek.gd:
--------------------------------------------------------------------------------
1 | # Calculates an acceleration to take an agent to a target agent's position
2 | # directly.
3 | # @category - Individual behaviors
4 | class_name GSAISeek
5 | extends GSAISteeringBehavior
6 |
7 | # The target that the behavior aims to move the agent to.
8 | var target: GSAIAgentLocation
9 |
10 |
11 | func _init(agent: GSAISteeringAgent, _target: GSAIAgentLocation) -> void:
12 | super._init(agent)
13 | self.target = _target
14 |
15 |
16 | func _calculate_steering(acceleration: GSAITargetAcceleration) -> void:
17 | acceleration.linear = (
18 | (target.position - agent.position).normalized()
19 | * agent.linear_acceleration_max
20 | )
21 | acceleration.angular = 0
22 |
--------------------------------------------------------------------------------
/godot/addons/com.gdquest.godot-steering-ai-framework/Behaviors/GSAISeparation.gd:
--------------------------------------------------------------------------------
1 | # Calculates an acceleration that repels the agent from its neighbors in the
2 | # given `GSAIProximity`.
3 | #
4 | # The acceleration is an average based on all neighbors, multiplied by a
5 | # strength decreasing by the inverse square law in relation to distance, and it
6 | # accumulates.
7 | # @category - Group behaviors
8 | class_name GSAISeparation
9 | extends GSAIGroupBehavior
10 |
11 | # The coefficient to calculate how fast the separation strength decays with distance.
12 | var decay_coefficient := 1.0
13 |
14 | var _acceleration: GSAITargetAcceleration
15 |
16 |
17 | func _init(agent: GSAISteeringAgent, proximity: GSAIProximity) -> void:
18 | super._init(agent, proximity)
19 | pass
20 |
21 |
22 | func _calculate_steering(acceleration: GSAITargetAcceleration) -> void:
23 | acceleration.set_zero()
24 | self._acceleration = acceleration
25 | # warning-ignore:return_value_discarded
26 | proximity._find_neighbors(_callback)
27 |
28 |
29 | # Callback for the proximity to call when finding neighbors. Determines the amount of
30 | # acceleration that `neighbor` imposes based on its distance from the owner agent.
31 | # @tags - virtual
32 | func _report_neighbor(neighbor: GSAISteeringAgent) -> bool:
33 | var to_agent := agent.position - neighbor.position
34 |
35 | var distance_squared := to_agent.length_squared()
36 | var acceleration_max := agent.linear_acceleration_max
37 |
38 | var strength := decay_coefficient / distance_squared
39 | if strength > acceleration_max:
40 | strength = acceleration_max
41 |
42 | _acceleration.linear += to_agent * (strength / sqrt(distance_squared))
43 |
44 | return true
45 |
--------------------------------------------------------------------------------
/godot/addons/com.gdquest.godot-steering-ai-framework/GSAIAgentLocation.gd:
--------------------------------------------------------------------------------
1 | # Represents an agent with only a location and an orientation.
2 | # @category - Base types
3 | class_name GSAIAgentLocation
4 |
5 | # The agent's position in space.
6 | var position := Vector3.ZERO
7 | # The agent's orientation on its Y axis rotation.
8 | var orientation := 0.0
9 |
--------------------------------------------------------------------------------
/godot/addons/com.gdquest.godot-steering-ai-framework/GSAIGroupBehavior.gd:
--------------------------------------------------------------------------------
1 | # Base type for group-based steering behaviors.
2 | # @category - Base types
3 | class_name GSAIGroupBehavior
4 | extends GSAISteeringBehavior
5 |
6 | # Container to find neighbors of the agent and calculate group behavior.
7 | var proximity: GSAIProximity
8 |
9 | var _callback := Callable(self, "_report_neighbor")
10 |
11 |
12 | func _init(agent: GSAISteeringAgent, _proximity: GSAIProximity) -> void:
13 | super._init(agent)
14 | self.proximity = _proximity
15 |
16 |
17 | # Internal callback for the behavior to define whether or not a member is
18 | # relevant
19 | # @tags - virtual
20 | func _report_neighbor(_neighbor: GSAISteeringAgent) -> bool:
21 | return false
22 |
--------------------------------------------------------------------------------
/godot/addons/com.gdquest.godot-steering-ai-framework/GSAIPath.gd:
--------------------------------------------------------------------------------
1 | # Represents a path made up of Vector3 waypoints, split into segments path
2 | # follow behaviors can use.
3 | # @category - Base types
4 | class_name GSAIPath
5 | extends RefCounted
6 |
7 | # If `false`, the path loops.
8 | var is_open: bool
9 | # Total length of the path.
10 | var length: float
11 |
12 | var _segments: Array
13 |
14 | var _nearest_point_on_segment: Vector3
15 | var _nearest_point_on_path: Vector3
16 |
17 |
18 | func _init(waypoints: Array, _is_open := false) -> void:
19 | self.is_open = _is_open
20 | create_path(waypoints)
21 | _nearest_point_on_segment = waypoints[0]
22 | _nearest_point_on_path = waypoints[0]
23 |
24 |
25 | # Creates a path from a list of waypoints.
26 | func create_path(waypoints: Array) -> void:
27 | if not waypoints or waypoints.size() < 2:
28 | printerr("Waypoints cannot be null and must contain at least two (2) waypoints.")
29 | return
30 |
31 | _segments = []
32 | length = 0
33 | var current: Vector3 = waypoints.front()
34 | var previous: Vector3
35 |
36 | for i in range(1, waypoints.size(), 1):
37 | previous = current
38 | if i < waypoints.size():
39 | current = waypoints[i]
40 | elif is_open:
41 | break
42 | else:
43 | current = waypoints[0]
44 | var segment := GSAISegment.new(previous, current)
45 | length += segment.length
46 | segment.cumulative_length = length
47 | _segments.append(segment)
48 |
49 |
50 | # Returns the distance from `agent_current_position` to the next waypoint.
51 | func calculate_distance(agent_current_position: Vector3) -> float:
52 | if _segments.size() == 0:
53 | return 0.0
54 | var smallest_distance_squared: float = INF
55 | var nearest_segment: GSAISegment
56 | for i in range(_segments.size()):
57 | var segment: GSAISegment = _segments[i]
58 | var distance_squared := _calculate_point_segment_distance_squared(
59 | segment.begin, segment.end, agent_current_position
60 | )
61 |
62 | if distance_squared < smallest_distance_squared:
63 | _nearest_point_on_path = _nearest_point_on_segment
64 | smallest_distance_squared = distance_squared
65 | nearest_segment = segment
66 |
67 | var length_on_path := (
68 | nearest_segment.cumulative_length
69 | - _nearest_point_on_path.distance_to(nearest_segment.end)
70 | )
71 |
72 | return length_on_path
73 |
74 |
75 | # Calculates a target position from the path's starting point based on the `target_distance`.
76 | func calculate_target_position(target_distance: float) -> Vector3:
77 | if is_open:
78 | target_distance = clamp(target_distance, 0.0, length)
79 | else:
80 | if target_distance < 0:
81 | target_distance = length + fmod(target_distance, length)
82 | elif target_distance > length:
83 | target_distance = fmod(target_distance, length)
84 |
85 | var desired_segment: GSAISegment
86 | for i in range(_segments.size()):
87 | var segment: GSAISegment = _segments[i]
88 | if segment.cumulative_length >= target_distance:
89 | desired_segment = segment
90 | break
91 |
92 | if not desired_segment:
93 | desired_segment = _segments.back()
94 |
95 | var distance := desired_segment.cumulative_length - target_distance
96 |
97 | return (
98 | ((desired_segment.begin - desired_segment.end) * (distance / desired_segment.length))
99 | + desired_segment.end
100 | )
101 |
102 |
103 | # Returns the position of the first point on the path.
104 | func get_start_point() -> Vector3:
105 | return _segments.front().begin
106 |
107 |
108 | # Returns the position of the last point on the path.
109 | func get_end_point() -> Vector3:
110 | return _segments.back().end
111 |
112 |
113 | func _calculate_point_segment_distance_squared(start: Vector3, end: Vector3, position: Vector3) -> float:
114 | _nearest_point_on_segment = start
115 | var start_end := end - start
116 | var start_end_length_squared := start_end.length_squared()
117 | if start_end_length_squared != 0:
118 | var t = (position - start).dot(start_end) / start_end_length_squared
119 | _nearest_point_on_segment += start_end * clamp(t, 0.0, 1)
120 |
121 | return _nearest_point_on_segment.distance_squared_to(position)
122 |
123 |
124 | class GSAISegment:
125 | var begin: Vector3
126 | var end: Vector3
127 | var length: float
128 | var cumulative_length: float
129 |
130 | func _init(_begin: Vector3, _end: Vector3) -> void:
131 | self.begin = _begin
132 | self.end = _end
133 | length = _begin.distance_to(_end)
134 |
--------------------------------------------------------------------------------
/godot/addons/com.gdquest.godot-steering-ai-framework/GSAISteeringAgent.gd:
--------------------------------------------------------------------------------
1 | # Adds velocity, speed, and size data to `GSAIAgentLocation`.
2 | #
3 | # It is the character's responsibility to keep this information up to date for
4 | # the steering toolkit to work correctly.
5 | # @category - Base types
6 | class_name GSAISteeringAgent
7 | extends GSAIAgentLocation
8 |
9 | # The amount of velocity to be considered effectively not moving.
10 | var zero_linear_speed_threshold := 0.01
11 | # The maximum speed at which the agent can move.
12 | var linear_speed_max := 0.0
13 | # The maximum amount of acceleration that any behavior can apply to the agent.
14 | var linear_acceleration_max := 0.0
15 | # The maximum amount of angular speed at which the agent can rotate.
16 | var angular_speed_max := 0.0
17 | # The maximum amount of angular acceleration that any behavior can apply to an
18 | # agent.
19 | var angular_acceleration_max := 0.0
20 | # Current velocity of the agent.
21 | var linear_velocity := Vector3.ZERO
22 | # Current angular velocity of the agent.
23 | var angular_velocity := 0.0
24 | # The radius of the sphere that approximates the agent's size in space.
25 | var bounding_radius := 0.0
26 | # Used internally by group behaviors and proximities to mark the agent as already
27 | # considered.
28 | var is_tagged := false
29 |
--------------------------------------------------------------------------------
/godot/addons/com.gdquest.godot-steering-ai-framework/GSAISteeringBehavior.gd:
--------------------------------------------------------------------------------
1 | # Base class for all steering behaviors.
2 | #
3 | # Steering behaviors calculate the linear and the angular acceleration to be
4 | # to the agent that owns them.
5 | #
6 | # The `calculate_steering` function is the entry point for all behaviors.
7 | # Individual steering behaviors encapsulate the steering logic.
8 | # @category - Base types
9 | class_name GSAISteeringBehavior
10 |
11 | # If `false`, all calculations return zero amounts of acceleration.
12 | var is_enabled := true
13 | # The AI agent on which the steering behavior bases its calculations.
14 | var agent: GSAISteeringAgent
15 |
16 |
17 | func _init(_agent: GSAISteeringAgent) -> void:
18 | self.agent = _agent
19 |
20 |
21 | # Sets the `acceleration` with the behavior's desired amount of acceleration.
22 | func calculate_steering(acceleration: GSAITargetAcceleration) -> void:
23 | if is_enabled:
24 | _calculate_steering(acceleration)
25 | else:
26 | acceleration.set_zero()
27 |
28 |
29 | func _calculate_steering(acceleration: GSAITargetAcceleration) -> void:
30 | acceleration.set_zero()
31 |
--------------------------------------------------------------------------------
/godot/addons/com.gdquest.godot-steering-ai-framework/GSAITargetAcceleration.gd:
--------------------------------------------------------------------------------
1 | # A desired linear and angular amount of acceleration requested by the steering
2 | # system.
3 | # @category - Base types
4 | class_name GSAITargetAcceleration
5 |
6 | # Linear acceleration
7 | var linear := Vector3.ZERO
8 | # Angular acceleration
9 | var angular := 0.0
10 |
11 |
12 | # Sets the linear and angular components to 0.
13 | func set_zero() -> void:
14 | linear.x = 0.0
15 | linear.y = 0.0
16 | linear.z = 0.0
17 | angular = 0.0
18 |
19 |
20 | # Adds `accel`'s components, multiplied by `scalar`, to this one.
21 | func add_scaled_accel(accel: GSAITargetAcceleration, scalar: float) -> void:
22 | linear += accel.linear * scalar
23 | angular += accel.angular * scalar
24 |
25 |
26 | # Returns the squared magnitude of the linear and angular components.
27 | func get_magnitude_squared() -> float:
28 | return linear.length_squared() + angular * angular
29 |
30 |
31 | # Returns the magnitude of the linear and angular components.
32 | func get_magnitude() -> float:
33 | return sqrt(get_magnitude_squared())
34 |
--------------------------------------------------------------------------------
/godot/addons/com.gdquest.godot-steering-ai-framework/GSAIUtils.gd:
--------------------------------------------------------------------------------
1 | # Math and vector utility functions.
2 | # @Category - Utilities
3 | class_name GSAIUtils
4 |
5 | # Returns the `vector` with its length capped to `limit`.
6 | static func clampedv3(vector: Vector3, limit: float) -> Vector3:
7 | var length_squared := vector.length_squared()
8 | var limit_squared := limit * limit
9 | if length_squared > limit_squared:
10 | vector *= sqrt(limit_squared / length_squared)
11 | return vector
12 |
13 | # Returns an angle in radians between the positive X axis and the `vector`.
14 | #
15 | # This assumes orientation for 3D agents that are upright and rotate
16 | # around the Y axis.
17 | static func vector3_to_angle(vector: Vector3) -> float:
18 | return atan2(vector.x, vector.z)
19 |
20 | # Returns an angle in radians between the positive X axis and the `vector`.
21 | static func vector2_to_angle(vector: Vector2) -> float:
22 | return atan2(vector.x, -vector.y)
23 |
24 | # Returns a directional vector from the given orientation angle.
25 | #
26 | # This assumes orientation for 2D agents or 3D agents that are upright and
27 | # rotate around the Y axis.
28 | static func angle_to_vector2(angle: float) -> Vector2:
29 | return Vector2(sin(-angle), cos(angle))
30 |
31 | # Returns a vector2 with `vector`'s x and y components.
32 | static func to_vector2(vector: Vector3) -> Vector2:
33 | return Vector2(vector.x, vector.y)
34 |
35 | # Returns a vector3 with `vector`'s x and y components and 0 in z.
36 | static func to_vector3(vector: Vector2) -> Vector3:
37 | return Vector3(vector.x, vector.y, 0)
38 |
--------------------------------------------------------------------------------
/godot/addons/com.gdquest.godot-steering-ai-framework/Proximities/GSAIInfiniteProximity.gd:
--------------------------------------------------------------------------------
1 | # Determines any agent that is in the specified list as being neighbors with the
2 | # owner agent, regardless of distance.
3 | # @category - Proximities
4 | class_name GSAIInfiniteProximity
5 | extends GSAIProximity
6 |
7 |
8 | func _init(agent: GSAISteeringAgent, agents: Array) -> void:
9 | super._init(agent, agents)
10 |
11 |
12 | # Returns a number of neighbors based on a `callback` function.
13 | #
14 | # `_find_neighbors` calls `callback` for each agent in the `agents` array and
15 | # adds one to the count if its `callback` returns true.
16 | # @tags - virtual
17 | func _find_neighbors(callback: Callable) -> int:
18 | var neighbor_count := 0
19 | var agent_count := agents.size()
20 | for i in range(agent_count):
21 | var current_agent := agents[i] as GSAISteeringAgent
22 |
23 | if current_agent != agent:
24 | if callback.call(current_agent):
25 | neighbor_count += 1
26 |
27 | return neighbor_count
28 |
--------------------------------------------------------------------------------
/godot/addons/com.gdquest.godot-steering-ai-framework/Proximities/GSAIProximity.gd:
--------------------------------------------------------------------------------
1 | # Base container type that stores data to find the neighbors of an agent.
2 | # @category - Proximities
3 | # @tags - abstract
4 | class_name GSAIProximity
5 | extends RefCounted
6 |
7 | # The owning agent whose neighbors are found in the group
8 | var agent: GSAISteeringAgent
9 | # The agents who are part of this group and could be potential neighbors
10 | var agents := []
11 |
12 |
13 | func _init(_agent: GSAISteeringAgent, _agents: Array) -> void:
14 | self.agent = _agent
15 | self.agents = _agents
16 |
17 |
18 | # Returns a number of neighbors based on a `callback` function.
19 | #
20 | # `_find_neighbors` calls `callback` for each agent in the `agents` array and
21 | # adds one to the count if its `callback` returns true.
22 | # @tags - virtual
23 | func _find_neighbors(_callback: Callable) -> int:
24 | return 0
25 |
--------------------------------------------------------------------------------
/godot/addons/com.gdquest.godot-steering-ai-framework/Proximities/GSAIRadiusProximity.gd:
--------------------------------------------------------------------------------
1 | # Determines any agent that is in the specified list as being neighbors with the owner agent if
2 | # they lie within the specified radius.
3 | # @category - Proximities
4 | class_name GSAIRadiusProximity
5 | extends GSAIProximity
6 |
7 | # The radius around the owning agent to find neighbors in
8 | var radius := 0.0
9 |
10 | var _last_frame := 0
11 | var _scene_tree: SceneTree
12 |
13 |
14 | func _init(agent: GSAISteeringAgent, agents: Array, _radius: float) -> void:
15 | super._init(agent, agents)
16 | self.radius = _radius
17 | _scene_tree = Engine.get_main_loop()
18 |
19 |
20 | # Returns a number of neighbors based on a `callback` function.
21 | #
22 | # `_find_neighbors` calls `callback` for each agent in the `agents` array that lie within
23 | # the radius around the owning agent and adds one to the count if its `callback` returns true.
24 | # @tags - virtual
25 | func _find_neighbors(callback: Callable) -> int:
26 | var agent_count := agents.size()
27 | var neighbor_count := 0
28 |
29 | var current_frame := _scene_tree.get_frame() if _scene_tree else -_last_frame
30 | if current_frame != _last_frame:
31 | _last_frame = current_frame
32 |
33 | var owner_position := agent.position
34 |
35 | for i in range(agent_count):
36 | var current_agent := agents[i] as GSAISteeringAgent
37 |
38 | if current_agent != agent:
39 | var distance_squared := owner_position.distance_squared_to(current_agent.position)
40 |
41 | var range_to := radius + current_agent.bounding_radius
42 |
43 | if distance_squared < range_to * range_to:
44 | if callback.call(current_agent):
45 | current_agent.is_tagged = true
46 | neighbor_count += 1
47 | continue
48 |
49 | current_agent.is_tagged = false
50 | else:
51 | for i in range(agent_count):
52 | var current_agent = agents[i] as GSAISteeringAgent
53 |
54 | if current_agent != agent and current_agent.is_tagged:
55 | if callback.call(current_agent):
56 | neighbor_count += 1
57 |
58 | return neighbor_count
59 |
--------------------------------------------------------------------------------
/godot/assets/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GDQuest/godot-steering-ai-framework/9d7cf0deff805d8f2a5316c33f3c85cffa3376a7/godot/assets/icon.png
--------------------------------------------------------------------------------
/godot/assets/icon.png.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://dq0rkxna1uokj"
6 | path="res://.godot/imported/icon.png-b6a7fb2db36edd3d95dc42f1dc8c1c5d.ctex"
7 | metadata={
8 | "vram_texture": false
9 | }
10 |
11 | [deps]
12 |
13 | source_file="res://assets/icon.png"
14 | dest_files=["res://.godot/imported/icon.png-b6a7fb2db36edd3d95dc42f1dc8c1c5d.ctex"]
15 |
16 | [params]
17 |
18 | compress/mode=0
19 | compress/high_quality=false
20 | compress/lossy_quality=0.7
21 | compress/hdr_compression=1
22 | compress/normal_map=0
23 | compress/channel_pack=0
24 | mipmaps/generate=false
25 | mipmaps/limit=-1
26 | roughness/mode=0
27 | roughness/src_normal=""
28 | process/fix_alpha_border=true
29 | process/premult_alpha=false
30 | process/normal_map_invert_y=false
31 | process/hdr_as_srgb=false
32 | process/hdr_clamp_exposure=false
33 | process/size_limit=0
34 | detect_3d/compress_to=1
35 |
--------------------------------------------------------------------------------
/godot/assets/icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
152 |
--------------------------------------------------------------------------------
/godot/assets/icon.svg.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://cpcwiyms61who"
6 | path="res://.godot/imported/icon.svg-56083ea2a1f1a4f1e49773bdc6d7826c.ctex"
7 | metadata={
8 | "vram_texture": false
9 | }
10 |
11 | [deps]
12 |
13 | source_file="res://assets/icon.svg"
14 | dest_files=["res://.godot/imported/icon.svg-56083ea2a1f1a4f1e49773bdc6d7826c.ctex"]
15 |
16 | [params]
17 |
18 | compress/mode=0
19 | compress/high_quality=false
20 | compress/lossy_quality=0.7
21 | compress/hdr_compression=1
22 | compress/normal_map=0
23 | compress/channel_pack=0
24 | mipmaps/generate=false
25 | mipmaps/limit=-1
26 | roughness/mode=0
27 | roughness/src_normal=""
28 | process/fix_alpha_border=true
29 | process/premult_alpha=false
30 | process/normal_map_invert_y=false
31 | process/hdr_as_srgb=false
32 | process/hdr_clamp_exposure=false
33 | process/size_limit=0
34 | detect_3d/compress_to=1
35 | svg/scale=1.0
36 | editor/scale_with_editor_scale=false
37 | editor/convert_colors_with_editor_theme=false
38 |
--------------------------------------------------------------------------------
/godot/assets/sprites/background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GDQuest/godot-steering-ai-framework/9d7cf0deff805d8f2a5316c33f3c85cffa3376a7/godot/assets/sprites/background.png
--------------------------------------------------------------------------------
/godot/assets/sprites/background.png.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://chqsknldl55hp"
6 | path="res://.godot/imported/background.png-dde469fb1f19281f3784b52d4bea96cd.ctex"
7 | metadata={
8 | "vram_texture": false
9 | }
10 |
11 | [deps]
12 |
13 | source_file="res://assets/sprites/background.png"
14 | dest_files=["res://.godot/imported/background.png-dde469fb1f19281f3784b52d4bea96cd.ctex"]
15 |
16 | [params]
17 |
18 | compress/mode=0
19 | compress/high_quality=false
20 | compress/lossy_quality=0.7
21 | compress/hdr_compression=1
22 | compress/normal_map=0
23 | compress/channel_pack=0
24 | mipmaps/generate=false
25 | mipmaps/limit=-1
26 | roughness/mode=0
27 | roughness/src_normal=""
28 | process/fix_alpha_border=true
29 | process/premult_alpha=false
30 | process/normal_map_invert_y=false
31 | process/hdr_as_srgb=false
32 | process/hdr_clamp_exposure=false
33 | process/size_limit=0
34 | detect_3d/compress_to=1
35 |
--------------------------------------------------------------------------------
/godot/assets/theme/button/disabled.stylebox:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GDQuest/godot-steering-ai-framework/9d7cf0deff805d8f2a5316c33f3c85cffa3376a7/godot/assets/theme/button/disabled.stylebox
--------------------------------------------------------------------------------
/godot/assets/theme/button/focused.stylebox:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GDQuest/godot-steering-ai-framework/9d7cf0deff805d8f2a5316c33f3c85cffa3376a7/godot/assets/theme/button/focused.stylebox
--------------------------------------------------------------------------------
/godot/assets/theme/button/hover.stylebox:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GDQuest/godot-steering-ai-framework/9d7cf0deff805d8f2a5316c33f3c85cffa3376a7/godot/assets/theme/button/hover.stylebox
--------------------------------------------------------------------------------
/godot/assets/theme/button/normal.stylebox:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GDQuest/godot-steering-ai-framework/9d7cf0deff805d8f2a5316c33f3c85cffa3376a7/godot/assets/theme/button/normal.stylebox
--------------------------------------------------------------------------------
/godot/assets/theme/button/pressed.stylebox:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GDQuest/godot-steering-ai-framework/9d7cf0deff805d8f2a5316c33f3c85cffa3376a7/godot/assets/theme/button/pressed.stylebox
--------------------------------------------------------------------------------
/godot/assets/theme/empty.stylebox:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GDQuest/godot-steering-ai-framework/9d7cf0deff805d8f2a5316c33f3c85cffa3376a7/godot/assets/theme/empty.stylebox
--------------------------------------------------------------------------------
/godot/assets/theme/fonts/default_font.tres:
--------------------------------------------------------------------------------
1 | [gd_resource type="FontFile" load_steps=2 format=2]
2 |
3 | [ext_resource path="res://assets/theme/fonts/montserrat/Montserrat-Medium.ttf" type="FontFile" id=1]
4 |
5 | [resource]
6 | size = 26
7 | use_filter = true
8 | font_data = ExtResource( 1 )
9 |
--------------------------------------------------------------------------------
/godot/assets/theme/fonts/default_font_bold.tres:
--------------------------------------------------------------------------------
1 | [gd_resource type="FontFile" load_steps=2 format=2]
2 |
3 | [ext_resource path="res://assets/theme/fonts/montserrat/Montserrat-Bold.ttf" type="FontFile" id=1]
4 |
5 | [resource]
6 | size = 26
7 | use_filter = true
8 | font_data = ExtResource( 1 )
9 |
--------------------------------------------------------------------------------
/godot/assets/theme/fonts/default_font_code.tres:
--------------------------------------------------------------------------------
1 | [gd_resource type="FontFile" load_steps=2 format=2]
2 |
3 | [ext_resource path="res://assets/theme/fonts/source_code_pro/SourceCodePro-Medium.otf" type="FontFile" id=1]
4 |
5 | [resource]
6 | size = 26
7 | use_filter = true
8 | font_data = ExtResource( 1 )
9 |
--------------------------------------------------------------------------------
/godot/assets/theme/fonts/font_title.tres:
--------------------------------------------------------------------------------
1 | [gd_resource type="FontFile" load_steps=2 format=2]
2 |
3 | [ext_resource path="res://assets/theme/fonts/montserrat/Montserrat-Black.ttf" type="FontFile" id=1]
4 |
5 | [resource]
6 | size = 34
7 | use_filter = true
8 | font_data = ExtResource( 1 )
9 |
--------------------------------------------------------------------------------
/godot/assets/theme/fonts/montserrat/Montserrat-Black.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GDQuest/godot-steering-ai-framework/9d7cf0deff805d8f2a5316c33f3c85cffa3376a7/godot/assets/theme/fonts/montserrat/Montserrat-Black.ttf
--------------------------------------------------------------------------------
/godot/assets/theme/fonts/montserrat/Montserrat-Black.ttf.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="font_data_dynamic"
4 | type="FontFile"
5 | uid="uid://pcjxb4kqetvl"
6 | path="res://.godot/imported/Montserrat-Black.ttf-4f24dd70747a5bb4cafbf2886a965dd2.fontdata"
7 |
8 | [deps]
9 |
10 | source_file="res://assets/theme/fonts/montserrat/Montserrat-Black.ttf"
11 | dest_files=["res://.godot/imported/Montserrat-Black.ttf-4f24dd70747a5bb4cafbf2886a965dd2.fontdata"]
12 |
13 | [params]
14 |
15 | Rendering=null
16 | antialiasing=1
17 | generate_mipmaps=false
18 | multichannel_signed_distance_field=false
19 | msdf_pixel_range=8
20 | msdf_size=48
21 | allow_system_fallback=true
22 | force_autohinter=false
23 | hinting=1
24 | subpixel_positioning=1
25 | oversampling=0.0
26 | Fallbacks=null
27 | fallbacks=[]
28 | Compress=null
29 | compress=true
30 | preload=[]
31 | language_support={}
32 | script_support={}
33 | opentype_features={}
34 |
--------------------------------------------------------------------------------
/godot/assets/theme/fonts/montserrat/Montserrat-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GDQuest/godot-steering-ai-framework/9d7cf0deff805d8f2a5316c33f3c85cffa3376a7/godot/assets/theme/fonts/montserrat/Montserrat-Bold.ttf
--------------------------------------------------------------------------------
/godot/assets/theme/fonts/montserrat/Montserrat-Bold.ttf.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="font_data_dynamic"
4 | type="FontFile"
5 | uid="uid://l58dwe6yst4g"
6 | path="res://.godot/imported/Montserrat-Bold.ttf-176851e31e8921676232ab82847873b4.fontdata"
7 |
8 | [deps]
9 |
10 | source_file="res://assets/theme/fonts/montserrat/Montserrat-Bold.ttf"
11 | dest_files=["res://.godot/imported/Montserrat-Bold.ttf-176851e31e8921676232ab82847873b4.fontdata"]
12 |
13 | [params]
14 |
15 | Rendering=null
16 | antialiasing=1
17 | generate_mipmaps=false
18 | multichannel_signed_distance_field=false
19 | msdf_pixel_range=8
20 | msdf_size=48
21 | allow_system_fallback=true
22 | force_autohinter=false
23 | hinting=1
24 | subpixel_positioning=1
25 | oversampling=0.0
26 | Fallbacks=null
27 | fallbacks=[]
28 | Compress=null
29 | compress=true
30 | preload=[]
31 | language_support={}
32 | script_support={}
33 | opentype_features={}
34 |
--------------------------------------------------------------------------------
/godot/assets/theme/fonts/montserrat/Montserrat-Medium.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GDQuest/godot-steering-ai-framework/9d7cf0deff805d8f2a5316c33f3c85cffa3376a7/godot/assets/theme/fonts/montserrat/Montserrat-Medium.ttf
--------------------------------------------------------------------------------
/godot/assets/theme/fonts/montserrat/Montserrat-Medium.ttf.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="font_data_dynamic"
4 | type="FontFile"
5 | uid="uid://bi0y78lvti47v"
6 | path="res://.godot/imported/Montserrat-Medium.ttf-7fc5d430671081632a988a048bbff4a1.fontdata"
7 |
8 | [deps]
9 |
10 | source_file="res://assets/theme/fonts/montserrat/Montserrat-Medium.ttf"
11 | dest_files=["res://.godot/imported/Montserrat-Medium.ttf-7fc5d430671081632a988a048bbff4a1.fontdata"]
12 |
13 | [params]
14 |
15 | Rendering=null
16 | antialiasing=1
17 | generate_mipmaps=false
18 | multichannel_signed_distance_field=false
19 | msdf_pixel_range=8
20 | msdf_size=48
21 | allow_system_fallback=true
22 | force_autohinter=false
23 | hinting=1
24 | subpixel_positioning=1
25 | oversampling=0.0
26 | Fallbacks=null
27 | fallbacks=[]
28 | Compress=null
29 | compress=true
30 | preload=[]
31 | language_support={}
32 | script_support={}
33 | opentype_features={}
34 |
--------------------------------------------------------------------------------
/godot/assets/theme/fonts/source_code_pro/SourceCodePro-Medium.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GDQuest/godot-steering-ai-framework/9d7cf0deff805d8f2a5316c33f3c85cffa3376a7/godot/assets/theme/fonts/source_code_pro/SourceCodePro-Medium.otf
--------------------------------------------------------------------------------
/godot/assets/theme/fonts/source_code_pro/SourceCodePro-Medium.otf.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="font_data_dynamic"
4 | type="FontFile"
5 | uid="uid://b57k738dnna2"
6 | path="res://.godot/imported/SourceCodePro-Medium.otf-ff236cf0f0951c0f62afda9a5cecbc43.fontdata"
7 |
8 | [deps]
9 |
10 | source_file="res://assets/theme/fonts/source_code_pro/SourceCodePro-Medium.otf"
11 | dest_files=["res://.godot/imported/SourceCodePro-Medium.otf-ff236cf0f0951c0f62afda9a5cecbc43.fontdata"]
12 |
13 | [params]
14 |
15 | Rendering=null
16 | antialiasing=1
17 | generate_mipmaps=false
18 | multichannel_signed_distance_field=false
19 | msdf_pixel_range=8
20 | msdf_size=48
21 | allow_system_fallback=true
22 | force_autohinter=false
23 | hinting=1
24 | subpixel_positioning=1
25 | oversampling=0.0
26 | Fallbacks=null
27 | fallbacks=[]
28 | Compress=null
29 | compress=true
30 | preload=[]
31 | language_support={}
32 | script_support={}
33 | opentype_features={}
34 |
--------------------------------------------------------------------------------
/godot/assets/theme/gdquest.theme:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GDQuest/godot-steering-ai-framework/9d7cf0deff805d8f2a5316c33f3c85cffa3376a7/godot/assets/theme/gdquest.theme
--------------------------------------------------------------------------------
/godot/assets/theme/icons/chevron-right.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/godot/assets/theme/icons/chevron-right.svg.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://blkd4jv7up4mq"
6 | path="res://.godot/imported/chevron-right.svg-f77dee7a088177a2ac1d467f4c7cd3e1.ctex"
7 | metadata={
8 | "vram_texture": false
9 | }
10 |
11 | [deps]
12 |
13 | source_file="res://assets/theme/icons/chevron-right.svg"
14 | dest_files=["res://.godot/imported/chevron-right.svg-f77dee7a088177a2ac1d467f4c7cd3e1.ctex"]
15 |
16 | [params]
17 |
18 | compress/mode=0
19 | compress/high_quality=false
20 | compress/lossy_quality=0.7
21 | compress/hdr_compression=1
22 | compress/normal_map=0
23 | compress/channel_pack=0
24 | mipmaps/generate=false
25 | mipmaps/limit=-1
26 | roughness/mode=0
27 | roughness/src_normal=""
28 | process/fix_alpha_border=true
29 | process/premult_alpha=false
30 | process/normal_map_invert_y=false
31 | process/hdr_as_srgb=false
32 | process/hdr_clamp_exposure=false
33 | process/size_limit=0
34 | detect_3d/compress_to=1
35 | svg/scale=1.0
36 | editor/scale_with_editor_scale=false
37 | editor/convert_colors_with_editor_theme=false
38 |
--------------------------------------------------------------------------------
/godot/assets/theme/icons/chevron-up.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/godot/assets/theme/icons/chevron-up.svg.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://vj32yem78krr"
6 | path="res://.godot/imported/chevron-up.svg-48b5b69265734774d0a7516dcc6f0863.ctex"
7 | metadata={
8 | "vram_texture": false
9 | }
10 |
11 | [deps]
12 |
13 | source_file="res://assets/theme/icons/chevron-up.svg"
14 | dest_files=["res://.godot/imported/chevron-up.svg-48b5b69265734774d0a7516dcc6f0863.ctex"]
15 |
16 | [params]
17 |
18 | compress/mode=0
19 | compress/high_quality=false
20 | compress/lossy_quality=0.7
21 | compress/hdr_compression=1
22 | compress/normal_map=0
23 | compress/channel_pack=0
24 | mipmaps/generate=false
25 | mipmaps/limit=-1
26 | roughness/mode=0
27 | roughness/src_normal=""
28 | process/fix_alpha_border=true
29 | process/premult_alpha=false
30 | process/normal_map_invert_y=false
31 | process/hdr_as_srgb=false
32 | process/hdr_clamp_exposure=false
33 | process/size_limit=0
34 | detect_3d/compress_to=1
35 | svg/scale=1.0
36 | editor/scale_with_editor_scale=false
37 | editor/convert_colors_with_editor_theme=false
38 |
--------------------------------------------------------------------------------
/godot/assets/theme/panel/panel.stylebox:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GDQuest/godot-steering-ai-framework/9d7cf0deff805d8f2a5316c33f3c85cffa3376a7/godot/assets/theme/panel/panel.stylebox
--------------------------------------------------------------------------------
/godot/assets/theme/separator/line.tres:
--------------------------------------------------------------------------------
1 | [gd_resource type="StyleBoxLine" format=2]
2 |
3 | [resource]
4 | color = Color( 1, 1, 1, 0.196078 )
5 | thickness = 2
6 |
--------------------------------------------------------------------------------
/godot/assets/theme/slider/grabber_area.stylebox:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GDQuest/godot-steering-ai-framework/9d7cf0deff805d8f2a5316c33f3c85cffa3376a7/godot/assets/theme/slider/grabber_area.stylebox
--------------------------------------------------------------------------------
/godot/assets/theme/slider/slider.stylebox:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GDQuest/godot-steering-ai-framework/9d7cf0deff805d8f2a5316c33f3c85cffa3376a7/godot/assets/theme/slider/slider.stylebox
--------------------------------------------------------------------------------
/godot/project.godot:
--------------------------------------------------------------------------------
1 | ; Engine configuration file.
2 | ; It's best edited using the editor UI and not directly,
3 | ; since the parameters that go here are not all obvious.
4 | ;
5 | ; Format:
6 | ; [section] ; section goes between []
7 | ; param=value ; assign values to parameters
8 |
9 | config_version=5
10 |
11 | [application]
12 |
13 | config/name="Godot Steering AI Framework Demos"
14 | run/main_scene="res://Demos/DemoSelector.tscn"
15 | config/features=PackedStringArray("4.1")
16 | config/icon="res://assets/icon.png"
17 |
18 | [display]
19 |
20 | window/size/viewport_width=1280
21 | window/size/viewport_height=720
22 | window/size/always_on_top=true
23 | window/stretch/mode="2d"
24 | window/stretch/aspect="expand"
25 |
26 | [input]
27 |
28 | sf_left={
29 | "deadzone": 0.5,
30 | "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":65,"physical_keycode":0,"key_label":0,"unicode":0,"echo":false,"script":null)
31 | , Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":0,"axis_value":-1.0,"script":null)
32 | ]
33 | }
34 | sf_right={
35 | "deadzone": 0.5,
36 | "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":68,"physical_keycode":0,"key_label":0,"unicode":0,"echo":false,"script":null)
37 | , Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":0,"axis_value":1.0,"script":null)
38 | ]
39 | }
40 | sf_up={
41 | "deadzone": 0.5,
42 | "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":87,"physical_keycode":0,"key_label":0,"unicode":0,"echo":false,"script":null)
43 | , Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":1,"axis_value":-1.0,"script":null)
44 | ]
45 | }
46 | sf_down={
47 | "deadzone": 0.5,
48 | "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":83,"physical_keycode":0,"key_label":0,"unicode":0,"echo":false,"script":null)
49 | , Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":1,"axis_value":1.0,"script":null)
50 | ]
51 | }
52 | toggle_fullscreen={
53 | "deadzone": 0.5,
54 | "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194342,"physical_keycode":0,"key_label":0,"unicode":0,"echo":false,"script":null)
55 | ]
56 | }
57 | click={
58 | "deadzone": 0.5,
59 | "events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":0,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":1,"canceled":false,"pressed":false,"double_click":false,"script":null)
60 | ]
61 | }
62 |
--------------------------------------------------------------------------------