From 77c5d46dd53be4b905d18247ac88b9fe97cf1fd7 Mon Sep 17 00:00:00 2001 From: Rob Kelly Date: Sat, 6 Sep 2025 16:55:58 -0600 Subject: [PATCH] Beast can't spawn right next to player --- project.godot | 1 + src/player/player.tscn | 14 +++++++++++++- .../beast_spawn_zone/beast_spawn_zone.gd | 18 +++++++----------- .../grunk_beast/beast_spawner/beast_spawner.gd | 9 +++++++++ .../beast_spawner/floor_spawner.tscn | 18 ++++++++++++++++-- .../beast_spawner/transient_floor_spawner.tscn | 18 ++++++++++++++++-- src/world/grunk_beast/shambler/shambler.gd | 1 + src/world/spook_manager/spook_manager.gd | 5 +++-- vault/elements/grunk beast.md | 4 +++- 9 files changed, 69 insertions(+), 19 deletions(-) diff --git a/project.godot b/project.godot index d793adf..56af3e3 100644 --- a/project.godot +++ b/project.godot @@ -261,6 +261,7 @@ locale/translations=PackedStringArray("res://assets/text/text.en.translation") 3d_physics/layer_7="Prop Physics" 3d_physics/layer_8="Beam Passthrough" 3d_physics/layer_9="Beast Spawn Zones" +3d_physics/layer_10="Beast Spawn Exclusion" [physics] diff --git a/src/player/player.tscn b/src/player/player.tscn index f7f0ec8..3623022 100644 --- a/src/player/player.tscn +++ b/src/player/player.tscn @@ -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://cx1yt0drthpw3" path="res://src/player/camera_controller.gd" id="2_veeqv"] @@ -187,6 +187,9 @@ height = 1.9 radius = 0.4 height = 1.2 +[sub_resource type="CylinderShape3D" id="CylinderShape3D_utg71"] +radius = 1.6 + [sub_resource type="AudioStreamRandomizer" id="AudioStreamRandomizer_8ydov"] random_pitch = 1.1 streams_count = 6 @@ -716,6 +719,7 @@ collision_mask = 256 [node name="CollisionShape3D" type="CollisionShape3D" parent="CameraPosition/CameraPivot/SpawnZoneFinder"] shape = SubResource("SphereShape3D_utg71") +debug_color = Color(0.602755, 0.367301, 1, 0.42) [node name="StandingCollider" type="CollisionShape3D" parent="."] 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")] 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="."] unique_name_in_owner = true transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -1, 0) diff --git a/src/world/grunk_beast/beast_spawner/beast_spawn_zone/beast_spawn_zone.gd b/src/world/grunk_beast/beast_spawner/beast_spawn_zone/beast_spawn_zone.gd index d12a34a..a7f95a1 100644 --- a/src/world/grunk_beast/beast_spawner/beast_spawn_zone/beast_spawn_zone.gd +++ b/src/world/grunk_beast/beast_spawner/beast_spawn_zone/beast_spawn_zone.gd @@ -2,18 +2,14 @@ class_name BeastSpawnZone extends Area3D ## An area which contains and manages prioritized BeastSpawners. -## Return the list of spawners in this area with the highest spawn priority -func get_priority_spawners() -> Array[BeastSpawner]: - var spawns: Array[BeastSpawner] = [] - 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) +func _ready() -> void: + # Adjust position to very slightly above placement position for raycasts + global_position.y += 0.001 - 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 diff --git a/src/world/grunk_beast/beast_spawner/beast_spawner.gd b/src/world/grunk_beast/beast_spawner/beast_spawner.gd index 743b761..9a9354b 100644 --- a/src/world/grunk_beast/beast_spawner/beast_spawner.gd +++ b/src/world/grunk_beast/beast_spawner/beast_spawner.gd @@ -6,13 +6,22 @@ const GROUP = "BeastSpawnPoint" ## Scene to instantiate @export var beast_scene: PackedScene +## Area for checking spawn exclusion +@export var spawn_exclusion_area: Area3D + ## Spawners with higher priority will be picked first @export var spawn_priority: int = 0 +## Create a new GrunkBeast instance func instantiate() -> GrunkBeast: 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: pass # Implemented in derived type diff --git a/src/world/grunk_beast/beast_spawner/floor_spawner.tscn b/src/world/grunk_beast/beast_spawner/floor_spawner.tscn index 248c83e..b1da601 100644 --- a/src/world/grunk_beast/beast_spawner/floor_spawner.tscn +++ b/src/world/grunk_beast/beast_spawner/floor_spawner.tscn @@ -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="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") beast_scene = ExtResource("2_dgp11") +spawn_exclusion_area = NodePath("SpawnExclusionArea") spawn_priority = 1 [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) diff --git a/src/world/grunk_beast/beast_spawner/transient_floor_spawner.tscn b/src/world/grunk_beast/beast_spawner/transient_floor_spawner.tscn index 656cdce..cd504d3 100644 --- a/src/world/grunk_beast/beast_spawner/transient_floor_spawner.tscn +++ b/src/world/grunk_beast/beast_spawner/transient_floor_spawner.tscn @@ -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="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 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") beast_scene = ExtResource("2_e5rh0") +spawn_exclusion_area = NodePath("SpawnExclusionArea") spawn_priority = 10 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"] 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] diff --git a/src/world/grunk_beast/shambler/shambler.gd b/src/world/grunk_beast/shambler/shambler.gd index 268fcc0..9f3d4ed 100644 --- a/src/world/grunk_beast/shambler/shambler.gd +++ b/src/world/grunk_beast/shambler/shambler.gd @@ -30,6 +30,7 @@ func _ready() -> void: animation_player.speed_scale = time_scale_factor _sync_pivot_to_root() _update_transform() + tree_exiting.connect(_end_player_rumble) func _start_player_rumble() -> void: diff --git a/src/world/spook_manager/spook_manager.gd b/src/world/spook_manager/spook_manager.gd index c8a6408..2a30cf8 100644 --- a/src/world/spook_manager/spook_manager.gd +++ b/src/world/spook_manager/spook_manager.gd @@ -8,7 +8,7 @@ func _get_best_spawners() -> Array[BeastSpawner]: # Get all spawners in the spawn zone(s) occupied by the player var zones: Array[BeastSpawnZone] = Player.instance.get_spawn_zones() for zone: BeastSpawnZone in zones: - spawners.append_array(zone.get_spawners()) + spawners.append_array(zone.get_valid_spawners()) if spawners: # Get spawners which player has unobstructed sightline to var visible_spawners := spawners.filter( @@ -32,7 +32,8 @@ func _get_best_spawners() -> Array[BeastSpawner]: # Fallback behavior: just get all spawners print_debug("Falling back to all world spawners") 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]: diff --git a/vault/elements/grunk beast.md b/vault/elements/grunk beast.md index ff9a939..efe5ca8 100644 --- a/vault/elements/grunk beast.md +++ b/vault/elements/grunk beast.md @@ -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 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 there are no map-default spawns, log an error and do not spawn a beast. \ No newline at end of file +- 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. \ No newline at end of file