Advanced stalking logic

This commit is contained in:
Rob Kelly 2025-04-17 16:26:41 -06:00
parent b8c689bcf5
commit 7de318a613
7 changed files with 105 additions and 16 deletions

View File

@ -23,12 +23,12 @@ run/max_fps=60
[autoload] [autoload]
BeehaveGlobalMetrics="*res://addons/beehave/metrics/beehave_global_metrics.gd"
BeehaveGlobalDebugger="*res://addons/beehave/debug/global_debugger.gd"
GameRuntime="*res://src/game/game_runtime.gd" GameRuntime="*res://src/game/game_runtime.gd"
ItemCatalog="*res://src/items/item_catalog.tscn" ItemCatalog="*res://src/items/item_catalog.tscn"
GameSettings="*res://src/game/game_settings.gd" GameSettings="*res://src/game/game_settings.gd"
GameManager="*res://src/game/game_manager.tscn" GameManager="*res://src/game/game_manager.tscn"
BeehaveGlobalMetrics="*res://addons/beehave/metrics/beehave_global_metrics.gd"
BeehaveGlobalDebugger="*res://addons/beehave/debug/global_debugger.gd"
[debug] [debug]

View File

@ -0,0 +1,30 @@
@tool
class_name GetRandomTarget extends ActionLeaf
## Picks a random navigable point near the actor and writes it to the blackboard.
##
## If the random point is not navigable, will return RUNNING and pick a new point next tick.
## Blackboard key under which to store the random target.
@export var blackboard_key := "target"
## Standard deviation of position in each direction, in game units.
@export var st_dev := Vector3(1, 0, 1)
## Maximum path length considered navigable, in game units.
@export var max_path_len := 16.0
func tick(actor: Node, blackboard: Blackboard) -> int:
var beast := actor as GrunkBeast
var target := Vector3(
randfn(beast.global_position.x, st_dev.x),
randfn(beast.global_position.y, st_dev.y),
randfn(beast.global_position.z, st_dev.z)
)
if not beast.path_shorter_than(target, max_path_len):
return RUNNING
blackboard.set_value(blackboard_key, target)
return SUCCESS

View File

@ -0,0 +1 @@
uid://ubcelage67hs

View File

@ -0,0 +1,9 @@
@tool
class_name Sniff extends ActionLeaf
## The wily grunkbeest sniffs for its prey!
func tick(_actor: Node, _blackboard: Blackboard) -> int:
# TODO
print_debug("SNIFF SNIFF...")
return SUCCESS

View File

@ -0,0 +1 @@
uid://bksvlal4gjhfr

View File

@ -44,18 +44,28 @@ func get_speed() -> float:
func path_shorter_than(target: Vector3, limit: float) -> bool: func path_shorter_than(target: Vector3, limit: float) -> bool:
var limit_sq := limit * limit var length := 0.0
var length_sq := 0.0
var last_pos := global_position var last_pos := global_position
nav_probe.target_position = target nav_probe.target_position = target
for waypoint: Vector3 in nav_probe.get_current_navigation_path().slice( # Fail early if the target is unreachable.
# NOTE: this call also forces a navigation path refresh! Do not remove!
if not nav_probe.is_target_reachable():
return false
var path := nav_probe.get_current_navigation_path().slice(
nav_probe.get_current_navigation_path_index() nav_probe.get_current_navigation_path_index()
): )
# Using distance squared to save a sqrt instruction if not path:
length_sq += last_pos.distance_squared_to(waypoint) # Shouldn't be possible, but if it is it would cause problems if we didn't fail here
if length_sq > limit_sq: print_debug("Target is reachable but has no path (tell the developer!)")
return false
# Integrate along path
for waypoint: Vector3 in path:
length += last_pos.distance_to(waypoint)
if length > limit:
return false return false
last_pos = waypoint last_pos = waypoint

View File

@ -1,4 +1,4 @@
[gd_scene load_steps=37 format=3 uid="uid://ehf5sg3ahvbf"] [gd_scene load_steps=41 format=3 uid="uid://ehf5sg3ahvbf"]
[ext_resource type="Script" uid="uid://gwwmqwixqqr5" path="res://src/world/grunk_beast/grunk_beast.gd" id="2_qqnhb"] [ext_resource type="Script" uid="uid://gwwmqwixqqr5" path="res://src/world/grunk_beast/grunk_beast.gd" id="2_qqnhb"]
[ext_resource type="Shader" uid="uid://ckxc0ngd37rtk" path="res://src/shaders/gunk.gdshader" id="4_0gxpq"] [ext_resource type="Shader" uid="uid://ckxc0ngd37rtk" path="res://src/shaders/gunk.gdshader" id="4_0gxpq"]
@ -25,6 +25,9 @@
[ext_resource type="Script" uid="uid://wfd5nbethyw6" path="res://src/world/grunk_beast/behaviors/actions/blackboard_copy.gd" id="15_umoec"] [ext_resource type="Script" uid="uid://wfd5nbethyw6" path="res://src/world/grunk_beast/behaviors/actions/blackboard_copy.gd" id="15_umoec"]
[ext_resource type="Script" uid="uid://ykfqlqp7e8og" path="res://src/world/grunk_beast/behaviors/actions/start_timer.gd" id="16_asd50"] [ext_resource type="Script" uid="uid://ykfqlqp7e8og" path="res://src/world/grunk_beast/behaviors/actions/start_timer.gd" id="16_asd50"]
[ext_resource type="Script" uid="uid://h0cp58nswpml" path="res://src/world/grunk_beast/behaviors/conditions/timer_running.gd" id="16_oons1"] [ext_resource type="Script" uid="uid://h0cp58nswpml" path="res://src/world/grunk_beast/behaviors/conditions/timer_running.gd" id="16_oons1"]
[ext_resource type="Script" uid="uid://ubcelage67hs" path="res://src/world/grunk_beast/behaviors/actions/get_random_target.gd" id="24_asd50"]
[ext_resource type="Script" uid="uid://dvnmhlldp23hg" path="res://addons/beehave/nodes/composites/selector_random.gd" id="24_xs4mp"]
[ext_resource type="Script" uid="uid://bksvlal4gjhfr" path="res://src/world/grunk_beast/behaviors/actions/sniff.gd" id="26_lak6w"]
[sub_resource type="NoiseTexture3D" id="NoiseTexture3D_faau1"] [sub_resource type="NoiseTexture3D" id="NoiseTexture3D_faau1"]
width = 256 width = 256
@ -96,6 +99,9 @@ radius = 20.0
[sub_resource type="SphereShape3D" id="SphereShape3D_oons1"] [sub_resource type="SphereShape3D" id="SphereShape3D_oons1"]
radius = 3.0 radius = 3.0
[sub_resource type="SphereShape3D" id="SphereShape3D_lak6w"]
radius = 10.0
[node name="GrunkBeast" type="CharacterBody3D"] [node name="GrunkBeast" type="CharacterBody3D"]
collision_layer = 36 collision_layer = 36
script = ExtResource("2_qqnhb") script = ExtResource("2_qqnhb")
@ -115,12 +121,15 @@ shape = SubResource("CapsuleShape3D_faau1")
[node name="NavAgent" type="NavigationAgent3D" parent="."] [node name="NavAgent" type="NavigationAgent3D" parent="."]
unique_name_in_owner = true unique_name_in_owner = true
path_desired_distance = 0.75 path_desired_distance = 0.75
avoidance_enabled = true
debug_enabled = true debug_enabled = true
[node name="NavProbe" type="NavigationAgent3D" parent="."] [node name="NavProbe" type="NavigationAgent3D" parent="."]
unique_name_in_owner = true unique_name_in_owner = true
path_desired_distance = 0.75 path_desired_distance = 0.75
debug_enabled = true
debug_use_custom = true
debug_path_custom_color = Color(0, 1, 0, 1)
debug_path_custom_point_size = 8.0
[node name="GameSoundListener" type="StaticBody3D" parent="."] [node name="GameSoundListener" type="StaticBody3D" parent="."]
collision_layer = 16 collision_layer = 16
@ -157,7 +166,7 @@ collision_layer = 0
collision_mask = 8 collision_mask = 8
[node name="CollisionShape3D" type="CollisionShape3D" parent="SniffRange"] [node name="CollisionShape3D" type="CollisionShape3D" parent="SniffRange"]
shape = SubResource("SphereShape3D_wffas") shape = SubResource("SphereShape3D_lak6w")
[node name="StalkingTimer" type="Timer" parent="."] [node name="StalkingTimer" type="Timer" parent="."]
unique_name_in_owner = true unique_name_in_owner = true
@ -257,9 +266,38 @@ metadata/_custom_type_script = "uid://u1ntpwjwjqhj"
script = ExtResource("14_4y64f") script = ExtResource("14_4y64f")
metadata/_custom_type_script = "uid://om57w2acvgb7" metadata/_custom_type_script = "uid://om57w2acvgb7"
[node name="SelectorReactiveComposite" type="Node" parent="GrunkBeastBehavior/StateSelector/StalkingSequence"] [node name="RandomDelay" type="Node" parent="GrunkBeastBehavior/StateSelector/StalkingSequence"]
script = ExtResource("7_vvw1q") script = ExtResource("11_mbqcc")
metadata/_custom_type_script = "uid://cw22yurt5l74k" mean_time = 4.0
st_dev_time = 0.6
wait_time = 3.87336
metadata/_custom_type_script = "uid://beyk2xtbjrsg4"
[node name="RandomStalkingBehavior" type="Node" parent="GrunkBeastBehavior/StateSelector/StalkingSequence/RandomDelay"]
script = ExtResource("24_xs4mp")
use_weights = true
Weights/PickRandomStalkTarget = 10
Weights/SniffSequence = 1
metadata/_custom_type_script = "uid://dvnmhlldp23hg"
[node name="PickRandomStalkTarget" type="Node" parent="GrunkBeastBehavior/StateSelector/StalkingSequence/RandomDelay/RandomStalkingBehavior"]
script = ExtResource("24_asd50")
blackboard_key = "stalking_target"
metadata/_custom_type_script = "uid://ubcelage67hs"
[node name="SniffSequence" type="Node" parent="GrunkBeastBehavior/StateSelector/StalkingSequence/RandomDelay/RandomStalkingBehavior"]
script = ExtResource("8_0gxpq")
metadata/_custom_type_script = "uid://cg016dbe7gs1x"
[node name="Sniff" type="Node" parent="GrunkBeastBehavior/StateSelector/StalkingSequence/RandomDelay/RandomStalkingBehavior/SniffSequence"]
script = ExtResource("26_lak6w")
metadata/_custom_type_script = "uid://bksvlal4gjhfr"
[node name="SetTargetFromArea" type="Node" parent="GrunkBeastBehavior/StateSelector/StalkingSequence/RandomDelay/RandomStalkingBehavior/SniffSequence" node_paths=PackedStringArray("area")]
script = ExtResource("13_x8l6r")
blackboard_key = "stalking_target"
area = NodePath("../../../../../../../SniffRange")
metadata/_custom_type_script = "uid://b34l3v4sr8rmq"
[node name="AlwaysFailDecorator2" type="Node" parent="GrunkBeastBehavior/StateSelector"] [node name="AlwaysFailDecorator2" type="Node" parent="GrunkBeastBehavior/StateSelector"]
script = ExtResource("15_oons1") script = ExtResource("15_oons1")
@ -278,7 +316,7 @@ metadata/_custom_type_script = "uid://cg016dbe7gs1x"
script = ExtResource("11_mbqcc") script = ExtResource("11_mbqcc")
mean_time = 5.0 mean_time = 5.0
st_dev_time = 1.0 st_dev_time = 1.0
wait_time = 4.6164 wait_time = 5.5859
metadata/_custom_type_script = "uid://beyk2xtbjrsg4" metadata/_custom_type_script = "uid://beyk2xtbjrsg4"
[node name="PickRandomLurkTarget" type="Node" parent="GrunkBeastBehavior/StateSelector/LurkSequence/RandomDelay"] [node name="PickRandomLurkTarget" type="Node" parent="GrunkBeastBehavior/StateSelector/LurkSequence/RandomDelay"]