mirror of
https://github.com/Relintai/godot-steering-ai-framework.git
synced 2024-11-14 04:57:19 +01:00
Add path following toy demo
This commit is contained in:
parent
0a8551e5c9
commit
b325976139
57
project/demos/follow_path/Drawer.gd
Normal file
57
project/demos/follow_path/Drawer.gd
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
extends Node2D
|
||||||
|
|
||||||
|
|
||||||
|
signal path_established(points)
|
||||||
|
|
||||||
|
|
||||||
|
var active_points: = []
|
||||||
|
var drawing: = false
|
||||||
|
var distance_threshold: = 100.0
|
||||||
|
|
||||||
|
|
||||||
|
func _unhandled_input(event: InputEvent) -> void:
|
||||||
|
if event is InputEventMouseMotion:
|
||||||
|
if drawing:
|
||||||
|
active_points.append(event.position)
|
||||||
|
update()
|
||||||
|
elif event is InputEventMouseButton:
|
||||||
|
if event.pressed and event.button_index == BUTTON_LEFT:
|
||||||
|
active_points.clear()
|
||||||
|
active_points.append(event.position)
|
||||||
|
drawing = true
|
||||||
|
update()
|
||||||
|
elif not event.pressed:
|
||||||
|
drawing = false
|
||||||
|
_simplify()
|
||||||
|
|
||||||
|
|
||||||
|
func _draw() -> void:
|
||||||
|
if drawing:
|
||||||
|
for point in active_points:
|
||||||
|
draw_circle(point, 1, Color.red)
|
||||||
|
else:
|
||||||
|
if active_points.size() > 0:
|
||||||
|
draw_circle(active_points.front(), 2, Color.red)
|
||||||
|
draw_circle(active_points.back(), 2, Color.yellow)
|
||||||
|
for i in range(1, active_points.size()):
|
||||||
|
var start: Vector2 = active_points[i-1]
|
||||||
|
var end: Vector2 = active_points[i]
|
||||||
|
draw_line(start, end, Color.skyblue)
|
||||||
|
|
||||||
|
|
||||||
|
func _simplify() -> void:
|
||||||
|
var first: Vector2 = active_points.front()
|
||||||
|
var last: Vector2 = active_points.back()
|
||||||
|
var key: = first
|
||||||
|
var simplified_path: = [first]
|
||||||
|
for i in range(1, active_points.size()):
|
||||||
|
var point: Vector2 = active_points[i]
|
||||||
|
var distance: = point.distance_to(key)
|
||||||
|
if distance > distance_threshold:
|
||||||
|
key = point
|
||||||
|
simplified_path.append(key)
|
||||||
|
active_points = simplified_path
|
||||||
|
if active_points.back() != last:
|
||||||
|
active_points.append(last)
|
||||||
|
update()
|
||||||
|
emit_signal("path_established", active_points)
|
8
project/demos/follow_path/FollowPathDemo.gd
Normal file
8
project/demos/follow_path/FollowPathDemo.gd
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
extends Node2D
|
||||||
|
|
||||||
|
|
||||||
|
onready var drawer: = $Drawer
|
||||||
|
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
$PathFollower.setup()
|
@ -1,3 +1,26 @@
|
|||||||
[gd_scene format=2]
|
[gd_scene load_steps=6 format=2]
|
||||||
|
|
||||||
|
[ext_resource path="res://demos/follow_path/Drawer.gd" type="Script" id=1]
|
||||||
|
[ext_resource path="res://assets/sprites/small_circle.png" type="Texture" id=2]
|
||||||
|
[ext_resource path="res://demos/follow_path/PathFollower.gd" type="Script" id=3]
|
||||||
|
[ext_resource path="res://demos/follow_path/FollowPathDemo.gd" type="Script" id=4]
|
||||||
|
|
||||||
|
[sub_resource type="CircleShape2D" id=1]
|
||||||
|
radius = 16.0
|
||||||
|
|
||||||
[node name="FollowPathDemo" type="Node2D"]
|
[node name="FollowPathDemo" type="Node2D"]
|
||||||
|
script = ExtResource( 4 )
|
||||||
|
|
||||||
|
[node name="Drawer" type="Node2D" parent="."]
|
||||||
|
script = ExtResource( 1 )
|
||||||
|
|
||||||
|
[node name="PathFollower" type="KinematicBody2D" parent="."]
|
||||||
|
position = Vector2( 512, 300 )
|
||||||
|
script = ExtResource( 3 )
|
||||||
|
|
||||||
|
[node name="CollisionShape2D" type="CollisionShape2D" parent="PathFollower"]
|
||||||
|
shape = SubResource( 1 )
|
||||||
|
|
||||||
|
[node name="Sprite" type="Sprite" parent="PathFollower"]
|
||||||
|
modulate = Color( 0.960784, 0.231373, 0.0392157, 1 )
|
||||||
|
texture = ExtResource( 2 )
|
||||||
|
43
project/demos/follow_path/PathFollower.gd
Normal file
43
project/demos/follow_path/PathFollower.gd
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
extends KinematicBody2D
|
||||||
|
|
||||||
|
|
||||||
|
onready var agent: = GSTSteeringAgent.new()
|
||||||
|
onready var path: = GSTPath.new([
|
||||||
|
Vector3(global_position.x, global_position.y, 0),
|
||||||
|
Vector3(global_position.x, global_position.y, 0)
|
||||||
|
], true)
|
||||||
|
onready var follow: = GSTFollowPath.new(agent, path, 20, 0)
|
||||||
|
|
||||||
|
var _velocity: = Vector2.ZERO
|
||||||
|
var _accel: = GSTTargetAcceleration.new()
|
||||||
|
var _valid: = false
|
||||||
|
|
||||||
|
|
||||||
|
func setup() -> void:
|
||||||
|
owner.drawer.connect("path_established", self, "_on_Drawer_path_established")
|
||||||
|
agent.max_linear_acceleration = 20
|
||||||
|
agent.max_linear_speed = 200
|
||||||
|
|
||||||
|
|
||||||
|
func _physics_process(delta: float) -> void:
|
||||||
|
if _valid:
|
||||||
|
_update_agent()
|
||||||
|
_accel = follow.calculate_steering(_accel)
|
||||||
|
_velocity += Vector2(_accel.linear.x, _accel.linear.y)
|
||||||
|
_velocity = _velocity.clamped(agent.max_linear_speed)
|
||||||
|
_velocity = move_and_slide(_velocity)
|
||||||
|
|
||||||
|
|
||||||
|
func _update_agent() -> void:
|
||||||
|
agent.position.x = global_position.x
|
||||||
|
agent.position.y = global_position.y
|
||||||
|
agent.linear_velocity.x = _velocity.x
|
||||||
|
agent.linear_velocity.y = _velocity.y
|
||||||
|
|
||||||
|
|
||||||
|
func _on_Drawer_path_established(points: Array) -> void:
|
||||||
|
var points3: = []
|
||||||
|
for p in points:
|
||||||
|
points3.append(Vector3(p.x, p.y, 0))
|
||||||
|
path.create_path(points3)
|
||||||
|
_valid = true
|
@ -161,10 +161,6 @@ _global_script_class_icons={
|
|||||||
config/name="SteeringToolkit"
|
config/name="SteeringToolkit"
|
||||||
config/icon="res://icon.png"
|
config/icon="res://icon.png"
|
||||||
|
|
||||||
[display]
|
|
||||||
|
|
||||||
window/size/always_on_top=true
|
|
||||||
|
|
||||||
[input]
|
[input]
|
||||||
|
|
||||||
sf_left={
|
sf_left={
|
||||||
|
@ -7,7 +7,7 @@ class_name GSTPath
|
|||||||
|
|
||||||
|
|
||||||
var open: bool
|
var open: bool
|
||||||
var path_length: float
|
var length: float
|
||||||
|
|
||||||
var _segments: Array
|
var _segments: Array
|
||||||
|
|
||||||
@ -16,7 +16,7 @@ var _nearest_point_on_path: Vector3
|
|||||||
|
|
||||||
|
|
||||||
func _init(waypoints: Array, is_open: = false) -> void:
|
func _init(waypoints: Array, is_open: = false) -> void:
|
||||||
self.is_open = is_open
|
open = is_open
|
||||||
create_path(waypoints)
|
create_path(waypoints)
|
||||||
_nearest_point_on_segment = waypoints[0]
|
_nearest_point_on_segment = waypoints[0]
|
||||||
_nearest_point_on_path = waypoints[0]
|
_nearest_point_on_path = waypoints[0]
|
||||||
@ -25,10 +25,11 @@ func _init(waypoints: Array, is_open: = false) -> void:
|
|||||||
func create_path(waypoints: Array) -> void:
|
func create_path(waypoints: Array) -> void:
|
||||||
if not waypoints or waypoints.size() < 2:
|
if not waypoints or waypoints.size() < 2:
|
||||||
printerr("Waypoints cannot be null and must contain at least two (2) waypoints.")
|
printerr("Waypoints cannot be null and must contain at least two (2) waypoints.")
|
||||||
|
return
|
||||||
|
|
||||||
_segments = []
|
_segments = []
|
||||||
path_length = 0
|
length = 0
|
||||||
var current: Vector3 = _segments[0]
|
var current: Vector3 = waypoints.front()
|
||||||
var previous: Vector3
|
var previous: Vector3
|
||||||
|
|
||||||
for i in range(1, waypoints.size(), 1):
|
for i in range(1, waypoints.size(), 1):
|
||||||
@ -38,14 +39,16 @@ func create_path(waypoints: Array) -> void:
|
|||||||
elif open:
|
elif open:
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
current = waypoints[0]
|
current = waypoints.front()
|
||||||
var segment: = GSTSegment.new(previous, current)
|
var segment: = GSTSegment.new(previous, current)
|
||||||
path_length += segment.length
|
length += segment.length
|
||||||
segment.cumulative_length = path_length
|
segment.cumulative_length = length
|
||||||
_segments.append(segment)
|
_segments.append(segment)
|
||||||
|
|
||||||
|
|
||||||
func calculate_distance(agent_current_position: Vector3, path_parameter: Dictionary) -> float:
|
func calculate_distance(agent_current_position: Vector3, path_parameter: Dictionary) -> float:
|
||||||
|
if _segments.size() == 0:
|
||||||
|
return 0.0
|
||||||
var smallest_distance_squared: float = INF
|
var smallest_distance_squared: float = INF
|
||||||
var nearest_segment: GSTSegment
|
var nearest_segment: GSTSegment
|
||||||
for i in range(_segments.size()):
|
for i in range(_segments.size()):
|
||||||
@ -53,7 +56,8 @@ func calculate_distance(agent_current_position: Vector3, path_parameter: Diction
|
|||||||
var distance_squared: = _calculate_point_segment_distance_squared(
|
var distance_squared: = _calculate_point_segment_distance_squared(
|
||||||
segment.begin,
|
segment.begin,
|
||||||
segment.end,
|
segment.end,
|
||||||
agent_current_position)
|
agent_current_position
|
||||||
|
)
|
||||||
|
|
||||||
if distance_squared < smallest_distance_squared:
|
if distance_squared < smallest_distance_squared:
|
||||||
_nearest_point_on_path = _nearest_point_on_segment
|
_nearest_point_on_path = _nearest_point_on_segment
|
||||||
@ -72,12 +76,12 @@ func calculate_distance(agent_current_position: Vector3, path_parameter: Diction
|
|||||||
|
|
||||||
func calculate_target_position(param: Dictionary, target_distance: float) -> Vector3:
|
func calculate_target_position(param: Dictionary, target_distance: float) -> Vector3:
|
||||||
if open:
|
if open:
|
||||||
target_distance = clamp(target_distance, 0, path_length)
|
target_distance = clamp(target_distance, 0, length)
|
||||||
else:
|
else:
|
||||||
if target_distance < 0:
|
if target_distance < 0:
|
||||||
target_distance = path_length + fmod(target_distance, path_length)
|
target_distance = length + fmod(target_distance, length)
|
||||||
elif target_distance > path_length:
|
elif target_distance > length:
|
||||||
target_distance = fmod(target_distance, path_length)
|
target_distance = fmod(target_distance, length)
|
||||||
|
|
||||||
var desired_segment: GSTSegment
|
var desired_segment: GSTSegment
|
||||||
for i in range(_segments.size()):
|
for i in range(_segments.size()):
|
||||||
@ -86,6 +90,9 @@ func calculate_target_position(param: Dictionary, target_distance: float) -> Vec
|
|||||||
desired_segment = segment
|
desired_segment = segment
|
||||||
break
|
break
|
||||||
|
|
||||||
|
if not desired_segment:
|
||||||
|
desired_segment = _segments.back()
|
||||||
|
|
||||||
var distance: = desired_segment.cumulative_length - target_distance
|
var distance: = desired_segment.cumulative_length - target_distance
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -6,7 +6,7 @@ class_name GSTFollowPath
|
|||||||
var path: GSTPath
|
var path: GSTPath
|
||||||
var path_offset: = 0.0
|
var path_offset: = 0.0
|
||||||
|
|
||||||
var path_param: = {}
|
var path_param: = {segment_index = 0, distance = 0}
|
||||||
|
|
||||||
var arrive_enabled: = true
|
var arrive_enabled: = true
|
||||||
var prediction_time: = 0.0
|
var prediction_time: = 0.0
|
||||||
@ -32,7 +32,7 @@ func _calculate_steering(acceleration: GSTTargetAcceleration) -> GSTTargetAccele
|
|||||||
|
|
||||||
var target_position: = path.calculate_target_position(path_param, target_distance)
|
var target_position: = path.calculate_target_position(path_param, target_distance)
|
||||||
|
|
||||||
if arrive_enabled and path.is_open:
|
if arrive_enabled and path.open:
|
||||||
if path_offset >= 0:
|
if path_offset >= 0:
|
||||||
if target_distance > path.length - deceleration_radius:
|
if target_distance > path.length - deceleration_radius:
|
||||||
return _arrive(acceleration, target_position)
|
return _arrive(acceleration, target_position)
|
||||||
|
Loading…
Reference in New Issue
Block a user