├── .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 | ![Project banner](./assets/banner.svg) 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 | 17 | 19 | 22 | 29 | 30 | 31 | 55 | 57 | 58 | 60 | image/svg+xml 61 | 63 | 64 | 65 | 66 | 67 | 71 | 78 | 84 | 88 | 93 | 98 | 103 | 108 | 113 | 118 | 123 | 128 | 133 | 138 | 139 | 145 | 150 | 151 | 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 | --------------------------------------------------------------------------------