mirror of
https://github.com/Relintai/broken_seals.git
synced 2024-11-10 08:42:11 +01:00
Added GDQuest's steering framework.
This commit is contained in:
parent
a1fd74a34a
commit
b14866b143
@ -39,6 +39,151 @@ _global_script_classes=[ {
|
||||
"language": "GDScript",
|
||||
"path": "res://voxelman/world/CubicVoxelMesher.gd"
|
||||
}, {
|
||||
"base": "Reference",
|
||||
"class": "GSAIAgentLocation",
|
||||
"language": "GDScript",
|
||||
"path": "res://steering_ai_framework/GSAIAgentLocation.gd"
|
||||
}, {
|
||||
"base": "GSAISteeringBehavior",
|
||||
"class": "GSAIArrive",
|
||||
"language": "GDScript",
|
||||
"path": "res://steering_ai_framework/Behaviors/GSAIArrive.gd"
|
||||
}, {
|
||||
"base": "GSAIGroupBehavior",
|
||||
"class": "GSAIAvoidCollisions",
|
||||
"language": "GDScript",
|
||||
"path": "res://steering_ai_framework/Behaviors/GSAIAvoidCollisions.gd"
|
||||
}, {
|
||||
"base": "GSAISteeringBehavior",
|
||||
"class": "GSAIBlend",
|
||||
"language": "GDScript",
|
||||
"path": "res://steering_ai_framework/Behaviors/GSAIBlend.gd"
|
||||
}, {
|
||||
"base": "GSAIGroupBehavior",
|
||||
"class": "GSAICohesion",
|
||||
"language": "GDScript",
|
||||
"path": "res://steering_ai_framework/Behaviors/GSAICohesion.gd"
|
||||
}, {
|
||||
"base": "GSAIPursue",
|
||||
"class": "GSAIEvade",
|
||||
"language": "GDScript",
|
||||
"path": "res://steering_ai_framework/Behaviors/GSAIEvade.gd"
|
||||
}, {
|
||||
"base": "GSAIMatchOrientation",
|
||||
"class": "GSAIFace",
|
||||
"language": "GDScript",
|
||||
"path": "res://steering_ai_framework/Behaviors/GSAIFace.gd"
|
||||
}, {
|
||||
"base": "GSAISeek",
|
||||
"class": "GSAIFlee",
|
||||
"language": "GDScript",
|
||||
"path": "res://steering_ai_framework/Behaviors/GSAIFlee.gd"
|
||||
}, {
|
||||
"base": "GSAIArrive",
|
||||
"class": "GSAIFollowPath",
|
||||
"language": "GDScript",
|
||||
"path": "res://steering_ai_framework/Behaviors/GSAIFollowPath.gd"
|
||||
}, {
|
||||
"base": "GSAISteeringBehavior",
|
||||
"class": "GSAIGroupBehavior",
|
||||
"language": "GDScript",
|
||||
"path": "res://steering_ai_framework/GSAIGroupBehavior.gd"
|
||||
}, {
|
||||
"base": "GSAIProximity",
|
||||
"class": "GSAIInfiniteProximity",
|
||||
"language": "GDScript",
|
||||
"path": "res://steering_ai_framework/Proximities/GSAIInfiniteProximity.gd"
|
||||
}, {
|
||||
"base": "GSAISpecializedAgent",
|
||||
"class": "GSAIKinematicBody2DAgent",
|
||||
"language": "GDScript",
|
||||
"path": "res://steering_ai_framework/Agents/GSAIKinematicBody2DAgent.gd"
|
||||
}, {
|
||||
"base": "GSAISpecializedAgent",
|
||||
"class": "GSAIKinematicBody3DAgent",
|
||||
"language": "GDScript",
|
||||
"path": "res://steering_ai_framework/Agents/GSAIKinematicBody3DAgent.gd"
|
||||
}, {
|
||||
"base": "GSAIMatchOrientation",
|
||||
"class": "GSAILookWhereYouGo",
|
||||
"language": "GDScript",
|
||||
"path": "res://steering_ai_framework/Behaviors/GSAILookWhereYouGo.gd"
|
||||
}, {
|
||||
"base": "GSAISteeringBehavior",
|
||||
"class": "GSAIMatchOrientation",
|
||||
"language": "GDScript",
|
||||
"path": "res://steering_ai_framework/Behaviors/GSAIMatchOrientation.gd"
|
||||
}, {
|
||||
"base": "Reference",
|
||||
"class": "GSAIPath",
|
||||
"language": "GDScript",
|
||||
"path": "res://steering_ai_framework/GSAIPath.gd"
|
||||
}, {
|
||||
"base": "GSAISteeringBehavior",
|
||||
"class": "GSAIPriority",
|
||||
"language": "GDScript",
|
||||
"path": "res://steering_ai_framework/Behaviors/GSAIPriority.gd"
|
||||
}, {
|
||||
"base": "Reference",
|
||||
"class": "GSAIProximity",
|
||||
"language": "GDScript",
|
||||
"path": "res://steering_ai_framework/Proximities/GSAIProximity.gd"
|
||||
}, {
|
||||
"base": "GSAISteeringBehavior",
|
||||
"class": "GSAIPursue",
|
||||
"language": "GDScript",
|
||||
"path": "res://steering_ai_framework/Behaviors/GSAIPursue.gd"
|
||||
}, {
|
||||
"base": "GSAIProximity",
|
||||
"class": "GSAIRadiusProximity",
|
||||
"language": "GDScript",
|
||||
"path": "res://steering_ai_framework/Proximities/GSAIRadiusProximity.gd"
|
||||
}, {
|
||||
"base": "GSAISpecializedAgent",
|
||||
"class": "GSAIRigidBody2DAgent",
|
||||
"language": "GDScript",
|
||||
"path": "res://steering_ai_framework/Agents/GSAIRigidBody2DAgent.gd"
|
||||
}, {
|
||||
"base": "GSAISpecializedAgent",
|
||||
"class": "GSAIRigidBody3DAgent",
|
||||
"language": "GDScript",
|
||||
"path": "res://steering_ai_framework/Agents/GSAIRigidBody3DAgent.gd"
|
||||
}, {
|
||||
"base": "GSAISteeringBehavior",
|
||||
"class": "GSAISeek",
|
||||
"language": "GDScript",
|
||||
"path": "res://steering_ai_framework/Behaviors/GSAISeek.gd"
|
||||
}, {
|
||||
"base": "GSAIGroupBehavior",
|
||||
"class": "GSAISeparation",
|
||||
"language": "GDScript",
|
||||
"path": "res://steering_ai_framework/Behaviors/GSAISeparation.gd"
|
||||
}, {
|
||||
"base": "GSAISteeringAgent",
|
||||
"class": "GSAISpecializedAgent",
|
||||
"language": "GDScript",
|
||||
"path": "res://steering_ai_framework/Agents/GSAISpecializedAgent.gd"
|
||||
}, {
|
||||
"base": "GSAIAgentLocation",
|
||||
"class": "GSAISteeringAgent",
|
||||
"language": "GDScript",
|
||||
"path": "res://steering_ai_framework/GSAISteeringAgent.gd"
|
||||
}, {
|
||||
"base": "Reference",
|
||||
"class": "GSAISteeringBehavior",
|
||||
"language": "GDScript",
|
||||
"path": "res://steering_ai_framework/GSAISteeringBehavior.gd"
|
||||
}, {
|
||||
"base": "Reference",
|
||||
"class": "GSAITargetAcceleration",
|
||||
"language": "GDScript",
|
||||
"path": "res://steering_ai_framework/GSAITargetAcceleration.gd"
|
||||
}, {
|
||||
"base": "Reference",
|
||||
"class": "GSAIUtils",
|
||||
"language": "GDScript",
|
||||
"path": "res://steering_ai_framework/GSAIUtils.gd"
|
||||
}, {
|
||||
"base": "ItemTemplate",
|
||||
"class": "ItemTemplateGD",
|
||||
"language": "GDScript",
|
||||
@ -156,6 +301,35 @@ _global_script_class_icons={
|
||||
"EntityAIGD": "",
|
||||
"EntityDataGD": "",
|
||||
"GDCubicVoxelMesher": "",
|
||||
"GSAIAgentLocation": "",
|
||||
"GSAIArrive": "",
|
||||
"GSAIAvoidCollisions": "",
|
||||
"GSAIBlend": "",
|
||||
"GSAICohesion": "",
|
||||
"GSAIEvade": "",
|
||||
"GSAIFace": "",
|
||||
"GSAIFlee": "",
|
||||
"GSAIFollowPath": "",
|
||||
"GSAIGroupBehavior": "",
|
||||
"GSAIInfiniteProximity": "",
|
||||
"GSAIKinematicBody2DAgent": "",
|
||||
"GSAIKinematicBody3DAgent": "",
|
||||
"GSAILookWhereYouGo": "",
|
||||
"GSAIMatchOrientation": "",
|
||||
"GSAIPath": "",
|
||||
"GSAIPriority": "",
|
||||
"GSAIProximity": "",
|
||||
"GSAIPursue": "",
|
||||
"GSAIRadiusProximity": "",
|
||||
"GSAIRigidBody2DAgent": "",
|
||||
"GSAIRigidBody3DAgent": "",
|
||||
"GSAISeek": "",
|
||||
"GSAISeparation": "",
|
||||
"GSAISpecializedAgent": "",
|
||||
"GSAISteeringAgent": "",
|
||||
"GSAISteeringBehavior": "",
|
||||
"GSAITargetAcceleration": "",
|
||||
"GSAIUtils": "",
|
||||
"ItemTemplateGD": "",
|
||||
"LayeredTextureMaker": "",
|
||||
"Main": "",
|
||||
|
120
game/steering_ai_framework/Agents/GSAIKinematicBody2DAgent.gd
Normal file
120
game/steering_ai_framework/Agents/GSAIKinematicBody2DAgent.gd
Normal file
@ -0,0 +1,120 @@
|
||||
# A specialized steering agent that updates itself every frame so the user does
|
||||
# not have to using a KinematicBody2D
|
||||
extends GSAISpecializedAgent
|
||||
class_name GSAIKinematicBody2DAgent
|
||||
|
||||
# SLIDE uses `move_and_slide`
|
||||
# COLLIDE uses `move_and_collide`
|
||||
# POSITION changes the `global_position` directly
|
||||
enum MovementType { SLIDE, COLLIDE, POSITION }
|
||||
|
||||
# The KinematicBody2D to keep track of
|
||||
var body: KinematicBody2D setget _set_body
|
||||
|
||||
# The type of movement the body executes
|
||||
var movement_type: int
|
||||
|
||||
var _last_position: Vector2
|
||||
|
||||
|
||||
func _init(_body: KinematicBody2D, _movement_type: int = MovementType.SLIDE) -> void:
|
||||
if not _body.is_inside_tree():
|
||||
yield(_body, "ready")
|
||||
|
||||
self.body = _body
|
||||
self.movement_type = _movement_type
|
||||
|
||||
# warning-ignore:return_value_discarded
|
||||
body.get_tree().connect("physics_frame", self, "_on_SceneTree_physics_frame")
|
||||
|
||||
|
||||
# Moves the agent's `body` by target `acceleration`.
|
||||
# tags: virtual
|
||||
func _apply_steering(acceleration: GSAITargetAcceleration, delta: float) -> void:
|
||||
_applied_steering = true
|
||||
match movement_type:
|
||||
MovementType.COLLIDE:
|
||||
_apply_collide_steering(acceleration.linear, delta)
|
||||
MovementType.SLIDE:
|
||||
_apply_sliding_steering(acceleration.linear)
|
||||
_:
|
||||
_apply_position_steering(acceleration.linear, delta)
|
||||
|
||||
_apply_orientation_steering(acceleration.angular, delta)
|
||||
|
||||
|
||||
func _apply_sliding_steering(accel: Vector3) -> void:
|
||||
var velocity := GSAIUtils.to_vector2(linear_velocity + accel).clamped(linear_speed_max)
|
||||
if apply_linear_drag:
|
||||
velocity = velocity.linear_interpolate(Vector2.ZERO, linear_drag_percentage)
|
||||
velocity = body.move_and_slide(velocity)
|
||||
if calculate_velocities:
|
||||
linear_velocity = GSAIUtils.to_vector3(velocity)
|
||||
|
||||
|
||||
func _apply_collide_steering(accel: Vector3, delta: float) -> void:
|
||||
var velocity := GSAIUtils.clampedv3(linear_velocity + accel, linear_speed_max)
|
||||
if apply_linear_drag:
|
||||
velocity = velocity.linear_interpolate(Vector3.ZERO, linear_drag_percentage)
|
||||
# warning-ignore:return_value_discarded
|
||||
body.move_and_collide(GSAIUtils.to_vector2(velocity) * delta)
|
||||
if calculate_velocities:
|
||||
linear_velocity = velocity
|
||||
|
||||
|
||||
func _apply_position_steering(accel: Vector3, delta: float) -> void:
|
||||
var velocity := GSAIUtils.clampedv3(linear_velocity + accel, linear_speed_max)
|
||||
if apply_linear_drag:
|
||||
velocity = velocity.linear_interpolate(Vector3.ZERO, linear_drag_percentage)
|
||||
body.global_position += GSAIUtils.to_vector2(velocity) * delta
|
||||
if calculate_velocities:
|
||||
linear_velocity = velocity
|
||||
|
||||
|
||||
func _apply_orientation_steering(angular_acceleration: float, delta: float) -> void:
|
||||
var velocity = angular_velocity + angular_acceleration
|
||||
if apply_angular_drag:
|
||||
velocity = lerp(velocity, 0, angular_drag_percentage)
|
||||
body.rotation += velocity * delta
|
||||
if calculate_velocities:
|
||||
angular_velocity = velocity
|
||||
|
||||
|
||||
func _set_body(value: KinematicBody2D) -> void:
|
||||
body = value
|
||||
|
||||
_last_position = body.global_position
|
||||
_last_orientation = body.rotation
|
||||
|
||||
position = GSAIUtils.to_vector3(_last_position)
|
||||
orientation = _last_orientation
|
||||
|
||||
|
||||
func _on_SceneTree_physics_frame() -> void:
|
||||
var current_position := body.global_position
|
||||
var current_orientation := body.rotation
|
||||
|
||||
position = GSAIUtils.to_vector3(current_position)
|
||||
orientation = current_orientation
|
||||
|
||||
if calculate_velocities:
|
||||
if _applied_steering:
|
||||
_applied_steering = false
|
||||
else:
|
||||
linear_velocity = GSAIUtils.clampedv3(
|
||||
GSAIUtils.to_vector3(_last_position - current_position), linear_speed_max
|
||||
)
|
||||
if apply_linear_drag:
|
||||
linear_velocity = linear_velocity.linear_interpolate(
|
||||
Vector3.ZERO, linear_drag_percentage
|
||||
)
|
||||
|
||||
angular_velocity = clamp(
|
||||
_last_orientation - current_orientation, -angular_speed_max, angular_speed_max
|
||||
)
|
||||
|
||||
if apply_angular_drag:
|
||||
angular_velocity = lerp(angular_velocity, 0, angular_drag_percentage)
|
||||
|
||||
_last_position = current_position
|
||||
_last_orientation = current_orientation
|
120
game/steering_ai_framework/Agents/GSAIKinematicBody3DAgent.gd
Normal file
120
game/steering_ai_framework/Agents/GSAIKinematicBody3DAgent.gd
Normal file
@ -0,0 +1,120 @@
|
||||
# A specialized steering agent that updates itself every frame so the user does
|
||||
# not have to using a KinematicBody
|
||||
extends GSAISpecializedAgent
|
||||
class_name GSAIKinematicBody3DAgent
|
||||
|
||||
# SLIDE uses `move_and_slide`
|
||||
# COLLIDE uses `move_and_collide`
|
||||
# POSITION changes the global_position directly
|
||||
enum MovementType { SLIDE, COLLIDE, POSITION }
|
||||
|
||||
# The KinematicBody to keep track of
|
||||
var body: KinematicBody setget _set_body
|
||||
|
||||
# The type of movement the body executes
|
||||
var movement_type: int
|
||||
|
||||
var _last_position: Vector3
|
||||
|
||||
|
||||
func _init(_body: KinematicBody, _movement_type: int = MovementType.SLIDE) -> void:
|
||||
if not _body.is_inside_tree():
|
||||
yield(_body, "ready")
|
||||
|
||||
self.body = _body
|
||||
self.movement_type = _movement_type
|
||||
|
||||
# warning-ignore:return_value_discarded
|
||||
self.body.get_tree().connect("physics_frame", self, "_on_SceneTree_physics_frame")
|
||||
|
||||
|
||||
# Moves the agent's `body` by target `acceleration`.
|
||||
# tags: virtual
|
||||
func _apply_steering(acceleration: GSAITargetAcceleration, delta: float) -> void:
|
||||
_applied_steering = true
|
||||
match movement_type:
|
||||
MovementType.COLLIDE:
|
||||
_apply_collide_steering(acceleration.linear, delta)
|
||||
MovementType.SLIDE:
|
||||
_apply_sliding_steering(acceleration.linear)
|
||||
_:
|
||||
_apply_position_steering(acceleration.linear, delta)
|
||||
|
||||
_apply_orientation_steering(acceleration.angular, delta)
|
||||
|
||||
|
||||
func _apply_sliding_steering(accel: Vector3) -> void:
|
||||
var velocity := GSAIUtils.clampedv3(linear_velocity + accel, linear_speed_max)
|
||||
if apply_linear_drag:
|
||||
velocity = velocity.linear_interpolate(Vector3.ZERO, linear_drag_percentage)
|
||||
velocity = body.move_and_slide(velocity)
|
||||
if calculate_velocities:
|
||||
linear_velocity = velocity
|
||||
|
||||
|
||||
func _apply_collide_steering(accel: Vector3, delta: float) -> void:
|
||||
var velocity := GSAIUtils.clampedv3(linear_velocity + accel, linear_speed_max)
|
||||
if apply_linear_drag:
|
||||
velocity = velocity.linear_interpolate(Vector3.ZERO, linear_drag_percentage)
|
||||
# warning-ignore:return_value_discarded
|
||||
body.move_and_collide(velocity * delta)
|
||||
if calculate_velocities:
|
||||
linear_velocity = velocity
|
||||
|
||||
|
||||
func _apply_position_steering(accel: Vector3, delta: float) -> void:
|
||||
var velocity := GSAIUtils.clampedv3(linear_velocity + accel, linear_speed_max)
|
||||
if apply_linear_drag:
|
||||
velocity = velocity.linear_interpolate(Vector3.ZERO, linear_drag_percentage)
|
||||
body.global_position += velocity * delta
|
||||
if calculate_velocities:
|
||||
linear_velocity = velocity
|
||||
|
||||
|
||||
func _apply_orientation_steering(angular_acceleration: float, delta: float) -> void:
|
||||
var velocity = angular_velocity + angular_acceleration
|
||||
if apply_angular_drag:
|
||||
velocity = lerp(velocity, 0, angular_drag_percentage)
|
||||
body.rotation.y += velocity * delta
|
||||
if calculate_velocities:
|
||||
angular_velocity = velocity
|
||||
|
||||
|
||||
func _set_body(value: KinematicBody) -> void:
|
||||
body = value
|
||||
|
||||
_last_position = body.transform.origin
|
||||
_last_orientation = body.rotation.y
|
||||
|
||||
position = _last_position
|
||||
orientation = _last_orientation
|
||||
|
||||
|
||||
func _on_SceneTree_physics_frame() -> void:
|
||||
var current_position := body.transform.origin
|
||||
var current_orientation := body.rotation.y
|
||||
|
||||
position = current_position
|
||||
orientation = current_orientation
|
||||
|
||||
if calculate_velocities:
|
||||
if _applied_steering:
|
||||
_applied_steering = false
|
||||
else:
|
||||
linear_velocity = GSAIUtils.clampedv3(
|
||||
_last_position - current_position, linear_speed_max
|
||||
)
|
||||
if apply_linear_drag:
|
||||
linear_velocity = linear_velocity.linear_interpolate(
|
||||
Vector3.ZERO, linear_drag_percentage
|
||||
)
|
||||
|
||||
angular_velocity = clamp(
|
||||
_last_orientation - current_orientation, -angular_speed_max, angular_speed_max
|
||||
)
|
||||
|
||||
if apply_angular_drag:
|
||||
angular_velocity = lerp(angular_velocity, 0, angular_drag_percentage)
|
||||
|
||||
_last_position = current_position
|
||||
_last_orientation = current_orientation
|
58
game/steering_ai_framework/Agents/GSAIRigidBody2DAgent.gd
Normal file
58
game/steering_ai_framework/Agents/GSAIRigidBody2DAgent.gd
Normal file
@ -0,0 +1,58 @@
|
||||
# A specialized steering agent that updates itself every frame so the user does
|
||||
# not have to using a RigidBody2D
|
||||
extends GSAISpecializedAgent
|
||||
class_name GSAIRigidBody2DAgent
|
||||
|
||||
# The RigidBody2D to keep track of
|
||||
var body: RigidBody2D setget _set_body
|
||||
|
||||
var _last_position: Vector2
|
||||
|
||||
|
||||
func _init(_body: RigidBody2D) -> void:
|
||||
if not _body.is_inside_tree():
|
||||
yield(_body, "ready")
|
||||
|
||||
self.body = _body
|
||||
|
||||
|
||||
# Moves the agent's `body` by target `acceleration`.
|
||||
# tags: virtual
|
||||
func _apply_steering(acceleration: GSAITargetAcceleration, _delta: float) -> void:
|
||||
_applied_steering = true
|
||||
body.apply_central_impulse(GSAIUtils.to_vector2(acceleration.linear))
|
||||
body.apply_torque_impulse(acceleration.angular)
|
||||
if calculate_velocities:
|
||||
linear_velocity = GSAIUtils.to_vector3(body.linear_velocity)
|
||||
angular_velocity = body.angular_velocity
|
||||
|
||||
|
||||
func _set_body(value: RigidBody2D) -> void:
|
||||
body = value
|
||||
|
||||
_last_position = body.global_position
|
||||
_last_orientation = body.rotation
|
||||
|
||||
position = GSAIUtils.to_vector3(_last_position)
|
||||
orientation = _last_orientation
|
||||
|
||||
|
||||
func _on_body_ready() -> void:
|
||||
# warning-ignore:return_value_discarded
|
||||
body.get_tree().connect("physics_frame", self, "_on_SceneTree_frame")
|
||||
_set_body(body)
|
||||
|
||||
|
||||
func _on_SceneTree_frame() -> void:
|
||||
var current_position := body.global_position
|
||||
var current_orientation := body.rotation
|
||||
|
||||
position = GSAIUtils.to_vector3(current_position)
|
||||
orientation = current_orientation
|
||||
|
||||
if calculate_velocities:
|
||||
if _applied_steering:
|
||||
_applied_steering = false
|
||||
else:
|
||||
linear_velocity = GSAIUtils.to_vector3(body.linear_velocity)
|
||||
angular_velocity = body.angular_velocity
|
60
game/steering_ai_framework/Agents/GSAIRigidBody3DAgent.gd
Normal file
60
game/steering_ai_framework/Agents/GSAIRigidBody3DAgent.gd
Normal file
@ -0,0 +1,60 @@
|
||||
# A specialized steering agent that updates itself every frame so the user does
|
||||
# not have to using a RigidBody
|
||||
extends GSAISpecializedAgent
|
||||
class_name GSAIRigidBody3DAgent
|
||||
|
||||
# The RigidBody to keep track of
|
||||
var body: RigidBody setget _set_body
|
||||
|
||||
var _last_position: Vector3
|
||||
|
||||
|
||||
func _init(_body: RigidBody) -> void:
|
||||
if not _body.is_inside_tree():
|
||||
yield(_body, "ready")
|
||||
|
||||
self.body = _body
|
||||
# warning-ignore:return_value_discarded
|
||||
self.body.get_tree().connect("physics_frame", self, "_on_SceneTree_frame")
|
||||
|
||||
|
||||
# Moves the agent's `body` by target `acceleration`.
|
||||
# tags: virtual
|
||||
func _apply_steering(acceleration: GSAITargetAcceleration, _delta: float) -> void:
|
||||
_applied_steering = true
|
||||
body.apply_central_impulse(acceleration.linear)
|
||||
body.apply_torque_impulse(Vector3.UP * acceleration.angular)
|
||||
if calculate_velocities:
|
||||
linear_velocity = body.linear_velocity
|
||||
angular_velocity = body.angular_velocity.y
|
||||
|
||||
|
||||
func _set_body(value: RigidBody) -> void:
|
||||
body = value
|
||||
|
||||
_last_position = body.transform.origin
|
||||
_last_orientation = body.rotation.y
|
||||
|
||||
position = _last_position
|
||||
orientation = _last_orientation
|
||||
|
||||
|
||||
func _on_body_ready() -> void:
|
||||
# warning-ignore:return_value_discarded
|
||||
body.get_tree().connect("physics_frame", self, "_on_SceneTree_frame")
|
||||
_set_body(body)
|
||||
|
||||
|
||||
func _on_SceneTree_frame() -> void:
|
||||
var current_position := body.transform.origin
|
||||
var current_orientation := body.rotation.y
|
||||
|
||||
position = current_position
|
||||
orientation = current_orientation
|
||||
|
||||
if calculate_velocities:
|
||||
if _applied_steering:
|
||||
_applied_steering = false
|
||||
else:
|
||||
linear_velocity = body.linear_velocity
|
||||
angular_velocity = body.angular_velocity.y
|
39
game/steering_ai_framework/Agents/GSAISpecializedAgent.gd
Normal file
39
game/steering_ai_framework/Agents/GSAISpecializedAgent.gd
Normal file
@ -0,0 +1,39 @@
|
||||
# A base class for a specialized steering agent that updates itself every frame
|
||||
# so the user does not have to. All other specialized agents derive from this.
|
||||
# tags: abstract
|
||||
extends GSAISteeringAgent
|
||||
class_name GSAISpecializedAgent
|
||||
|
||||
# If `true`, calculates linear and angular velocities based on the previous
|
||||
# frame. When `false`, the user must keep those values updated.
|
||||
var calculate_velocities := true
|
||||
|
||||
# If `true`, interpolates the current linear velocity towards 0 by the
|
||||
# `linear_drag_percentage` value.
|
||||
# Does not apply to `RigidBody` and `RigidBody2D` nodes.
|
||||
var apply_linear_drag := true
|
||||
|
||||
# If `true`, interpolates the current angular velocity towards 0 by the
|
||||
# `angular_drag_percentage` value.
|
||||
# Does not apply to `RigidBody` and `RigidBody2D` nodes.
|
||||
var apply_angular_drag := true
|
||||
|
||||
# The percentage between the current linear velocity and 0 to interpolate by if
|
||||
# `apply_linear_drag` is true.
|
||||
# Does not apply to `RigidBody` and `RigidBody2D` nodes.
|
||||
var linear_drag_percentage := 0.0
|
||||
|
||||
# The percentage between the current angular velocity and 0 to interpolate by if
|
||||
# `apply_angular_drag` is true.
|
||||
# Does not apply to `RigidBody` and `RigidBody2D` nodes.
|
||||
var angular_drag_percentage := 0.0
|
||||
|
||||
var _last_orientation: float
|
||||
var _body_type: int
|
||||
var _applied_steering := false
|
||||
|
||||
|
||||
# Moves the agent's body by target `acceleration`.
|
||||
# tags: virtual
|
||||
func _apply_steering(_acceleration: GSAITargetAcceleration, _delta: float) -> void:
|
||||
pass
|
42
game/steering_ai_framework/Behaviors/GSAIArrive.gd
Normal file
42
game/steering_ai_framework/Behaviors/GSAIArrive.gd
Normal file
@ -0,0 +1,42 @@
|
||||
# Calculates acceleration to take an agent to its target's location. The
|
||||
# calculation attempts to arrive with zero remaining velocity.
|
||||
class_name GSAIArrive
|
||||
extends GSAISteeringBehavior
|
||||
|
||||
# Target agent to arrive to.
|
||||
var target: GSAIAgentLocation
|
||||
# Distance from the target for the agent to be considered successfully
|
||||
# arrived.
|
||||
var arrival_tolerance: float
|
||||
# Distance from the target for the agent to begin slowing down.
|
||||
var deceleration_radius: float
|
||||
# Represents the time it takes to change acceleration.
|
||||
var time_to_reach := 0.1
|
||||
|
||||
|
||||
func _init(agent: GSAISteeringAgent, _target: GSAIAgentLocation).(agent) -> void:
|
||||
self.target = _target
|
||||
|
||||
|
||||
func _arrive(acceleration: GSAITargetAcceleration, target_position: Vector3) -> void:
|
||||
var to_target := target_position - agent.position
|
||||
var distance := to_target.length()
|
||||
|
||||
if distance <= arrival_tolerance:
|
||||
acceleration.set_zero()
|
||||
else:
|
||||
var desired_speed := agent.linear_speed_max
|
||||
|
||||
if distance <= deceleration_radius:
|
||||
desired_speed *= distance / deceleration_radius
|
||||
|
||||
var desired_velocity := to_target * desired_speed / distance
|
||||
|
||||
desired_velocity = ((desired_velocity - agent.linear_velocity) * 1.0 / time_to_reach)
|
||||
|
||||
acceleration.linear = GSAIUtils.clampedv3(desired_velocity, agent.linear_acceleration_max)
|
||||
acceleration.angular = 0
|
||||
|
||||
|
||||
func _calculate_steering(acceleration: GSAITargetAcceleration) -> void:
|
||||
_arrive(acceleration, target.position)
|
74
game/steering_ai_framework/Behaviors/GSAIAvoidCollisions.gd
Normal file
74
game/steering_ai_framework/Behaviors/GSAIAvoidCollisions.gd
Normal file
@ -0,0 +1,74 @@
|
||||
# Steers the agent to avoid obstacles in its path. Approximates obstacles as
|
||||
# spheres.
|
||||
class_name GSAIAvoidCollisions
|
||||
extends GSAIGroupBehavior
|
||||
|
||||
var _first_neighbor: GSAISteeringAgent
|
||||
var _shortest_time: float
|
||||
var _first_minimum_separation: float
|
||||
var _first_distance: float
|
||||
var _first_relative_position: Vector3
|
||||
var _first_relative_velocity: Vector3
|
||||
|
||||
|
||||
func _init(agent: GSAISteeringAgent, proximity: GSAIProximity).(agent, proximity) -> void:
|
||||
pass
|
||||
|
||||
|
||||
func _calculate_steering(acceleration: GSAITargetAcceleration) -> void:
|
||||
_shortest_time = INF
|
||||
_first_neighbor = null
|
||||
_first_minimum_separation = 0
|
||||
_first_distance = 0
|
||||
|
||||
var neighbor_count := proximity._find_neighbors(_callback)
|
||||
|
||||
if neighbor_count == 0 or not _first_neighbor:
|
||||
acceleration.set_zero()
|
||||
else:
|
||||
if (
|
||||
_first_minimum_separation <= 0
|
||||
or _first_distance < agent.bounding_radius + _first_neighbor.bounding_radius
|
||||
):
|
||||
acceleration.linear = _first_neighbor.position - agent.position
|
||||
else:
|
||||
acceleration.linear = (
|
||||
_first_relative_position
|
||||
+ (_first_relative_velocity * _shortest_time)
|
||||
)
|
||||
|
||||
acceleration.linear = (acceleration.linear.normalized() * -agent.linear_acceleration_max)
|
||||
acceleration.angular = 0
|
||||
|
||||
|
||||
# Callback for the proximity to call when finding neighbors. Keeps track of every `neighbor`
|
||||
# that was found but only keeps the one the owning agent will most likely collide with.
|
||||
# tags: virtual
|
||||
func _report_neighbor(neighbor: GSAISteeringAgent) -> 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()
|
||||
|
||||
if relative_speed_squared == 0:
|
||||
return false
|
||||
else:
|
||||
var time_to_collision = -relative_position.dot(relative_velocity) / relative_speed_squared
|
||||
|
||||
if time_to_collision <= 0 or time_to_collision >= _shortest_time:
|
||||
return false
|
||||
else:
|
||||
var distance = relative_position.length()
|
||||
var minimum_separation: float = (
|
||||
distance
|
||||
- sqrt(relative_speed_squared) * time_to_collision
|
||||
)
|
||||
if minimum_separation > agent.bounding_radius + neighbor.bounding_radius:
|
||||
return false
|
||||
else:
|
||||
_shortest_time = time_to_collision
|
||||
_first_neighbor = neighbor
|
||||
_first_minimum_separation = minimum_separation
|
||||
_first_distance = distance
|
||||
_first_relative_position = relative_position
|
||||
_first_relative_velocity = relative_velocity
|
||||
return true
|
47
game/steering_ai_framework/Behaviors/GSAIBlend.gd
Normal file
47
game/steering_ai_framework/Behaviors/GSAIBlend.gd
Normal file
@ -0,0 +1,47 @@
|
||||
# Blends multiple steering behaviors into one, and returns a weighted
|
||||
# acceleration from their calculations.
|
||||
#
|
||||
# Stores the behaviors internally as dictionaries of the form
|
||||
# {
|
||||
# behavior : GSAISteeringBehavior,
|
||||
# weight : float
|
||||
# }
|
||||
class_name GSAIBlend
|
||||
extends GSAISteeringBehavior
|
||||
|
||||
var _behaviors := []
|
||||
var _accel := GSAITargetAcceleration.new()
|
||||
|
||||
|
||||
func _init(agent: GSAISteeringAgent).(agent) -> void:
|
||||
pass
|
||||
|
||||
|
||||
# Appends a behavior to the internal array along with its `weight`.
|
||||
func add(behavior: GSAISteeringBehavior, weight: float) -> void:
|
||||
behavior.agent = agent
|
||||
_behaviors.append({behavior = behavior, weight = weight})
|
||||
|
||||
|
||||
# Returns the behavior at the specified `index`, or an empty `Dictionary` if
|
||||
# none was found.
|
||||
func get_behavior_at(index: int) -> Dictionary:
|
||||
if _behaviors.size() > index:
|
||||
return _behaviors[index]
|
||||
printerr("Tried to get index " + str(index) + " in array of size " + str(_behaviors.size()))
|
||||
return {}
|
||||
|
||||
|
||||
func _calculate_steering(blended_accel: GSAITargetAcceleration) -> void:
|
||||
blended_accel.set_zero()
|
||||
|
||||
for i in range(_behaviors.size()):
|
||||
var bw: Dictionary = _behaviors[i]
|
||||
bw.behavior.calculate_steering(_accel)
|
||||
|
||||
blended_accel.add_scaled_accel(_accel, bw.weight)
|
||||
|
||||
blended_accel.linear = GSAIUtils.clampedv3(blended_accel.linear, agent.linear_acceleration_max)
|
||||
blended_accel.angular = clamp(
|
||||
blended_accel.angular, -agent.angular_acceleration_max, agent.angular_acceleration_max
|
||||
)
|
30
game/steering_ai_framework/Behaviors/GSAICohesion.gd
Normal file
30
game/steering_ai_framework/Behaviors/GSAICohesion.gd
Normal file
@ -0,0 +1,30 @@
|
||||
# Calculates an acceleration that attempts to move the agent towards the center
|
||||
# of mass of the agents in the area defined by the `GSAIProximity`.
|
||||
class_name GSAICohesion
|
||||
extends GSAIGroupBehavior
|
||||
|
||||
var _center_of_mass: Vector3
|
||||
|
||||
|
||||
func _init(agent: GSAISteeringAgent, proximity: GSAIProximity).(agent, proximity) -> void:
|
||||
pass
|
||||
|
||||
|
||||
func _calculate_steering(acceleration: GSAITargetAcceleration) -> void:
|
||||
acceleration.set_zero()
|
||||
_center_of_mass = Vector3.ZERO
|
||||
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.linear_acceleration_max
|
||||
)
|
||||
|
||||
|
||||
# Callback for the proximity to call when finding neighbors. Adds `neighbor`'s position
|
||||
# to the center of mass of the group.
|
||||
# tags: virtual
|
||||
func _report_neighbor(neighbor: GSAISteeringAgent) -> bool:
|
||||
_center_of_mass += neighbor.position
|
||||
return true
|
14
game/steering_ai_framework/Behaviors/GSAIEvade.gd
Normal file
14
game/steering_ai_framework/Behaviors/GSAIEvade.gd
Normal file
@ -0,0 +1,14 @@
|
||||
# Calculates acceleration to take an agent away from where a target agent is
|
||||
# moving.
|
||||
class_name GSAIEvade
|
||||
extends GSAIPursue
|
||||
|
||||
|
||||
func _init(agent: GSAISteeringAgent, target: GSAISteeringAgent, predict_time_max := 1.0).(
|
||||
agent, target, predict_time_max
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
func _get_modified_acceleration() -> float:
|
||||
return -agent.linear_acceleration_max
|
29
game/steering_ai_framework/Behaviors/GSAIFace.gd
Normal file
29
game/steering_ai_framework/Behaviors/GSAIFace.gd
Normal file
@ -0,0 +1,29 @@
|
||||
# Calculates angular acceleration to rotate a target to face its target's
|
||||
# position. The behavior attemps to arrive with zero remaining angular velocity.
|
||||
class_name GSAIFace
|
||||
extends GSAIMatchOrientation
|
||||
|
||||
|
||||
func _init(agent: GSAISteeringAgent, target: GSAIAgentLocation, use_z := false).(
|
||||
agent, target, use_z
|
||||
) -> void:
|
||||
pass
|
||||
|
||||
|
||||
func _face(acceleration: GSAITargetAcceleration, target_position: Vector3) -> void:
|
||||
var to_target := target_position - agent.position
|
||||
var distance_squared := to_target.length_squared()
|
||||
|
||||
if distance_squared < agent.zero_linear_speed_threshold:
|
||||
acceleration.set_zero()
|
||||
else:
|
||||
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)
|
||||
|
||||
|
||||
func _calculate_steering(acceleration: GSAITargetAcceleration) -> void:
|
||||
_face(acceleration, target.position)
|
15
game/steering_ai_framework/Behaviors/GSAIFlee.gd
Normal file
15
game/steering_ai_framework/Behaviors/GSAIFlee.gd
Normal file
@ -0,0 +1,15 @@
|
||||
# Calculates acceleration to take an agent directly away from a target agent.
|
||||
class_name GSAIFlee
|
||||
extends GSAISeek
|
||||
|
||||
|
||||
func _init(agent: GSAISteeringAgent, target: GSAIAgentLocation).(agent, target) -> void:
|
||||
pass
|
||||
|
||||
|
||||
func _calculate_steering(acceleration: GSAITargetAcceleration) -> void:
|
||||
acceleration.linear = (
|
||||
(agent.position - target.position).normalized()
|
||||
* agent.linear_acceleration_max
|
||||
)
|
||||
acceleration.angular = 0
|
53
game/steering_ai_framework/Behaviors/GSAIFollowPath.gd
Normal file
53
game/steering_ai_framework/Behaviors/GSAIFollowPath.gd
Normal file
@ -0,0 +1,53 @@
|
||||
# Produces a linear acceleration that moves the agent along the specified path.
|
||||
class_name GSAIFollowPath
|
||||
extends GSAIArrive
|
||||
|
||||
# The path to follow and travel along.
|
||||
var path: GSAIPath
|
||||
# The distance along the path to generate the next target position.
|
||||
var path_offset := 0.0
|
||||
|
||||
# Whether to use `GSAIArrive` behavior on an open path.
|
||||
var is_arrive_enabled := true
|
||||
# The amount of time in the future to predict the owning agent's position along
|
||||
# the path. Setting it to 0.0 will force non-predictive path following.
|
||||
var prediction_time := 0.0
|
||||
|
||||
|
||||
func _init(agent: GSAISteeringAgent, _path: GSAIPath, _path_offset := 0.0, _prediction_time := 0.0).(
|
||||
agent, null
|
||||
) -> void:
|
||||
self.path = _path
|
||||
self.path_offset = _path_offset
|
||||
self.prediction_time = _prediction_time
|
||||
|
||||
|
||||
func _calculate_steering(acceleration: GSAITargetAcceleration) -> void:
|
||||
var location := (
|
||||
agent.position
|
||||
if prediction_time == 0
|
||||
else agent.position + (agent.linear_velocity * prediction_time)
|
||||
)
|
||||
|
||||
var distance := path.calculate_distance(location)
|
||||
var target_distance := distance + path_offset
|
||||
|
||||
if prediction_time > 0 and path.is_open:
|
||||
if target_distance < path.calculate_distance(agent.position):
|
||||
target_distance = path.length
|
||||
|
||||
var target_position := path.calculate_target_position(target_distance)
|
||||
|
||||
if is_arrive_enabled and path.is_open:
|
||||
if path_offset >= 0:
|
||||
if target_distance > path.length - deceleration_radius:
|
||||
_arrive(acceleration, target_position)
|
||||
return
|
||||
else:
|
||||
if target_distance < deceleration_radius:
|
||||
_arrive(acceleration, target_position)
|
||||
return
|
||||
|
||||
acceleration.linear = (target_position - agent.position).normalized()
|
||||
acceleration.linear *= agent.linear_acceleration_max
|
||||
acceleration.angular = 0
|
20
game/steering_ai_framework/Behaviors/GSAILookWhereYouGo.gd
Normal file
20
game/steering_ai_framework/Behaviors/GSAILookWhereYouGo.gd
Normal file
@ -0,0 +1,20 @@
|
||||
# Calculates an angular acceleration to match an agent's orientation to its
|
||||
# direction of travel.
|
||||
class_name GSAILookWhereYouGo
|
||||
extends GSAIMatchOrientation
|
||||
|
||||
|
||||
func _init(agent: GSAISteeringAgent, use_z := false).(agent, null, use_z) -> void:
|
||||
pass
|
||||
|
||||
|
||||
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)
|
||||
if use_z
|
||||
else GSAIUtils.vector2_to_angle(GSAIUtils.to_vector2(agent.linear_velocity))
|
||||
)
|
||||
_match_orientation(accel, orientation)
|
51
game/steering_ai_framework/Behaviors/GSAIMatchOrientation.gd
Normal file
51
game/steering_ai_framework/Behaviors/GSAIMatchOrientation.gd
Normal file
@ -0,0 +1,51 @@
|
||||
# Calculates an angular acceleration to match an agent's orientation to that of
|
||||
# its target. Attempts to make the agent arrive with zero remaining angular
|
||||
# velocity.
|
||||
class_name GSAIMatchOrientation
|
||||
extends GSAISteeringBehavior
|
||||
|
||||
# The target orientation for the behavior to try and match rotations to.
|
||||
var target: GSAIAgentLocation
|
||||
# The amount of distance in radians for the behavior to consider itself close
|
||||
# enough to be matching the target agent's rotation.
|
||||
var alignment_tolerance: float
|
||||
# The amount of distance in radians from the goal to start slowing down.
|
||||
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, _use_z := false).(agent) -> void:
|
||||
self.use_z = _use_z
|
||||
self.target = _target
|
||||
|
||||
|
||||
func _match_orientation(acceleration: GSAITargetAcceleration, desired_orientation: float) -> void:
|
||||
var rotation := wrapf(desired_orientation - agent.orientation, -PI, PI)
|
||||
|
||||
var rotation_size := abs(rotation)
|
||||
|
||||
if rotation_size <= alignment_tolerance:
|
||||
acceleration.set_zero()
|
||||
else:
|
||||
var desired_rotation := agent.angular_speed_max
|
||||
|
||||
if rotation_size <= deceleration_radius:
|
||||
desired_rotation *= rotation_size / deceleration_radius
|
||||
|
||||
desired_rotation *= rotation / rotation_size
|
||||
|
||||
acceleration.angular = ((desired_rotation - agent.angular_velocity) / time_to_reach)
|
||||
|
||||
var limited_acceleration := abs(acceleration.angular)
|
||||
if limited_acceleration > agent.angular_acceleration_max:
|
||||
acceleration.angular *= (agent.angular_acceleration_max / limited_acceleration)
|
||||
|
||||
acceleration.linear = Vector3.ZERO
|
||||
|
||||
|
||||
func _calculate_steering(acceleration: GSAITargetAcceleration) -> void:
|
||||
_match_orientation(acceleration, target.orientation)
|
49
game/steering_ai_framework/Behaviors/GSAIPriority.gd
Normal file
49
game/steering_ai_framework/Behaviors/GSAIPriority.gd
Normal file
@ -0,0 +1,49 @@
|
||||
# Container for multiple behaviors that returns the result of the first child
|
||||
# behavior with non-zero acceleration.
|
||||
class_name GSAIPriority
|
||||
extends GSAISteeringBehavior
|
||||
|
||||
var _behaviors := []
|
||||
|
||||
# The index of the last behavior the container prioritized.
|
||||
var last_selected_index: int
|
||||
# If a behavior's acceleration is lower than this threshold, the container
|
||||
# considers it has an acceleration of zero.
|
||||
var zero_threshold: float
|
||||
|
||||
|
||||
func _init(agent: GSAISteeringAgent, _zero_threshold := 0.001).(agent) -> void:
|
||||
self.zero_threshold = _zero_threshold
|
||||
|
||||
|
||||
# Appends a steering behavior as a child of this container.
|
||||
func add(behavior: GSAISteeringBehavior) -> void:
|
||||
_behaviors.append(behavior)
|
||||
|
||||
|
||||
# Returns the behavior at the position in the pool referred to by `index`, or
|
||||
# `null` if no behavior was found.
|
||||
func get_behavior_at(index: int) -> GSAISteeringBehavior:
|
||||
if _behaviors.size() > index:
|
||||
return _behaviors[index]
|
||||
printerr("Tried to get index " + str(index) + " in array of size " + str(_behaviors.size()))
|
||||
return null
|
||||
|
||||
|
||||
func _calculate_steering(accel: GSAITargetAcceleration) -> void:
|
||||
var threshold_squared := zero_threshold * zero_threshold
|
||||
|
||||
last_selected_index = -1
|
||||
|
||||
var size := _behaviors.size()
|
||||
|
||||
if size > 0:
|
||||
for i in range(size):
|
||||
last_selected_index = i
|
||||
var behavior: GSAISteeringBehavior = _behaviors[i]
|
||||
behavior.calculate_steering(accel)
|
||||
|
||||
if accel.get_magnitude_squared() > threshold_squared:
|
||||
break
|
||||
else:
|
||||
accel.set_zero()
|
37
game/steering_ai_framework/Behaviors/GSAIPursue.gd
Normal file
37
game/steering_ai_framework/Behaviors/GSAIPursue.gd
Normal file
@ -0,0 +1,37 @@
|
||||
# Calculates an acceleration to make an agent intercept another based on the
|
||||
# target agent's movement.
|
||||
class_name GSAIPursue
|
||||
extends GSAISteeringBehavior
|
||||
|
||||
# The target agent that the behavior is trying to intercept.
|
||||
var target: GSAISteeringAgent
|
||||
# The maximum amount of time in the future the behavior predicts the target's
|
||||
# location.
|
||||
var predict_time_max: float
|
||||
|
||||
|
||||
func _init(agent: GSAISteeringAgent, _target: GSAISteeringAgent, _predict_time_max := 1.0).(agent) -> void:
|
||||
self.target = _target
|
||||
self.predict_time_max = _predict_time_max
|
||||
|
||||
|
||||
func _calculate_steering(acceleration: GSAITargetAcceleration) -> void:
|
||||
var target_position := target.position
|
||||
var distance_squared := (target_position - agent.position).length_squared()
|
||||
|
||||
var speed_squared := agent.linear_velocity.length_squared()
|
||||
var predict_time := predict_time_max
|
||||
|
||||
if speed_squared > 0:
|
||||
var predict_time_squared := distance_squared / speed_squared
|
||||
if predict_time_squared < predict_time_max * predict_time_max:
|
||||
predict_time = sqrt(predict_time_squared)
|
||||
|
||||
acceleration.linear = ((target_position + (target.linear_velocity * predict_time)) - agent.position).normalized()
|
||||
acceleration.linear *= _get_modified_acceleration()
|
||||
|
||||
acceleration.angular = 0
|
||||
|
||||
|
||||
func _get_modified_acceleration() -> float:
|
||||
return agent.linear_acceleration_max
|
19
game/steering_ai_framework/Behaviors/GSAISeek.gd
Normal file
19
game/steering_ai_framework/Behaviors/GSAISeek.gd
Normal file
@ -0,0 +1,19 @@
|
||||
# Calculates an acceleration to take an agent to a target agent's position
|
||||
# directly.
|
||||
class_name GSAISeek
|
||||
extends GSAISteeringBehavior
|
||||
|
||||
# The target that the behavior aims to move the agent to.
|
||||
var target: GSAIAgentLocation
|
||||
|
||||
|
||||
func _init(agent: GSAISteeringAgent, _target: GSAIAgentLocation).(agent) -> void:
|
||||
self.target = _target
|
||||
|
||||
|
||||
func _calculate_steering(acceleration: GSAITargetAcceleration) -> void:
|
||||
acceleration.linear = (
|
||||
(target.position - agent.position).normalized()
|
||||
* agent.linear_acceleration_max
|
||||
)
|
||||
acceleration.angular = 0
|
42
game/steering_ai_framework/Behaviors/GSAISeparation.gd
Normal file
42
game/steering_ai_framework/Behaviors/GSAISeparation.gd
Normal file
@ -0,0 +1,42 @@
|
||||
# Calculates an acceleration that repels the agent from its neighbors in the
|
||||
# given `GSAIProximity`.
|
||||
#
|
||||
# The acceleration is an average based on all neighbors, multiplied by a
|
||||
# strength decreasing by the inverse square law in relation to distance, and it
|
||||
# accumulates.
|
||||
class_name GSAISeparation
|
||||
extends GSAIGroupBehavior
|
||||
|
||||
# The coefficient to calculate how fast the separation strength decays with distance.
|
||||
var decay_coefficient := 1.0
|
||||
|
||||
var _acceleration: GSAITargetAcceleration
|
||||
|
||||
|
||||
func _init(agent: GSAISteeringAgent, proximity: GSAIProximity).(agent, proximity) -> void:
|
||||
pass
|
||||
|
||||
|
||||
func _calculate_steering(acceleration: GSAITargetAcceleration) -> void:
|
||||
acceleration.set_zero()
|
||||
self._acceleration = acceleration
|
||||
# warning-ignore:return_value_discarded
|
||||
proximity._find_neighbors(_callback)
|
||||
|
||||
|
||||
# Callback for the proximity to call when finding neighbors. Determines the amount of
|
||||
# acceleration that `neighbor` imposes based on its distance from the owner agent.
|
||||
# tags: virtual
|
||||
func _report_neighbor(neighbor: GSAISteeringAgent) -> bool:
|
||||
var to_agent := agent.position - neighbor.position
|
||||
|
||||
var distance_squared := to_agent.length_squared()
|
||||
var acceleration_max := agent.linear_acceleration_max
|
||||
|
||||
var strength := decay_coefficient / distance_squared
|
||||
if strength > acceleration_max:
|
||||
strength = acceleration_max
|
||||
|
||||
_acceleration.linear += to_agent * (strength / sqrt(distance_squared))
|
||||
|
||||
return true
|
80
game/steering_ai_framework/CHANGELOG.md
Normal file
80
game/steering_ai_framework/CHANGELOG.md
Normal file
@ -0,0 +1,80 @@
|
||||
# Changelog #
|
||||
|
||||
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 an `Arrive3d` demo to showcase 3D movement.
|
||||
|
||||
### Improvements ###
|
||||
|
||||
- All the demos got a bit of attention to improve their feel.
|
||||
|
||||
### 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.
|
||||
- Fixed error when double clicking an item in the DemoPicker.
|
||||
- Fixed the background sometimes not covering the entire viewport in demos.
|
||||
|
||||
## 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.
|
||||
|
||||
**Important**: we renamed all classes from GST\* to GSAI\* (Godot Steering AI). When you upgrade the framework in your project, use the project search and replace feature in Godot (<kbd>Ctrl</kbd> <kbd>Shift</kbd> <kbd>F</kbd>) to find and replace `GST` with `GSAI`.
|
||||
|
||||
If you were using `GSTKinematicBodyAgent` or `GSTRigidBodyAgent`, search and replace them respectively with `GSAIKinematicBody3DAgent` and `GSAIRigidBody3DAgent`.
|
||||
|
||||
We decided to make this change as soon as possible, as the framework was released a few days ago.
|
||||
|
||||
### Features ###
|
||||
|
||||
- There is now a main scene with a demo picker, so you can select and play any demo on the fly.
|
||||
- The demo projects now support resizing and toggling fullscreen with <kbd>F11</kbd>.
|
||||
|
||||
### Improvements ###
|
||||
|
||||
- We handled all warnings in the framework, so using it won't add warnings to your projects.
|
||||
|
||||
### Changes ###
|
||||
|
||||
- Renamed all classes from `GST*` (Godot Steering Toolkit) to `GSAI*` (Godot Steering AI).
|
||||
- Removed `GSTNode2DAgent`, `GSTNodeAgent`, and `GSTSpatialAgent` classes.
|
||||
- For specialized steering agents, `GSAIKinematicBody2DAgent`, `GSAIRigidBody2DAgent`, or their 3D equivalent.
|
||||
- If you intend to write your own movement system instead of using Godot's, the base class `GSTSpecializedAgent` is there to help you.
|
||||
- Renamed `GSAIRigidBodyAgent` and `GSAIRigidBodyAgent` to `GSAIRigidBody3DAgent` and `GSAIRigidBody3DAgent` respectively.
|
||||
- 3D nodes like `Sprite`, `KinematicBody`, etc. are being renamed to `Sprite3D`, `KinematicBody3D`, etc. in the upcoming Godot 4.0 release, to be consistent with 2D nodes. We decided to rename them now instead of breaking compatibility in a future release.
|
||||
|
||||
### Bug fixes ###
|
||||
|
||||
- GSTFollowPath no longer loops back around itself on open paths when `predict_time` is non-zero.
|
||||
|
||||
## Godot Steering AI Framework 1.0.0 ##
|
||||
|
||||
This is the first major release of the framework. It comes with:
|
||||
|
||||
- All the essential steering behaviors: `Arrive`, `AvoidCollisions`, `Blend`, `Cohesion`, `Evade`, `Face`, `Flee`, `FollowPath`, `LookWhereYouGo`, `MatchOrientation`, `Priority`, `Pursue`, `Seek`, `Separation`.
|
||||
- Group behaviors and detecting neighbors.
|
||||
- Blending and prioritized behaviors.
|
||||
- Specialized types to code agents based on physics bodies:
|
||||
- For 2D games, `KinematicBody2DAgent` and `RigidBody2DAgent`.
|
||||
- For 3D games, `KinematicBody3DAgent` and `RigidBody3DAgent`.
|
||||
- 9 Godot demos to learn straight from the code.
|
||||
|
||||
### Manual ###
|
||||
|
||||
To get started, check out the framework's [manual](https://www.gdquest.com/docs/godot-steering-toolkit/).
|
||||
|
||||
There, you can also find the full [code reference](https://www.gdquest.com/docs/godot-steering-toolkit/reference/).
|
||||
|
||||
*Note*: we generate the code reference from docstrings in the source code with [GDScript Docs Maker](https://github.com/GDQuest/gdscript-docs-maker).
|
||||
|
7
game/steering_ai_framework/GSAIAgentLocation.gd
Normal file
7
game/steering_ai_framework/GSAIAgentLocation.gd
Normal file
@ -0,0 +1,7 @@
|
||||
# Represents an agent with only a location and an orientation.
|
||||
class_name GSAIAgentLocation
|
||||
|
||||
# The agent's position in space.
|
||||
var position := Vector3.ZERO
|
||||
# The agent's orientation on its Y axis rotation.
|
||||
var orientation := 0.0
|
19
game/steering_ai_framework/GSAIGroupBehavior.gd
Normal file
19
game/steering_ai_framework/GSAIGroupBehavior.gd
Normal file
@ -0,0 +1,19 @@
|
||||
# Base type for group-based steering behaviors.
|
||||
class_name GSAIGroupBehavior
|
||||
extends GSAISteeringBehavior
|
||||
|
||||
# Container to find neighbors of the agent and calculate group behavior.
|
||||
var proximity: GSAIProximity
|
||||
|
||||
var _callback := funcref(self, "_report_neighbor")
|
||||
|
||||
|
||||
func _init(agent: GSAISteeringAgent, _proximity: GSAIProximity).(agent) -> void:
|
||||
self.proximity = _proximity
|
||||
|
||||
|
||||
# Internal callback for the behavior to define whether or not a member is
|
||||
# relevant
|
||||
# tags: virtual
|
||||
func _report_neighbor(_neighbor: GSAISteeringAgent) -> bool:
|
||||
return false
|
132
game/steering_ai_framework/GSAIPath.gd
Normal file
132
game/steering_ai_framework/GSAIPath.gd
Normal file
@ -0,0 +1,132 @@
|
||||
# Represents a path made up of Vector3 waypoints, split into segments path
|
||||
# follow behaviors can use.
|
||||
class_name GSAIPath
|
||||
extends Reference
|
||||
|
||||
# If `false`, the path loops.
|
||||
var is_open: bool
|
||||
# Total length of the path.
|
||||
var length: float
|
||||
|
||||
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]
|
||||
|
||||
|
||||
# Creates a path from a list of waypoints.
|
||||
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
|
||||
|
||||
_segments = []
|
||||
length = 0
|
||||
var current: Vector3 = waypoints.front()
|
||||
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 := GSAISegment.new(previous, current)
|
||||
length += segment.length
|
||||
segment.cumulative_length = length
|
||||
_segments.append(segment)
|
||||
|
||||
|
||||
# Returns the distance from `agent_current_position` to the next waypoint.
|
||||
func calculate_distance(agent_current_position: Vector3) -> float:
|
||||
if _segments.size() == 0:
|
||||
return 0.0
|
||||
var smallest_distance_squared: float = INF
|
||||
var nearest_segment: GSAISegment
|
||||
for i in range(_segments.size()):
|
||||
var segment: GSAISegment = _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
|
||||
smallest_distance_squared = distance_squared
|
||||
nearest_segment = segment
|
||||
|
||||
var length_on_path := (
|
||||
nearest_segment.cumulative_length
|
||||
- _nearest_point_on_path.distance_to(nearest_segment.end)
|
||||
)
|
||||
|
||||
return length_on_path
|
||||
|
||||
|
||||
# Calculates a target position from the path's starting point based on the `target_distance`.
|
||||
func calculate_target_position(target_distance: float) -> Vector3:
|
||||
if is_open:
|
||||
target_distance = clamp(target_distance, 0, length)
|
||||
else:
|
||||
if target_distance < 0:
|
||||
target_distance = length + fmod(target_distance, length)
|
||||
elif target_distance > length:
|
||||
target_distance = fmod(target_distance, length)
|
||||
|
||||
var desired_segment: GSAISegment
|
||||
for i in range(_segments.size()):
|
||||
var segment: GSAISegment = _segments[i]
|
||||
if segment.cumulative_length >= target_distance:
|
||||
desired_segment = segment
|
||||
break
|
||||
|
||||
if not desired_segment:
|
||||
desired_segment = _segments.back()
|
||||
|
||||
var distance := desired_segment.cumulative_length - target_distance
|
||||
|
||||
return (
|
||||
((desired_segment.begin - desired_segment.end) * (distance / desired_segment.length))
|
||||
+ desired_segment.end
|
||||
)
|
||||
|
||||
|
||||
# Returns the position of the first point on the path.
|
||||
func get_start_point() -> Vector3:
|
||||
return _segments.front().begin
|
||||
|
||||
|
||||
# Returns the position of the last point on the path.
|
||||
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)
|
||||
|
||||
return _nearest_point_on_segment.distance_squared_to(position)
|
||||
|
||||
|
||||
class GSAISegment:
|
||||
var begin: Vector3
|
||||
var end: Vector3
|
||||
var length: float
|
||||
var cumulative_length: float
|
||||
|
||||
func _init(_begin: Vector3, _end: Vector3) -> void:
|
||||
self.begin = _begin
|
||||
self.end = _end
|
||||
length = _begin.distance_to(_end)
|
27
game/steering_ai_framework/GSAISteeringAgent.gd
Normal file
27
game/steering_ai_framework/GSAISteeringAgent.gd
Normal file
@ -0,0 +1,27 @@
|
||||
# Adds velocity, speed, and size data to `GSAIAgentLocation`.
|
||||
#
|
||||
# It is the character's responsibility to keep this information up to date for
|
||||
# the steering toolkit to work correctly.
|
||||
extends GSAIAgentLocation
|
||||
class_name GSAISteeringAgent
|
||||
|
||||
# The amount of velocity to be considered effectively not moving.
|
||||
var zero_linear_speed_threshold := 0.01
|
||||
# The maximum speed at which the agent can move.
|
||||
var linear_speed_max := 0.0
|
||||
# The maximum amount of acceleration that any behavior can apply to the agent.
|
||||
var linear_acceleration_max := 0.0
|
||||
# The maximum amount of angular speed at which the agent can rotate.
|
||||
var angular_speed_max := 0.0
|
||||
# The maximum amount of angular acceleration that any behavior can apply to an
|
||||
# agent.
|
||||
var angular_acceleration_max := 0.0
|
||||
# Current velocity of the agent.
|
||||
var linear_velocity := Vector3.ZERO
|
||||
# Current angular velocity of the agent.
|
||||
var angular_velocity := 0.0
|
||||
# The radius of the sphere that approximates the agent's size in space.
|
||||
var bounding_radius := 0.0
|
||||
# Used internally by group behaviors and proximities to mark the agent as already
|
||||
# considered.
|
||||
var is_tagged := false
|
29
game/steering_ai_framework/GSAISteeringBehavior.gd
Normal file
29
game/steering_ai_framework/GSAISteeringBehavior.gd
Normal file
@ -0,0 +1,29 @@
|
||||
# Base class for all steering behaviors.
|
||||
#
|
||||
# Steering behaviors calculate the linear and the angular acceleration to be
|
||||
# to the agent that owns them.
|
||||
#
|
||||
# The `calculate_steering` function is the entry point for all behaviors.
|
||||
# Individual steering behaviors encapsulate the steering logic.
|
||||
class_name GSAISteeringBehavior
|
||||
|
||||
# If `false`, all calculations return zero amounts of acceleration.
|
||||
var is_enabled := true
|
||||
# The AI agent on which the steering behavior bases its calculations.
|
||||
var agent: GSAISteeringAgent
|
||||
|
||||
|
||||
func _init(_agent: GSAISteeringAgent) -> void:
|
||||
self.agent = _agent
|
||||
|
||||
|
||||
# Sets the `acceleration` with the behavior's desired amount of acceleration.
|
||||
func calculate_steering(acceleration: GSAITargetAcceleration) -> void:
|
||||
if is_enabled:
|
||||
_calculate_steering(acceleration)
|
||||
else:
|
||||
acceleration.set_zero()
|
||||
|
||||
|
||||
func _calculate_steering(acceleration: GSAITargetAcceleration) -> void:
|
||||
acceleration.set_zero()
|
32
game/steering_ai_framework/GSAITargetAcceleration.gd
Normal file
32
game/steering_ai_framework/GSAITargetAcceleration.gd
Normal file
@ -0,0 +1,32 @@
|
||||
# A desired linear and angular amount of acceleration requested by the steering
|
||||
# system.
|
||||
class_name GSAITargetAcceleration
|
||||
|
||||
# Linear acceleration
|
||||
var linear := Vector3.ZERO
|
||||
# Angular acceleration
|
||||
var angular := 0.0
|
||||
|
||||
|
||||
# Sets the linear and angular components to 0.
|
||||
func set_zero() -> void:
|
||||
linear.x = 0.0
|
||||
linear.y = 0.0
|
||||
linear.z = 0.0
|
||||
angular = 0.0
|
||||
|
||||
|
||||
# Adds `accel`'s components, multiplied by `scalar`, to this one.
|
||||
func add_scaled_accel(accel: GSAITargetAcceleration, scalar: float) -> void:
|
||||
linear += accel.linear * scalar
|
||||
angular += accel.angular * scalar
|
||||
|
||||
|
||||
# Returns the squared magnitude of the linear and angular components.
|
||||
func get_magnitude_squared() -> float:
|
||||
return linear.length_squared() + angular * angular
|
||||
|
||||
|
||||
# Returns the magnitude of the linear and angular components.
|
||||
func get_magnitude() -> float:
|
||||
return sqrt(get_magnitude_squared())
|
36
game/steering_ai_framework/GSAIUtils.gd
Normal file
36
game/steering_ai_framework/GSAIUtils.gd
Normal file
@ -0,0 +1,36 @@
|
||||
# Math and vector utility functions.
|
||||
class_name GSAIUtils
|
||||
|
||||
# Returns the `vector` with its length capped to `limit`.
|
||||
static func clampedv3(vector: Vector3, limit: float) -> Vector3:
|
||||
var length_squared := vector.length_squared()
|
||||
var limit_squared := limit * limit
|
||||
if length_squared > limit_squared:
|
||||
vector *= sqrt(limit_squared / length_squared)
|
||||
return vector
|
||||
|
||||
# Returns an angle in radians between the positive X axis and the `vector`.
|
||||
#
|
||||
# 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)
|
||||
|
||||
# Returns a directional vector from the given orientation angle.
|
||||
#
|
||||
# This assumes orientation for 2D agents or 3D agents that are upright and
|
||||
# rotate around the Y axis.
|
||||
static func angle_to_vector2(angle: float) -> Vector2:
|
||||
return Vector2(sin(-angle), cos(angle))
|
||||
|
||||
# Returns a vector2 with `vector`'s x and y components.
|
||||
static func to_vector2(vector: Vector3) -> Vector2:
|
||||
return Vector2(vector.x, vector.y)
|
||||
|
||||
# Returns a vector3 with `vector`'s x and y components and 0 in z.
|
||||
static func to_vector3(vector: Vector2) -> Vector3:
|
||||
return Vector3(vector.x, vector.y, 0)
|
21
game/steering_ai_framework/LICENSE
Normal file
21
game/steering_ai_framework/LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 GDQuest
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
@ -0,0 +1,26 @@
|
||||
# Determines any agent that is in the specified list as being neighbors with the
|
||||
# owner agent, regardless of distance.
|
||||
extends GSAIProximity
|
||||
class_name GSAIInfiniteProximity
|
||||
|
||||
|
||||
func _init(agent: GSAISteeringAgent, agents: Array).(agent, agents) -> void:
|
||||
pass
|
||||
|
||||
|
||||
# Returns a number of neighbors based on a `callback` function.
|
||||
#
|
||||
# `_find_neighbors` calls `callback` for each agent in the `agents` array and
|
||||
# adds one to the count if its `callback` returns true.
|
||||
# tags: virtual
|
||||
func _find_neighbors(callback: FuncRef) -> int:
|
||||
var neighbor_count := 0
|
||||
var agent_count := agents.size()
|
||||
for i in range(agent_count):
|
||||
var current_agent := agents[i] as GSAISteeringAgent
|
||||
|
||||
if current_agent != agent:
|
||||
if callback.call_func(current_agent):
|
||||
neighbor_count += 1
|
||||
|
||||
return neighbor_count
|
22
game/steering_ai_framework/Proximities/GSAIProximity.gd
Normal file
22
game/steering_ai_framework/Proximities/GSAIProximity.gd
Normal file
@ -0,0 +1,22 @@
|
||||
# Base container type that stores data to find the neighbors of an agent.
|
||||
extends Reference
|
||||
class_name GSAIProximity
|
||||
|
||||
# The owning agent whose neighbors are found in the group
|
||||
var agent: GSAISteeringAgent
|
||||
# The agents who are part of this group and could be potential neighbors
|
||||
var agents := []
|
||||
|
||||
|
||||
func _init(_agent: GSAISteeringAgent, _agents: Array) -> void:
|
||||
self.agent = _agent
|
||||
self.agents = _agents
|
||||
|
||||
|
||||
# Returns a number of neighbors based on a `callback` function.
|
||||
#
|
||||
# `_find_neighbors` calls `callback` for each agent in the `agents` array and
|
||||
# adds one to the count if its `callback` returns true.
|
||||
# tags: virtual
|
||||
func _find_neighbors(_callback: FuncRef) -> int:
|
||||
return 0
|
@ -0,0 +1,56 @@
|
||||
# Determines any agent that is in the specified list as being neighbors with the owner agent if
|
||||
# they lie within the specified radius.
|
||||
extends GSAIProximity
|
||||
class_name GSAIRadiusProximity
|
||||
|
||||
# The radius around the owning agent to find neighbors in
|
||||
var radius := 0.0
|
||||
|
||||
var _last_frame := 0
|
||||
var _scene_tree: SceneTree
|
||||
|
||||
|
||||
func _init(agent: GSAISteeringAgent, agents: Array, _radius: float).(agent, agents) -> void:
|
||||
self.radius = _radius
|
||||
_scene_tree = Engine.get_main_loop()
|
||||
|
||||
|
||||
# Returns a number of neighbors based on a `callback` function.
|
||||
#
|
||||
# `_find_neighbors` calls `callback` for each agent in the `agents` array that lie within
|
||||
# the radius around the owning agent and adds one to the count if its `callback` returns true.
|
||||
# tags: virtual
|
||||
func _find_neighbors(callback: FuncRef) -> int:
|
||||
var agent_count := agents.size()
|
||||
var neighbor_count := 0
|
||||
|
||||
var current_frame := _scene_tree.get_frame() if _scene_tree else -_last_frame
|
||||
if current_frame != _last_frame:
|
||||
_last_frame = current_frame
|
||||
|
||||
var owner_position := agent.position
|
||||
|
||||
for i in range(agent_count):
|
||||
var current_agent := agents[i] as GSAISteeringAgent
|
||||
|
||||
if current_agent != agent:
|
||||
var distance_squared := owner_position.distance_squared_to(current_agent.position)
|
||||
|
||||
var range_to := radius + current_agent.bounding_radius
|
||||
|
||||
if distance_squared < range_to * range_to:
|
||||
if callback.call_func(current_agent):
|
||||
current_agent.is_tagged = true
|
||||
neighbor_count += 1
|
||||
continue
|
||||
|
||||
current_agent.is_tagged = false
|
||||
else:
|
||||
for i in range(agent_count):
|
||||
var current_agent = agents[i] as GSAISteeringAgent
|
||||
|
||||
if current_agent != agent and current_agent.is_tagged:
|
||||
if callback.call_func(current_agent):
|
||||
neighbor_count += 1
|
||||
|
||||
return neighbor_count
|
69
game/steering_ai_framework/README.md
Normal file
69
game/steering_ai_framework/README.md
Normal file
@ -0,0 +1,69 @@
|
||||
# Godot Steering AI Framework #
|
||||
|
||||
![Project banner](./assets/banner.svg)
|
||||
|
||||
This project is a framework to code complex and smooth AI movement in GDScript, using steering behaviors. It works in both 2D and 3D games.
|
||||
|
||||
It supports all essential steering behaviors like flee, follow, look at, but also blended behaviors, group behaviors, avoiding neighbors, following a path, following the leader, and much more.
|
||||
|
||||
<!-- markdown-toc start - Don't edit this section. Run M-x markdown-toc-refresh-toc -->
|
||||
**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)
|
||||
<!-- markdown-toc end -->
|
||||
|
||||
|
||||
## Introduction ##
|
||||
|
||||
In the 1990s, [Craig Reynolds](http://www.red3d.com/cwr/) developed algorithms for common AI behaviors. They allowed AI agents to seek out or flee from a target, follow a pre-defined path, or face in a particular direction. They were simple, repeatable tasks that could be broken down into programming algorithms, which made them easy to reuse, maintain, combine, and extend.
|
||||
|
||||
While an AI agent's next action is based on decision making and planning algorithms, steering behaviors dictate how it will move from one frame to the next. They use available information and calculate where to move at that moment.
|
||||
|
||||
Joining these systems together can give sophisticated and graceful movement while also being more efficient than complex pathfinding algorithms like A\*.
|
||||
|
||||
## The framework ##
|
||||
|
||||
This project is a framework for the [Godot game engine](https://godotengine.org/). It takes inspiration from the excellent [GDX-AI](https://github.com/libgdx/gdx-ai) framework for the [LibGDX](https://libgdx.badlogicgames.com/) java-based framework.
|
||||
|
||||
Every class in the framework extends Godot's [Reference](https://docs.godotengine.org/en/latest/classes/class_reference.html) type. There is no need to have a complex scene tree; you can contain that has to do with the AI's movement inside GDScript classes.
|
||||
|
||||
### How it works ###
|
||||
|
||||
In GSAI, a steering agent represents a character or a vehicle. The agent stores its position, orientation, maximum speeds, and current velocity. The agent stores a steering behavior that calculates a linear or angular change in velocity based on its information.
|
||||
|
||||
The coder then applies that acceleration in whatever ways is appropriate to the character to change its velocities, like RigidBody's `apply_impulse`, or a KinematicBody's `move_and_slide`.
|
||||
|
||||
## Documentation ##
|
||||
|
||||
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.
|
||||
|
||||
Here are some guides to get you started:
|
||||
|
||||
1. [How to install the framework](https://www.gdquest.com/docs/godot-steering-ai-framework/how-to-install/)
|
||||
1. [Getting Started](https://www.gdquest.com/docs/godot-steering-ai-framework/getting-started/)
|
||||
1. [Code reference](https://www.gdquest.com/docs/godot-steering-ai-framework/reference/)
|
||||
|
||||
## Contributing ##
|
||||
|
||||
If you encounter a bug or you have an idea to improve the tool, please [open an issue](https://github.com/GDQuest/gdscript-docs-maker/issues/new).
|
||||
|
||||
If you want to contribute to the project, for instance by fixing a bug or adding a feature, check out our:
|
||||
|
||||
1. [Contributor's guidelines](https://www.gdquest.com/docs/guidelines/contributing-to/gdquest-projects/).
|
||||
1. [GDScript style guide](https://www.gdquest.com/docs/guidelines/best-practices/godot-gdscript/)
|
||||
|
||||
## Support us ##
|
||||
|
||||
Our work on Free Software is sponsored by our [Godot game creation courses](https://gdquest.mavenseed.com/). Consider getting one to support us!
|
||||
|
||||
*If you like our work, please star the repository! This helps more people find it.*
|
||||
|
||||
## Join the community ##
|
||||
|
||||
- You can join the GDQuest community and come chat with us on [Discord](https://discord.gg/CHYVgar)
|
||||
- For quick news, follow us on [Twitter](https://twitter.com/nathangdquest)
|
||||
- We release video tutorials and major updates on [YouTube](https://youtube.com/c/gdquest)
|
Loading…
Reference in New Issue
Block a user