generated from krampus/template-godot4
Grunkbeast state machine
This commit is contained in:
parent
27ec21745b
commit
f5e05a7e08
@ -550,5 +550,11 @@ source_gunk_material = ExtResource("14_olej6")
|
|||||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="NavigationRegion3D/Corridor1/Hallway4/MessHallSign/GunkBody"]
|
[node name="CollisionShape3D" type="CollisionShape3D" parent="NavigationRegion3D/Corridor1/Hallway4/MessHallSign/GunkBody"]
|
||||||
shape = SubResource("ConcavePolygonShape3D_spe8j")
|
shape = SubResource("ConcavePolygonShape3D_spe8j")
|
||||||
|
|
||||||
|
[node name="LurkPoint" type="Marker3D" parent="NavigationRegion3D/Corridor1" groups=["LurkPoint"]]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.5, 4.5)
|
||||||
|
|
||||||
|
[node name="LurkPoint2" type="Marker3D" parent="NavigationRegion3D/Corridor1" groups=["LurkPoint"]]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -27, 1.5, -19.5)
|
||||||
|
|
||||||
[connection signal="activated" from="NavigationRegion3D/Corridor1/Hallway1/SwitchA2" to="NavigationRegion3D/Corridor1/Hallway1/SwitchA2" method="disable"]
|
[connection signal="activated" from="NavigationRegion3D/Corridor1/Hallway1/SwitchA2" to="NavigationRegion3D/Corridor1/Hallway1/SwitchA2" method="disable"]
|
||||||
[connection signal="activated" from="NavigationRegion3D/Corridor1/Hallway4/SwitchB1" to="NavigationRegion3D/Corridor1/Hallway4/SwitchB1" method="disable"]
|
[connection signal="activated" from="NavigationRegion3D/Corridor1/Hallway4/SwitchB1" to="NavigationRegion3D/Corridor1/Hallway4/SwitchB1" method="disable"]
|
||||||
|
@ -81,6 +81,7 @@ config/input/hold_to_sneak=true
|
|||||||
MetalMaterial="This surface is made of metal."
|
MetalMaterial="This surface is made of metal."
|
||||||
PlasticMaterial="This surface is made of plastic."
|
PlasticMaterial="This surface is made of plastic."
|
||||||
MeetSpookSource="meet-spook event sources"
|
MeetSpookSource="meet-spook event sources"
|
||||||
|
LurkPoint="Point which a lurking beast may wander to."
|
||||||
|
|
||||||
[importer_defaults]
|
[importer_defaults]
|
||||||
|
|
||||||
|
@ -1,30 +1,179 @@
|
|||||||
class_name GrunkBeast extends CharacterBody3D
|
class_name GrunkBeast extends CharacterBody3D
|
||||||
## Grunk beast controller
|
## Grunk beast controller
|
||||||
|
|
||||||
@export var base_speed := 1.0
|
#region Constants
|
||||||
|
signal state_changed(new_state: State)
|
||||||
|
|
||||||
|
enum State {
|
||||||
|
UNKNOWN,
|
||||||
|
LURKING,
|
||||||
|
STALKING,
|
||||||
|
PURSUIT,
|
||||||
|
}
|
||||||
|
|
||||||
|
const LURK_POINT_GROUP := "LurkPoint"
|
||||||
|
const STALKING_SOUND_LIMIT := 14.0
|
||||||
|
const IDLE_EPSILON := 0.001
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Exported Properties
|
||||||
|
@export var lurk_spontaneity := 0.02
|
||||||
|
@export var base_speed := 60.0
|
||||||
|
@export var pursuit_speed := 180.0
|
||||||
|
@export var initial_state := State.LURKING
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Member Variables
|
||||||
var gravity: Vector3 = (
|
var gravity: Vector3 = (
|
||||||
ProjectSettings.get_setting("physics/3d/default_gravity")
|
ProjectSettings.get_setting("physics/3d/default_gravity")
|
||||||
* ProjectSettings.get_setting("physics/3d/default_gravity_vector")
|
* ProjectSettings.get_setting("physics/3d/default_gravity_vector")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var state: State:
|
||||||
|
set(value):
|
||||||
|
if state != value:
|
||||||
|
_on_state_change(value)
|
||||||
|
state = value
|
||||||
|
|
||||||
|
var pathfinding := true
|
||||||
|
|
||||||
@onready var nav_agent: NavigationAgent3D = %NavAgent
|
@onready var nav_agent: NavigationAgent3D = %NavAgent
|
||||||
|
@onready var stalking_timeout: Timer = %StalkingTimeout
|
||||||
|
@onready var idle_timer: Timer = %IdleTimer
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Character Controller
|
||||||
|
|
||||||
|
|
||||||
|
func get_speed() -> float:
|
||||||
|
match state:
|
||||||
|
State.PURSUIT:
|
||||||
|
return pursuit_speed
|
||||||
|
return base_speed
|
||||||
|
|
||||||
|
|
||||||
func _physics_process(delta: float) -> void:
|
func _physics_process(delta: float) -> void:
|
||||||
var path_pos := nav_agent.get_next_path_position()
|
var motion := Vector3.ZERO
|
||||||
|
|
||||||
|
if pathfinding:
|
||||||
|
var path_pos := nav_agent.get_next_path_position()
|
||||||
|
var relative_pos := path_pos - global_position
|
||||||
|
motion = relative_pos.normalized() * get_speed() * delta
|
||||||
|
|
||||||
var relative_pos := path_pos - global_position
|
|
||||||
var motion := relative_pos.normalized() * base_speed * delta
|
|
||||||
velocity.x = motion.x
|
velocity.x = motion.x
|
||||||
velocity.z = motion.z
|
velocity.z = motion.z
|
||||||
|
|
||||||
if not is_on_floor():
|
if not is_on_floor():
|
||||||
velocity += gravity * delta
|
velocity += gravity * delta
|
||||||
|
|
||||||
|
if velocity.length_squared() > IDLE_EPSILON:
|
||||||
|
idle_timer.start()
|
||||||
|
|
||||||
move_and_slide()
|
move_and_slide()
|
||||||
|
|
||||||
|
|
||||||
func track_player() -> void:
|
#endregion
|
||||||
|
|
||||||
|
#region State Machine
|
||||||
|
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
state = initial_state
|
||||||
|
|
||||||
|
|
||||||
|
func _current_path_shorter_than(limit: float) -> bool:
|
||||||
|
var limit_sq := limit * limit
|
||||||
|
var length_sq := 0.0
|
||||||
|
var last_pos := global_position
|
||||||
|
|
||||||
|
for waypoint: Vector3 in nav_agent.get_current_navigation_path().slice(
|
||||||
|
nav_agent.get_current_navigation_path_index()
|
||||||
|
):
|
||||||
|
# Using distance squared to save a sqrt instruction
|
||||||
|
length_sq += last_pos.distance_squared_to(waypoint)
|
||||||
|
if length_sq > limit_sq:
|
||||||
|
return false
|
||||||
|
last_pos = waypoint
|
||||||
|
|
||||||
|
return true
|
||||||
|
|
||||||
|
|
||||||
|
func _set_new_lurk_point() -> void:
|
||||||
|
@warning_ignore("unsafe_cast")
|
||||||
|
var next_lurk_point := get_tree().get_nodes_in_group(LURK_POINT_GROUP).pick_random() as Node3D
|
||||||
|
if next_lurk_point:
|
||||||
|
nav_agent.target_position = next_lurk_point.global_position
|
||||||
|
else:
|
||||||
|
push_warning(self, " tried to lurk, but couldn't find a lurk point!")
|
||||||
|
|
||||||
|
|
||||||
|
## Lurking routine
|
||||||
|
func lurk() -> void:
|
||||||
|
if nav_agent.is_target_reached() or randf() < lurk_spontaneity:
|
||||||
|
_set_new_lurk_point()
|
||||||
|
|
||||||
|
|
||||||
|
## Stalking routine
|
||||||
|
func stalk() -> void:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
## Pursuit routine
|
||||||
|
func pursue() -> void:
|
||||||
if Player.instance:
|
if Player.instance:
|
||||||
nav_agent.target_position = Player.instance.global_position
|
nav_agent.target_position = Player.instance.global_position
|
||||||
|
|
||||||
|
|
||||||
|
func _on_state_change(new_state: State) -> void:
|
||||||
|
match new_state:
|
||||||
|
State.LURKING:
|
||||||
|
_set_new_lurk_point()
|
||||||
|
State.STALKING:
|
||||||
|
stalking_timeout.start()
|
||||||
|
|
||||||
|
state_changed.emit(new_state)
|
||||||
|
|
||||||
|
|
||||||
|
func update_behavior() -> void:
|
||||||
|
match state:
|
||||||
|
State.UNKNOWN:
|
||||||
|
state = State.LURKING
|
||||||
|
update_behavior()
|
||||||
|
State.LURKING:
|
||||||
|
lurk()
|
||||||
|
State.STALKING:
|
||||||
|
stalk()
|
||||||
|
State.PURSUIT:
|
||||||
|
pursue()
|
||||||
|
|
||||||
|
|
||||||
|
func on_sound_detected(source: Vector3) -> void:
|
||||||
|
var prev_target := nav_agent.target_position
|
||||||
|
nav_agent.target_position = source
|
||||||
|
# Check that the source isn't too far away, e.g. a sound from another room
|
||||||
|
if _current_path_shorter_than(STALKING_SOUND_LIMIT):
|
||||||
|
# First detection switches to stalking, second switches to pursuit
|
||||||
|
if state == State.STALKING:
|
||||||
|
state = State.PURSUIT
|
||||||
|
else:
|
||||||
|
state = State.STALKING
|
||||||
|
else:
|
||||||
|
nav_agent.target_position = prev_target
|
||||||
|
|
||||||
|
|
||||||
|
func on_player_entered_grabbing_range(_body: Node3D) -> void:
|
||||||
|
print_debug("GET GRABBED, IDIOT!")
|
||||||
|
|
||||||
|
|
||||||
|
func on_player_left_pursuit_range(_body: Node3D) -> void:
|
||||||
|
state = State.STALKING
|
||||||
|
|
||||||
|
|
||||||
|
func on_stalking_timeout_timeout() -> void:
|
||||||
|
state = State.LURKING
|
||||||
|
|
||||||
|
|
||||||
|
func on_stalking_idle_timer_timeout() -> void:
|
||||||
|
pass # Replace with function body.
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
[gd_scene load_steps=12 format=3 uid="uid://ehf5sg3ahvbf"]
|
[gd_scene load_steps=16 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"]
|
||||||
|
[ext_resource type="Script" uid="uid://cfsiyhhrcua6o" path="res://src/world/game_sound/game_sound_listener.gd" id="5_3gbao"]
|
||||||
[ext_resource type="Texture2D" uid="uid://cm1jrvx7ftx4c" path="res://assets/black.png" id="5_xuag8"]
|
[ext_resource type="Texture2D" uid="uid://cm1jrvx7ftx4c" path="res://assets/black.png" id="5_xuag8"]
|
||||||
[ext_resource type="FastNoiseLite" uid="uid://cnlvdtx68giv6" path="res://assets/materials/gunk_noise.tres" id="6_mbqcc"]
|
[ext_resource type="FastNoiseLite" uid="uid://cnlvdtx68giv6" path="res://assets/materials/gunk_noise.tres" id="6_mbqcc"]
|
||||||
|
|
||||||
@ -63,9 +64,17 @@ rings = 1
|
|||||||
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_faau1"]
|
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_faau1"]
|
||||||
radius = 0.45
|
radius = 0.45
|
||||||
|
|
||||||
|
[sub_resource type="SphereShape3D" id="SphereShape3D_wffas"]
|
||||||
|
radius = 7.0
|
||||||
|
|
||||||
|
[sub_resource type="SphereShape3D" id="SphereShape3D_3gbao"]
|
||||||
|
radius = 1.4
|
||||||
|
|
||||||
|
[sub_resource type="SphereShape3D" id="SphereShape3D_d4ex2"]
|
||||||
|
radius = 20.0
|
||||||
|
|
||||||
[node name="GrunkBeast" type="CharacterBody3D"]
|
[node name="GrunkBeast" type="CharacterBody3D"]
|
||||||
script = ExtResource("2_qqnhb")
|
script = ExtResource("2_qqnhb")
|
||||||
base_speed = 60.0
|
|
||||||
|
|
||||||
[node name="MeshInstance3D" type="MeshInstance3D" parent="."]
|
[node name="MeshInstance3D" type="MeshInstance3D" parent="."]
|
||||||
mesh = SubResource("CapsuleMesh_d4ex2")
|
mesh = SubResource("CapsuleMesh_d4ex2")
|
||||||
@ -82,7 +91,44 @@ 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
|
||||||
|
|
||||||
[node name="TrackingTimer" type="Timer" parent="."]
|
[node name="IdleTimer" type="Timer" parent="."]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
wait_time = 1.2
|
||||||
|
one_shot = true
|
||||||
|
|
||||||
|
[node name="StalkingTimeout" type="Timer" parent="."]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
wait_time = 25.0
|
||||||
|
one_shot = true
|
||||||
|
|
||||||
|
[node name="BehaviorUpdateTimer" type="Timer" parent="."]
|
||||||
autostart = true
|
autostart = true
|
||||||
|
|
||||||
[connection signal="timeout" from="TrackingTimer" to="." method="track_player"]
|
[node name="GameSoundListener" type="StaticBody3D" parent="."]
|
||||||
|
collision_layer = 16
|
||||||
|
collision_mask = 0
|
||||||
|
script = ExtResource("5_3gbao")
|
||||||
|
metadata/_custom_type_script = "uid://cfsiyhhrcua6o"
|
||||||
|
|
||||||
|
[node name="CollisionShape3D" type="CollisionShape3D" parent="GameSoundListener"]
|
||||||
|
shape = SubResource("SphereShape3D_wffas")
|
||||||
|
|
||||||
|
[node name="GrabbingRange" type="Area3D" parent="."]
|
||||||
|
collision_layer = 0
|
||||||
|
collision_mask = 8
|
||||||
|
|
||||||
|
[node name="CollisionShape3D" type="CollisionShape3D" parent="GrabbingRange"]
|
||||||
|
shape = SubResource("SphereShape3D_3gbao")
|
||||||
|
|
||||||
|
[node name="PursuitLimit" type="Area3D" parent="."]
|
||||||
|
collision_layer = 0
|
||||||
|
collision_mask = 8
|
||||||
|
|
||||||
|
[node name="CollisionShape3D" type="CollisionShape3D" parent="PursuitLimit"]
|
||||||
|
shape = SubResource("SphereShape3D_d4ex2")
|
||||||
|
|
||||||
|
[connection signal="timeout" from="StalkingTimeout" to="." method="on_stalking_timeout_timeout"]
|
||||||
|
[connection signal="timeout" from="BehaviorUpdateTimer" to="." method="update_behavior"]
|
||||||
|
[connection signal="sound_detected" from="GameSoundListener" to="." method="on_sound_detected"]
|
||||||
|
[connection signal="body_entered" from="GrabbingRange" to="." method="on_player_entered_grabbing_range"]
|
||||||
|
[connection signal="body_exited" from="PursuitLimit" to="." method="on_player_left_pursuit_range"]
|
||||||
|
@ -10,7 +10,7 @@ const CRITICAL_ANGLE := 0.1
|
|||||||
var stepping := false
|
var stepping := false
|
||||||
var _resting := false
|
var _resting := false
|
||||||
|
|
||||||
@onready var parent := owner as GrunkBeast
|
@onready var parent := owner as ProceduralGrunkBeast
|
||||||
|
|
||||||
|
|
||||||
func step() -> void:
|
func step() -> void:
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
extends Node3D
|
extends Node3D
|
||||||
|
|
||||||
@onready var parent := owner as GrunkBeast
|
@onready var parent := owner as ProceduralGrunkBeast
|
||||||
@onready var prev_position := parent.global_position
|
@onready var prev_position := parent.global_position
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user