mirror of
https://github.com/Relintai/godot-steering-ai-framework.git
synced 2024-12-24 05:37:15 +01:00
parent
534ade7282
commit
ca1839c633
18
CHANGELOG.md
18
CHANGELOG.md
@ -2,6 +2,24 @@
|
|||||||
|
|
||||||
This document lists new features, improvements, changes, and bug fixes in every release of the add-on.
|
This document lists new features, improvements, changes, and bug fixes in every release of the add-on.
|
||||||
|
|
||||||
|
## Godot Steering AI Framework 2.1.0 ##
|
||||||
|
|
||||||
|
### Features ###
|
||||||
|
|
||||||
|
- There is now a `Arrive3d` demo for a 3D usecase
|
||||||
|
|
||||||
|
### Changes ###
|
||||||
|
|
||||||
|
- `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.
|
||||||
|
- `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.
|
||||||
|
- The README now mentions a simple way to install the framework.
|
||||||
|
- Exposed `agent_count` inside the `AvoidCollisionsDemo`.
|
||||||
|
|
||||||
|
### Bug fixes ###
|
||||||
|
|
||||||
|
- Fixed `GSAIKinematicBody3DAgent` and `GSAIRigidBody3DAgent` trying to use `global_position` instead of `transform.origin`.
|
||||||
|
- The `SeekFleeDemo`'s boundaries will now match the size of the screen.
|
||||||
|
|
||||||
## Godot Steering AI Framework 2.0.0 ##
|
## Godot Steering AI Framework 2.0.0 ##
|
||||||
|
|
||||||
This release brings one new feature and bug fix, and breaking changes to the framework as we renamed all the classes.
|
This release brings one new feature and bug fix, and breaking changes to the framework as we renamed all the classes.
|
||||||
|
38
README.md
38
README.md
@ -8,10 +8,11 @@ It supports all essential steering behaviors like flee, follow, look at, but als
|
|||||||
|
|
||||||
<!-- markdown-toc start - Don't edit this section. Run M-x markdown-toc-refresh-toc -->
|
<!-- markdown-toc start - Don't edit this section. Run M-x markdown-toc-refresh-toc -->
|
||||||
**Table of Contents**
|
**Table of Contents**
|
||||||
|
|
||||||
- [Introduction](#introduction)
|
- [Introduction](#introduction)
|
||||||
- [The framework](#the-framework)
|
- [The framework](#the-framework)
|
||||||
|
- [Installation](#installation)
|
||||||
- [Getting Started](#getting-started)
|
- [Getting Started](#getting-started)
|
||||||
|
- [More information and resources](#more-information-and-resources)
|
||||||
- [Example usage](#example-usage)
|
- [Example usage](#example-usage)
|
||||||
<!-- markdown-toc end -->
|
<!-- markdown-toc end -->
|
||||||
|
|
||||||
@ -28,23 +29,27 @@ Joining these systems together can give complex and graceful movement while also
|
|||||||
|
|
||||||
This project is a framework for the [Godot game engine](https://godotengine.org/). It takes a lot of inspiration from the excellent [GDX-AI](https://github.com/libgdx/gdx-ai) framework for the [LibGDX](https://libgdx.badlogicgames.com/) java-based framework.
|
This project is a framework for the [Godot game engine](https://godotengine.org/). It takes a lot of inspiration from the excellent [GDX-AI](https://github.com/libgdx/gdx-ai) framework for the [LibGDX](https://libgdx.badlogicgames.com/) java-based framework.
|
||||||
|
|
||||||
Every class in the toolkit is based on Godot's [Reference](https://docs.godotengine.org/en/latest/classes/class_reference.html) type. There is no need to have a complex scene tree; everything that has to do with the AI's movement can be contained inside movement-oriented classes.
|
Every class in the framework is based on Godot's [Reference](https://docs.godotengine.org/en/latest/classes/class_reference.html) type. There is no need to have a complex scene tree; everything that has to do with the AI's movement can be contained inside movement-oriented classes.
|
||||||
|
|
||||||
As a short overview, a character is represented by a steering agent; it stores its position, orientation, maximum speeds and current velocity. A steering behavior is associated with a steering agent and calculates a linear and/or angular change in velocity based on its information. The coder then applies that acceleration in whatever ways is appropriate to the character to change its velocity, like RigidBody's apply_impulse, or a KinematicBody's move_and_slide.
|
As a short overview, a character is represented by a steering agent; it stores its position, orientation, maximum speeds and current velocity. A steering behavior is associated with a steering agent and calculates a linear and/or angular change in velocity based on its information. The coder then applies that acceleration in whatever ways is appropriate to the character to change its velocity, like RigidBody's apply_impulse, or a KinematicBody's move_and_slide.
|
||||||
|
|
||||||
|
## Installation ##
|
||||||
|
|
||||||
|
The cleanest way to install the framework onto your project is to take the `/src/` folder and copy it into your project. I recommend something like `res://systems/godot-steering-framework/`.
|
||||||
|
|
||||||
## Getting Started ##
|
## Getting Started ##
|
||||||
|
|
||||||
The framework's documentation and code reference are both available on the [GDQuest](https://www.gdquest.com/docs/godot-steering-toolkit/getting-started/) website's documents.
|
The framework's documentation and code reference are both available on the [GDQuest](https://www.gdquest.com/docs/godot-steering-ai-framework/getting-started) website's documents.
|
||||||
|
|
||||||
### More information and resources ###
|
### More information and resources ###
|
||||||
|
|
||||||
- [Understanding Steering Behaviors](https://gamedevelopment.tutsplus.com/series/understanding-steering-behaviors--gamedev-12732): Breakdowns of various behaviors by Fernando Bevilacqua with graphics and in-depth explanations.
|
- [Understanding Steering Behaviors](https://gamedevelopment.tutsplus.com/series/understanding-steering-behaviors--gamedev-12732): Breakdowns of various behaviors by Fernando Bevilacqua with graphics and in-depth explanations.
|
||||||
- [GDX-AI Wiki](https://github.com/libgdx/gdx-ai/wiki/Steering-Behaviors): Descriptions of how LibGDX's AI submodule uses steering behaviors with a few graphics. Since this toolkit uses it for inspiration, there will be some similarities.
|
- [GDX-AI Wiki](https://github.com/libgdx/gdx-ai/wiki/Steering-Behaviors): Descriptions of how LibGDX's AI submodule uses steering behaviors with a few graphics. Since this framework uses it for inspiration, there will be some similarities.
|
||||||
- [RedBlobGames](https://www.redblobgames.com/) - An excellent resources for complex pathfinding like A\*, graph theory, and other algorithms that are game-development related. Steering behaviors are not covered, but for anyone looking to study and bulk up on their algorithms, this is a great place.
|
- [RedBlobGames](https://www.redblobgames.com/): An excellent resources for complex pathfinding like A\*, graph theory, and other algorithms that are game-development related. Steering behaviors are not covered, but for anyone looking to study and bulk up on their algorithms, this is a great place.
|
||||||
|
|
||||||
## Example usage ##
|
## Example usage ##
|
||||||
|
|
||||||
The fastest way to get started is to look at a sample class that makes use of the toolkit.
|
The fastest way to get started is to look at a sample class that makes use of the framework.
|
||||||
|
|
||||||
The goal of this class is to show how an agent can chase a player and predict where the player *will* be while also maintaining a distance from them. When the agent’s health is low, it will flee from the player directly. The agent will keep facing the player while it’s chasing them, but will look where it's going while it’s fleeing.
|
The goal of this class is to show how an agent can chase a player and predict where the player *will* be while also maintaining a distance from them. When the agent’s health is low, it will flee from the player directly. The agent will keep facing the player while it’s chasing them, but will look where it's going while it’s fleeing.
|
||||||
|
|
||||||
@ -76,32 +81,42 @@ var angular_drag := 0.1
|
|||||||
# Holds the linear and angular components calculated by our steering behaviors.
|
# Holds the linear and angular components calculated by our steering behaviors.
|
||||||
var acceleration := GSAITargetAcceleration.new()
|
var acceleration := GSAITargetAcceleration.new()
|
||||||
|
|
||||||
|
|
||||||
onready var current_health := health_max
|
onready var current_health := health_max
|
||||||
|
|
||||||
# GSAISteeringAgent holds our agent's position, orientation, maximum speed and acceleration.
|
# GSAISteeringAgent holds our agent's position, orientation, maximum speed and acceleration.
|
||||||
|
|
||||||
onready var agent := GSAISteeringAgent.new()
|
onready var agent := GSAISteeringAgent.new()
|
||||||
|
|
||||||
|
|
||||||
onready var player: Node = get_tree().get_nodes_in_group("Player")[0]
|
onready var player: Node = get_tree().get_nodes_in_group("Player")[0]
|
||||||
# This assumes that our player class will keep its own agent updated.
|
# This assumes that our player class will keep its own agent updated.
|
||||||
onready var player_agent: GSAISteeringAgent = player.agent
|
onready var player_agent: GSAISteeringAgent = player.agent
|
||||||
|
|
||||||
|
|
||||||
# Proximities represent an area with which an agent can identify where neighbors in its relevant
|
# Proximities represent an area with which an agent can identify where neighbors in its relevant
|
||||||
# group are. In our case, the group will feature the player, which will be used to avoid a
|
# group are. In our case, the group will feature the player, which will be used to avoid a
|
||||||
# collision with them. We use a radius proximity so the player is only relevant inside 100 pixels
|
# collision with them. We use a radius proximity so the player is only relevant inside 100 pixels
|
||||||
onready var proximity := GSAIRadiusProximity.new(agent, [player_agent], 100).
|
onready var proximity := GSAIRadiusProximity.new(agent, [player_agent], 100).
|
||||||
|
|
||||||
|
|
||||||
# GSAIBlend combines behaviors together, calculating all of their acceleration together and adding
|
# GSAIBlend combines behaviors together, calculating all of their acceleration together and adding
|
||||||
|
|
||||||
# them together, multiplied by a strength. We will have one for fleeing, and one for pursuing,
|
# them together, multiplied by a strength. We will have one for fleeing, and one for pursuing,
|
||||||
# toggling them depending on the agent's health. Since we want the agent to rotate AND move, then
|
# toggling them depending on the agent's health. Since we want the agent to rotate AND move, then
|
||||||
# we aim to blend them together.
|
# we aim to blend them together.
|
||||||
onready var flee_blend := GSAIBlend.new(agent)
|
onready var flee_blend := GSAIBlend.new(agent)
|
||||||
|
|
||||||
onready var pursue_blend := GSAIBlend.new(agent)
|
onready var pursue_blend := GSAIBlend.new(agent)
|
||||||
|
|
||||||
|
|
||||||
# GSAIPriority will be the main steering behavior we use. It holds sub-behaviors and will pick the
|
# GSAIPriority will be the main steering behavior we use. It holds sub-behaviors and will pick the
|
||||||
|
|
||||||
# first one that returns non-zero acceleration, ignoring any afterwards.
|
# first one that returns non-zero acceleration, ignoring any afterwards.
|
||||||
onready var priority := GSAIPriority.new(agent)
|
onready var priority := GSAIPriority.new(agent)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
# ---------- Configuration for our agent ----------
|
# ---------- Configuration for our agent ----------
|
||||||
agent.linear_speed_max = speed_max
|
agent.linear_speed_max = speed_max
|
||||||
@ -115,21 +130,25 @@ func _ready() -> void:
|
|||||||
# Pursue will happen while the player is in good health. It produces acceleration that takes
|
# Pursue will happen while the player is in good health. It produces acceleration that takes
|
||||||
# the agent on an intercept course with the target, predicting its position in the future.
|
# the agent on an intercept course with the target, predicting its position in the future.
|
||||||
var pursue := GSAIPursue.new(agent, player_agent)
|
var pursue := GSAIPursue.new(agent, player_agent)
|
||||||
|
|
||||||
pursue.predict_time_max = 1.5
|
pursue.predict_time_max = 1.5
|
||||||
|
|
||||||
# Flee will happen while the agent is in bad health, so will start disabled. It produces
|
# Flee will happen while the agent is in bad health, so will start disabled. It produces
|
||||||
# acceleration that takes the agent directly away from the target with no prediction.
|
# acceleration that takes the agent directly away from the target with no prediction.
|
||||||
var flee := GSAIFlee.new(agent, player_agent)
|
var flee := GSAIFlee.new(agent, player_agent)
|
||||||
|
|
||||||
|
|
||||||
# AvoidCollision tries to keep the agent from running into any of the neighbors found in its
|
# AvoidCollision tries to keep the agent from running into any of the neighbors found in its
|
||||||
# proximity group. In our case, this will be the player if they are close enough.
|
# proximity group. In our case, this will be the player if they are close enough.
|
||||||
var avoid := GSAIAvoidCollisions.new(agent, proximity)
|
var avoid := GSAIAvoidCollisions.new(agent, proximity)
|
||||||
|
|
||||||
|
|
||||||
# Face turns the agent to keep looking towards its target. It will be enabled while the agent
|
# Face turns the agent to keep looking towards its target. It will be enabled while the agent
|
||||||
# is not fleeing due to low health. It tries to arrive 'on alignment' with 0 remaining velocity.
|
# is not fleeing due to low health. It tries to arrive 'on alignment' with 0 remaining velocity.
|
||||||
var face := GSAIFace.new(agent, player_agent)
|
var face := GSAIFace.new(agent, player_agent)
|
||||||
|
|
||||||
# We use deg2rad because the math in the toolkit assumes radians.
|
|
||||||
|
# We use deg2rad because the math in the framework assumes radians.
|
||||||
# How close for the agent to be 'aligned', if not exact.
|
# How close for the agent to be 'aligned', if not exact.
|
||||||
face.alignment_tolerance = deg2rad(5)
|
face.alignment_tolerance = deg2rad(5)
|
||||||
# When to start slowing down.
|
# When to start slowing down.
|
||||||
@ -138,6 +157,7 @@ func _ready() -> void:
|
|||||||
# LookWhereYouGo turns the agent to keep looking towards its direction of travel. It will only
|
# LookWhereYouGo turns the agent to keep looking towards its direction of travel. It will only
|
||||||
# be enabled while the agent is at low health.
|
# be enabled while the agent is at low health.
|
||||||
var look := GSAILookWhereYouGo.new(agent)
|
var look := GSAILookWhereYouGo.new(agent)
|
||||||
|
|
||||||
# How close for the agent to be 'aligned', if not exact.
|
# How close for the agent to be 'aligned', if not exact.
|
||||||
look.alignment_tolerance = deg2rad(5)
|
look.alignment_tolerance = deg2rad(5)
|
||||||
# When to start slowing down.
|
# When to start slowing down.
|
||||||
@ -175,7 +195,7 @@ func _physics_process(delta: float) -> void:
|
|||||||
# Calculate the desired acceleration.
|
# Calculate the desired acceleration.
|
||||||
priority.calculate_steering(acceleration)
|
priority.calculate_steering(acceleration)
|
||||||
|
|
||||||
# We add the discovered acceleration to our linear velocity. The toolkit does not limit
|
# We add the discovered acceleration to our linear velocity. The framework does not limit
|
||||||
# velocity, just acceleration, so we clamp the result ourselves here.
|
# velocity, just acceleration, so we clamp the result ourselves here.
|
||||||
velocity = (velocity + Vector2(
|
velocity = (velocity + Vector2(
|
||||||
acceleration.linear.x, acceleration.linear.y)
|
acceleration.linear.x, acceleration.linear.y)
|
||||||
@ -199,7 +219,7 @@ func _physics_process(delta: float) -> void:
|
|||||||
rotation += angular_velocity * delta
|
rotation += angular_velocity * delta
|
||||||
|
|
||||||
|
|
||||||
# In order to support both 2D and 3D, the toolkit uses Vector3, so the conversion is required
|
# In order to support both 2D and 3D, the framework uses Vector3, so the conversion is required
|
||||||
# when using 2D nodes. The Z component can be left to 0 safely.
|
# when using 2D nodes. The Z component can be left to 0 safely.
|
||||||
func update_agent() -> void:
|
func update_agent() -> void:
|
||||||
agent.position.x = global_position.x
|
agent.position.x = global_position.x
|
||||||
|
91
project/demos/Arrive3d/Arrive3dDemo.tscn
Normal file
91
project/demos/Arrive3d/Arrive3dDemo.tscn
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
[gd_scene load_steps=14 format=2]
|
||||||
|
|
||||||
|
[ext_resource path="res://demos/Utils/DemoInterface.tscn" type="PackedScene" id=1]
|
||||||
|
[ext_resource path="res://demos/Arrive3d/Camera.gd" type="Script" id=2]
|
||||||
|
[ext_resource path="res://demos/Arrive3d/Seek3dDemo.gd" type="Script" id=3]
|
||||||
|
[ext_resource path="res://demos/Arrive3d/Seeker.gd" type="Script" id=4]
|
||||||
|
[ext_resource path="res://demos/Arrive3d/SeekerMat.tres" type="Material" id=5]
|
||||||
|
|
||||||
|
[sub_resource type="CapsuleShape" id=1]
|
||||||
|
|
||||||
|
[sub_resource type="CapsuleMesh" id=2]
|
||||||
|
|
||||||
|
[sub_resource type="CubeMesh" id=3]
|
||||||
|
material = ExtResource( 5 )
|
||||||
|
size = Vector3( 0.5, 0.5, 1 )
|
||||||
|
|
||||||
|
[sub_resource type="CylinderMesh" id=4]
|
||||||
|
top_radius = 2.0
|
||||||
|
bottom_radius = 2.0
|
||||||
|
height = 0.1
|
||||||
|
|
||||||
|
[sub_resource type="SpatialMaterial" id=5]
|
||||||
|
albedo_color = Color( 0.945098, 0.85098, 0.0745098, 1 )
|
||||||
|
|
||||||
|
[sub_resource type="BoxShape" id=6]
|
||||||
|
extents = Vector3( 1000, 0.1, 1000 )
|
||||||
|
|
||||||
|
[sub_resource type="PlaneMesh" id=7]
|
||||||
|
size = Vector2( 250, 250 )
|
||||||
|
|
||||||
|
[sub_resource type="SpatialMaterial" id=8]
|
||||||
|
albedo_color = Color( 0.0941176, 0.235294, 0.486275, 1 )
|
||||||
|
|
||||||
|
[node name="Arrive3dDemo" type="Node"]
|
||||||
|
script = ExtResource( 3 )
|
||||||
|
|
||||||
|
[node name="Arriver" type="KinematicBody" parent="."]
|
||||||
|
script = ExtResource( 4 )
|
||||||
|
|
||||||
|
[node name="CollisionShape" type="CollisionShape" parent="Arriver"]
|
||||||
|
transform = Transform( 1, 0, 0, 0, -1.62921e-07, 1, 0, -1, -1.62921e-07, 0, 1.5, 0 )
|
||||||
|
shape = SubResource( 1 )
|
||||||
|
|
||||||
|
[node name="Capsule" type="MeshInstance" parent="Arriver"]
|
||||||
|
transform = Transform( 1, 0, 0, 0, -1.62921e-07, 1, 0, -1, -1.62921e-07, 0, 1.5, 0 )
|
||||||
|
mesh = SubResource( 2 )
|
||||||
|
material/0 = ExtResource( 5 )
|
||||||
|
|
||||||
|
[node name="Nose" type="MeshInstance" parent="Arriver"]
|
||||||
|
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2, 1.25 )
|
||||||
|
mesh = SubResource( 3 )
|
||||||
|
material/0 = null
|
||||||
|
|
||||||
|
[node name="Camera" type="Camera" parent="."]
|
||||||
|
transform = Transform( 0.989952, 0.0720094, -0.121693, 0.0339305, 0.714503, 0.69881, 0.137271, -0.695917, 0.70488, -7.68317, 14.1265, 25.616 )
|
||||||
|
current = true
|
||||||
|
script = ExtResource( 2 )
|
||||||
|
|
||||||
|
[node name="RayCast" type="RayCast" parent="Camera"]
|
||||||
|
enabled = true
|
||||||
|
cast_to = Vector3( -627, 200, -777 )
|
||||||
|
collision_mask = 2
|
||||||
|
|
||||||
|
[node name="MouseTarget" type="Spatial" parent="."]
|
||||||
|
transform = Transform( 1, 0, 7.45058e-09, 0, 1, 0, 7.45058e-09, 0, 1, -4.76837e-07, 9.53674e-07, 1.90735e-06 )
|
||||||
|
|
||||||
|
[node name="MeshInstance" type="MeshInstance" parent="MouseTarget"]
|
||||||
|
mesh = SubResource( 4 )
|
||||||
|
material/0 = SubResource( 5 )
|
||||||
|
|
||||||
|
[node name="StaticBody" type="StaticBody" parent="."]
|
||||||
|
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.1, 0 )
|
||||||
|
collision_layer = 2
|
||||||
|
collision_mask = 2
|
||||||
|
|
||||||
|
[node name="CollisionShape" type="CollisionShape" parent="StaticBody"]
|
||||||
|
shape = SubResource( 6 )
|
||||||
|
|
||||||
|
[node name="Ground" type="MeshInstance" parent="."]
|
||||||
|
mesh = SubResource( 7 )
|
||||||
|
material/0 = SubResource( 8 )
|
||||||
|
|
||||||
|
[node name="DirectionalLight" type="DirectionalLight" parent="."]
|
||||||
|
transform = Transform( -0.588165, 0.462179, -0.663666, -0.804031, -0.245728, 0.541436, 0.087159, 0.852061, 0.516134, -17.6076, 12.1748, 0 )
|
||||||
|
light_energy = 0.5
|
||||||
|
shadow_enabled = true
|
||||||
|
|
||||||
|
[node name="DemoInterface" parent="." instance=ExtResource( 1 )]
|
||||||
|
mouse_filter = 2
|
||||||
|
text_bbcode = "3D Arrive Demo
|
||||||
|
Move the mouse about the field to have the agent turn towards and smoothly arrive at the target marker."
|
25
project/demos/Arrive3d/Camera.gd
Normal file
25
project/demos/Arrive3d/Camera.gd
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
extends Camera
|
||||||
|
|
||||||
|
|
||||||
|
var target: Spatial
|
||||||
|
|
||||||
|
onready var ray := $RayCast
|
||||||
|
|
||||||
|
|
||||||
|
func _unhandled_input(event: InputEvent) -> void:
|
||||||
|
if event is InputEventMouseMotion:
|
||||||
|
_set_target_position(event.position)
|
||||||
|
|
||||||
|
|
||||||
|
func setup(_target: Spatial) -> void:
|
||||||
|
self.target = _target
|
||||||
|
_set_target_position(get_viewport().get_mouse_position())
|
||||||
|
|
||||||
|
|
||||||
|
func _set_target_position(position: Vector2) -> void:
|
||||||
|
var to = project_local_ray_normal(position) * 10000
|
||||||
|
ray.cast_to = to
|
||||||
|
ray.force_raycast_update()
|
||||||
|
if ray.is_colliding():
|
||||||
|
var point = ray.get_collision_point()
|
||||||
|
target.transform.origin = point
|
93
project/demos/Arrive3d/Seek3dDemo.gd
Normal file
93
project/demos/Arrive3d/Seek3dDemo.gd
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
extends Node
|
||||||
|
|
||||||
|
|
||||||
|
export(float, 0, 50, 0.1) var linear_speed_max := 10.0 setget set_linear_speed_max
|
||||||
|
export(float, 0, 50, 0.1) var linear_acceleration_max := 1.0 setget set_linear_acceleration_max
|
||||||
|
export(float, 0, 50, 0.1) var arrival_tolerance := 0.5 setget set_arrival_tolerance
|
||||||
|
export(float, 0, 50, 0.1) var deceleration_radius := 5.0 setget set_deceleration_radius
|
||||||
|
export(int, 0, 359, 2) var angular_speed_max := 270 setget set_angular_speed_max
|
||||||
|
export(int, 0, 359, 2) var angular_accel_max := 45 setget set_angular_accel_max
|
||||||
|
export(int, 0, 180, 2) var align_tolerance := 5 setget set_align_tolerance
|
||||||
|
export(int, 0, 359, 2) var angular_deceleration_radius := 45 setget set_angular_deceleration_radius
|
||||||
|
|
||||||
|
onready var target := $MouseTarget
|
||||||
|
onready var arriver := $Arriver
|
||||||
|
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
arriver.setup(
|
||||||
|
deg2rad(align_tolerance),
|
||||||
|
deg2rad(angular_deceleration_radius),
|
||||||
|
deg2rad(angular_accel_max),
|
||||||
|
deg2rad(angular_speed_max),
|
||||||
|
deceleration_radius,
|
||||||
|
arrival_tolerance,
|
||||||
|
linear_acceleration_max,
|
||||||
|
linear_speed_max,
|
||||||
|
target
|
||||||
|
)
|
||||||
|
$Camera.setup(target)
|
||||||
|
|
||||||
|
|
||||||
|
func set_align_tolerance(value: int) -> void:
|
||||||
|
align_tolerance = value
|
||||||
|
if not is_inside_tree():
|
||||||
|
return
|
||||||
|
|
||||||
|
arriver.face.alignment_tolerance = deg2rad(value)
|
||||||
|
|
||||||
|
|
||||||
|
func set_angular_deceleration_radius(value: int) -> void:
|
||||||
|
deceleration_radius = value
|
||||||
|
if not is_inside_tree():
|
||||||
|
return
|
||||||
|
|
||||||
|
arriver.face.deceleration_radius = deg2rad(value)
|
||||||
|
|
||||||
|
|
||||||
|
func set_angular_accel_max(value: int) -> void:
|
||||||
|
angular_accel_max = value
|
||||||
|
if not is_inside_tree():
|
||||||
|
return
|
||||||
|
|
||||||
|
arriver.agent.angular_acceleration_max = deg2rad(value)
|
||||||
|
|
||||||
|
|
||||||
|
func set_angular_speed_max(value: int) -> void:
|
||||||
|
angular_speed_max = value
|
||||||
|
if not is_inside_tree():
|
||||||
|
return
|
||||||
|
|
||||||
|
arriver.agent.angular_speed_max = deg2rad(value)
|
||||||
|
|
||||||
|
|
||||||
|
func set_arrival_tolerance(value: float) -> void:
|
||||||
|
arrival_tolerance = value
|
||||||
|
if not is_inside_tree():
|
||||||
|
return
|
||||||
|
|
||||||
|
arriver.arrive.arrival_tolerance = value
|
||||||
|
|
||||||
|
|
||||||
|
func set_deceleration_radius(value: float) -> void:
|
||||||
|
deceleration_radius = value
|
||||||
|
if not is_inside_tree():
|
||||||
|
return
|
||||||
|
|
||||||
|
arriver.arrive.deceleration_radius = value
|
||||||
|
|
||||||
|
|
||||||
|
func set_linear_speed_max(value: float) -> void:
|
||||||
|
linear_speed_max = value
|
||||||
|
if not is_inside_tree():
|
||||||
|
return
|
||||||
|
|
||||||
|
arriver.agent.linear_speed_max = value
|
||||||
|
|
||||||
|
|
||||||
|
func set_linear_acceleration_max(value: float) -> void:
|
||||||
|
linear_acceleration_max = value
|
||||||
|
if not is_inside_tree():
|
||||||
|
return
|
||||||
|
|
||||||
|
arriver.agent.linear_acceleration_max = value
|
48
project/demos/Arrive3d/Seeker.gd
Normal file
48
project/demos/Arrive3d/Seeker.gd
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
extends KinematicBody
|
||||||
|
|
||||||
|
|
||||||
|
var target_node: Spatial
|
||||||
|
|
||||||
|
onready var agent := GSAIKinematicBody3DAgent.new(self)
|
||||||
|
onready var target := GSAIAgentLocation.new()
|
||||||
|
onready var accel := GSAITargetAcceleration.new()
|
||||||
|
onready var blend := GSAIBlend.new(agent)
|
||||||
|
onready var face := GSAIFace.new(agent, target, true)
|
||||||
|
onready var arrive := GSAIArrive.new(agent, target)
|
||||||
|
|
||||||
|
|
||||||
|
func _physics_process(delta: float) -> void:
|
||||||
|
target.position = target_node.transform.origin
|
||||||
|
target.position.y = transform.origin.y
|
||||||
|
blend.calculate_steering(accel)
|
||||||
|
agent._apply_steering(accel, delta)
|
||||||
|
|
||||||
|
|
||||||
|
func setup(
|
||||||
|
align_tolerance: float,
|
||||||
|
angular_deceleration_radius: float,
|
||||||
|
angular_accel_max: float,
|
||||||
|
angular_speed_max: float,
|
||||||
|
deceleration_radius: float,
|
||||||
|
arrival_tolerance: float,
|
||||||
|
linear_acceleration_max: float,
|
||||||
|
linear_speed_max: float,
|
||||||
|
_target: Spatial
|
||||||
|
) -> void:
|
||||||
|
agent.linear_speed_max = linear_speed_max
|
||||||
|
agent.linear_acceleration_max = linear_acceleration_max
|
||||||
|
agent.linear_drag_percentage = 0.05
|
||||||
|
agent.angular_acceleration_max = angular_accel_max
|
||||||
|
agent.angular_speed_max = angular_speed_max
|
||||||
|
agent.angular_drag_percentage = 0.1
|
||||||
|
|
||||||
|
arrive.arrival_tolerance = arrival_tolerance
|
||||||
|
arrive.deceleration_radius = deceleration_radius
|
||||||
|
|
||||||
|
face.alignment_tolerance = align_tolerance
|
||||||
|
face.deceleration_radius = angular_deceleration_radius
|
||||||
|
|
||||||
|
target_node = _target
|
||||||
|
self.target.position = target_node.transform.origin
|
||||||
|
blend.add(arrive, 1)
|
||||||
|
blend.add(face, 1)
|
4
project/demos/Arrive3d/SeekerMat.tres
Normal file
4
project/demos/Arrive3d/SeekerMat.tres
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
[gd_resource type="SpatialMaterial" format=2]
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
albedo_color = Color( 0.152941, 0.764706, 0.247059, 1 )
|
@ -17,6 +17,7 @@ script = ExtResource( 1 )
|
|||||||
avoider_template = ExtResource( 3 )
|
avoider_template = ExtResource( 3 )
|
||||||
inner_color = Color( 0.235294, 0.639216, 0.439216, 1 )
|
inner_color = Color( 0.235294, 0.639216, 0.439216, 1 )
|
||||||
outer_color = Color( 0.560784, 0.870588, 0.364706, 1 )
|
outer_color = Color( 0.560784, 0.870588, 0.364706, 1 )
|
||||||
|
agent_count = 80
|
||||||
|
|
||||||
[node name="DemoInterface" parent="." instance=ExtResource( 4 )]
|
[node name="DemoInterface" parent="." instance=ExtResource( 4 )]
|
||||||
text_bbcode = "Avoid Collisions Demo
|
text_bbcode = "Avoid Collisions Demo
|
||||||
|
@ -4,6 +4,7 @@ extends Node2D
|
|||||||
export var avoider_template: PackedScene
|
export var avoider_template: PackedScene
|
||||||
export var inner_color := Color()
|
export var inner_color := Color()
|
||||||
export var outer_color := Color()
|
export var outer_color := Color()
|
||||||
|
export var agent_count := 60
|
||||||
|
|
||||||
var boundaries: Vector2
|
var boundaries: Vector2
|
||||||
|
|
||||||
@ -15,7 +16,7 @@ func _ready() -> void:
|
|||||||
var rng: = RandomNumberGenerator.new()
|
var rng: = RandomNumberGenerator.new()
|
||||||
var avoiders := []
|
var avoiders := []
|
||||||
var avoider_agents := []
|
var avoider_agents := []
|
||||||
for i in range(60):
|
for i in range(agent_count):
|
||||||
var avoider := avoider_template.instance()
|
var avoider := avoider_template.instance()
|
||||||
add_child(avoider)
|
add_child(avoider)
|
||||||
avoider.setup(
|
avoider.setup(
|
||||||
@ -33,6 +34,8 @@ func _ready() -> void:
|
|||||||
avoider.collision.inner_color = inner_color
|
avoider.collision.inner_color = inner_color
|
||||||
avoider.collision.outer_color = outer_color
|
avoider.collision.outer_color = outer_color
|
||||||
avoiders.append(avoider)
|
avoiders.append(avoider)
|
||||||
|
if i % 10 == 0:
|
||||||
|
yield(get_tree(), "idle_frame")
|
||||||
for child in get_children():
|
for child in get_children():
|
||||||
child.set_proximity_agents(avoider_agents)
|
child.set_proximity_agents(avoider_agents)
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ func _unhandled_input(event: InputEvent) -> void:
|
|||||||
func _draw() -> void:
|
func _draw() -> void:
|
||||||
if is_drawing:
|
if is_drawing:
|
||||||
for point in active_points:
|
for point in active_points:
|
||||||
draw_circle(point, 1, Color.red)
|
draw_circle(point, 2, Color.red)
|
||||||
else:
|
else:
|
||||||
if active_points.size() > 0:
|
if active_points.size() > 0:
|
||||||
draw_circle(active_points.front(), 2, Color.red)
|
draw_circle(active_points.front(), 2, Color.red)
|
||||||
|
@ -6,6 +6,8 @@ const COLOR := Color("8fde5d")
|
|||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
get_tree().root.connect("size_changed", self, "_on_SceneTree_size_changed")
|
get_tree().root.connect("size_changed", self, "_on_SceneTree_size_changed")
|
||||||
|
_on_SceneTree_size_changed()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
func _draw() -> void:
|
func _draw() -> void:
|
||||||
@ -15,7 +17,10 @@ func _draw() -> void:
|
|||||||
|
|
||||||
|
|
||||||
func _on_SceneTree_size_changed() -> void:
|
func _on_SceneTree_size_changed() -> void:
|
||||||
var size := get_tree().root.size
|
var size := Vector2(
|
||||||
|
ProjectSettings["display/window/size/width"],
|
||||||
|
ProjectSettings["display/window/size/height"]
|
||||||
|
)
|
||||||
for b in get_children():
|
for b in get_children():
|
||||||
var boundary: String = b.name.rsplit("Boundary")[0]
|
var boundary: String = b.name.rsplit("Boundary")[0]
|
||||||
match boundary:
|
match boundary:
|
||||||
|
@ -39,7 +39,7 @@ stroke = 4.0
|
|||||||
script = ExtResource( 9 )
|
script = ExtResource( 9 )
|
||||||
|
|
||||||
[node name="LeftBoundary" type="StaticBody2D" parent="Boundaries"]
|
[node name="LeftBoundary" type="StaticBody2D" parent="Boundaries"]
|
||||||
position = Vector2( 0, 300 )
|
position = Vector2( 0, 540 )
|
||||||
collision_layer = 2
|
collision_layer = 2
|
||||||
collision_mask = 5
|
collision_mask = 5
|
||||||
|
|
||||||
@ -47,7 +47,7 @@ collision_mask = 5
|
|||||||
shape = SubResource( 2 )
|
shape = SubResource( 2 )
|
||||||
|
|
||||||
[node name="RightBoundary" type="StaticBody2D" parent="Boundaries"]
|
[node name="RightBoundary" type="StaticBody2D" parent="Boundaries"]
|
||||||
position = Vector2( 1024, 300 )
|
position = Vector2( 1920, 540 )
|
||||||
collision_layer = 2
|
collision_layer = 2
|
||||||
collision_mask = 5
|
collision_mask = 5
|
||||||
|
|
||||||
@ -55,7 +55,7 @@ collision_mask = 5
|
|||||||
shape = SubResource( 2 )
|
shape = SubResource( 2 )
|
||||||
|
|
||||||
[node name="TopBoundary" type="StaticBody2D" parent="Boundaries"]
|
[node name="TopBoundary" type="StaticBody2D" parent="Boundaries"]
|
||||||
position = Vector2( 512, 2.00002 )
|
position = Vector2( 960, 0 )
|
||||||
collision_layer = 2
|
collision_layer = 2
|
||||||
collision_mask = 5
|
collision_mask = 5
|
||||||
|
|
||||||
@ -63,7 +63,7 @@ collision_mask = 5
|
|||||||
shape = SubResource( 3 )
|
shape = SubResource( 3 )
|
||||||
|
|
||||||
[node name="BottomBoundary" type="StaticBody2D" parent="Boundaries"]
|
[node name="BottomBoundary" type="StaticBody2D" parent="Boundaries"]
|
||||||
position = Vector2( 512, 600 )
|
position = Vector2( 960, 1080 )
|
||||||
collision_layer = 2
|
collision_layer = 2
|
||||||
collision_mask = 5
|
collision_mask = 5
|
||||||
|
|
||||||
|
@ -99,8 +99,8 @@ func _set_body(value: KinematicBody2D) -> void:
|
|||||||
|
|
||||||
|
|
||||||
func _on_SceneTree_physics_frame() -> void:
|
func _on_SceneTree_physics_frame() -> void:
|
||||||
var current_position: Vector2 = body.global_position
|
var current_position := body.global_position
|
||||||
var current_orientation: float = body.rotation
|
var current_orientation := body.rotation
|
||||||
|
|
||||||
position = GSAIUtils.to_vector3(current_position)
|
position = GSAIUtils.to_vector3(current_position)
|
||||||
orientation = current_orientation
|
orientation = current_orientation
|
||||||
|
@ -90,7 +90,7 @@ func _apply_orientation_steering(angular_acceleration: float, delta: float) -> v
|
|||||||
func _set_body(value: KinematicBody) -> void:
|
func _set_body(value: KinematicBody) -> void:
|
||||||
body = value
|
body = value
|
||||||
|
|
||||||
_last_position = body.global_position
|
_last_position = body.transform.origin
|
||||||
_last_orientation = body.rotation.y
|
_last_orientation = body.rotation.y
|
||||||
|
|
||||||
position = _last_position
|
position = _last_position
|
||||||
@ -98,8 +98,8 @@ func _set_body(value: KinematicBody) -> void:
|
|||||||
|
|
||||||
|
|
||||||
func _on_SceneTree_physics_frame() -> void:
|
func _on_SceneTree_physics_frame() -> void:
|
||||||
var current_position: Vector3 = body.global_position
|
var current_position := body.transform.origin
|
||||||
var current_orientation: float = body.rotation.y
|
var current_orientation := body.rotation.y
|
||||||
|
|
||||||
position = current_position
|
position = current_position
|
||||||
orientation = current_orientation
|
orientation = current_orientation
|
||||||
|
@ -45,8 +45,8 @@ func _on_body_ready() -> void:
|
|||||||
|
|
||||||
|
|
||||||
func _on_SceneTree_frame() -> void:
|
func _on_SceneTree_frame() -> void:
|
||||||
var current_position: Vector2 = body.global_position
|
var current_position := body.global_position
|
||||||
var current_orientation: float = body.rotation
|
var current_orientation := body.rotation
|
||||||
|
|
||||||
position = GSAIUtils.to_vector3(current_position)
|
position = GSAIUtils.to_vector3(current_position)
|
||||||
orientation = current_orientation
|
orientation = current_orientation
|
||||||
|
@ -34,7 +34,7 @@ func _apply_steering(acceleration: GSAITargetAcceleration, _delta: float) -> voi
|
|||||||
func _set_body(value: RigidBody) -> void:
|
func _set_body(value: RigidBody) -> void:
|
||||||
body = value
|
body = value
|
||||||
|
|
||||||
_last_position = body.global_position
|
_last_position = body.transform.origin
|
||||||
_last_orientation = body.rotation.y
|
_last_orientation = body.rotation.y
|
||||||
|
|
||||||
position = _last_position
|
position = _last_position
|
||||||
@ -48,8 +48,8 @@ func _on_body_ready() -> void:
|
|||||||
|
|
||||||
|
|
||||||
func _on_SceneTree_frame() -> void:
|
func _on_SceneTree_frame() -> void:
|
||||||
var current_position: Vector3 = body.global_position
|
var current_position := body.transform.origin
|
||||||
var current_orientation: float = body.rotation.y
|
var current_orientation := body.rotation.y
|
||||||
|
|
||||||
position = current_position
|
position = current_position
|
||||||
orientation = current_orientation
|
orientation = current_orientation
|
||||||
|
@ -4,7 +4,7 @@ class_name GSAIFace
|
|||||||
extends GSAIMatchOrientation
|
extends GSAIMatchOrientation
|
||||||
|
|
||||||
|
|
||||||
func _init(agent: GSAISteeringAgent, target: GSAIAgentLocation).(agent, target) -> void:
|
func _init(agent: GSAISteeringAgent, target: GSAIAgentLocation, use_z := false).(agent, target, use_z) -> void:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@ -15,7 +15,10 @@ func _face(acceleration: GSAITargetAcceleration, target_position: Vector3) -> vo
|
|||||||
if distance_squared < agent.zero_linear_speed_threshold:
|
if distance_squared < agent.zero_linear_speed_threshold:
|
||||||
acceleration.set_zero()
|
acceleration.set_zero()
|
||||||
else:
|
else:
|
||||||
var orientation = GSAIUtils.vector3_to_angle(to_target)
|
var orientation = (GSAIUtils.vector3_to_angle(to_target)
|
||||||
|
if use_z
|
||||||
|
else GSAIUtils.vector2_to_angle(GSAIUtils.to_vector2(to_target))
|
||||||
|
)
|
||||||
_match_orientation(acceleration, orientation)
|
_match_orientation(acceleration, orientation)
|
||||||
|
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ class_name GSAILookWhereYouGo
|
|||||||
extends GSAIMatchOrientation
|
extends GSAIMatchOrientation
|
||||||
|
|
||||||
|
|
||||||
func _init(agent: GSAISteeringAgent).(agent, null) -> void:
|
func _init(agent: GSAISteeringAgent, use_z := false).(agent, null, use_z) -> void:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@ -12,5 +12,8 @@ func _calculate_steering(accel: GSAITargetAcceleration) -> void:
|
|||||||
if agent.linear_velocity.length_squared() < agent.zero_linear_speed_threshold:
|
if agent.linear_velocity.length_squared() < agent.zero_linear_speed_threshold:
|
||||||
accel.set_zero()
|
accel.set_zero()
|
||||||
else:
|
else:
|
||||||
var orientation := GSAIUtils.vector3_to_angle(agent.linear_velocity)
|
var orientation := (GSAIUtils.vector3_to_angle(agent.linear_velocity)
|
||||||
|
if use_z
|
||||||
|
else GSAIUtils.vector2_to_angle(GSAIUtils.to_vector2(agent.linear_velocity))
|
||||||
|
)
|
||||||
_match_orientation(accel, orientation)
|
_match_orientation(accel, orientation)
|
||||||
|
@ -14,9 +14,13 @@ var alignment_tolerance: float
|
|||||||
var deceleration_radius: float
|
var deceleration_radius: float
|
||||||
# The amount of time to reach the target velocity
|
# The amount of time to reach the target velocity
|
||||||
var time_to_reach: float = 0.1
|
var time_to_reach: float = 0.1
|
||||||
|
# Whether to use the X and Z components instead of X and Y components when
|
||||||
|
# determining angles. X and Z should be used in 3D.
|
||||||
|
var use_z: bool
|
||||||
|
|
||||||
|
|
||||||
func _init(agent: GSAISteeringAgent, _target: GSAIAgentLocation).(agent) -> void:
|
func _init(agent: GSAISteeringAgent, _target: GSAIAgentLocation, _use_z := false).(agent) -> void:
|
||||||
|
self.use_z = _use_z
|
||||||
self.target = _target
|
self.target = _target
|
||||||
|
|
||||||
|
|
||||||
|
@ -13,9 +13,14 @@ static func clampedv3(vector: Vector3, limit: float) -> Vector3:
|
|||||||
|
|
||||||
# Returns an angle in radians between the positive X axis and the `vector`.
|
# Returns an angle in radians between the positive X axis and the `vector`.
|
||||||
#
|
#
|
||||||
# This assumes orientation for 2D agents or 3D agents that are upright and
|
# This assumes orientation for 3D agents that are upright and rotate
|
||||||
# rotate around the Y axis.
|
# around the Y axis.
|
||||||
static func vector3_to_angle(vector: Vector3) -> float:
|
static func vector3_to_angle(vector: Vector3) -> float:
|
||||||
|
return atan2(vector.x, vector.z)
|
||||||
|
|
||||||
|
|
||||||
|
# Returns an angle in radians between the positive X axis and the `vector`.
|
||||||
|
static func vector2_to_angle(vector: Vector2) -> float:
|
||||||
return atan2(vector.x, -vector.y)
|
return atan2(vector.x, -vector.y)
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user