generated from krampus/template-godot4
Grunkbeast spawning & alert response logic
This commit is contained in:
parent
efc62c16c1
commit
fb34e3b659
File diff suppressed because one or more lines are too long
@ -85,6 +85,8 @@ MetalMaterial="This surface is made of metal."
|
||||
PlasticMaterial="This surface is made of plastic."
|
||||
MeetSpookSource="meet-spook event sources"
|
||||
LurkPoint="Point which a lurking beast may wander to."
|
||||
BeastSpawnPoint="Spawn point for a grunkbeast"
|
||||
GrunkBeast="GrunkBeast instances."
|
||||
|
||||
[importer_defaults]
|
||||
|
||||
|
@ -1,6 +1,8 @@
|
||||
class_name SceneTools extends Object
|
||||
## Tools for specialized operations in a scene
|
||||
|
||||
const RAY_MASK := 0b00100101
|
||||
|
||||
|
||||
## Get the Node3D in the given group which is spatially closest to the target node.
|
||||
##
|
||||
@ -15,3 +17,39 @@ static func closest_in_group(target: Node3D, group_name: String) -> Node3D:
|
||||
min_dist_sq = dist_sq
|
||||
closest = n
|
||||
return closest
|
||||
|
||||
|
||||
## Does the player have an unobstructed line-of-sight to the given point?
|
||||
static func player_can_see(target: Vector3) -> bool:
|
||||
if not is_instance_valid(Player.instance):
|
||||
return false
|
||||
|
||||
var camera := Player.instance.get_viewport().get_camera_3d()
|
||||
|
||||
if not camera.is_position_in_frustum(target):
|
||||
return false
|
||||
|
||||
var query := PhysicsRayQueryParameters3D.create(camera.global_position, target, RAY_MASK)
|
||||
# TODO may need to add an "exceptions" parameter for the source's body
|
||||
var raycast := Player.instance.get_world_3d().direct_space_state.intersect_ray(query)
|
||||
# If raycast is empty, view is unoccluded and player has line-of-sight!
|
||||
return not raycast
|
||||
|
||||
|
||||
## Get a random Node3D from the given group which is not currently visible to the player
|
||||
##
|
||||
## Returns null is there is no such node, either because the group is empty
|
||||
## or the player can see every node in the group.
|
||||
static func pick_unseen_from_group(group_name: String) -> Node3D:
|
||||
if not is_instance_valid(Player.instance):
|
||||
return null
|
||||
|
||||
var nodes: Array[Node] = Player.instance.get_tree().get_nodes_in_group(group_name)
|
||||
while nodes:
|
||||
var candidate: Node = nodes.pick_random()
|
||||
nodes.erase(candidate)
|
||||
var target := candidate as Node3D
|
||||
if target and not SceneTools.player_can_see(target.global_position):
|
||||
return target
|
||||
|
||||
return null
|
||||
|
@ -0,0 +1,16 @@
|
||||
@tool
|
||||
class_name SetPlayerPriorityTarget extends ActionLeaf
|
||||
## Set the player as a pursuit target if the grunk alert level is above a priority threshold.
|
||||
|
||||
## Blackboard key to set.
|
||||
@export var blackboard_key := "pursuit_target"
|
||||
|
||||
## Set the player as target only if the grunk alert level is greater than or equal to this.
|
||||
@export var alert_threshold := 3
|
||||
|
||||
|
||||
func tick(_actor: Node, blackboard: Blackboard) -> int:
|
||||
if Game.manager.alert_level >= alert_threshold and is_instance_valid(Player.instance):
|
||||
blackboard.set_value(blackboard_key, Player.instance)
|
||||
return SUCCESS
|
||||
return FAILURE
|
@ -0,0 +1 @@
|
||||
uid://c5cikaa68tsto
|
@ -8,6 +8,10 @@ const STALKING_SOUND_LIMIT := 20.0
|
||||
#region Exported Properties
|
||||
@export var base_speed := 60.0
|
||||
@export var pursuit_speed := 180.0
|
||||
|
||||
@export var debug_destroy: bool:
|
||||
set(value):
|
||||
queue_free()
|
||||
#endregion
|
||||
|
||||
#region Member Variables
|
||||
|
@ -1,4 +1,4 @@
|
||||
[gd_scene load_steps=41 format=3 uid="uid://ehf5sg3ahvbf"]
|
||||
[gd_scene load_steps=42 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="Shader" uid="uid://ckxc0ngd37rtk" path="res://src/shaders/gunk.gdshader" id="4_0gxpq"]
|
||||
@ -19,6 +19,7 @@
|
||||
[ext_resource type="Script" uid="uid://dcojdhvj8qcw0" path="res://addons/beehave/nodes/composites/sequence_reactive.gd" id="12_xde72"]
|
||||
[ext_resource type="Script" uid="uid://b34l3v4sr8rmq" path="res://src/world/grunk_beast/behaviors/actions/set_target_from_area.gd" id="13_x8l6r"]
|
||||
[ext_resource type="Script" uid="uid://om57w2acvgb7" path="res://src/world/grunk_beast/behaviors/actions/travel_to_destination.gd" id="14_4y64f"]
|
||||
[ext_resource type="Script" uid="uid://c5cikaa68tsto" path="res://src/world/grunk_beast/behaviors/actions/set_player_priority_target.gd" id="14_csisu"]
|
||||
[ext_resource type="Script" uid="uid://bkdwuqv4tudka" path="res://src/world/grunk_beast/behaviors/actions/pursue_target.gd" id="14_x8l6r"]
|
||||
[ext_resource type="Script" uid="uid://demv7xh27ouvr" path="res://src/world/grunk_beast/behaviors/actions/blackboard_erase_safe.gd" id="15_4b27i"]
|
||||
[ext_resource type="Script" uid="uid://dwfdg523bk776" path="res://addons/beehave/nodes/decorators/failer.gd" id="15_oons1"]
|
||||
@ -102,7 +103,7 @@ radius = 3.0
|
||||
[sub_resource type="SphereShape3D" id="SphereShape3D_lak6w"]
|
||||
radius = 10.0
|
||||
|
||||
[node name="GrunkBeast" type="CharacterBody3D"]
|
||||
[node name="GrunkBeast" type="CharacterBody3D" groups=["GrunkBeast"]]
|
||||
collision_layer = 36
|
||||
script = ExtResource("2_qqnhb")
|
||||
|
||||
@ -124,6 +125,7 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.5, 0)
|
||||
[node name="NavAgent" type="NavigationAgent3D" parent="Navigation"]
|
||||
unique_name_in_owner = true
|
||||
path_postprocessing = 1
|
||||
avoidance_enabled = true
|
||||
debug_enabled = true
|
||||
|
||||
[node name="NavProbe" type="NavigationAgent3D" parent="Navigation"]
|
||||
@ -213,6 +215,10 @@ metadata/_custom_type_script = "uid://dcojdhvj8qcw0"
|
||||
script = ExtResource("12_dkcdj")
|
||||
metadata/_custom_type_script = "uid://8hn4kne15ac5"
|
||||
|
||||
[node name="SetPlayerPriorityTarget" type="Node" parent="GrunkBeastBehavior/StateSelector/PursuitSequence/TargetSelector"]
|
||||
script = ExtResource("14_csisu")
|
||||
metadata/_custom_type_script = "uid://c5cikaa68tsto"
|
||||
|
||||
[node name="TargetInPursuitRange" type="Node" parent="GrunkBeastBehavior/StateSelector/PursuitSequence/TargetSelector" node_paths=PackedStringArray("area")]
|
||||
script = ExtResource("9_xuag8")
|
||||
blackboard_key = "pursuit_target"
|
||||
|
@ -3,7 +3,6 @@ class_name MeetSpookMob extends Node3D
|
||||
|
||||
enum State { WAITING, FREEZE, MOVING }
|
||||
|
||||
const RAY_MASK := 0b00100101
|
||||
const BASE_SPEED := 4.0
|
||||
|
||||
@export var source: MeetSpook
|
||||
@ -41,15 +40,8 @@ func _physics_process(delta: float) -> void:
|
||||
match state:
|
||||
State.WAITING:
|
||||
# Not yet seen by the player
|
||||
if camera.is_position_in_frustum(global_position):
|
||||
var query := PhysicsRayQueryParameters3D.create(
|
||||
camera.global_position, global_position, RAY_MASK
|
||||
)
|
||||
# TODO exceptions should include this mob's collision body
|
||||
var raycast := get_world_3d().direct_space_state.intersect_ray(query)
|
||||
if not raycast:
|
||||
# Player has line-of-sight!
|
||||
activate()
|
||||
if SceneTools.player_can_see(global_position):
|
||||
activate()
|
||||
State.FREEZE:
|
||||
# Just stand there for a sec
|
||||
pass
|
||||
|
@ -1,6 +1,10 @@
|
||||
class_name SpookManager extends Resource
|
||||
## A strategy for handling horror elements through the level.
|
||||
|
||||
const SPAWN_GROUP := "BeastSpawnPoint"
|
||||
|
||||
@export var grunkbeast_scene: PackedScene
|
||||
|
||||
var debug_set_alert_level: int:
|
||||
set = _on_alert_raised
|
||||
|
||||
@ -10,6 +14,35 @@ func _init() -> void:
|
||||
Game.manager.alert_cleared.connect(_on_alert_cleared)
|
||||
|
||||
|
||||
func _spawn_beast_at_point(spawn_point: Node3D) -> void:
|
||||
print_debug("Spawning active beast at ", spawn_point.global_position)
|
||||
var beast: GrunkBeast = grunkbeast_scene.instantiate()
|
||||
spawn_point.add_child(beast)
|
||||
|
||||
|
||||
## Spawn a beast somewhere the player isn't looking.
|
||||
func spawn_beast() -> void:
|
||||
var spawn_point := SceneTools.pick_unseen_from_group(SPAWN_GROUP)
|
||||
if not spawn_point:
|
||||
print_debug("Couldn't find a hidden spawn point... Picking one at random.")
|
||||
var nodes := Game.manager.get_tree().get_nodes_in_group(SPAWN_GROUP)
|
||||
if not nodes:
|
||||
print_debug("Oh that's why. There aren't any spawn points. Complain to a developer.")
|
||||
return
|
||||
spawn_point = nodes.pick_random() as Node3D
|
||||
|
||||
_spawn_beast_at_point(spawn_point)
|
||||
|
||||
|
||||
## Spawn beasts at _every_ spawn point the player can't see.
|
||||
func spawn_many_beasts() -> void:
|
||||
var nodes := Game.manager.get_tree().get_nodes_in_group(SPAWN_GROUP)
|
||||
for node: Node in nodes:
|
||||
var target := node as Node3D
|
||||
if is_instance_valid(target) and not SceneTools.player_can_see(target.global_position):
|
||||
_spawn_beast_at_point(target)
|
||||
|
||||
|
||||
func _on_alert_raised(new_level: int) -> void:
|
||||
match new_level:
|
||||
0:
|
||||
@ -27,15 +60,16 @@ func _on_alert_raised(new_level: int) -> void:
|
||||
2:
|
||||
# LEVEL 2: AGGRESSIVE
|
||||
# Beast pursues player on sight.
|
||||
pass # TODO
|
||||
spawn_beast()
|
||||
3:
|
||||
# LEVEL 3: PREDATORY
|
||||
# Beast pursues player relentlessly.
|
||||
pass # TODO
|
||||
# The logic for this behavior change is handled directly in the GrunkBeast AI.
|
||||
pass
|
||||
4:
|
||||
# LEVEL 4: SWARMING
|
||||
# Many beasts spawn, overwhelming the player.
|
||||
pass # TODO
|
||||
spawn_many_beasts()
|
||||
5:
|
||||
# LEVEL 5: FUN
|
||||
# Just kill that fool!
|
||||
|
@ -1,12 +1,14 @@
|
||||
[gd_scene load_steps=6 format=3 uid="uid://884jqafhtrv0"]
|
||||
[gd_scene load_steps=7 format=3 uid="uid://884jqafhtrv0"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://cgqmhtemibxc5" path="res://src/world/world.gd" id="1_1k4gi"]
|
||||
[ext_resource type="PackedScene" uid="uid://byvjsvavbg5xe" path="res://src/ui/menus/pause_menu/pause_menu.tscn" id="2_6fy3g"]
|
||||
[ext_resource type="PackedScene" uid="uid://ehf5sg3ahvbf" path="res://src/world/grunk_beast/grunk_beast.tscn" id="2_43c6p"]
|
||||
[ext_resource type="Script" uid="uid://bsn026pxqwkbc" path="res://src/world/spook_manager/spook_manager.gd" id="2_bsf3i"]
|
||||
[ext_resource type="PackedScene" uid="uid://bov4ok76woyc" path="res://levels/ghost_ship/ghost_ship.tscn" id="2_jte2u"]
|
||||
|
||||
[sub_resource type="Resource" id="Resource_43c6p"]
|
||||
script = ExtResource("2_bsf3i")
|
||||
grunkbeast_scene = ExtResource("2_43c6p")
|
||||
metadata/_custom_type_script = "uid://bsn026pxqwkbc"
|
||||
|
||||
[node name="World" type="Node"]
|
||||
|
@ -28,7 +28,7 @@
|
||||
- [x] Model
|
||||
- [x] Animation
|
||||
- [ ] Controller
|
||||
- [ ] Nav/logic?
|
||||
- [x] Nav/logic?
|
||||
- [ ] [[level.canvas|level]]
|
||||
- [x] level planning
|
||||
- [ ] [[player ship]]
|
||||
|
Loading…
x
Reference in New Issue
Block a user