Beast can't spawn right next to player

This commit is contained in:
Rob Kelly 2025-09-06 16:55:58 -06:00
parent 850127ba85
commit 77c5d46dd5
9 changed files with 69 additions and 19 deletions

View File

@ -261,6 +261,7 @@ locale/translations=PackedStringArray("res://assets/text/text.en.translation")
3d_physics/layer_7="Prop Physics" 3d_physics/layer_7="Prop Physics"
3d_physics/layer_8="Beam Passthrough" 3d_physics/layer_8="Beam Passthrough"
3d_physics/layer_9="Beast Spawn Zones" 3d_physics/layer_9="Beast Spawn Zones"
3d_physics/layer_10="Beast Spawn Exclusion"
[physics] [physics]

View File

@ -1,4 +1,4 @@
[gd_scene load_steps=71 format=3 uid="uid://bwe2jdmvinhqd"] [gd_scene load_steps=72 format=3 uid="uid://bwe2jdmvinhqd"]
[ext_resource type="Script" uid="uid://buwh0g1ga2aka" path="res://src/player/player.gd" id="1_npueo"] [ext_resource type="Script" uid="uid://buwh0g1ga2aka" path="res://src/player/player.gd" id="1_npueo"]
[ext_resource type="Script" uid="uid://cx1yt0drthpw3" path="res://src/player/camera_controller.gd" id="2_veeqv"] [ext_resource type="Script" uid="uid://cx1yt0drthpw3" path="res://src/player/camera_controller.gd" id="2_veeqv"]
@ -187,6 +187,9 @@ height = 1.9
radius = 0.4 radius = 0.4
height = 1.2 height = 1.2
[sub_resource type="CylinderShape3D" id="CylinderShape3D_utg71"]
radius = 1.6
[sub_resource type="AudioStreamRandomizer" id="AudioStreamRandomizer_8ydov"] [sub_resource type="AudioStreamRandomizer" id="AudioStreamRandomizer_8ydov"]
random_pitch = 1.1 random_pitch = 1.1
streams_count = 6 streams_count = 6
@ -716,6 +719,7 @@ collision_mask = 256
[node name="CollisionShape3D" type="CollisionShape3D" parent="CameraPosition/CameraPivot/SpawnZoneFinder"] [node name="CollisionShape3D" type="CollisionShape3D" parent="CameraPosition/CameraPivot/SpawnZoneFinder"]
shape = SubResource("SphereShape3D_utg71") shape = SubResource("SphereShape3D_utg71")
debug_color = Color(0.602755, 0.367301, 1, 0.42)
[node name="StandingCollider" type="CollisionShape3D" parent="."] [node name="StandingCollider" type="CollisionShape3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.05, 0) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.05, 0)
@ -729,6 +733,14 @@ disabled = true
[node name="PlayerHUD" parent="." instance=ExtResource("5_jvafu")] [node name="PlayerHUD" parent="." instance=ExtResource("5_jvafu")]
unique_name_in_owner = true unique_name_in_owner = true
[node name="BeastSpawnExclusionZone" type="Area3D" parent="."]
collision_layer = 512
collision_mask = 0
[node name="CollisionShape3D" type="CollisionShape3D" parent="BeastSpawnExclusionZone"]
shape = SubResource("CylinderShape3D_utg71")
debug_color = Color(0.602755, 0.367301, 1, 0.42)
[node name="FootstepController" type="Node3D" parent="."] [node name="FootstepController" type="Node3D" parent="."]
unique_name_in_owner = true unique_name_in_owner = true
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -1, 0) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -1, 0)

View File

@ -2,18 +2,14 @@ class_name BeastSpawnZone extends Area3D
## An area which contains and manages prioritized BeastSpawners. ## An area which contains and manages prioritized BeastSpawners.
## Return the list of spawners in this area with the highest spawn priority func _ready() -> void:
func get_priority_spawners() -> Array[BeastSpawner]: # Adjust position to very slightly above placement position for raycasts
var spawns: Array[BeastSpawner] = [] global_position.y += 0.001
var max_priority: int = -1
for spawner: BeastSpawner in get_spawners():
if spawner.spawn_priority > max_priority:
spawns = [spawner]
max_priority = spawner.spawn_priority
elif spawner.spawn_priority == max_priority:
spawns.append(spawner)
return spawns
## Return the list of all non-excluded spawners in this zone
func get_valid_spawners() -> Array[BeastSpawner]:
return get_spawners().filter(func(s: BeastSpawner) -> bool: return s.can_spawn())
## Return the list of all spawners in this zone ## Return the list of all spawners in this zone

View File

@ -6,13 +6,22 @@ const GROUP = "BeastSpawnPoint"
## Scene to instantiate ## Scene to instantiate
@export var beast_scene: PackedScene @export var beast_scene: PackedScene
## Area for checking spawn exclusion
@export var spawn_exclusion_area: Area3D
## Spawners with higher priority will be picked first ## Spawners with higher priority will be picked first
@export var spawn_priority: int = 0 @export var spawn_priority: int = 0
## Create a new GrunkBeast instance
func instantiate() -> GrunkBeast: func instantiate() -> GrunkBeast:
return beast_scene.instantiate() return beast_scene.instantiate()
## Is this spawner able to spawn a beast (e.g. isn't occluded by a spawn excluder)?
func can_spawn() -> bool:
return (not spawn_exclusion_area.has_overlapping_areas()) if spawn_exclusion_area else true
func do_spawn() -> void: func do_spawn() -> void:
pass # Implemented in derived type pass # Implemented in derived type

View File

@ -1,11 +1,25 @@
[gd_scene load_steps=3 format=3 uid="uid://c8vbjgol5nenc"] [gd_scene load_steps=4 format=3 uid="uid://c8vbjgol5nenc"]
[ext_resource type="Script" uid="uid://cqildi0gndwp2" path="res://src/world/grunk_beast/beast_spawner/floor_spawner.gd" id="1_hrlgt"] [ext_resource type="Script" uid="uid://cqildi0gndwp2" path="res://src/world/grunk_beast/beast_spawner/floor_spawner.gd" id="1_hrlgt"]
[ext_resource type="PackedScene" uid="uid://ehf5sg3ahvbf" path="res://src/world/grunk_beast/grunk_beast.tscn" id="2_dgp11"] [ext_resource type="PackedScene" uid="uid://ehf5sg3ahvbf" path="res://src/world/grunk_beast/grunk_beast.tscn" id="2_dgp11"]
[node name="FloorSpawner" type="Node3D" groups=["BeastSpawnPoint"]] [sub_resource type="CylinderShape3D" id="CylinderShape3D_yfsqi"]
height = 0.1
radius = 1.2
[node name="FloorSpawner" type="Node3D" node_paths=PackedStringArray("spawn_exclusion_area") groups=["BeastSpawnPoint"]]
script = ExtResource("1_hrlgt") script = ExtResource("1_hrlgt")
beast_scene = ExtResource("2_dgp11") beast_scene = ExtResource("2_dgp11")
spawn_exclusion_area = NodePath("SpawnExclusionArea")
spawn_priority = 1 spawn_priority = 1
[node name="Marker3D" type="Marker3D" parent="."] [node name="Marker3D" type="Marker3D" parent="."]
[node name="SpawnExclusionArea" type="Area3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.05, 0)
collision_layer = 0
collision_mask = 512
[node name="CollisionShape3D" type="CollisionShape3D" parent="SpawnExclusionArea"]
shape = SubResource("CylinderShape3D_yfsqi")
debug_color = Color(0.602755, 0.367301, 1, 0.42)

View File

@ -1,4 +1,4 @@
[gd_scene load_steps=5 format=3 uid="uid://5vh7mnjavlle"] [gd_scene load_steps=6 format=3 uid="uid://5vh7mnjavlle"]
[ext_resource type="Script" uid="uid://cqildi0gndwp2" path="res://src/world/grunk_beast/beast_spawner/floor_spawner.gd" id="1_42oks"] [ext_resource type="Script" uid="uid://cqildi0gndwp2" path="res://src/world/grunk_beast/beast_spawner/floor_spawner.gd" id="1_42oks"]
[ext_resource type="PackedScene" uid="uid://ehf5sg3ahvbf" path="res://src/world/grunk_beast/grunk_beast.tscn" id="2_e5rh0"] [ext_resource type="PackedScene" uid="uid://ehf5sg3ahvbf" path="res://src/world/grunk_beast/grunk_beast.tscn" id="2_e5rh0"]
@ -12,9 +12,14 @@ top_radius = 1.2
bottom_radius = 1.2 bottom_radius = 1.2
height = 0.02 height = 0.02
[node name="TransientFloorSpawner" type="Node3D" groups=["BeastSpawnPoint"]] [sub_resource type="CylinderShape3D" id="CylinderShape3D_e5rh0"]
height = 0.1
radius = 1.2
[node name="TransientFloorSpawner" type="Node3D" node_paths=PackedStringArray("spawn_exclusion_area") groups=["BeastSpawnPoint"]]
script = ExtResource("1_42oks") script = ExtResource("1_42oks")
beast_scene = ExtResource("2_e5rh0") beast_scene = ExtResource("2_e5rh0")
spawn_exclusion_area = NodePath("SpawnExclusionArea")
spawn_priority = 10 spawn_priority = 10
metadata/_custom_type_script = "uid://cqildi0gndwp2" metadata/_custom_type_script = "uid://cqildi0gndwp2"
@ -29,4 +34,13 @@ debug_color = Color(0.980392, 0.854902, 0, 1)
[node name="MeshInstance3D" type="MeshInstance3D" parent="Area3D"] [node name="MeshInstance3D" type="MeshInstance3D" parent="Area3D"]
mesh = SubResource("CylinderMesh_e5rh0") mesh = SubResource("CylinderMesh_e5rh0")
[node name="SpawnExclusionArea" type="Area3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.05, 0)
collision_layer = 0
collision_mask = 512
[node name="CollisionShape3D" type="CollisionShape3D" parent="SpawnExclusionArea"]
shape = SubResource("CylinderShape3D_e5rh0")
debug_color = Color(0.602755, 0.367301, 1, 0.42)
[connection signal="area_entered" from="Area3D" to="." method="queue_free" unbinds=1] [connection signal="area_entered" from="Area3D" to="." method="queue_free" unbinds=1]

View File

@ -30,6 +30,7 @@ func _ready() -> void:
animation_player.speed_scale = time_scale_factor animation_player.speed_scale = time_scale_factor
_sync_pivot_to_root() _sync_pivot_to_root()
_update_transform() _update_transform()
tree_exiting.connect(_end_player_rumble)
func _start_player_rumble() -> void: func _start_player_rumble() -> void:

View File

@ -8,7 +8,7 @@ func _get_best_spawners() -> Array[BeastSpawner]:
# Get all spawners in the spawn zone(s) occupied by the player # Get all spawners in the spawn zone(s) occupied by the player
var zones: Array[BeastSpawnZone] = Player.instance.get_spawn_zones() var zones: Array[BeastSpawnZone] = Player.instance.get_spawn_zones()
for zone: BeastSpawnZone in zones: for zone: BeastSpawnZone in zones:
spawners.append_array(zone.get_spawners()) spawners.append_array(zone.get_valid_spawners())
if spawners: if spawners:
# Get spawners which player has unobstructed sightline to # Get spawners which player has unobstructed sightline to
var visible_spawners := spawners.filter( var visible_spawners := spawners.filter(
@ -32,7 +32,8 @@ func _get_best_spawners() -> Array[BeastSpawner]:
# Fallback behavior: just get all spawners # Fallback behavior: just get all spawners
print_debug("Falling back to all world spawners") print_debug("Falling back to all world spawners")
spawners.assign(World.instance.get_tree().get_nodes_in_group(BeastSpawner.GROUP)) spawners.assign(World.instance.get_tree().get_nodes_in_group(BeastSpawner.GROUP))
return spawners # Filter only valid spawners
return spawners.filter(func(s: BeastSpawner) -> bool: return s.can_spawn())
func _filter_top_priority(spawners: Array[BeastSpawner]) -> Array[BeastSpawner]: func _filter_top_priority(spawners: Array[BeastSpawner]) -> Array[BeastSpawner]:

View File

@ -86,4 +86,6 @@ When it's time to spawn a beast, we select a spawn point as follows:
- If the player cannot see any spawn points for this zone, use the highest-priority priority spawn. - If the player cannot see any spawn points for this zone, use the highest-priority priority spawn.
- If there are no priority spawns for this zone, use the default spawn with the highest priority. - If there are no priority spawns for this zone, use the default spawn with the highest priority.
- If the player is not in a spawn zone or the spawn zone has no default spawns, use a randomly-select map-default spawn. - If the player is not in a spawn zone or the spawn zone has no default spawns, use a randomly-select map-default spawn.
- If there are no map-default spawns, log an error and do not spawn a beast. - If there are no map-default spawns, log an error and do not spawn a beast.
**ADDITIONALLY:** A spawn point touching the player will never be used.