Added wander and look around behaviours.

main
Martin Felis 2024-12-28 14:57:56 +01:00
parent bec51332c7
commit 3ce12e24be
14 changed files with 611 additions and 314 deletions

View File

@ -0,0 +1 @@
{"included":{"res://tests/scenes/game_tests.gd":["test_save_game"]},"server_port":31002,"skipped":{},"version":"1.0"}

View File

@ -0,0 +1,37 @@
extends BTAction
@export var radius:float = 1.0
@export var output_var: StringName = &"target_location"
@export var navigatable_target:bool = false
@export var stay_in_radius:bool = true
var _reference_location:Vector3 = Vector3.INF
func _setup() -> void:
_reference_location = agent.global_position
func _tick(_delta: float) -> Status:
assert(_reference_location != Vector3.INF)
var target_is_navigatable = true
var random_location:Vector3 = Vector3.ZERO
var agent_npc:NonPlayerCharacter = agent as NonPlayerCharacter
if navigatable_target:
assert(agent_npc)
target_is_navigatable = false
if not stay_in_radius:
_reference_location = agent_npc.global_position
var random_angle:float = randf_range(-PI, PI)
random_location = _reference_location + radius * Vector3(cos(random_angle), 0, sin(random_angle))
if navigatable_target and agent_npc.is_target_navigatable(random_location):
target_is_navigatable = true
if target_is_navigatable:
blackboard.set_var(output_var, random_location)
return SUCCESS
return FAILURE

View File

@ -0,0 +1,42 @@
@tool
extends BTAction
@export var target_location_var: StringName = &"target_location"
var target_position: Vector3 = Vector3.ZERO
var _current_look_direction:Vector3 = Vector3.BACK
var _current_look_angle:float = 0
var _target_look_direction:Vector3 = Vector3.BACK
var _target_look_angle:float = 0
var _look_angle_damper:SpringDamper = SpringDamper.new(0, 2, 0.06, 0.003)
func _generate_name() -> String:
return "Look at Target " + LimboUtility.decorate_var(target_location_var)
func _tick(delta: float) -> Status:
target_position = blackboard.get_var(target_location_var)
update_look_direction(delta)
if absf(_target_look_angle - _current_look_angle) < deg_to_rad(5):
return SUCCESS
return RUNNING
func update_look_direction(delta:float) -> void:
_target_look_direction = (target_position - agent.global_position).normalized()
_current_look_direction = agent.basis.z
_current_look_angle = _current_look_direction.signed_angle_to(Vector3.BACK, Vector3.UP)
_target_look_angle = _target_look_direction.signed_angle_to(Vector3.BACK, Vector3.UP)
if _target_look_angle - _current_look_angle > PI:
_current_look_angle = _current_look_angle + PI * 2
elif _current_look_angle - _target_look_angle > PI:
_current_look_angle = _current_look_angle - PI * 2
var damped_look_angle:float = _look_angle_damper.calc(_current_look_angle, _target_look_angle, delta)
_current_look_direction = Vector3(sin(-damped_look_angle), 0, cos(-damped_look_angle))
agent.basis = Basis.looking_at(-_current_look_direction)

View File

@ -23,7 +23,7 @@ func _tick(delta: float) -> Status:
update_look_direction(delta) update_look_direction(delta)
if absf(_target_look_angle - _current_look_angle) < deg_to_rad(5): if absf(_target_look_angle - _current_look_angle) < deg_to_rad(5):
return SUCCESS return RUNNING
return FAILURE return FAILURE

33
ai/tasks/navigate_to.gd Normal file
View File

@ -0,0 +1,33 @@
@tool
extends BTAction
@export var target_var: StringName = &"target"
var target_node:Node3D = null
var current_target_name:String = ""
func _generate_name() -> String:
return "NavigateTo \"%s\"" % [
LimboUtility.decorate_var(target_var)
]
func _tick(_delta: float) -> Status:
target_node = blackboard.get_var(target_var)
if not is_instance_valid(target_node):
return FAILURE
if current_target_name != target_node.name:
current_target_name = target_node.name
print ("Next target: " + current_target_name)
var agent_npc:NonPlayerCharacter = agent as NonPlayerCharacter
if agent_npc:
agent_npc.navigate_to(target_node.global_position)
var agent_node:Node3D = agent as Node3D
if agent_node:
var distance_to_target:float = (agent.global_position - target_node.global_position).length()
if distance_to_target < 0.1:
return SUCCESS
return RUNNING

View File

@ -0,0 +1,25 @@
@tool
extends BTAction
@export var target_var: StringName = &"target_location"
var target_location:Vector3 = Vector3.ZERO
func _generate_name() -> String:
return "NavigateTo \"%s\"" % [
LimboUtility.decorate_var(target_var)
]
func _tick(_delta: float) -> Status:
target_location = blackboard.get_var(target_var)
var agent_npc:NonPlayerCharacter = agent as NonPlayerCharacter
if agent_npc:
agent_npc.navigate_to(target_location)
var agent_node:Node3D = agent as Node3D
if agent_node:
var distance_to_target:float = (agent_node.global_position - target_location).length()
if distance_to_target < 0.1:
return SUCCESS
return RUNNING

9
ai/trees/empty.tres Normal file
View File

@ -0,0 +1,9 @@
[gd_resource type="BehaviorTree" load_steps=3 format=3 uid="uid://dck7kl4210076"]
[sub_resource type="BlackboardPlan" id="BlackboardPlan_dsl36"]
[sub_resource type="BTAlwaysSucceed" id="BTAlwaysSucceed_c7j70"]
[resource]
blackboard_plan = SubResource("BlackboardPlan_dsl36")
root_task = SubResource("BTAlwaysSucceed_c7j70")

31
ai/trees/look_around.tres Normal file
View File

@ -0,0 +1,31 @@
[gd_resource type="BehaviorTree" load_steps=9 format=3 uid="uid://2v14hwp2gtg"]
[ext_resource type="Script" path="res://ai/tasks/look_at_location.gd" id="1_1lumv"]
[ext_resource type="Script" path="res://ai/tasks/find_nearby_random_location.gd" id="2_jrbpg"]
[sub_resource type="BlackboardPlan" id="BlackboardPlan_sdda8"]
[sub_resource type="BTAction" id="BTAction_ba1hn"]
script = ExtResource("2_jrbpg")
radius = 1.0
output_var = &"target_location"
navigatable_target = true
stay_in_radius = true
[sub_resource type="BTAction" id="BTAction_d7u7l"]
script = ExtResource("1_1lumv")
target_location_var = &"target_location"
[sub_resource type="BTWait" id="BTWait_jmtj2"]
duration = 2.0
[sub_resource type="BTSequence" id="BTSequence_ouv1e"]
children = [SubResource("BTAction_ba1hn"), SubResource("BTAction_d7u7l"), SubResource("BTWait_jmtj2")]
[sub_resource type="BTRepeat" id="BTRepeat_nqqvt"]
forever = true
children = [SubResource("BTSequence_ouv1e")]
[resource]
blackboard_plan = SubResource("BlackboardPlan_sdda8")
root_task = SubResource("BTRepeat_nqqvt")

View File

@ -1,7 +1,7 @@
[gd_resource type="BehaviorTree" load_steps=13 format=3 uid="uid://blccr23qjixws"] [gd_resource type="BehaviorTree" load_steps=12 format=3 uid="uid://blccr23qjixws"]
[ext_resource type="Script" path="res://ai/tasks/find_target_by_name.gd" id="1_v4edy"] [ext_resource type="Script" path="res://ai/tasks/find_target_by_name.gd" id="1_v4edy"]
[ext_resource type="Script" path="res://ai/tasks/look_at_target.gd" id="2_5373l"] [ext_resource type="Script" path="res://ai/tasks/navigate_to_location.gd" id="2_k36i0"]
[sub_resource type="BlackboardPlan" id="BlackboardPlan_yg732"] [sub_resource type="BlackboardPlan" id="BlackboardPlan_yg732"]
@ -11,25 +11,23 @@ target_name = "Player"
output_var = &"target" output_var = &"target"
[sub_resource type="BTWait" id="BTWait_8cgex"] [sub_resource type="BTWait" id="BTWait_8cgex"]
duration = 2.0
[sub_resource type="BTAction" id="BTAction_xlccj"] [sub_resource type="BTAction" id="BTAction_qfrs5"]
script = ExtResource("1_v4edy") script = ExtResource("1_v4edy")
target_name = "Bone" target_name = "Bone"
output_var = &"target" output_var = &"target"
[sub_resource type="BTWait" id="BTWait_8uvw7"]
duration = 3.0
[sub_resource type="BTSequence" id="BTSequence_u84lc"] [sub_resource type="BTSequence" id="BTSequence_u84lc"]
children = [SubResource("BTAction_7a1kv"), SubResource("BTWait_8cgex"), SubResource("BTAction_xlccj"), SubResource("BTWait_8uvw7")] children = [SubResource("BTAction_7a1kv"), SubResource("BTWait_8cgex"), SubResource("BTAction_qfrs5")]
[sub_resource type="BTAction" id="BTAction_jhwy8"] [sub_resource type="BTAction" id="BTAction_l2eti"]
script = ExtResource("2_5373l") script = ExtResource("2_k36i0")
target_var = &"target" target_var = &"target"
[sub_resource type="BTRepeat" id="BTRepeat_dam0f"] [sub_resource type="BTRepeat" id="BTRepeat_dam0f"]
forever = true forever = true
children = [SubResource("BTAction_jhwy8")] children = [SubResource("BTAction_l2eti")]
[sub_resource type="BTParallel" id="BTParallel_l5msj"] [sub_resource type="BTParallel" id="BTParallel_l5msj"]
children = [SubResource("BTSequence_u84lc"), SubResource("BTRepeat_dam0f")] children = [SubResource("BTSequence_u84lc"), SubResource("BTRepeat_dam0f")]

36
ai/trees/wander.tres Normal file
View File

@ -0,0 +1,36 @@
[gd_resource type="BehaviorTree" load_steps=11 format=3 uid="uid://caxmpcyhpt6em"]
[ext_resource type="Script" path="res://ai/tasks/find_nearby_random_location.gd" id="1_5wpmm"]
[ext_resource type="Script" path="res://ai/tasks/look_at_location.gd" id="2_d6sgo"]
[ext_resource type="Script" path="res://ai/tasks/navigate_to_location.gd" id="3_ch0o3"]
[sub_resource type="BlackboardPlan" id="BlackboardPlan_yo8p7"]
[sub_resource type="BTAction" id="BTAction_s4a8v"]
script = ExtResource("1_5wpmm")
radius = 1.0
output_var = &"target_location"
navigatable_target = true
stay_in_radius = true
[sub_resource type="BTAction" id="BTAction_lschq"]
script = ExtResource("2_d6sgo")
target_location_var = &"target_location"
[sub_resource type="BTAction" id="BTAction_onf00"]
script = ExtResource("3_ch0o3")
target_var = &"target_location"
[sub_resource type="BTWait" id="BTWait_yneys"]
duration = 3.0
[sub_resource type="BTSequence" id="BTSequence_o4qno"]
children = [SubResource("BTAction_s4a8v"), SubResource("BTAction_lschq"), SubResource("BTAction_onf00"), SubResource("BTWait_yneys")]
[sub_resource type="BTRepeat" id="BTRepeat_o23de"]
forever = true
children = [SubResource("BTSequence_o4qno")]
[resource]
blackboard_plan = SubResource("BlackboardPlan_yo8p7")
root_task = SubResource("BTRepeat_o23de")

View File

@ -7,16 +7,30 @@ extends CharacterBody3D
get: get:
return reacts_to_player return reacts_to_player
@export var behaviour:BehaviorTree = null:
set (value):
behaviour = value
if is_instance_valid(bt_player):
bt_player.behavior_tree = behaviour
get:
return behaviour
@onready var geometry: Node3D = null @onready var geometry: Node3D = null
@onready var player_detection: Area3D = %PlayerDetection @onready var player_detection: Area3D = %PlayerDetection
@onready var default_geometry: Node3D = %DefaultGeometry @onready var default_geometry: Node3D = %DefaultGeometry
@onready var animation_player: AnimationPlayer = %AnimationPlayer @onready var animation_player: AnimationPlayer = %AnimationPlayer
@onready var navigation_agent: NavigationAgent3D = %NavigationAgent
@onready var bt_player: BTPlayer = %BTPlayer
const SPEED = 5.0 const SPEED = 5.0
const JUMP_VELOCITY = 4.5 const JUMP_VELOCITY = 4.5
var tool_damage:bool = false var tool_damage:bool = false
var tracking_node:Node3D = null var tracking_node:Node3D = null
var navigation_active:bool = false
var navigation_query_parameters:NavigationPathQueryParameters3D = NavigationPathQueryParameters3D.new()
var navigation_query_result:NavigationPathQueryResult3D = NavigationPathQueryResult3D.new()
func _set_reacts_to_player(value:bool) -> bool: func _set_reacts_to_player(value:bool) -> bool:
if player_detection != null: if player_detection != null:
@ -32,6 +46,25 @@ func _ready() -> void:
else: else:
default_geometry.visible = false default_geometry.visible = false
animation_player.set_root("Geometry") animation_player.set_root("Geometry")
if is_instance_valid(bt_player):
bt_player.behavior_tree = behaviour
func _physics_process(_delta: float) -> void:
if navigation_active:
var next_position:Vector3 = navigation_agent.get_next_path_position()
print("distance to target: %s" % navigation_agent.distance_to_target())
if navigation_agent.is_target_reached():
print ("Target reached. Distance: %s" % navigation_agent.distance_to_target())
navigation_active = false
else:
var direction = (next_position - global_position).normalized()
basis = Basis.looking_at(-direction)
velocity = direction * 2
move_and_slide()
func _on_player_detection_body_entered(body: Node3D) -> void: func _on_player_detection_body_entered(body: Node3D) -> void:
if body is Player: if body is Player:
@ -40,3 +73,25 @@ func _on_player_detection_body_entered(body: Node3D) -> void:
func _on_player_detection_body_exited(body: Node3D) -> void: func _on_player_detection_body_exited(body: Node3D) -> void:
if body == tracking_node: if body == tracking_node:
tracking_node = null tracking_node = null
func is_target_navigatable(target_position: Vector3) -> bool:
navigation_query_parameters.map = get_world_3d().navigation_map
navigation_query_parameters.start_position = global_position
navigation_query_parameters.target_position = target_position
navigation_query_parameters.navigation_layers = navigation_agent.navigation_layers
if NavigationServer3D.map_get_iteration_id(navigation_query_parameters.map) == 0:
return false
NavigationServer3D.query_path(navigation_query_parameters, navigation_query_result)
var query_path: PackedVector3Array = navigation_query_result.path
if query_path.size() > 0 and (query_path[query_path.size() - 1] - target_position).length() <= navigation_agent.target_desired_distance:
return true
return false
func navigate_to(target_position: Vector3) -> void:
navigation_agent.target_position = target_position
assert (is_target_navigatable(target_position))
navigation_active = not navigation_agent.is_target_reached()

View File

@ -2,8 +2,8 @@
[ext_resource type="PackedScene" uid="uid://xykil2ydlxoc" path="res://assets/3rdparty/KayKit_Adventurers_1.0_EXTRA/Characters/gltf/Rogue_Hooded.glb" id="1_25fog"] [ext_resource type="PackedScene" uid="uid://xykil2ydlxoc" path="res://assets/3rdparty/KayKit_Adventurers_1.0_EXTRA/Characters/gltf/Rogue_Hooded.glb" id="1_25fog"]
[ext_resource type="Script" path="res://objects/non_player_character.gd" id="1_c2apr"] [ext_resource type="Script" path="res://objects/non_player_character.gd" id="1_c2apr"]
[ext_resource type="BehaviorTree" uid="uid://dck7kl4210076" path="res://ai/trees/empty.tres" id="2_3dryb"]
[ext_resource type="AnimationLibrary" uid="uid://dbaynxuqbkor6" path="res://assets/characters/rogue_animation_library.tres" id="2_sp2o4"] [ext_resource type="AnimationLibrary" uid="uid://dbaynxuqbkor6" path="res://assets/characters/rogue_animation_library.tres" id="2_sp2o4"]
[ext_resource type="BehaviorTree" uid="uid://blccr23qjixws" path="res://ai/trees/pug.tres" id="4_1jdgy"]
[sub_resource type="CylinderShape3D" id="CylinderShape3D_uynrb"] [sub_resource type="CylinderShape3D" id="CylinderShape3D_uynrb"]
height = 0.8 height = 0.8
@ -28,6 +28,7 @@ radius = 1.0
[node name="NonPlayerCharacter" type="CharacterBody3D" groups=["non_player_character"]] [node name="NonPlayerCharacter" type="CharacterBody3D" groups=["non_player_character"]]
script = ExtResource("1_c2apr") script = ExtResource("1_c2apr")
behaviour = ExtResource("2_3dryb")
[node name="CollisionShape3D" type="CollisionShape3D" parent="."] [node name="CollisionShape3D" type="CollisionShape3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.4, 0) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.4, 0)
@ -60,8 +61,16 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.322685, 0)
shape = SubResource("CylinderShape3D_3da0u") shape = SubResource("CylinderShape3D_3da0u")
[node name="BTPlayer" type="BTPlayer" parent="."] [node name="BTPlayer" type="BTPlayer" parent="."]
behavior_tree = ExtResource("4_1jdgy") behavior_tree = ExtResource("2_3dryb")
blackboard_plan = SubResource("BlackboardPlan_yqrfn") blackboard_plan = SubResource("BlackboardPlan_yqrfn")
unique_name_in_owner = true
[node name="NavigationAgent" type="NavigationAgent3D" parent="."]
unique_name_in_owner = true
path_desired_distance = 0.1
target_desired_distance = 0.1
simplify_path = true
debug_enabled = true
[connection signal="body_entered" from="PlayerDetection" to="." method="_on_player_detection_body_entered"] [connection signal="body_entered" from="PlayerDetection" to="." method="_on_player_detection_body_entered"]
[connection signal="body_exited" from="PlayerDetection" to="." method="_on_player_detection_body_exited"] [connection signal="body_exited" from="PlayerDetection" to="." method="_on_player_detection_body_exited"]

View File

@ -39,6 +39,8 @@ enabled=PackedStringArray("res://addons/dialogue_manager/plugin.cfg", "res://add
[gdunit4] [gdunit4]
settings/test/test_lookup_folder="tests" settings/test/test_lookup_folder="tests"
settings/common/update_notification_enabled=false
settings/test/test_discovery=true
[global_group] [global_group]

File diff suppressed because one or more lines are too long