From ca1839c633fde002b21208379c9bb8730702bf58 Mon Sep 17 00:00:00 2001 From: Francois Belair Date: Thu, 13 Feb 2020 03:33:03 -0500 Subject: [PATCH] Add 3D demo and fix 3D agents Closes #30, closes #31, closes #33 --- CHANGELOG.md | 18 ++++ README.md | 68 +++++++++----- project/demos/Arrive3d/Arrive3dDemo.tscn | 91 ++++++++++++++++++ project/demos/Arrive3d/Camera.gd | 25 +++++ project/demos/Arrive3d/Seek3dDemo.gd | 93 +++++++++++++++++++ project/demos/Arrive3d/Seeker.gd | 48 ++++++++++ project/demos/Arrive3d/SeekerMat.tres | 4 + .../AvoidCollisions/AvoidCollisionsDemo.tscn | 1 + project/demos/AvoidCollisions/Spawner.gd | 5 +- project/demos/FollowPath/Drawer.gd | 2 +- project/demos/SeekFlee/Boundaries.gd | 7 +- project/demos/SeekFlee/SeekFleeDemo.tscn | 8 +- .../src/Agents/GSAIKinematicBody2DAgent.gd | 4 +- .../src/Agents/GSAIKinematicBody3DAgent.gd | 6 +- project/src/Agents/GSAIRigidBody2DAgent.gd | 4 +- project/src/Agents/GSAIRigidBody3DAgent.gd | 6 +- project/src/Behaviors/GSAIFace.gd | 7 +- project/src/Behaviors/GSAILookWhereYouGo.gd | 7 +- project/src/Behaviors/GSAIMatchOrientation.gd | 6 +- project/src/GSAIUtils.gd | 9 +- 20 files changed, 371 insertions(+), 48 deletions(-) create mode 100644 project/demos/Arrive3d/Arrive3dDemo.tscn create mode 100644 project/demos/Arrive3d/Camera.gd create mode 100644 project/demos/Arrive3d/Seek3dDemo.gd create mode 100644 project/demos/Arrive3d/Seeker.gd create mode 100644 project/demos/Arrive3d/SeekerMat.tres diff --git a/CHANGELOG.md b/CHANGELOG.md index ccdc033..137a3bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,24 @@ 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 ## This release brings one new feature and bug fix, and breaking changes to the framework as we renamed all the classes. diff --git a/README.md b/README.md index f72a9a9..fca75bf 100644 --- a/README.md +++ b/README.md @@ -8,10 +8,11 @@ It supports all essential steering behaviors like flee, follow, look at, but als **Table of Contents** - - [Introduction](#introduction) - [The framework](#the-framework) +- [Installation](#installation) - [Getting Started](#getting-started) + - [More information and resources](#more-information-and-resources) - [Example usage](#example-usage) @@ -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. -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. +## 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 ## -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 ### - [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. -- [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. +- [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. ## 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. @@ -74,32 +79,42 @@ var linear_drag := 0.1 var angular_drag := 0.1 # 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 -# GSAISteeringAgent holds our agent's position, orientation, maximum speed and acceleration. -onready var agent := GSAISteeringAgent.new() +# GSAISteeringAgent holds our agent's position, orientation, maximum speed and acceleration. + +onready var agent := GSAISteeringAgent.new() + onready var player: Node = get_tree().get_nodes_in_group("Player")[0] # 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 # 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 -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, # toggling them depending on the agent's health. Since we want the agent to rotate AND move, then # we aim to blend them together. -onready var flee_blend := GSAIBlend.new(agent) -onready var pursue_blend := GSAIBlend.new(agent) +onready var flee_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. -onready var priority := GSAIPriority.new(agent) +onready var priority := GSAIPriority.new(agent) + func _ready() -> void: @@ -114,22 +129,26 @@ func _ready() -> void: # ---------- Configuration for our behaviors ---------- # 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. - var pursue := GSAIPursue.new(agent, player_agent) + var pursue := GSAIPursue.new(agent, player_agent) + pursue.predict_time_max = 1.5 # Flee will happen while the agent is in bad health, so will start disabled. It produces # acceleration that takes the agent directly away from the target with no prediction. - var flee := GSAIFlee.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 # 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 # 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. face.alignment_tolerance = deg2rad(5) # When to start slowing down. @@ -137,7 +156,8 @@ func _ready() -> void: # LookWhereYouGo turns the agent to keep looking towards its direction of travel. It will only # be enabled while the agent is at low health. - var look := GSAILookWhereYouGo.new(agent) + var look := GSAILookWhereYouGo.new(agent) + # How close for the agent to be 'aligned', if not exact. look.alignment_tolerance = deg2rad(5) # When to start slowing down. @@ -175,7 +195,7 @@ func _physics_process(delta: float) -> void: # Calculate the desired 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 = (velocity + Vector2( acceleration.linear.x, acceleration.linear.y) @@ -199,7 +219,7 @@ func _physics_process(delta: float) -> void: 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. func update_agent() -> void: agent.position.x = global_position.x diff --git a/project/demos/Arrive3d/Arrive3dDemo.tscn b/project/demos/Arrive3d/Arrive3dDemo.tscn new file mode 100644 index 0000000..4a4ca42 --- /dev/null +++ b/project/demos/Arrive3d/Arrive3dDemo.tscn @@ -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." diff --git a/project/demos/Arrive3d/Camera.gd b/project/demos/Arrive3d/Camera.gd new file mode 100644 index 0000000..284c755 --- /dev/null +++ b/project/demos/Arrive3d/Camera.gd @@ -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 diff --git a/project/demos/Arrive3d/Seek3dDemo.gd b/project/demos/Arrive3d/Seek3dDemo.gd new file mode 100644 index 0000000..039c780 --- /dev/null +++ b/project/demos/Arrive3d/Seek3dDemo.gd @@ -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 diff --git a/project/demos/Arrive3d/Seeker.gd b/project/demos/Arrive3d/Seeker.gd new file mode 100644 index 0000000..fcc5bb8 --- /dev/null +++ b/project/demos/Arrive3d/Seeker.gd @@ -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) diff --git a/project/demos/Arrive3d/SeekerMat.tres b/project/demos/Arrive3d/SeekerMat.tres new file mode 100644 index 0000000..8a9f1f9 --- /dev/null +++ b/project/demos/Arrive3d/SeekerMat.tres @@ -0,0 +1,4 @@ +[gd_resource type="SpatialMaterial" format=2] + +[resource] +albedo_color = Color( 0.152941, 0.764706, 0.247059, 1 ) diff --git a/project/demos/AvoidCollisions/AvoidCollisionsDemo.tscn b/project/demos/AvoidCollisions/AvoidCollisionsDemo.tscn index 18b1fcb..ba1c2f9 100644 --- a/project/demos/AvoidCollisions/AvoidCollisionsDemo.tscn +++ b/project/demos/AvoidCollisions/AvoidCollisionsDemo.tscn @@ -17,6 +17,7 @@ script = ExtResource( 1 ) avoider_template = ExtResource( 3 ) inner_color = Color( 0.235294, 0.639216, 0.439216, 1 ) outer_color = Color( 0.560784, 0.870588, 0.364706, 1 ) +agent_count = 80 [node name="DemoInterface" parent="." instance=ExtResource( 4 )] text_bbcode = "Avoid Collisions Demo diff --git a/project/demos/AvoidCollisions/Spawner.gd b/project/demos/AvoidCollisions/Spawner.gd index e6c68ec..2a22b85 100644 --- a/project/demos/AvoidCollisions/Spawner.gd +++ b/project/demos/AvoidCollisions/Spawner.gd @@ -4,6 +4,7 @@ extends Node2D export var avoider_template: PackedScene export var inner_color := Color() export var outer_color := Color() +export var agent_count := 60 var boundaries: Vector2 @@ -15,7 +16,7 @@ func _ready() -> void: var rng: = RandomNumberGenerator.new() var avoiders := [] var avoider_agents := [] - for i in range(60): + for i in range(agent_count): var avoider := avoider_template.instance() add_child(avoider) avoider.setup( @@ -33,6 +34,8 @@ func _ready() -> void: avoider.collision.inner_color = inner_color avoider.collision.outer_color = outer_color avoiders.append(avoider) + if i % 10 == 0: + yield(get_tree(), "idle_frame") for child in get_children(): child.set_proximity_agents(avoider_agents) diff --git a/project/demos/FollowPath/Drawer.gd b/project/demos/FollowPath/Drawer.gd index e4a6d34..7b1f586 100644 --- a/project/demos/FollowPath/Drawer.gd +++ b/project/demos/FollowPath/Drawer.gd @@ -29,7 +29,7 @@ func _unhandled_input(event: InputEvent) -> void: func _draw() -> void: if is_drawing: for point in active_points: - draw_circle(point, 1, Color.red) + draw_circle(point, 2, Color.red) else: if active_points.size() > 0: draw_circle(active_points.front(), 2, Color.red) diff --git a/project/demos/SeekFlee/Boundaries.gd b/project/demos/SeekFlee/Boundaries.gd index 83cbce5..b04e1ac 100644 --- a/project/demos/SeekFlee/Boundaries.gd +++ b/project/demos/SeekFlee/Boundaries.gd @@ -6,6 +6,8 @@ const COLOR := Color("8fde5d") func _ready() -> void: get_tree().root.connect("size_changed", self, "_on_SceneTree_size_changed") + _on_SceneTree_size_changed() + func _draw() -> void: @@ -15,7 +17,10 @@ func _draw() -> 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(): var boundary: String = b.name.rsplit("Boundary")[0] match boundary: diff --git a/project/demos/SeekFlee/SeekFleeDemo.tscn b/project/demos/SeekFlee/SeekFleeDemo.tscn index 56d529f..6fe2652 100644 --- a/project/demos/SeekFlee/SeekFleeDemo.tscn +++ b/project/demos/SeekFlee/SeekFleeDemo.tscn @@ -39,7 +39,7 @@ stroke = 4.0 script = ExtResource( 9 ) [node name="LeftBoundary" type="StaticBody2D" parent="Boundaries"] -position = Vector2( 0, 300 ) +position = Vector2( 0, 540 ) collision_layer = 2 collision_mask = 5 @@ -47,7 +47,7 @@ collision_mask = 5 shape = SubResource( 2 ) [node name="RightBoundary" type="StaticBody2D" parent="Boundaries"] -position = Vector2( 1024, 300 ) +position = Vector2( 1920, 540 ) collision_layer = 2 collision_mask = 5 @@ -55,7 +55,7 @@ collision_mask = 5 shape = SubResource( 2 ) [node name="TopBoundary" type="StaticBody2D" parent="Boundaries"] -position = Vector2( 512, 2.00002 ) +position = Vector2( 960, 0 ) collision_layer = 2 collision_mask = 5 @@ -63,7 +63,7 @@ collision_mask = 5 shape = SubResource( 3 ) [node name="BottomBoundary" type="StaticBody2D" parent="Boundaries"] -position = Vector2( 512, 600 ) +position = Vector2( 960, 1080 ) collision_layer = 2 collision_mask = 5 diff --git a/project/src/Agents/GSAIKinematicBody2DAgent.gd b/project/src/Agents/GSAIKinematicBody2DAgent.gd index 4f4c270..502b66f 100644 --- a/project/src/Agents/GSAIKinematicBody2DAgent.gd +++ b/project/src/Agents/GSAIKinematicBody2DAgent.gd @@ -99,8 +99,8 @@ func _set_body(value: KinematicBody2D) -> void: func _on_SceneTree_physics_frame() -> void: - var current_position: Vector2 = body.global_position - var current_orientation: float = body.rotation + var current_position := body.global_position + var current_orientation := body.rotation position = GSAIUtils.to_vector3(current_position) orientation = current_orientation diff --git a/project/src/Agents/GSAIKinematicBody3DAgent.gd b/project/src/Agents/GSAIKinematicBody3DAgent.gd index f696e93..032c104 100644 --- a/project/src/Agents/GSAIKinematicBody3DAgent.gd +++ b/project/src/Agents/GSAIKinematicBody3DAgent.gd @@ -90,7 +90,7 @@ func _apply_orientation_steering(angular_acceleration: float, delta: float) -> v func _set_body(value: KinematicBody) -> void: body = value - _last_position = body.global_position + _last_position = body.transform.origin _last_orientation = body.rotation.y position = _last_position @@ -98,8 +98,8 @@ func _set_body(value: KinematicBody) -> void: func _on_SceneTree_physics_frame() -> void: - var current_position: Vector3 = body.global_position - var current_orientation: float = body.rotation.y + var current_position := body.transform.origin + var current_orientation := body.rotation.y position = current_position orientation = current_orientation diff --git a/project/src/Agents/GSAIRigidBody2DAgent.gd b/project/src/Agents/GSAIRigidBody2DAgent.gd index 9d2cc49..726ab5b 100644 --- a/project/src/Agents/GSAIRigidBody2DAgent.gd +++ b/project/src/Agents/GSAIRigidBody2DAgent.gd @@ -45,8 +45,8 @@ func _on_body_ready() -> void: func _on_SceneTree_frame() -> void: - var current_position: Vector2 = body.global_position - var current_orientation: float = body.rotation + var current_position := body.global_position + var current_orientation := body.rotation position = GSAIUtils.to_vector3(current_position) orientation = current_orientation diff --git a/project/src/Agents/GSAIRigidBody3DAgent.gd b/project/src/Agents/GSAIRigidBody3DAgent.gd index 6b776c9..92f6088 100644 --- a/project/src/Agents/GSAIRigidBody3DAgent.gd +++ b/project/src/Agents/GSAIRigidBody3DAgent.gd @@ -34,7 +34,7 @@ func _apply_steering(acceleration: GSAITargetAcceleration, _delta: float) -> voi func _set_body(value: RigidBody) -> void: body = value - _last_position = body.global_position + _last_position = body.transform.origin _last_orientation = body.rotation.y position = _last_position @@ -48,8 +48,8 @@ func _on_body_ready() -> void: func _on_SceneTree_frame() -> void: - var current_position: Vector3 = body.global_position - var current_orientation: float = body.rotation.y + var current_position := body.transform.origin + var current_orientation := body.rotation.y position = current_position orientation = current_orientation diff --git a/project/src/Behaviors/GSAIFace.gd b/project/src/Behaviors/GSAIFace.gd index 62ba90e..bb49df2 100644 --- a/project/src/Behaviors/GSAIFace.gd +++ b/project/src/Behaviors/GSAIFace.gd @@ -4,7 +4,7 @@ class_name GSAIFace 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 @@ -15,7 +15,10 @@ func _face(acceleration: GSAITargetAcceleration, target_position: Vector3) -> vo if distance_squared < agent.zero_linear_speed_threshold: acceleration.set_zero() 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) diff --git a/project/src/Behaviors/GSAILookWhereYouGo.gd b/project/src/Behaviors/GSAILookWhereYouGo.gd index 42800f3..4a70d09 100644 --- a/project/src/Behaviors/GSAILookWhereYouGo.gd +++ b/project/src/Behaviors/GSAILookWhereYouGo.gd @@ -4,7 +4,7 @@ class_name GSAILookWhereYouGo extends GSAIMatchOrientation -func _init(agent: GSAISteeringAgent).(agent, null) -> void: +func _init(agent: GSAISteeringAgent, use_z := false).(agent, null, use_z) -> void: pass @@ -12,5 +12,8 @@ func _calculate_steering(accel: GSAITargetAcceleration) -> void: if agent.linear_velocity.length_squared() < agent.zero_linear_speed_threshold: accel.set_zero() 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) diff --git a/project/src/Behaviors/GSAIMatchOrientation.gd b/project/src/Behaviors/GSAIMatchOrientation.gd index d6c90fe..57c0a1e 100644 --- a/project/src/Behaviors/GSAIMatchOrientation.gd +++ b/project/src/Behaviors/GSAIMatchOrientation.gd @@ -14,9 +14,13 @@ var alignment_tolerance: float var deceleration_radius: float # The amount of time to reach the target velocity var time_to_reach: float = 0.1 +# Whether to use the X and Z components instead of X and Y components when +# determining angles. X and Z should be used in 3D. +var use_z: bool -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 diff --git a/project/src/GSAIUtils.gd b/project/src/GSAIUtils.gd index c299351..40cd9ad 100644 --- a/project/src/GSAIUtils.gd +++ b/project/src/GSAIUtils.gd @@ -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`. # -# This assumes orientation for 2D agents or 3D agents that are upright and -# rotate around the Y axis. +# This assumes orientation for 3D agents that are upright and rotate +# around the Y axis. static func vector3_to_angle(vector: Vector3) -> float: + return atan2(vector.x, vector.z) + + +# Returns an angle in radians between the positive X axis and the `vector`. +static func vector2_to_angle(vector: Vector2) -> float: return atan2(vector.x, -vector.y)