diff --git a/assets/materials/gunk_lowrez.material b/assets/materials/gunk_lowrez.material index 21aa1d5..a350d28 100644 Binary files a/assets/materials/gunk_lowrez.material and b/assets/materials/gunk_lowrez.material differ diff --git a/levels/mechanic_test/mechanic_test.gd b/levels/mechanic_test/mechanic_test.gd index dcca6de..17b00a5 100644 --- a/levels/mechanic_test/mechanic_test.gd +++ b/levels/mechanic_test/mechanic_test.gd @@ -4,12 +4,14 @@ extends Node3D @onready var gunk_hall: GunkBody = %GunkHall @onready var bulkhead: Node3D = $Bulkhead -@onready var open_switch: Node3D = $Bulkhead/Podium/OpenSwitch -@onready var close_switch: Node3D = $Bulkhead/Podium2/CloseSwitch +@onready var open_switch: Node3D = %OpenSwitch +@onready var close_switch: Node3D = %CloseSwitch -@onready var nodule_spawn_point: Marker3D = $NoduleSpawner/NoduleSpawnPoint +@onready var nodule_spawn_point: Marker3D = %NoduleSpawnPoint +@onready var alarm_spawn_point: Marker3D = %AlarmSpawnPoint static var nodule_scene: PackedScene = load("res://src/world/gunk_node/grunk_nodule.tscn") +static var alarm_scene: PackedScene = load("res://src/world/mechanics/alarm/gunk_alarm.tscn") func reset() -> void: @@ -20,8 +22,22 @@ func reset() -> void: Callable(close_switch, "disable").call() -func spawn_nodule() -> void: - for c: Node in nodule_spawn_point.get_children(): +func _do_spawn(spawn_point: Node3D, scene: PackedScene) -> void: + for c: Node in spawn_point.get_children(): c.queue_free() - var instance := nodule_scene.instantiate() - nodule_spawn_point.add_child(instance) + var instance := scene.instantiate() + spawn_point.add_child(instance) + + +func spawn_nodule() -> void: + _do_spawn(nodule_spawn_point, nodule_scene) + + +func spawn_alarm() -> void: + _do_spawn(alarm_spawn_point, alarm_scene) + + +func trigger_spawned_alarm() -> void: + var alarm: GunkAlarm = alarm_spawn_point.get_child(0) + if alarm: + alarm.trigger() diff --git a/levels/mechanic_test/mechanic_test.tscn b/levels/mechanic_test/mechanic_test.tscn index a08a65c..2170ef2 100644 --- a/levels/mechanic_test/mechanic_test.tscn +++ b/levels/mechanic_test/mechanic_test.tscn @@ -173,17 +173,58 @@ mesh = SubResource("BoxMesh_goufh") [node name="CollisionShape3D" type="CollisionShape3D" parent="NoduleSpawner"] shape = SubResource("ConcavePolygonShape3D_bg05n") -[node name="SpawnSwitch" parent="NoduleSpawner" instance=ExtResource("2_pka60")] +[node name="WallSwitch" parent="NoduleSpawner" instance=ExtResource("2_pka60")] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.4, 0.35) enabled = true [node name="Label3D" type="Label3D" parent="NoduleSpawner"] -transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.9, 0.3) -text = "Spawn" +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0.3) +text = "Spawn +Nodule" [node name="NoduleSpawnPoint" type="Marker3D" parent="NoduleSpawner"] +unique_name_in_owner = true transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, -3) +[node name="AlarmSpawner" type="StaticBody3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -4.5, 1, -6) + +[node name="MeshInstance3D" type="MeshInstance3D" parent="AlarmSpawner"] +mesh = SubResource("BoxMesh_goufh") + +[node name="CollisionShape3D" type="CollisionShape3D" parent="AlarmSpawner"] +shape = SubResource("ConcavePolygonShape3D_bg05n") + +[node name="WallSwitch" parent="AlarmSpawner" instance=ExtResource("2_pka60")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.4, 0.35) +enabled = true + +[node name="Label3D" type="Label3D" parent="AlarmSpawner"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0.3) +text = "Spawn +Alarm" + +[node name="AlarmSpawnPoint" type="Marker3D" parent="AlarmSpawner"] +unique_name_in_owner = true +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.7, -4) + +[node name="AlarmTrigger" type="StaticBody3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -3.5, 1, -6) + +[node name="MeshInstance3D" type="MeshInstance3D" parent="AlarmTrigger"] +mesh = SubResource("BoxMesh_goufh") + +[node name="CollisionShape3D" type="CollisionShape3D" parent="AlarmTrigger"] +shape = SubResource("ConcavePolygonShape3D_bg05n") + +[node name="AlarmTriggerSwitch" parent="AlarmTrigger" instance=ExtResource("2_pka60")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.4, 0.35) +enabled = true + +[node name="Label3D" type="Label3D" parent="AlarmTrigger"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0.3) +text = "Trigger" + [node name="GunkHall" parent="." instance=ExtResource("4_2uiim")] unique_name_in_owner = true transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.5, 0, -31) @@ -197,6 +238,7 @@ mesh = SubResource("ArrayMesh_x2vho") shape = SubResource("ConcavePolygonShape3D_qjnj2") [node name="Bulkhead" parent="." instance=ExtResource("8_0j1ke")] +unique_name_in_owner = true transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -6.5, 0, -2) [node name="Podium" type="StaticBody3D" parent="Bulkhead"] @@ -209,9 +251,14 @@ mesh = SubResource("BoxMesh_goufh") shape = SubResource("ConcavePolygonShape3D_bg05n") [node name="OpenSwitch" parent="Bulkhead/Podium" instance=ExtResource("2_pka60")] +unique_name_in_owner = true transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.4, 0.35) enabled = true +[node name="Label3D" type="Label3D" parent="Bulkhead/Podium"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.9, 0.3) +text = "Open" + [node name="Podium2" type="StaticBody3D" parent="Bulkhead"] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 3, 1, 0) @@ -222,10 +269,17 @@ mesh = SubResource("BoxMesh_goufh") shape = SubResource("ConcavePolygonShape3D_bg05n") [node name="CloseSwitch" parent="Bulkhead/Podium2" instance=ExtResource("2_pka60")] +unique_name_in_owner = true transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.4, 0.35) +[node name="Label3D" type="Label3D" parent="Bulkhead/Podium2"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.9, 0.3) +text = "Close" + [connection signal="activated" from="ResetPodium/ResetSwitch" to="." method="reset"] -[connection signal="activated" from="NoduleSpawner/SpawnSwitch" to="." method="spawn_nodule"] +[connection signal="activated" from="NoduleSpawner/WallSwitch" to="." method="spawn_nodule"] +[connection signal="activated" from="AlarmSpawner/WallSwitch" to="." method="spawn_alarm"] +[connection signal="activated" from="AlarmTrigger/AlarmTriggerSwitch" to="." method="trigger_spawned_alarm"] [connection signal="activated" from="Bulkhead/Podium/OpenSwitch" to="Bulkhead" method="open"] [connection signal="activated" from="Bulkhead/Podium/OpenSwitch" to="Bulkhead/Podium/OpenSwitch" method="disable"] [connection signal="activated" from="Bulkhead/Podium/OpenSwitch" to="Bulkhead/Podium2/CloseSwitch" method="enable"] diff --git a/src/game/game_manager.gd b/src/game/game_manager.gd index f8c84bb..8a4c84b 100644 --- a/src/game/game_manager.gd +++ b/src/game/game_manager.gd @@ -1,13 +1,37 @@ class_name GameManagerType extends Node ## Autoloaded singleton encapsulating game state. +## Emitted just after `delta` is added to the player's grunk tank. signal grunk_collected(delta: float) +## Emitted just before the alert level is raised to `new_value`. +signal alert_raised(new_value: int) + +## Emitted just before the alert level is reset to zero. +signal alert_cleared + +const MAX_ALERT := 6 + var grunk_tank := 0.0 var alert_level := 0 +## Add to the player's grunk tank. func collect_grunk(delta: float) -> void: grunk_tank += delta grunk_collected.emit(delta) + + +## Raise the alert level, if possible. +func raise_alert(delta: int) -> void: + var new_value := clampi(alert_level + delta, 0, MAX_ALERT) + if new_value != alert_level: + alert_raised.emit(new_value) + alert_level = new_value + + +## Reset the alert level to zero. +func clear_alert() -> void: + alert_cleared.emit() + alert_level = 0 diff --git a/src/world/mechanics/alarm/gunk_alarm.gd b/src/world/mechanics/alarm/gunk_alarm.gd new file mode 100644 index 0000000..1a0c822 --- /dev/null +++ b/src/world/mechanics/alarm/gunk_alarm.gd @@ -0,0 +1,36 @@ +class_name GunkAlarm extends GunkNode +## Raises the grunk alert when triggered. + +## Emitted when this alarm is triggered, just after the alert level is raised. +signal triggered + +const ALERT_DELTA := 1 + +var _busy := false + +@onready var mesh_instance_3d: MeshInstance3D = %MeshInstance3D +@onready var animation_player: AnimationPlayer = %AnimationPlayer + + +## Trigger this alarm. +## +## Will not trigger if this alarm is busy (e.g. on cooldown) +func trigger() -> void: + if not _busy: + Game.manager.raise_alert(ALERT_DELTA) + _busy = true + animation_player.play("trigger") + triggered.emit() + + +func _process(delta: float) -> void: + super._process(delta) + # TODO actual model & animation + var material: StandardMaterial3D = mesh_instance_3d.mesh.surface_get_material(0) + var value := 1.0 - _sustained_damage / durability + material.albedo_color = Color(value, value, value) + + +func _on_animation_finished(anim_name: StringName) -> void: + if anim_name == "trigger": + _busy = false diff --git a/src/world/mechanics/alarm/gunk_alarm.gd.uid b/src/world/mechanics/alarm/gunk_alarm.gd.uid new file mode 100644 index 0000000..45103f9 --- /dev/null +++ b/src/world/mechanics/alarm/gunk_alarm.gd.uid @@ -0,0 +1 @@ +uid://djb5tabjcvwkt diff --git a/src/world/mechanics/alarm/gunk_alarm.tscn b/src/world/mechanics/alarm/gunk_alarm.tscn new file mode 100644 index 0000000..f9f304b --- /dev/null +++ b/src/world/mechanics/alarm/gunk_alarm.tscn @@ -0,0 +1,85 @@ +[gd_scene load_steps=8 format=3 uid="uid://dgeg3kkogm71m"] + +[ext_resource type="Script" uid="uid://djb5tabjcvwkt" path="res://src/world/mechanics/alarm/gunk_alarm.gd" id="1_piaxx"] + +[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_piaxx"] +resource_local_to_scene = true +emission_enabled = true +emission = Color(1, 0, 0, 1) +emission_energy_multiplier = 0.0 + +[sub_resource type="CylinderMesh" id="CylinderMesh_1uge1"] +resource_local_to_scene = true +material = SubResource("StandardMaterial3D_piaxx") +top_radius = 0.3 +bottom_radius = 0.3 +height = 0.3 + +[sub_resource type="CylinderShape3D" id="CylinderShape3D_b4qsp"] +height = 0.3 +radius = 0.3 + +[sub_resource type="Animation" id="Animation_piaxx"] +resource_name = "trigger" +length = 4.0 +step = 0.1 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath("MeshInstance3D:mesh:material:emission_energy_multiplier") +tracks/0/interp = 2 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0, 0.5, 1, 1.5, 2, 2.5, 3, 3.5, 4), +"transitions": PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1), +"update": 0, +"values": [0.0, 4.0, 0.0, 4.0, 0.0, 4.0, 0.0, 4.0, 0.0] +} + +[sub_resource type="Animation" id="Animation_b6er8"] +length = 0.001 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath("MeshInstance3D:mesh:material:emission_energy_multiplier") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0), +"transitions": PackedFloat32Array(1), +"update": 0, +"values": [0.0] +} + +[sub_resource type="AnimationLibrary" id="AnimationLibrary_b6er8"] +_data = { +&"RESET": SubResource("Animation_b6er8"), +&"trigger": SubResource("Animation_piaxx") +} + +[node name="GunkAlarm" type="StaticBody3D"] +collision_layer = 5 +collision_mask = 0 +script = ExtResource("1_piaxx") +durability = 3.0 +value = 10000.0 +metadata/_custom_type_script = "uid://bypgxi0gy56yk" + +[node name="Label3D" type="Label3D" parent="."] +transform = Transform3D(1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, 0, 0.2, 0) +text = "ALARM" + +[node name="MeshInstance3D" type="MeshInstance3D" parent="."] +unique_name_in_owner = true +mesh = SubResource("CylinderMesh_1uge1") + +[node name="CollisionShape3D" type="CollisionShape3D" parent="."] +shape = SubResource("CylinderShape3D_b4qsp") + +[node name="AnimationPlayer" type="AnimationPlayer" parent="."] +unique_name_in_owner = true +libraries = { +&"": SubResource("AnimationLibrary_b6er8") +} + +[connection signal="animation_finished" from="AnimationPlayer" to="." method="_on_animation_finished"]