generated from krampus/template-godot4
Meet-spook tech
This commit is contained in:
parent
b04f45b22b
commit
0c10a95cbf
Binary file not shown.
File diff suppressed because one or more lines are too long
18
levels/meet_spook_test/debug_mob.tscn
Normal file
18
levels/meet_spook_test/debug_mob.tscn
Normal file
@ -0,0 +1,18 @@
|
||||
[gd_scene load_steps=4 format=3 uid="uid://8m6kh4f6pily"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://1trqaximxxmx" path="res://src/world/meet_spook/meet_spook_mob.gd" id="1_545xt"]
|
||||
[ext_resource type="Material" uid="uid://dutkfm4ek0ysh" path="res://assets/materials/gunk_bright.material" id="2_2vvgf"]
|
||||
|
||||
[sub_resource type="CapsuleMesh" id="CapsuleMesh_k8gi4"]
|
||||
material = ExtResource("2_2vvgf")
|
||||
|
||||
[node name="DebugMob" type="Node3D"]
|
||||
script = ExtResource("1_545xt")
|
||||
|
||||
[node name="MeshInstance3D" type="MeshInstance3D" parent="."]
|
||||
mesh = SubResource("CapsuleMesh_k8gi4")
|
||||
|
||||
[node name="Label3D" type="Label3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.2, 0)
|
||||
billboard = 2
|
||||
text = "SPOOKY!"
|
98
levels/meet_spook_test/meet_spook_test.tscn
Normal file
98
levels/meet_spook_test/meet_spook_test.tscn
Normal file
@ -0,0 +1,98 @@
|
||||
[gd_scene load_steps=15 format=3 uid="uid://1kreuo8tc4jv"]
|
||||
|
||||
[ext_resource type="PackedScene" uid="uid://bwe2jdmvinhqd" path="res://src/player/player.tscn" id="1_f3l4i"]
|
||||
[ext_resource type="PackedScene" uid="uid://b6eg8t04rkh0c" path="res://src/props/wall_switch/wall_switch.tscn" id="2_eywuc"]
|
||||
[ext_resource type="Script" uid="uid://cvx514gdjd5ev" path="res://src/world/meet_spook/meet_spook.gd" id="4_b6st5"]
|
||||
[ext_resource type="PackedScene" uid="uid://8m6kh4f6pily" path="res://levels/meet_spook_test/debug_mob.tscn" id="5_cr4pm"]
|
||||
[ext_resource type="Script" uid="uid://cisuvlqvqfqgo" path="res://src/world/meet_spook/meet_spook_spawner.gd" id="5_tak24"]
|
||||
[ext_resource type="Script" uid="uid://c1gy2b2wcq127" path="res://levels/meet_spook_test/tween_debug.gd" id="6_cr4pm"]
|
||||
|
||||
[sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_eywuc"]
|
||||
|
||||
[sub_resource type="Sky" id="Sky_pka60"]
|
||||
sky_material = SubResource("ProceduralSkyMaterial_eywuc")
|
||||
|
||||
[sub_resource type="Environment" id="Environment_nynr7"]
|
||||
background_mode = 2
|
||||
sky = SubResource("Sky_pka60")
|
||||
|
||||
[sub_resource type="PlaneMesh" id="PlaneMesh_b6st5"]
|
||||
size = Vector2(50, 50)
|
||||
|
||||
[sub_resource type="WorldBoundaryShape3D" id="WorldBoundaryShape3D_tak24"]
|
||||
|
||||
[sub_resource type="BoxMesh" id="BoxMesh_nynr7"]
|
||||
size = Vector3(0.5, 2, 0.5)
|
||||
|
||||
[sub_resource type="ConcavePolygonShape3D" id="ConcavePolygonShape3D_b6st5"]
|
||||
data = PackedVector3Array(-0.25, 1, 0.25, 0.25, 1, 0.25, -0.25, -1, 0.25, 0.25, 1, 0.25, 0.25, -1, 0.25, -0.25, -1, 0.25, 0.25, 1, -0.25, -0.25, 1, -0.25, 0.25, -1, -0.25, -0.25, 1, -0.25, -0.25, -1, -0.25, 0.25, -1, -0.25, 0.25, 1, 0.25, 0.25, 1, -0.25, 0.25, -1, 0.25, 0.25, 1, -0.25, 0.25, -1, -0.25, 0.25, -1, 0.25, -0.25, 1, -0.25, -0.25, 1, 0.25, -0.25, -1, -0.25, -0.25, 1, 0.25, -0.25, -1, 0.25, -0.25, -1, -0.25, 0.25, 1, 0.25, -0.25, 1, 0.25, 0.25, 1, -0.25, -0.25, 1, 0.25, -0.25, 1, -0.25, 0.25, 1, -0.25, -0.25, -1, 0.25, 0.25, -1, 0.25, -0.25, -1, -0.25, 0.25, -1, 0.25, 0.25, -1, -0.25, -0.25, -1, -0.25)
|
||||
|
||||
[sub_resource type="SphereMesh" id="SphereMesh_tak24"]
|
||||
|
||||
[node name="MeetSpookTest" type="Node3D"]
|
||||
|
||||
[node name="Player" parent="." instance=ExtResource("1_f3l4i")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
|
||||
|
||||
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
|
||||
transform = Transform3D(0.866025, -0.156955, -0.474726, 0.5, 0.271854, 0.82225, 0, -0.949453, 0.31391, 0, 0, 0)
|
||||
|
||||
[node name="WorldEnvironment" type="WorldEnvironment" parent="."]
|
||||
environment = SubResource("Environment_nynr7")
|
||||
|
||||
[node name="WorldFloor" type="StaticBody3D" parent="." groups=["PlasticMaterial"]]
|
||||
|
||||
[node name="MeshInstance3D" type="MeshInstance3D" parent="WorldFloor"]
|
||||
mesh = SubResource("PlaneMesh_b6st5")
|
||||
skeleton = NodePath("../..")
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="WorldFloor"]
|
||||
shape = SubResource("WorldBoundaryShape3D_tak24")
|
||||
|
||||
[node name="TriggerPodium" type="StaticBody3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, -2)
|
||||
|
||||
[node name="MeshInstance3D" type="MeshInstance3D" parent="TriggerPodium"]
|
||||
mesh = SubResource("BoxMesh_nynr7")
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="TriggerPodium"]
|
||||
shape = SubResource("ConcavePolygonShape3D_b6st5")
|
||||
|
||||
[node name="TriggerSwitch" parent="TriggerPodium" instance=ExtResource("2_eywuc")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.4, 0.35)
|
||||
clean = true
|
||||
|
||||
[node name="Label3D" type="Label3D" parent="TriggerPodium"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.9, 0.3)
|
||||
text = "GET SCARED"
|
||||
|
||||
[node name="MeetSpook" type="Marker3D" parent="." node_paths=PackedStringArray("spawners")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -3, 1.5, 0)
|
||||
script = ExtResource("4_b6st5")
|
||||
mob_scene = ExtResource("5_cr4pm")
|
||||
spawners = [NodePath("../MeetSpookSpawner"), NodePath("../MeetSpookSpawner2")]
|
||||
metadata/_custom_type_script = "uid://cvx514gdjd5ev"
|
||||
|
||||
[node name="MeetSpookSpawner" type="Marker3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.5, 6)
|
||||
script = ExtResource("5_tak24")
|
||||
metadata/_custom_type_script = "uid://cisuvlqvqfqgo"
|
||||
|
||||
[node name="Target" type="Marker3D" parent="MeetSpookSpawner"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 9, 0, 0)
|
||||
|
||||
[node name="MeetSpookSpawner2" type="Marker3D" parent="."]
|
||||
transform = Transform3D(-4.37114e-08, 0, -1, 0, 1, 0, 1, 0, -4.37114e-08, 9, 1.5, 0)
|
||||
script = ExtResource("5_tak24")
|
||||
metadata/_custom_type_script = "uid://cisuvlqvqfqgo"
|
||||
|
||||
[node name="Target" type="Marker3D" parent="MeetSpookSpawner2"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 9, 0, 0)
|
||||
|
||||
[node name="TweenDebug" type="MeshInstance3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.5, -4.5)
|
||||
mesh = SubResource("SphereMesh_tak24")
|
||||
script = ExtResource("6_cr4pm")
|
||||
|
||||
[connection signal="activated" from="TriggerPodium/TriggerSwitch" to="MeetSpook" method="prepare"]
|
||||
[connection signal="activated" from="TriggerPodium/TriggerSwitch" to="TweenDebug" method="activate"]
|
19
levels/meet_spook_test/tween_debug.gd
Normal file
19
levels/meet_spook_test/tween_debug.gd
Normal file
@ -0,0 +1,19 @@
|
||||
extends MeshInstance3D
|
||||
|
||||
@export var movement := Vector3(3, 0, 0)
|
||||
@export var duration := 2.0
|
||||
@export var transition_type := Tween.TRANS_SINE
|
||||
@export var ease_type := Tween.EASE_IN
|
||||
|
||||
@onready var start_position := position
|
||||
|
||||
|
||||
func activate() -> void:
|
||||
var end_position := start_position + movement
|
||||
position = start_position
|
||||
(
|
||||
create_tween()
|
||||
. tween_property(self, "position", end_position, duration)
|
||||
. set_trans(transition_type)
|
||||
. set_ease(ease_type)
|
||||
)
|
1
levels/meet_spook_test/tween_debug.gd.uid
Normal file
1
levels/meet_spook_test/tween_debug.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://c1gy2b2wcq127
|
@ -59,7 +59,10 @@ enabled=PackedStringArray("res://addons/format_on_save/plugin.cfg", "res://addon
|
||||
folder_colors={
|
||||
"res://assets/": "red",
|
||||
"res://levels/": "blue",
|
||||
"res://src/": "green"
|
||||
"res://levels/ghost_ship/": "purple",
|
||||
"res://src/": "green",
|
||||
"res://src/ui/": "yellow",
|
||||
"res://src/world/": "teal"
|
||||
}
|
||||
|
||||
[game]
|
||||
@ -77,6 +80,7 @@ config/input/hold_to_sneak=true
|
||||
|
||||
MetalMaterial="This surface is made of metal."
|
||||
PlasticMaterial="This surface is made of plastic."
|
||||
MeetSpookSource="meet-spook event sources"
|
||||
|
||||
[importer_defaults]
|
||||
|
||||
|
@ -1,6 +1,8 @@
|
||||
class_name GameManagerType extends Node
|
||||
## Autoloaded singleton encapsulating game state.
|
||||
|
||||
# TODO a lot of this should really be a property of the world.
|
||||
|
||||
## Emitted just after `delta` is added to the player's grunk tank.
|
||||
signal grunk_collected(delta: float)
|
||||
|
||||
@ -27,6 +29,10 @@ const MAX_ALERT := 6
|
||||
## Grunk collection milestones
|
||||
@export var vault_milestones: Dictionary[int, Milestone]
|
||||
|
||||
var debug_emit_alert_signal: int:
|
||||
set(value):
|
||||
alert_raised.emit(value)
|
||||
|
||||
## Amount of grunk the player is currently carrying.
|
||||
var grunk_tank := 0.0
|
||||
|
||||
|
17
src/util/scene_tools.gd
Normal file
17
src/util/scene_tools.gd
Normal file
@ -0,0 +1,17 @@
|
||||
class_name SceneTools extends Object
|
||||
## Tools for specialized operations in a scene
|
||||
|
||||
|
||||
## Get the Node3D in the given group which is spatially closest to the target node.
|
||||
##
|
||||
## If there is no such node, e.g. if the group is empty, the result will be null.
|
||||
static func closest_in_group(target: Node3D, group_name: String) -> Node3D:
|
||||
var closest: Node3D
|
||||
var min_dist_sq := INF
|
||||
for n: Node in target.get_tree().get_nodes_in_group(group_name):
|
||||
if n is Node3D:
|
||||
var dist_sq := (n as Node3D).global_position.distance_squared_to(target.global_position)
|
||||
if dist_sq < min_dist_sq:
|
||||
min_dist_sq = dist_sq
|
||||
closest = n
|
||||
return closest
|
1
src/util/scene_tools.gd.uid
Normal file
1
src/util/scene_tools.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://dsh37qra42bm2
|
29
src/world/meet_spook/meet_spook.gd
Normal file
29
src/world/meet_spook/meet_spook.gd
Normal file
@ -0,0 +1,29 @@
|
||||
class_name MeetSpook extends Marker3D
|
||||
## Source for a "meet-spook" event.
|
||||
##
|
||||
## When the event conditions are met, the MeetSpook closest to the player prepares contextual
|
||||
## resources for the event.
|
||||
|
||||
const GROUP := "MeetSpookSource"
|
||||
|
||||
@export var mob_scene: PackedScene
|
||||
@export var spawners: Array[MeetSpookSpawner] = []
|
||||
|
||||
var event_prepared := false
|
||||
|
||||
|
||||
func prepare() -> void:
|
||||
if event_prepared:
|
||||
return
|
||||
|
||||
for spawner: MeetSpookSpawner in spawners:
|
||||
var instance: MeetSpookMob = mob_scene.instantiate()
|
||||
instance.source = self
|
||||
spawner.spawn(instance)
|
||||
event_prepared = true
|
||||
|
||||
|
||||
func resolve() -> void:
|
||||
for spawner: MeetSpookSpawner in spawners:
|
||||
spawner.resolve()
|
||||
event_prepared = false
|
1
src/world/meet_spook/meet_spook.gd.uid
Normal file
1
src/world/meet_spook/meet_spook.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://cvx514gdjd5ev
|
62
src/world/meet_spook/meet_spook_mob.gd
Normal file
62
src/world/meet_spook/meet_spook_mob.gd
Normal file
@ -0,0 +1,62 @@
|
||||
class_name MeetSpookMob extends Node3D
|
||||
## Mob spawned and controlled by a meet-spook event
|
||||
|
||||
enum State { WAITING, FREEZE, MOVING }
|
||||
|
||||
const RAY_MASK := 0b00000101
|
||||
const BASE_SPEED := 6.0
|
||||
|
||||
@export var source: MeetSpook
|
||||
@export var target: Node3D
|
||||
@export var freeze_time: float
|
||||
@export var lifespan: float
|
||||
|
||||
var state := State.WAITING
|
||||
|
||||
@onready var camera := get_viewport().get_camera_3d()
|
||||
|
||||
|
||||
func activate() -> void:
|
||||
print_debug(self, " spotted!")
|
||||
state = State.FREEZE
|
||||
get_tree().create_timer(freeze_time).timeout.connect(_unfreeze)
|
||||
get_tree().create_timer(lifespan).timeout.connect(destroy)
|
||||
source.resolve()
|
||||
|
||||
|
||||
func is_active() -> bool:
|
||||
return state != State.WAITING
|
||||
|
||||
|
||||
func move_toward_target(delta: float) -> void:
|
||||
# Should probably be overridden by implementations
|
||||
global_position = global_position.move_toward(target.global_position, BASE_SPEED * delta)
|
||||
|
||||
|
||||
func _unfreeze() -> void:
|
||||
state = State.MOVING
|
||||
|
||||
|
||||
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()
|
||||
State.FREEZE:
|
||||
# Just stand there for a sec
|
||||
pass
|
||||
State.MOVING:
|
||||
move_toward_target(delta)
|
||||
|
||||
|
||||
func destroy() -> void:
|
||||
print_debug("Destroying ", self)
|
||||
queue_free()
|
1
src/world/meet_spook/meet_spook_mob.gd.uid
Normal file
1
src/world/meet_spook/meet_spook_mob.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://1trqaximxxmx
|
34
src/world/meet_spook/meet_spook_spawner.gd
Normal file
34
src/world/meet_spook/meet_spook_spawner.gd
Normal file
@ -0,0 +1,34 @@
|
||||
class_name MeetSpookSpawner extends Marker3D
|
||||
## Spawner which is part of a MeetSpook event
|
||||
|
||||
## Time to freeze the mob before moving after being spotted, in seconds.
|
||||
@export var freeze_time := 0.4
|
||||
|
||||
## Time after which the mob will despawn after being spotted, in seconds.
|
||||
@export var mob_lifespan := 1.0
|
||||
|
||||
var _instance: MeetSpookMob
|
||||
|
||||
|
||||
func get_target() -> Marker3D:
|
||||
for c: Node in get_children():
|
||||
if c is Marker3D:
|
||||
return c
|
||||
return null
|
||||
|
||||
|
||||
func spawn(instance: MeetSpookMob) -> void:
|
||||
if is_instance_valid(_instance):
|
||||
_instance.queue_free()
|
||||
_instance = instance
|
||||
_instance.target = get_target()
|
||||
_instance.freeze_time = freeze_time
|
||||
_instance.lifespan = mob_lifespan
|
||||
add_child(_instance)
|
||||
_instance.global_transform = global_transform
|
||||
|
||||
|
||||
func resolve() -> void:
|
||||
## Despawn if instance is not active
|
||||
if is_instance_valid(_instance) and not _instance.is_active():
|
||||
_instance.queue_free()
|
1
src/world/meet_spook/meet_spook_spawner.gd.uid
Normal file
1
src/world/meet_spook/meet_spook_spawner.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://cisuvlqvqfqgo
|
46
src/world/spook_manager/spook_manager.gd
Normal file
46
src/world/spook_manager/spook_manager.gd
Normal file
@ -0,0 +1,46 @@
|
||||
class_name SpookManager extends Resource
|
||||
## A strategy for handling horror elements through the level.
|
||||
|
||||
var debug_set_alert_level: int:
|
||||
set = _on_alert_raised
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
Game.manager.alert_raised.connect(_on_alert_raised)
|
||||
Game.manager.alert_cleared.connect(_on_alert_cleared)
|
||||
|
||||
|
||||
func _on_alert_raised(new_level: int) -> void:
|
||||
match new_level:
|
||||
0:
|
||||
# LEVEL 0: UNAWARE
|
||||
pass
|
||||
1:
|
||||
# LEVEL 1: PASSIVE
|
||||
# Beast avoids the player.
|
||||
# Set up meet-spook.
|
||||
print_debug("Preparing meet-spook...")
|
||||
# Get closest MeetSpook point to player.
|
||||
if Player.instance:
|
||||
var closest := SceneTools.closest_in_group(Player.instance, MeetSpook.GROUP)
|
||||
(closest as MeetSpook).prepare()
|
||||
2:
|
||||
# LEVEL 2: AGGRESSIVE
|
||||
# Beast pursues player on sight.
|
||||
pass # TODO
|
||||
3:
|
||||
# LEVEL 3: PREDATORY
|
||||
# Beast pursues player relentlessly.
|
||||
pass # TODO
|
||||
4:
|
||||
# LEVEL 4: SWARMING
|
||||
# Many beasts spawn, overwhelming the player.
|
||||
pass # TODO
|
||||
5:
|
||||
# LEVEL 5: FUN
|
||||
# Just kill that fool!
|
||||
pass # TODO
|
||||
|
||||
|
||||
func _on_alert_cleared() -> void:
|
||||
pass # TODO
|
1
src/world/spook_manager/spook_manager.gd.uid
Normal file
1
src/world/spook_manager/spook_manager.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://bsn026pxqwkbc
|
@ -1,6 +1,9 @@
|
||||
class_name World extends Node
|
||||
## Access and flow control for the game world.
|
||||
|
||||
@export var spook_manager: SpookManager
|
||||
|
||||
@export_category("Game Scenes")
|
||||
@export var initial_level: PackedScene
|
||||
|
||||
@export var pause_scene: PackedScene
|
||||
|
@ -1,11 +1,17 @@
|
||||
[gd_scene load_steps=4 format=3 uid="uid://884jqafhtrv0"]
|
||||
[gd_scene load_steps=6 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="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")
|
||||
metadata/_custom_type_script = "uid://bsn026pxqwkbc"
|
||||
|
||||
[node name="World" type="Node"]
|
||||
script = ExtResource("1_1k4gi")
|
||||
spook_manager = SubResource("Resource_43c6p")
|
||||
initial_level = ExtResource("2_jte2u")
|
||||
pause_scene = ExtResource("2_6fy3g")
|
||||
|
||||
|
@ -7,4 +7,24 @@ Summoned when the [[grunk alert]] maxes out. Hunts down the player and grunkifie
|
||||
Concept: No head, tall spindly legs.
|
||||
Visually, I imagine it straining to pull itself out of the grunk.
|
||||
|
||||
### Behavior
|
||||
Behavior changes based on [[grunk alert]]
|
||||
##### LEVEL 0 - Unaware
|
||||
Inactive. No spooky until the player fucks up.
|
||||
##### LEVEL 1 - Passive
|
||||
The beast spawns somewhere the player can't see. It will avoid the player.
|
||||
|
||||
Can we orchestrate a spooky sighting? ([[meet spook]]?)
|
||||
##### LEVEL 2 - Aggressive
|
||||
Beast will pursue the player on sight.
|
||||
##### LEVEL 3 - Predatory
|
||||
Beast will pursue player automatically and relentlessly.
|
||||
##### LEVEL 4 - Swarming
|
||||
Beasts spawn at every spawn point, all pursuing the player.
|
||||
##### LEVEL 5 - He's right behind me, isn't he?
|
||||
Unavoidable death. Show cutscene with player grabbed by beast.
|
||||
|
||||
|
||||
The player can reset the alert level to 2 by returning to the safe zone.
|
||||
|
||||
tags: #mechanics #lore
|
1
vault/elements/meet spook.md
Normal file
1
vault/elements/meet spook.md
Normal file
@ -0,0 +1 @@
|
||||
After hitting [[grunk alert]] level 1, trigger a "meet spook" event. Mark all sightline boundaries (corners etc) for each area of the level, and spawn a passive [[grunk beast]] at each. When the player has line of sight to the beast, immediately scurry out of sight ( #maybe play a sting sfx?) and despawn.
|
Loading…
x
Reference in New Issue
Block a user