Progress persistence

This commit is contained in:
Rob Kelly 2025-04-22 12:08:09 -06:00
parent cfb7b28971
commit 7957a47243
27 changed files with 283 additions and 78 deletions

View File

@ -1,6 +1,7 @@
[gd_scene load_steps=12 format=4 uid="uid://cfqirm2o3uo4k"] [gd_scene load_steps=13 format=4 uid="uid://cfqirm2o3uo4k"]
[ext_resource type="PackedScene" uid="uid://c2omlx4ptrc01" path="res://src/world/gunk_body/gunk_body.tscn" id="1_cr8wn"] [ext_resource type="PackedScene" uid="uid://c2omlx4ptrc01" path="res://src/world/gunk_body/gunk_body.tscn" id="1_cr8wn"]
[ext_resource type="Texture2D" uid="uid://8cm835h4gxwe" path="res://assets/debug_mask.png" id="2_7477u"]
[ext_resource type="Shader" uid="uid://ckxc0ngd37rtk" path="res://src/shaders/gunk.gdshader" id="2_lrgpr"] [ext_resource type="Shader" uid="uid://ckxc0ngd37rtk" path="res://src/shaders/gunk.gdshader" id="2_lrgpr"]
[ext_resource type="FastNoiseLite" uid="uid://cnlvdtx68giv6" path="res://assets/materials/gunk_noise.tres" id="3_7477u"] [ext_resource type="FastNoiseLite" uid="uid://cnlvdtx68giv6" path="res://assets/materials/gunk_noise.tres" id="3_7477u"]
@ -57,7 +58,7 @@ seamless = true
seamless_blend_skirt = 0.5 seamless_blend_skirt = 0.5
noise = ExtResource("3_7477u") noise = ExtResource("3_7477u")
[sub_resource type="ShaderMaterial" id="ShaderMaterial_qjnj2"] [sub_resource type="ShaderMaterial" id="ShaderMaterial_lrgpr"]
resource_local_to_scene = true resource_local_to_scene = true
render_priority = 0 render_priority = 0
shader = ExtResource("2_lrgpr") shader = ExtResource("2_lrgpr")
@ -69,6 +70,7 @@ shader_parameter/time_pixellation = 30.0
shader_parameter/roughness = 0.15 shader_parameter/roughness = 0.15
shader_parameter/specular_contribution = 0.8 shader_parameter/specular_contribution = 0.8
shader_parameter/emission_strength = 0.02 shader_parameter/emission_strength = 0.02
shader_parameter/normal_scale = 1.0
shader_parameter/uv_scale = Vector2(4, 4) shader_parameter/uv_scale = Vector2(4, 4)
shader_parameter/time_scale = 0.2 shader_parameter/time_scale = 0.2
shader_parameter/edge_bleed = 0.25 shader_parameter/edge_bleed = 0.25
@ -79,6 +81,7 @@ shader_parameter/jitter_magnitude = 0.0
shader_parameter/jitter_time_scale = 0.1 shader_parameter/jitter_time_scale = 0.1
shader_parameter/vertex_inflation = 0.0 shader_parameter/vertex_inflation = 0.0
shader_parameter/inflation_pixellation = 10.0 shader_parameter/inflation_pixellation = 10.0
shader_parameter/overlay_emission_scale = 1.0
[sub_resource type="ConcavePolygonShape3D" id="ConcavePolygonShape3D_x2vho"] [sub_resource type="ConcavePolygonShape3D" id="ConcavePolygonShape3D_x2vho"]
data = PackedVector3Array(-1, 1, 0.1, -1, -0.8, 0.1, -1, 1, -0.1, -1, 1, -0.1, -1, -0.8, 0.1, -1, -0.8, -0.1, -1, -1, 0.1, -1, -0.8, -0.1, -1, -0.8, 0.1, -1, -1, -2.3, -1, -0.8, -0.1, -1, -1, 0.1, -1, -0.8, -2.3, -1, -0.8, -0.1, -1, -1, -2.3, -1, -0.8, -2.1, -1, -0.8, -0.1, -1, -0.8, -2.3, -1, -0.8, -2.1, -1, -0.8, -2.3, -1, 1, -2.1, -1, 1, -2.1, -1, -0.8, -2.3, -1, 1, -2.3, -1, 1, -0.1, -1, -0.8, -0.1, 1, 1, -0.1, 1, 1, -0.1, -1, -0.8, -0.1, 1, -0.8, -0.1, 1, 1, -0.1, 1, -0.8, -0.1, 1, 1, 0.1, 1, 1, 0.1, 1, -0.8, -0.1, 1, -0.8, 0.1, 1, -0.8, -0.1, 1, -1, 0.1, 1, -0.8, 0.1, 1, -0.8, -2.1, 1, -1, 0.1, 1, -0.8, -0.1, 1, -0.8, -2.3, 1, -1, 0.1, 1, -0.8, -2.1, 1, -1, -2.3, 1, -1, 0.1, 1, -0.8, -2.3, 1, -0.8, -2.3, 1, -0.8, -2.1, 1, 1, -2.3, 1, 1, -2.3, 1, -0.8, -2.1, 1, 1, -2.1, 1, 1, 0.1, 1, -0.8, 0.1, -1, 1, 0.1, -1, 1, 0.1, 1, -0.8, 0.1, -1, -0.8, 0.1, 1, -0.8, 0.1, 1, -1, 0.1, -1, -0.8, 0.1, -1, -0.8, 0.1, 1, -1, 0.1, -1, -1, 0.1, -1, -1, -2.3, -1, -1, 0.1, 1, -1, -2.3, 1, -1, -2.3, -1, -1, 0.1, 1, -1, 0.1, -1, 1, -0.1, 1, 1, -0.1, -1, 1, 0.1, -1, 1, 0.1, 1, 1, -0.1, 1, 1, 0.1, 1, -0.8, -0.1, -1, -0.8, -0.1, 1, -0.8, -2.1, 1, -0.8, -2.1, -1, -0.8, -0.1, -1, -0.8, -2.1, -1, -0.8, -2.3, -1, -1, -2.3, 1, -0.8, -2.3, 1, -0.8, -2.3, -1, -1, -2.3, 1, -1, -2.3, -1, -0.8, -2.3, 1, -0.8, -2.3, -1, 1, -2.3, -1, 1, -2.3, 1, -0.8, -2.3, 1, 1, -2.3, 1, 1, -2.1, -1, 1, -2.1, 1, 1, -2.3, 1, 1, -2.3, -1, 1, -2.1, -1, 1, -2.3, 1, -0.8, -2.1, -1, -0.8, -2.1, 1, 1, -2.1, 1, 1, -2.1, -1, -0.8, -2.1, -1, 1, -2.1) data = PackedVector3Array(-1, 1, 0.1, -1, -0.8, 0.1, -1, 1, -0.1, -1, 1, -0.1, -1, -0.8, 0.1, -1, -0.8, -0.1, -1, -1, 0.1, -1, -0.8, -0.1, -1, -0.8, 0.1, -1, -1, -2.3, -1, -0.8, -0.1, -1, -1, 0.1, -1, -0.8, -2.3, -1, -0.8, -0.1, -1, -1, -2.3, -1, -0.8, -2.1, -1, -0.8, -0.1, -1, -0.8, -2.3, -1, -0.8, -2.1, -1, -0.8, -2.3, -1, 1, -2.1, -1, 1, -2.1, -1, -0.8, -2.3, -1, 1, -2.3, -1, 1, -0.1, -1, -0.8, -0.1, 1, 1, -0.1, 1, 1, -0.1, -1, -0.8, -0.1, 1, -0.8, -0.1, 1, 1, -0.1, 1, -0.8, -0.1, 1, 1, 0.1, 1, 1, 0.1, 1, -0.8, -0.1, 1, -0.8, 0.1, 1, -0.8, -0.1, 1, -1, 0.1, 1, -0.8, 0.1, 1, -0.8, -2.1, 1, -1, 0.1, 1, -0.8, -0.1, 1, -0.8, -2.3, 1, -1, 0.1, 1, -0.8, -2.1, 1, -1, -2.3, 1, -1, 0.1, 1, -0.8, -2.3, 1, -0.8, -2.3, 1, -0.8, -2.1, 1, 1, -2.3, 1, 1, -2.3, 1, -0.8, -2.1, 1, 1, -2.1, 1, 1, 0.1, 1, -0.8, 0.1, -1, 1, 0.1, -1, 1, 0.1, 1, -0.8, 0.1, -1, -0.8, 0.1, 1, -0.8, 0.1, 1, -1, 0.1, -1, -0.8, 0.1, -1, -0.8, 0.1, 1, -1, 0.1, -1, -1, 0.1, -1, -1, -2.3, -1, -1, 0.1, 1, -1, -2.3, 1, -1, -2.3, -1, -1, 0.1, 1, -1, 0.1, -1, 1, -0.1, 1, 1, -0.1, -1, 1, 0.1, -1, 1, 0.1, 1, 1, -0.1, 1, 1, 0.1, 1, -0.8, -0.1, -1, -0.8, -0.1, 1, -0.8, -2.1, 1, -0.8, -2.1, -1, -0.8, -0.1, -1, -0.8, -2.1, -1, -0.8, -2.3, -1, -1, -2.3, 1, -0.8, -2.3, 1, -0.8, -2.3, -1, -1, -2.3, 1, -1, -2.3, -1, -0.8, -2.3, 1, -0.8, -2.3, -1, 1, -2.3, -1, 1, -2.3, 1, -0.8, -2.3, 1, 1, -2.3, 1, 1, -2.1, -1, 1, -2.1, 1, 1, -2.3, 1, 1, -2.3, -1, 1, -2.1, -1, 1, -2.3, 1, -0.8, -2.1, -1, -0.8, -2.1, 1, 1, -2.1, 1, 1, -2.1, -1, -0.8, -2.1, -1, 1, -2.1)
@ -92,7 +95,8 @@ skeleton = NodePath("GunkBody")
[node name="GunkBody" parent="Parallel" instance=ExtResource("1_cr8wn")] [node name="GunkBody" parent="Parallel" instance=ExtResource("1_cr8wn")]
mask_dim = 128 mask_dim = 128
source_gunk_material = SubResource("ShaderMaterial_qjnj2") initial_mask = ExtResource("2_7477u")
source_gunk_material = SubResource("ShaderMaterial_lrgpr")
[node name="CollisionShape3D" type="CollisionShape3D" parent="Parallel/GunkBody"] [node name="CollisionShape3D" type="CollisionShape3D" parent="Parallel/GunkBody"]
shape = SubResource("ConcavePolygonShape3D_x2vho") shape = SubResource("ConcavePolygonShape3D_x2vho")

View File

@ -88,6 +88,7 @@ MeetSpookSource="meet-spook event sources"
LurkPoint="Point which a lurking beast may wander to." LurkPoint="Point which a lurking beast may wander to."
BeastSpawnPoint="Spawn point for a grunkbeast" BeastSpawnPoint="Spawn point for a grunkbeast"
GrunkBeast="GrunkBeast instances." GrunkBeast="GrunkBeast instances."
Persistent="Nodes which implement save and load methods"
[importer_defaults] [importer_defaults]

View File

@ -10,46 +10,6 @@ Label/constants/outline_size = 16
Label/font_sizes/font_size = 32 Label/font_sizes/font_size = 32
Label/fonts/font = ExtResource("2_s6lek") Label/fonts/font = ExtResource("2_s6lek")
[sub_resource type="Animation" id="Animation_s6lek"]
resource_name = "initial_display"
step = 0.05
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("MarginContainer/HBoxContainer/Loading:visible_ratio")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0, 0.8, 1),
"transitions": PackedFloat32Array(1, 1, 1),
"update": 0,
"values": [0.0, 0.0, 1.0]
}
tracks/1/type = "value"
tracks/1/imported = false
tracks/1/enabled = true
tracks/1/path = NodePath("MarginContainer/HBoxContainer/Ellipsis:visible_characters")
tracks/1/interp = 1
tracks/1/loop_wrap = true
tracks/1/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 1,
"values": [0]
}
tracks/2/type = "value"
tracks/2/imported = false
tracks/2/enabled = true
tracks/2/path = NodePath("MarginContainer/HBoxContainer/Blinker:visible")
tracks/2/interp = 1
tracks/2/loop_wrap = true
tracks/2/keys = {
"times": PackedFloat32Array(0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8),
"transitions": PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1),
"update": 1,
"values": [false, true, false, true, false, true, false, true, false]
}
[sub_resource type="Animation" id="Animation_kgj8g"] [sub_resource type="Animation" id="Animation_kgj8g"]
length = 0.001 length = 0.001
tracks/0/type = "value" tracks/0/type = "value"
@ -131,6 +91,46 @@ tracks/2/keys = {
"values": [0, 1, 2, 3] "values": [0, 1, 2, 3]
} }
[sub_resource type="Animation" id="Animation_s6lek"]
resource_name = "initial_display"
step = 0.05
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("MarginContainer/HBoxContainer/Loading:visible_ratio")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0, 0.8, 1),
"transitions": PackedFloat32Array(1, 1, 1),
"update": 0,
"values": [0.0, 0.0, 1.0]
}
tracks/1/type = "value"
tracks/1/imported = false
tracks/1/enabled = true
tracks/1/path = NodePath("MarginContainer/HBoxContainer/Ellipsis:visible_characters")
tracks/1/interp = 1
tracks/1/loop_wrap = true
tracks/1/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 1,
"values": [0]
}
tracks/2/type = "value"
tracks/2/imported = false
tracks/2/enabled = true
tracks/2/path = NodePath("MarginContainer/HBoxContainer/Blinker:visible")
tracks/2/interp = 1
tracks/2/loop_wrap = true
tracks/2/keys = {
"times": PackedFloat32Array(0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8),
"transitions": PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1),
"update": 1,
"values": [false, true, false, true, false, true, false, true, false]
}
[sub_resource type="AnimationLibrary" id="AnimationLibrary_kgj8g"] [sub_resource type="AnimationLibrary" id="AnimationLibrary_kgj8g"]
_data = { _data = {
&"RESET": SubResource("Animation_kgj8g"), &"RESET": SubResource("Animation_kgj8g"),

View File

@ -264,4 +264,18 @@ func _physics_process(delta: float) -> void:
move_and_slide() move_and_slide()
#endregion
#region Persistence
func serialize() -> Dictionary:
return {
"inventory": inventory,
}
func deserialize(data: Dictionary) -> void:
@warning_ignore("unsafe_cast")
inventory.assign(data["inventory"] as Dictionary)
#endregion #endregion

View File

@ -592,7 +592,7 @@ _data = {
[sub_resource type="PlaneMesh" id="PlaneMesh_p6grl"] [sub_resource type="PlaneMesh" id="PlaneMesh_p6grl"]
[node name="Player" type="CharacterBody3D"] [node name="Player" type="CharacterBody3D" groups=["Persistent"]]
collision_layer = 8 collision_layer = 8
collision_mask = 33 collision_mask = 33
script = ExtResource("1_npueo") script = ExtResource("1_npueo")

View File

@ -1,5 +1,8 @@
extends Node3D extends Node3D
@export var start_open := false
@export_category("Editor Tools")
@export var debug_open: bool: @export var debug_open: bool:
set(value): set(value):
open() open()
@ -15,6 +18,28 @@ extends Node3D
@onready var nav_link: NavigationLink3D = %NavLink @onready var nav_link: NavigationLink3D = %NavLink
func _ready() -> void:
if start_open:
_instant_open()
func _instant_open() -> void:
nav_link.enabled = true
animation.play("open")
animation.advance(100)
func _instant_close() -> void:
nav_link.enabled = false
animation.play("open")
animation.advance(0)
animation.stop()
func is_open() -> bool:
return nav_link.enabled
func open() -> void: func open() -> void:
nav_link.enabled = true nav_link.enabled = true
animation.play("open") animation.play("open")
@ -27,3 +52,14 @@ func close() -> void:
# TODO bespoke close anim? # TODO bespoke close anim?
animation.play_backwards("open") animation.play_backwards("open")
nav_link.enabled = false nav_link.enabled = false
func serialize() -> Dictionary:
return {"open": is_open()}
func deserialize(state: Dictionary) -> void:
if state["open"]:
_instant_open()
else:
_instant_close() # unneccessary?

View File

@ -281,7 +281,7 @@ _data = {
&"spray": SubResource("Animation_88qrs") &"spray": SubResource("Animation_88qrs")
} }
[node name="Bulkhead" instance=ExtResource("1_77udb")] [node name="Bulkhead" groups=["Persistent"] instance=ExtResource("1_77udb")]
script = ExtResource("2_hknvo") script = ExtResource("2_hknvo")
[node name="Frame" parent="." index="0"] [node name="Frame" parent="." index="0"]

View File

@ -40,3 +40,12 @@ func _activate() -> void:
func _on_interactive_selected() -> void: func _on_interactive_selected() -> void:
if enabled: if enabled:
interactive.enabled = _has_item() interactive.enabled = _has_item()
func serialize() -> Dictionary:
return {"enabled": enabled}
func deserialize(state: Dictionary) -> void:
if state["enabled"]:
enable()

View File

@ -9,7 +9,7 @@ size = Vector3(0.475, 0.65, 0.2)
[sub_resource type="BoxShape3D" id="BoxShape3D_5bfyo"] [sub_resource type="BoxShape3D" id="BoxShape3D_5bfyo"]
size = Vector3(0.475, 0.65, 0.2) size = Vector3(0.475, 0.65, 0.2)
[node name="RetinalScanner" type="Node3D"] [node name="RetinalScanner" type="Node3D" groups=["Persistent"]]
script = ExtResource("1_c71b5") script = ExtResource("1_c71b5")
[node name="MeshInstance3D" type="MeshInstance3D" parent="."] [node name="MeshInstance3D" type="MeshInstance3D" parent="."]

View File

@ -85,3 +85,14 @@ func _on_gunk_body_clear_total_updated(clear_total: float) -> void:
func _on_action_delay_timeout() -> void: func _on_action_delay_timeout() -> void:
activated.emit() activated.emit()
func serialize() -> Dictionary:
return {"enabled": enabled}
func deserialize(state: Dictionary) -> void:
if state["enabled"]:
enable()
else:
disable()

View File

@ -189,7 +189,7 @@ size = Vector3(0.475, 0.65, 0.2)
[sub_resource type="SphereShape3D" id="SphereShape3D_mxsyy"] [sub_resource type="SphereShape3D" id="SphereShape3D_mxsyy"]
radius = 3.0 radius = 3.0
[node name="WallSwitch" instance=ExtResource("2_whafo")] [node name="WallSwitch" groups=["Persistent"] instance=ExtResource("2_whafo")]
script = ExtResource("2_kfvqd") script = ExtResource("2_kfvqd")
enabled = true enabled = true
label = "INTERACTIVE_SWITCH_LABEL" label = "INTERACTIVE_SWITCH_LABEL"

View File

@ -61,8 +61,7 @@ func _ready() -> void:
# Overlay mesh with gunk material # Overlay mesh with gunk material
mesh_instance.material_overlay = mat_instance mesh_instance.material_overlay = mat_instance
if initial_mask: _deferred_init.call_deferred()
mask_texture.texture = initial_mask
# Initialize meshtool # Initialize meshtool
meshtool.create_from_surface(mesh_instance.mesh as ArrayMesh, 0) meshtool.create_from_surface(mesh_instance.mesh as ArrayMesh, 0)
@ -71,6 +70,12 @@ func _ready() -> void:
_thread.start(_async_compute_clear_total) _thread.start(_async_compute_clear_total)
func _deferred_init() -> void:
if initial_mask:
mask_texture.texture = initial_mask
mask_texture.visible = true
func _trigger_recompute_deferred() -> void: func _trigger_recompute_deferred() -> void:
_mutex.lock() _mutex.lock()
_mask_tx = mask_viewport.get_texture() _mask_tx = mask_viewport.get_texture()
@ -286,3 +291,13 @@ func _process(_delta: float) -> void:
func _on_mask_painted() -> void: func _on_mask_painted() -> void:
# XXX any problem with posting each frame? # XXX any problem with posting each frame?
_trigger_recompute_deferred.call_deferred() _trigger_recompute_deferred.call_deferred()
func serialize() -> Dictionary:
var state := {"mask": mask_viewport.get_texture().get_image()}
return state
func deserialize(state: Dictionary) -> void:
@warning_ignore("unsafe_cast")
initial_mask = ImageTexture.create_from_image(state["mask"] as Image)

View File

@ -5,7 +5,7 @@
[ext_resource type="Script" uid="uid://bom5qysgfvap1" path="res://src/world/gunk_body/draw_controller.gd" id="2_kkcjw"] [ext_resource type="Script" uid="uid://bom5qysgfvap1" path="res://src/world/gunk_body/draw_controller.gd" id="2_kkcjw"]
[ext_resource type="Script" uid="uid://ba7480ara8eo" path="res://levels/sandbox/debug_draw.gd" id="3_m8wx4"] [ext_resource type="Script" uid="uid://ba7480ara8eo" path="res://levels/sandbox/debug_draw.gd" id="3_m8wx4"]
[node name="GunkBody" type="StaticBody3D"] [node name="GunkBody" type="StaticBody3D" groups=["Persistent"]]
collision_layer = 5 collision_layer = 5
collision_mask = 0 collision_mask = 0
script = ExtResource("1_qqbpr") script = ExtResource("1_qqbpr")

View File

@ -19,7 +19,7 @@ stream_1/stream = ExtResource("5_omayi")
stream_2/stream = ExtResource("6_yg8lg") stream_2/stream = ExtResource("6_yg8lg")
stream_3/stream = ExtResource("7_4kci5") stream_3/stream = ExtResource("7_4kci5")
[node name="GrunkNodule" type="StaticBody3D"] [node name="GrunkNodule" type="StaticBody3D" groups=["Persistent"]]
collision_layer = 36 collision_layer = 36
collision_mask = 0 collision_mask = 0
script = ExtResource("1_iyr82") script = ExtResource("1_iyr82")

View File

@ -20,6 +20,10 @@ var _sustained_damage := 0.0
var _hit_this_frame := false var _hit_this_frame := false
func _enter_tree() -> void:
add_to_group("Persistent", true)
## Called each frame this node takes a hit. ## Called each frame this node takes a hit.
## ##
## Derived types should override `_hit()` as a lifecycle method. ## Derived types should override `_hit()` as a lifecycle method.
@ -68,3 +72,13 @@ func destroy() -> void:
func _destroy() -> void: func _destroy() -> void:
pass # Implemented in derived type pass # Implemented in derived type
func serialize() -> Dictionary:
# Nothing to serialize, but we need a placeholder value to show we haven't been destroyed.
return {}
func deserialize(_state: Dictionary) -> void:
# Nothing to deserialize, but we won't be freed!
pass

View File

@ -243,7 +243,7 @@ _data = {
&"pulse": SubResource("Animation_vokcn") &"pulse": SubResource("Animation_vokcn")
} }
[node name="GunkAlarm" type="StaticBody3D"] [node name="GunkAlarm" type="StaticBody3D" groups=["Persistent"]]
collision_layer = 36 collision_layer = 36
collision_mask = 0 collision_mask = 0
script = ExtResource("1_piaxx") script = ExtResource("1_piaxx")

View File

@ -99,7 +99,7 @@ _data = {
&"pulse": SubResource("Animation_eu6st") &"pulse": SubResource("Animation_eu6st")
} }
[node name="GunkHeart" type="StaticBody3D"] [node name="GunkHeart" type="StaticBody3D" groups=["Persistent"]]
collision_layer = 36 collision_layer = 36
collision_mask = 0 collision_mask = 0
script = ExtResource("1_ftym0") script = ExtResource("1_ftym0")

View File

@ -53,7 +53,7 @@ _data = {
&"trigger": SubResource("Animation_htscg") &"trigger": SubResource("Animation_htscg")
} }
[node name="Listener" type="StaticBody3D"] [node name="Listener" type="StaticBody3D" groups=["Persistent"]]
collision_layer = 36 collision_layer = 36
collision_mask = 0 collision_mask = 0
script = ExtResource("1_htscg") script = ExtResource("1_htscg")

View File

@ -111,7 +111,7 @@ _data = {
&"trigger": SubResource("Animation_rdv5j") &"trigger": SubResource("Animation_rdv5j")
} }
[node name="GunkRelay" type="StaticBody3D"] [node name="GunkRelay" type="StaticBody3D" groups=["Persistent"]]
collision_layer = 36 collision_layer = 36
collision_mask = 0 collision_mask = 0
script = ExtResource("1_rdv5j") script = ExtResource("1_rdv5j")

View File

@ -13,7 +13,7 @@ emission_energy_multiplier = 0.0
[sub_resource type="ConcavePolygonShape3D" id="ConcavePolygonShape3D_t1c4j"] [sub_resource type="ConcavePolygonShape3D" id="ConcavePolygonShape3D_t1c4j"]
data = PackedVector3Array(0.7236, -0.4472, 0.5257, 0, -1, 0, -0.2764, -0.4472, 0.8506, 0, -1, 0, 0.7236, -0.4472, 0.5257, 0.7236, -0.4472, -0.5257, -0.2764, -0.4472, 0.8506, 0, -1, 0, -0.8944, -0.4472, 0, -0.8944, -0.4472, 0, 0, -1, 0, -0.2764, -0.4472, -0.8506, -0.2764, -0.4472, -0.8506, 0, -1, 0, 0.7236, -0.4472, -0.5257, 0.7236, -0.4472, -0.5257, 0.7236, -0.4472, 0.5257, 0.8944, 0.4472, 0, 0.7236, -0.4472, 0.5257, -0.2764, -0.4472, 0.8506, 0.2764, 0.4472, 0.8506, -0.2764, -0.4472, 0.8506, -0.8944, -0.4472, 0, -0.7236, 0.4472, 0.5257, -0.8944, -0.4472, 0, -0.2764, -0.4472, -0.8506, -0.7236, 0.4472, -0.5257, -0.2764, -0.4472, -0.8506, 0.7236, -0.4472, -0.5257, 0.2764, 0.4472, -0.8506, 0.8944, 0.4472, 0, 0.7236, -0.4472, 0.5257, 0.2764, 0.4472, 0.8506, 0.2764, 0.4472, 0.8506, -0.2764, -0.4472, 0.8506, -0.7236, 0.4472, 0.5257, -0.7236, 0.4472, 0.5257, -0.8944, -0.4472, 0, -0.7236, 0.4472, -0.5257, -0.7236, 0.4472, -0.5257, -0.2764, -0.4472, -0.8506, 0.2764, 0.4472, -0.8506, 0.2764, 0.4472, -0.8506, 0.7236, -0.4472, -0.5257, 0.8944, 0.4472, 0, 0.8944, 0.4472, 0, 0.2764, 0.4472, 0.8506, 0, 1, 0, 0.2764, 0.4472, 0.8506, -0.7236, 0.4472, 0.5257, 0, 1, 0, -0.7236, 0.4472, 0.5257, -0.7236, 0.4472, -0.5257, 0, 1, 0, -0.7236, 0.4472, -0.5257, 0.2764, 0.4472, -0.8506, 0, 1, 0, 0.2764, 0.4472, -0.8506, 0.8944, 0.4472, 0, 0, 1, 0) data = PackedVector3Array(0.7236, -0.4472, 0.5257, 0, -1, 0, -0.2764, -0.4472, 0.8506, 0, -1, 0, 0.7236, -0.4472, 0.5257, 0.7236, -0.4472, -0.5257, -0.2764, -0.4472, 0.8506, 0, -1, 0, -0.8944, -0.4472, 0, -0.8944, -0.4472, 0, 0, -1, 0, -0.2764, -0.4472, -0.8506, -0.2764, -0.4472, -0.8506, 0, -1, 0, 0.7236, -0.4472, -0.5257, 0.7236, -0.4472, -0.5257, 0.7236, -0.4472, 0.5257, 0.8944, 0.4472, 0, 0.7236, -0.4472, 0.5257, -0.2764, -0.4472, 0.8506, 0.2764, 0.4472, 0.8506, -0.2764, -0.4472, 0.8506, -0.8944, -0.4472, 0, -0.7236, 0.4472, 0.5257, -0.8944, -0.4472, 0, -0.2764, -0.4472, -0.8506, -0.7236, 0.4472, -0.5257, -0.2764, -0.4472, -0.8506, 0.7236, -0.4472, -0.5257, 0.2764, 0.4472, -0.8506, 0.8944, 0.4472, 0, 0.7236, -0.4472, 0.5257, 0.2764, 0.4472, 0.8506, 0.2764, 0.4472, 0.8506, -0.2764, -0.4472, 0.8506, -0.7236, 0.4472, 0.5257, -0.7236, 0.4472, 0.5257, -0.8944, -0.4472, 0, -0.7236, 0.4472, -0.5257, -0.7236, 0.4472, -0.5257, -0.2764, -0.4472, -0.8506, 0.2764, 0.4472, -0.8506, 0.2764, 0.4472, -0.8506, 0.7236, -0.4472, -0.5257, 0.8944, 0.4472, 0, 0.8944, 0.4472, 0, 0.2764, 0.4472, 0.8506, 0, 1, 0, 0.2764, 0.4472, 0.8506, -0.7236, 0.4472, 0.5257, 0, 1, 0, -0.7236, 0.4472, 0.5257, -0.7236, 0.4472, -0.5257, 0, 1, 0, -0.7236, 0.4472, -0.5257, 0.2764, 0.4472, -0.8506, 0, 1, 0, 0.2764, 0.4472, -0.8506, 0.8944, 0.4472, 0, 0, 1, 0)
[node name="GunkTrigger" type="StaticBody3D"] [node name="GunkTrigger" type="StaticBody3D" groups=["Persistent"]]
collision_layer = 36 collision_layer = 36
collision_mask = 0 collision_mask = 0
script = ExtResource("1_t1c4j") script = ExtResource("1_t1c4j")

81
src/world/save_state.gd Normal file
View File

@ -0,0 +1,81 @@
class_name SaveState extends Resource
## Serializable container for gameplay state.
const CURRENT_VERSION := 0
const PERSISTENT_GROUP := "Persistent"
const SERIALIZE_METHOD := "serialize"
const DESERIALIZE_METHOD := "deserialize"
@export var save_version := CURRENT_VERSION
@export var level_path: String
@export var persistent_state: Dictionary[String, Dictionary] = {}
@export_group("WorldManager State")
@export var grunk_tank_limit: int
@export var mp3_player_unlocked: bool
@export var toothbrush_unlocked: bool
@export var stickers_unlocked: bool
@export var grunk_tank: float
@export var grunk_vault: float
@export var alert_level: int
static func node_key(node: Node, world: World) -> String:
return str(world.level_root.get_path_to(node))
func load_to_world(world: World) -> void:
if level_path != world.current_level.resource_path:
push_warning(
"This save is for ",
level_path,
" but the loaded level is for ",
world.current_level.resource_path
)
world.manager.grunk_tank_limit = grunk_tank_limit
world.manager.mp3_player_unlocked = mp3_player_unlocked
world.manager.toothbrush_unlocked = toothbrush_unlocked
world.manager.stickers_unlocked = stickers_unlocked
world.manager.grunk_tank = grunk_tank
world.manager.grunk_vault = grunk_vault
world.manager.alert_level = alert_level
var persistent := world.get_tree().get_nodes_in_group(PERSISTENT_GROUP)
for node: Node in persistent:
var key := SaveState.node_key(node, world)
if key in persistent_state:
# Node is in our persistent state, so load it with data.
Callable(node, DESERIALIZE_METHOD).call(persistent_state[key])
else:
# Node isn't in our persistent state, so it must have been destroyed.
node.queue_free()
static func serialize(world: World) -> SaveState:
var save := SaveState.new()
save.level_path = world.current_level.resource_path
save.grunk_tank_limit = world.manager.grunk_tank_limit
save.mp3_player_unlocked = world.manager.mp3_player_unlocked
save.toothbrush_unlocked = world.manager.toothbrush_unlocked
save.stickers_unlocked = world.manager.stickers_unlocked
save.grunk_tank = world.manager.grunk_tank
save.grunk_vault = world.manager.grunk_vault
save.alert_level = world.manager.alert_level
# NOTE: I'm assuming that `persistent` will have the same order ever time the world is loaded.
# This may not be the case. If so, we need to find a different way to uniquely identify nodes.
var persistent := world.get_tree().get_nodes_in_group(PERSISTENT_GROUP)
for node: Node in persistent:
var key := SaveState.node_key(node, world)
var data: Dictionary = Callable(node, SERIALIZE_METHOD).call()
save.persistent_state[key] = data
return save

View File

@ -0,0 +1 @@
uid://dsyyk7muaabf5

View File

@ -51,8 +51,11 @@ func on_alert_raised(new_level: int) -> void:
# Set up meet-spook. # Set up meet-spook.
# Get closest MeetSpook point to player. # Get closest MeetSpook point to player.
if Player.instance: if Player.instance:
var closest := SceneTools.closest_in_group(Player.instance, MeetSpook.GROUP) var closest := (
(closest as MeetSpook).prepare() SceneTools.closest_in_group(Player.instance, MeetSpook.GROUP) as MeetSpook
)
if closest:
closest.prepare()
2: 2:
# LEVEL 2: AGGRESSIVE # LEVEL 2: AGGRESSIVE
# Beast pursues player on sight. # Beast pursues player on sight.

View File

@ -2,7 +2,7 @@ class_name World extends Node
## Access and flow control for the game world. ## Access and flow control for the game world.
@export var pause_enabled := true @export var pause_enabled := true
@export var save_path := "user://saved_game.tscn" @export var save_path := "user://save_state.res"
@export var manager: WorldManager @export var manager: WorldManager
@export var spook_manager: SpookManager @export var spook_manager: SpookManager
@ -46,11 +46,19 @@ func unpause() -> void:
get_tree().paused = false get_tree().paused = false
func load_level(level: PackedScene) -> void: func load_level(level: PackedScene, save: SaveState = null) -> void:
for c: Node in level_root.get_children(): for c: Node in level_root.get_children():
c.queue_free() c.queue_free()
level_root.remove_child(c)
current_level = level current_level = level
level_root.add_child(level.instantiate()) print("Instantiating level from ", level.resource_path)
var level_instance := level.instantiate()
#if save:
#level_instance.ready.connect(func() -> void: save.load_to_world(self), CONNECT_ONE_SHOT)
level_root.add_child(level_instance)
if save:
save.load_to_world(self)
print("Done!")
func on_player_death() -> void: func on_player_death() -> void:
@ -59,23 +67,21 @@ func on_player_death() -> void:
ui_root.add_child(kill_screen) ui_root.add_child(kill_screen)
func _reload_saved(save: SaveState) -> void:
load_level(current_level, save)
func on_game_over() -> void: func on_game_over() -> void:
# TODO: reload from last checkpoint # reload the level from the last save
# in the mean time, just reload the level if ResourceLoader.exists(save_path):
Game.instance.queue_scene(save_path) Game.instance.queue_load(save_path).finally(_reload_saved)
else:
load_level(current_level)
func _reown_tree(node: Node) -> void:
for c: Node in node.get_children():
c.set_owner(self)
_reown_tree(c)
func save_progress() -> void: func save_progress() -> void:
# No way this works, right? print("Preparing save state...")
print("Preparing world for save...") var save := SaveState.serialize(self)
var save := PackedScene.new()
_reown_tree(self)
save.pack(self)
print("Writing save to ", save_path) print("Writing save to ", save_path)
ResourceSaver.save(save, save_path) ResourceSaver.save(save, save_path)
print("Done!")

View File

@ -3,15 +3,15 @@
[ext_resource type="Script" uid="uid://cgqmhtemibxc5" path="res://src/world/world.gd" id="1_1k4gi"] [ext_resource type="Script" uid="uid://cgqmhtemibxc5" path="res://src/world/world.gd" id="1_1k4gi"]
[ext_resource type="Resource" uid="uid://tgac5tnfx56r" path="res://src/world/world_manager.tres" id="2_5kmgb"] [ext_resource type="Resource" uid="uid://tgac5tnfx56r" path="res://src/world/world_manager.tres" id="2_5kmgb"]
[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://byvjsvavbg5xe" path="res://src/ui/menus/pause_menu/pause_menu.tscn" id="2_6fy3g"]
[ext_resource type="PackedScene" uid="uid://bov4ok76woyc" path="res://levels/ghost_ship/ghost_ship.tscn" id="2_jte2u"]
[ext_resource type="Resource" uid="uid://0i72bf8ip1lx" path="res://src/world/spook_manager.tres" id="3_l0av5"] [ext_resource type="Resource" uid="uid://0i72bf8ip1lx" path="res://src/world/spook_manager.tres" id="3_l0av5"]
[ext_resource type="PackedScene" uid="uid://b8rv6dg4tgaeb" path="res://src/world/gunk_node/mechanic_test.tscn" id="4_l0av5"]
[ext_resource type="PackedScene" uid="uid://c0uitm5cg88h1" path="res://src/ui/menus/kill_screen/kill_screen.tscn" id="6_l0av5"] [ext_resource type="PackedScene" uid="uid://c0uitm5cg88h1" path="res://src/ui/menus/kill_screen/kill_screen.tscn" id="6_l0av5"]
[node name="World" type="Node"] [node name="World" type="Node"]
script = ExtResource("1_1k4gi") script = ExtResource("1_1k4gi")
manager = ExtResource("2_5kmgb") manager = ExtResource("2_5kmgb")
spook_manager = ExtResource("3_l0av5") spook_manager = ExtResource("3_l0av5")
initial_level = ExtResource("2_jte2u") initial_level = ExtResource("4_l0av5")
pause_scene = ExtResource("2_6fy3g") pause_scene = ExtResource("2_6fy3g")
kill_screen_scene = ExtResource("6_l0av5") kill_screen_scene = ExtResource("6_l0av5")

View File

@ -32,3 +32,13 @@ func _on_interactive_activated() -> void:
Player.instance.add_item(item) Player.instance.add_item(item)
# TODO: animation, sfx on collect? # TODO: animation, sfx on collect?
queue_free() queue_free()
func serialize() -> Dictionary:
# Nothing to serialize, but we need a placeholder value to show we haven't been destroyed.
return {}
func deserialize(_state: Dictionary) -> void:
# Nothing to deserialize, but we won't be freed!
pass

View File

@ -6,7 +6,7 @@
[sub_resource type="SphereShape3D" id="SphereShape3D_0mein"] [sub_resource type="SphereShape3D" id="SphereShape3D_0mein"]
radius = 0.25 radius = 0.25
[node name="WorldItem" type="MeshInstance3D"] [node name="WorldItem" type="MeshInstance3D" groups=["Persistent"]]
script = ExtResource("1_sptcj") script = ExtResource("1_sptcj")
[node name="Interactive" type="StaticBody3D" parent="."] [node name="Interactive" type="StaticBody3D" parent="."]