mirror of
https://github.com/Relintai/godot-steering-ai-framework.git
synced 2024-11-10 00:52:10 +01:00
Clean up and improve group behaviors' code
The first pass was somewhat obtuse and lacked a common class that differentiated them from behaviors that didn't use Proximities.
This commit is contained in:
parent
780e6038b8
commit
a01f5d5b2e
@ -19,7 +19,7 @@ _global_script_classes=[ {
|
||||
"language": "GDScript",
|
||||
"path": "res://src/behaviors/GSTArrive.gd"
|
||||
}, {
|
||||
"base": "GSTSteeringBehavior",
|
||||
"base": "GSTGroupBehavior",
|
||||
"class": "GSTAvoidCollisions",
|
||||
"language": "GDScript",
|
||||
"path": "res://src/behaviors/GSTAvoidCollisions.gd"
|
||||
@ -29,7 +29,7 @@ _global_script_classes=[ {
|
||||
"language": "GDScript",
|
||||
"path": "res://src/behaviors/GSTBlend.gd"
|
||||
}, {
|
||||
"base": "GSTSteeringBehavior",
|
||||
"base": "GSTGroupBehavior",
|
||||
"class": "GSTCohesion",
|
||||
"language": "GDScript",
|
||||
"path": "res://src/behaviors/GSTCohesion.gd"
|
||||
@ -54,6 +54,11 @@ _global_script_classes=[ {
|
||||
"language": "GDScript",
|
||||
"path": "res://src/behaviors/GSTFollowPath.gd"
|
||||
}, {
|
||||
"base": "GSTSteeringBehavior",
|
||||
"class": "GSTGroupBehavior",
|
||||
"language": "GDScript",
|
||||
"path": "res://src/GSTGroupBehavior.gd"
|
||||
}, {
|
||||
"base": "GSTProximity",
|
||||
"class": "GSTInfiniteProximity",
|
||||
"language": "GDScript",
|
||||
@ -99,7 +104,7 @@ _global_script_classes=[ {
|
||||
"language": "GDScript",
|
||||
"path": "res://src/behaviors/GSTSeek.gd"
|
||||
}, {
|
||||
"base": "GSTSteeringBehavior",
|
||||
"base": "GSTGroupBehavior",
|
||||
"class": "GSTSeparation",
|
||||
"language": "GDScript",
|
||||
"path": "res://src/behaviors/GSTSeparation.gd"
|
||||
@ -120,9 +125,9 @@ _global_script_classes=[ {
|
||||
"path": "res://src/GSTTargetAcceleration.gd"
|
||||
}, {
|
||||
"base": "Reference",
|
||||
"class": "Utils",
|
||||
"class": "GSTUtils",
|
||||
"language": "GDScript",
|
||||
"path": "res://src/Utils.gd"
|
||||
"path": "res://src/GSTUtils.gd"
|
||||
} ]
|
||||
_global_script_class_icons={
|
||||
"GSTAgentLocation": "",
|
||||
@ -134,6 +139,7 @@ _global_script_class_icons={
|
||||
"GSTFace": "",
|
||||
"GSTFlee": "",
|
||||
"GSTFollowPath": "",
|
||||
"GSTGroupBehavior": "",
|
||||
"GSTInfiniteProximity": "",
|
||||
"GSTLookWhereYouGo": "",
|
||||
"GSTMatchOrientation": "",
|
||||
@ -147,7 +153,7 @@ _global_script_class_icons={
|
||||
"GSTSteeringAgent": "",
|
||||
"GSTSteeringBehavior": "",
|
||||
"GSTTargetAcceleration": "",
|
||||
"Utils": ""
|
||||
"GSTUtils": ""
|
||||
}
|
||||
|
||||
[application]
|
||||
|
16
project/src/GSTGroupBehavior.gd
Normal file
16
project/src/GSTGroupBehavior.gd
Normal file
@ -0,0 +1,16 @@
|
||||
extends GSTSteeringBehavior
|
||||
class_name GSTGroupBehavior
|
||||
# Extended behavior that features a Proximity group for group-based behaviors.
|
||||
|
||||
|
||||
var proximity: GSTProximity
|
||||
|
||||
var _callback: = funcref(self, "report_neighbor")
|
||||
|
||||
|
||||
func _init(agent: GSTSteeringAgent, proximity: GSTProximity).(agent) -> void:
|
||||
self.proximity = proximity
|
||||
|
||||
|
||||
func report_neighbor(neighbor: GSTSteeringAgent) -> bool:
|
||||
return false
|
@ -1,60 +1,69 @@
|
||||
extends Reference
|
||||
class_name GSTPath
|
||||
# Represents a path made up of Vector3 waypoints, split into path segments for use by path
|
||||
# following algorithms.
|
||||
|
||||
# # Keeping it updated requires calling `create_path` to update the path.
|
||||
|
||||
|
||||
var segments: Array
|
||||
var is_open: bool
|
||||
var open: bool
|
||||
var path_length: float
|
||||
|
||||
var nearest_point_on_segment: Vector3
|
||||
var nearest_point_on_path: Vector3
|
||||
var _segments: Array
|
||||
|
||||
var _nearest_point_on_segment: Vector3
|
||||
var _nearest_point_on_path: Vector3
|
||||
|
||||
|
||||
func _init(waypoints: Array, is_open: = false) -> void:
|
||||
self.is_open = is_open
|
||||
_create_path(waypoints)
|
||||
nearest_point_on_segment = waypoints[0]
|
||||
nearest_point_on_path = waypoints[0]
|
||||
create_path(waypoints)
|
||||
_nearest_point_on_segment = waypoints[0]
|
||||
_nearest_point_on_path = waypoints[0]
|
||||
|
||||
|
||||
func get_start_point() -> Vector3:
|
||||
return segments.front().begin
|
||||
|
||||
|
||||
func get_end_point() -> Vector3:
|
||||
return segments.back().end
|
||||
|
||||
|
||||
func calculate_point_segment_distance_squared(a: Vector3, b: Vector3, c: Vector3) -> float:
|
||||
nearest_point_on_segment = a
|
||||
var ab: = b - a
|
||||
var ab_length_squared: = ab.length_squared()
|
||||
if ab_length_squared != 0:
|
||||
var t = (c - a).dot(ab) / ab_length_squared
|
||||
nearest_point_on_segment += ab * clamp(t, 0, 1)
|
||||
func create_path(waypoints: Array) -> void:
|
||||
if not waypoints or waypoints.size() < 2:
|
||||
printerr("Waypoints cannot be null and must contain at least two (2) waypoints.")
|
||||
|
||||
return nearest_point_on_segment.distance_squared_to(c)
|
||||
_segments = []
|
||||
path_length = 0
|
||||
var current: Vector3 = _segments[0]
|
||||
var previous: Vector3
|
||||
|
||||
for i in range(1, waypoints.size(), 1):
|
||||
previous = current
|
||||
if i < waypoints.size():
|
||||
current = waypoints[i]
|
||||
elif open:
|
||||
break
|
||||
else:
|
||||
current = waypoints[0]
|
||||
var segment: = GSTSegment.new(previous, current)
|
||||
path_length += segment.length
|
||||
segment.cumulative_length = path_length
|
||||
_segments.append(segment)
|
||||
|
||||
|
||||
func calculate_distance(agent_current_position: Vector3, path_parameter: Dictionary) -> float:
|
||||
var smallest_distance_squared: float = INF
|
||||
var nearest_segment: GSTSegment
|
||||
for i in range(segments.size()):
|
||||
var segment: GSTSegment = segments[i]
|
||||
var distance_squared: = calculate_point_segment_distance_squared(
|
||||
for i in range(_segments.size()):
|
||||
var segment: GSTSegment = _segments[i]
|
||||
var distance_squared: = _calculate_point_segment_distance_squared(
|
||||
segment.begin,
|
||||
segment.end,
|
||||
agent_current_position)
|
||||
|
||||
if distance_squared < smallest_distance_squared:
|
||||
nearest_point_on_path = nearest_point_on_segment
|
||||
_nearest_point_on_path = _nearest_point_on_segment
|
||||
smallest_distance_squared = distance_squared
|
||||
nearest_segment = segment
|
||||
path_parameter.segment_index = i
|
||||
|
||||
var length_on_path: = (
|
||||
nearest_segment.cumulative_length -
|
||||
nearest_point_on_path.distance_to(nearest_segment.end))
|
||||
_nearest_point_on_path.distance_to(nearest_segment.end))
|
||||
|
||||
path_parameter.distance = length_on_path
|
||||
|
||||
@ -62,7 +71,7 @@ func calculate_distance(agent_current_position: Vector3, path_parameter: Diction
|
||||
|
||||
|
||||
func calculate_target_position(param: Dictionary, target_distance: float) -> Vector3:
|
||||
if is_open:
|
||||
if open:
|
||||
target_distance = clamp(target_distance, 0, path_length)
|
||||
else:
|
||||
if target_distance < 0:
|
||||
@ -71,8 +80,8 @@ func calculate_target_position(param: Dictionary, target_distance: float) -> Vec
|
||||
target_distance = fmod(target_distance, path_length)
|
||||
|
||||
var desired_segment: GSTSegment
|
||||
for i in range(segments.size()):
|
||||
var segment: GSTSegment = segments[i]
|
||||
for i in range(_segments.size()):
|
||||
var segment: GSTSegment = _segments[i]
|
||||
if segment.cumulative_length >= target_distance:
|
||||
desired_segment = segment
|
||||
break
|
||||
@ -84,27 +93,23 @@ func calculate_target_position(param: Dictionary, target_distance: float) -> Vec
|
||||
(distance / desired_segment.length) + desired_segment.end)
|
||||
|
||||
|
||||
func _create_path(waypoints: Array) -> void:
|
||||
if not waypoints or waypoints.size() < 2:
|
||||
printerr("Waypoints cannot be null and must contain at least two (2) waypoints.")
|
||||
func get_start_point() -> Vector3:
|
||||
return _segments.front().begin
|
||||
|
||||
|
||||
func get_end_point() -> Vector3:
|
||||
return _segments.back().end
|
||||
|
||||
|
||||
func _calculate_point_segment_distance_squared(start: Vector3, end: Vector3, position: Vector3) -> float:
|
||||
_nearest_point_on_segment = start
|
||||
var start_end: = end - start
|
||||
var start_end_length_squared: = start_end.length_squared()
|
||||
if start_end_length_squared != 0:
|
||||
var t = (position - start).dot(start_end) / start_end_length_squared
|
||||
_nearest_point_on_segment += start_end * clamp(t, 0, 1)
|
||||
|
||||
segments = []
|
||||
path_length = 0
|
||||
var current: Vector3 = segments[0]
|
||||
var previous: Vector3
|
||||
|
||||
for i in range(1, waypoints.size(), 1):
|
||||
previous = current
|
||||
if i < waypoints.size():
|
||||
current = waypoints[i]
|
||||
elif is_open:
|
||||
break
|
||||
else:
|
||||
current = waypoints[0]
|
||||
var segment: = GSTSegment.new(previous, current)
|
||||
path_length += segment.length
|
||||
segment.cumulative_length = path_length
|
||||
segments.append(segment)
|
||||
return _nearest_point_on_segment.distance_squared_to(position)
|
||||
|
||||
|
||||
class GSTSegment:
|
||||
|
@ -1,6 +1,6 @@
|
||||
extends GSTAgentLocation
|
||||
class_name GSTSteeringAgent
|
||||
# Extended agent data type that adds velocity and speed data.
|
||||
# Extended agent data type that adds velocity, speed, and size data
|
||||
|
||||
|
||||
var zero_linear_speed_threshold: = 0.01
|
||||
|
@ -1,4 +1,4 @@
|
||||
class_name Utils
|
||||
class_name GSTUtils
|
||||
# Useful math and utility functions to complement Godot's own.
|
||||
|
||||
|
@ -30,7 +30,7 @@ func _arrive(acceleration: GSTTargetAcceleration, target_position: Vector3) -> G
|
||||
|
||||
desired_velocity = (desired_velocity - agent.linear_velocity) * 1.0 / time_to_reach
|
||||
|
||||
acceleration.linear = Utils.clampedv3(desired_velocity, agent.max_linear_acceleration)
|
||||
acceleration.linear = GSTUtils.clampedv3(desired_velocity, agent.max_linear_acceleration)
|
||||
acceleration.angular = 0
|
||||
|
||||
return acceleration
|
||||
|
@ -1,9 +1,8 @@
|
||||
extends GSTSteeringBehavior
|
||||
extends GSTGroupBehavior
|
||||
class_name GSTAvoidCollisions
|
||||
# Behavior that steers the agent to avoid obstacles lying in its path, approximated by a sphere.
|
||||
|
||||
|
||||
var proximity: GSTProximity
|
||||
var first_neighbor: GSTSteeringAgent
|
||||
var shortest_time: float
|
||||
var first_minimum_separation: float
|
||||
@ -12,8 +11,8 @@ var first_relative_position: Vector3
|
||||
var first_relative_velocity: Vector3
|
||||
|
||||
|
||||
func _init(agent: GSTSteeringAgent, proximity: GSTProximity).(agent) -> void:
|
||||
self.proximity = proximity
|
||||
func _init(agent: GSTSteeringAgent, proximity: GSTProximity).(agent, proximity) -> void:
|
||||
pass
|
||||
|
||||
|
||||
func _calculate_steering(acceleration: GSTTargetAcceleration) -> GSTTargetAcceleration:
|
||||
@ -22,7 +21,7 @@ func _calculate_steering(acceleration: GSTTargetAcceleration) -> GSTTargetAccele
|
||||
first_minimum_separation = 0
|
||||
first_distance = 0
|
||||
|
||||
var neighbor_count: = proximity.find_neighbors(funcref(self, "_report_neighbor"))
|
||||
var neighbor_count: = proximity.find_neighbors(_callback)
|
||||
|
||||
if neighbor_count == 0 or not first_neighbor:
|
||||
acceleration.set_zero()
|
||||
@ -39,7 +38,7 @@ func _calculate_steering(acceleration: GSTTargetAcceleration) -> GSTTargetAccele
|
||||
return acceleration
|
||||
|
||||
|
||||
func _report_neighbor(neighbor: GSTSteeringAgent) -> bool:
|
||||
func report_neighbor(neighbor: GSTSteeringAgent) -> bool:
|
||||
var relative_position: = neighbor.position - agent.position
|
||||
var relative_velocity: = neighbor.linear_velocity - agent.linear_velocity
|
||||
var relative_speed_squared: = relative_velocity.length_squared()
|
||||
|
@ -38,7 +38,7 @@ func _calculate_steering(blended_accel: GSTTargetAcceleration) -> GSTTargetAccel
|
||||
|
||||
blended_accel.add_scaled_accel(_accel, bw.weight)
|
||||
|
||||
blended_accel.linear = Utils.clampedv3(blended_accel.linear, agent.max_linear_acceleration)
|
||||
blended_accel.linear = GSTUtils.clampedv3(blended_accel.linear, agent.max_linear_acceleration)
|
||||
blended_accel.angular = min(blended_accel.angular, agent.max_angular_acceleration)
|
||||
|
||||
return blended_accel
|
||||
|
@ -1,27 +1,26 @@
|
||||
extends GSTSteeringBehavior
|
||||
extends GSTGroupBehavior
|
||||
class_name GSTCohesion
|
||||
# Group behavior that produces linear acceleration that attempts to move the agent towards the
|
||||
# center of mass of the agents in the area defined by the defined Proximity.
|
||||
|
||||
|
||||
var center_of_mass: Vector3
|
||||
var proximity: GSTProximity
|
||||
|
||||
|
||||
func _init(agent: GSTSteeringAgent, proximity: GSTProximity).(agent) -> void:
|
||||
self.proximity = proximity
|
||||
func _init(agent: GSTSteeringAgent, proximity: GSTProximity).(agent, proximity) -> void:
|
||||
pass
|
||||
|
||||
|
||||
func _calculate_steering(acceleration: GSTTargetAcceleration) -> GSTTargetAcceleration:
|
||||
acceleration.set_zero()
|
||||
center_of_mass = Vector3.ZERO
|
||||
var neighbor_count = proximity.find_neighbors(funcref(self, "_report_neighbor"))
|
||||
var neighbor_count = proximity.find_neighbors(_callback)
|
||||
if neighbor_count > 0:
|
||||
center_of_mass *= 1.0 / neighbor_count
|
||||
acceleration.linear = (center_of_mass - agent.position).normalized() * agent.max_linear_acceleration
|
||||
return acceleration
|
||||
|
||||
|
||||
func _report_neighbor(neighbor: GSTSteeringAgent) -> bool:
|
||||
func report_neighbor(neighbor: GSTSteeringAgent) -> bool:
|
||||
center_of_mass += neighbor.position
|
||||
return true
|
||||
|
@ -1,4 +1,4 @@
|
||||
extends GSTSteeringBehavior
|
||||
extends GSTGroupBehavior
|
||||
class_name GSTSeparation
|
||||
# Group behavior that produces acceleration repelling from the other neighbors that are in the
|
||||
# immediate area defined by the given `GSTProximity`.
|
||||
@ -11,21 +11,20 @@ class_name GSTSeparation
|
||||
var decay_coefficient: = 1.0
|
||||
|
||||
var acceleration: GSTTargetAcceleration
|
||||
var proximity: GSTProximity
|
||||
|
||||
|
||||
func _init(agent: GSTSteeringAgent, proximity: GSTProximity).(agent) -> void:
|
||||
self.proximity = proximity
|
||||
func _init(agent: GSTSteeringAgent, proximity: GSTProximity).(agent, proximity) -> void:
|
||||
pass
|
||||
|
||||
|
||||
func _calculate_steering(acceleration: GSTTargetAcceleration) -> GSTTargetAcceleration:
|
||||
acceleration.set_zero()
|
||||
self.acceleration = acceleration
|
||||
proximity.find_neighbors(funcref(self, "_report_neighbor"))
|
||||
proximity.find_neighbors(_callback)
|
||||
return acceleration
|
||||
|
||||
|
||||
func _report_neighbor(neighbor: GSTSteeringAgent) -> bool:
|
||||
func report_neighbor(neighbor: GSTSteeringAgent) -> bool:
|
||||
var to_agent: = agent.position - neighbor.position
|
||||
|
||||
var distance_squared: = to_agent.length_squared()
|
||||
|
@ -1,6 +1,6 @@
|
||||
extends GSTProximity
|
||||
class_name GSTInfiniteProximity
|
||||
# Specifies any agent that is in the specified list as being neighbors with the owner agent.
|
||||
# Determines any agent that is in the specified list as being neighbors with the owner agent.
|
||||
|
||||
|
||||
func _init(agent: GSTSteeringAgent, agents: Array).(agent, agents) -> void:
|
||||
|
@ -1,6 +1,7 @@
|
||||
extends Reference
|
||||
class_name GSTProximity
|
||||
# Defines an area that is used by group behaviors to find and process the owner's neighbors.
|
||||
# Defines a way to determine any agent that is in the specified list as being neighbors with the
|
||||
# owner agent.
|
||||
|
||||
|
||||
var agent: GSTSteeringAgent
|
||||
|
@ -1,7 +1,7 @@
|
||||
extends GSTProximity
|
||||
class_name GSTRadiusProximity
|
||||
# Specifies any agent that is in the specified list as being neighbors with the owner agent if they
|
||||
# lie within the specified radius.
|
||||
# Determines any agent that is in the specified list as being neighbors with the owner agent if
|
||||
# they lie within the specified radius.
|
||||
|
||||
|
||||
var radius: = 0.0
|
||||
@ -34,7 +34,7 @@ func find_neighbors(callback: FuncRef) -> int:
|
||||
var range_to: = radius + current_agent.bounding_radius
|
||||
|
||||
if distance_squared < range_to * range_to:
|
||||
if callback.call_func(current_agent) == true:
|
||||
if callback.call_func(current_agent):
|
||||
current_agent.tagged = true
|
||||
neighbor_count += 1
|
||||
continue
|
||||
@ -45,7 +45,7 @@ func find_neighbors(callback: FuncRef) -> int:
|
||||
var current_agent = agents[i] as GSTSteeringAgent
|
||||
|
||||
if current_agent != agent and current_agent.tagged:
|
||||
if callback.call_func(current_agent) == true:
|
||||
if callback.call_func(current_agent):
|
||||
neighbor_count += 1
|
||||
|
||||
return neighbor_count
|
||||
|
Loading…
Reference in New Issue
Block a user