Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions scenes/dev/level_design/void_persistence/quest.tres
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[gd_resource type="Resource" script_class="Quest" format=3 uid="uid://28abe4vgj3ul"]

[ext_resource type="Script" uid="uid://dts1hwdy3phin" path="res://scenes/menus/storybook/components/quest.gd" id="1_ikuea"]

[resource]
script = ExtResource("1_ikuea")
title = "Void Persistence"
description = "Learn how to set up your checkpoints for persistent enemies!"
first_scene = "uid://dwgvjv2wxnr0k"
threads_to_collect = 0
metadata/_custom_type_script = "uid://dts1hwdy3phin"
312 changes: 312 additions & 0 deletions scenes/dev/level_design/void_persistence/void_persistence_test.tscn

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ const _DURATION: float = 0.25
## Nodes whose direct children (but not grandchildren) can be consumed.
@export var consumable_node_holders: Array[Node]

## Wether to save the tilemap cover in this scene state.
@export var save_state: bool

var _unconsumed_nodes: Dictionary[Vector2i, Array] = { }
var _consumed_nodes: Dictionary[Vector2i, Array] = { }

Expand Down Expand Up @@ -101,6 +104,9 @@ func _ready() -> void:
return

for parent: Node in consumable_node_holders:
# Filter unsassigned elements:
if parent == null:
continue
Comment on lines +108 to +109

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can see that you added this to avoid the problem of unassigned elements in the inspector, which the void_persistence_test.tscn has:

image

I think it's fine, but I will add a comment about it in the code.

for child: Node in parent.get_children():
if child is Node2D:
var coord := coord_for(child)
Expand All @@ -110,6 +116,9 @@ func _ready() -> void:

for coord: Vector2i in get_used_cells():
consume(coord, true)
if save_state:
GameState.scene.checkpoint_activated.connect(_on_checkpoint_activated)
_load_state()


## Cover all [param cells] with [member terrain_name], hiding any nodes in those cells which are
Expand Down Expand Up @@ -179,3 +188,18 @@ func uncover_all(duration: float, animate_modulate: bool = false) -> void:
clear()
if animate_modulate:
self.modulate.a = 1.0


## Responds to the checkpoint's activated signal and saves consumed tiles.
func _on_checkpoint_activated() -> void:
var save_key := str(get_path())
GameState.scene.facts[save_key] = get_used_cells()


## Reconstructs the holes in the TileMap immediately on scene load.
## Executes without animation to prevent visual glitches upon respawn.
func _load_state() -> void:
var save_key := str(get_path())
if GameState.scene.facts.has(save_key):
var saved_tiles: Array = GameState.scene.facts[save_key]
consume_cells(saved_tiles, true)
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ const IDLE_EMIT_DISTANCE := sqrt(2 * (64.0 ** 2))
## area)
@export var void_particles: PackedScene

## Wether to save the enemy position and direction in this scene state.
@export var save_state: bool

var node_to_follow: Node2D:
set = _set_node_to_follow

Expand Down Expand Up @@ -134,6 +137,11 @@ func _ready() -> void:
if Engine.is_editor_hint():
set_process(false)
set_physics_process(false)
return

if save_state:
GameState.scene.checkpoint_activated.connect(_on_checkpoint_activated)
_load_state()


func start(detected_node: Node2D) -> void:
Expand Down Expand Up @@ -202,3 +210,31 @@ func _on_player_capture_area_body_entered(body: Node2D) -> void:

var player := body as Player
player.defeat(true)


## Responds to the checkpoint's activated signal and records position and path progress.
func _on_checkpoint_activated() -> void:
var save_key := str(get_path())
var data := { "global_position": global_position, "last_position": _last_position }

if path_walk_behavior:
data["path_direction"] = path_walk_behavior.direction

GameState.scene.facts[save_key] = data


## Reconstructs the enemy's exact position and path progress on scene load.
## Prevents massive particle bursts caused by teleportation.
func _load_state() -> void:
if GameState.scene == null or not "facts" in GameState.scene:
return

var save_key := str(get_path())
if GameState.scene.facts.has(save_key):
var data: Dictionary = GameState.scene.facts[save_key]

global_position = data["global_position"]
_last_position = data["last_position"]

if path_walk_behavior and data.has("path_direction"):
path_walk_behavior.direction = data["path_direction"]
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ const REQUIRED_ANIMATIONS := [&"idle", &"appear"]
## be able to interact with the checkpoint.
@export var dialogue: DialogueResource = preload("uid://bug2aqd47jgyu")

## Determines if the enemies and the void layer should save their state when
## the player activates this checkpoint.
@export var save_void_and_enemies: bool = false

## The point where the player will spawn.
@onready var spawn_point: SpawnPoint = %SpawnPoint

Expand Down Expand Up @@ -75,6 +79,7 @@ func _ready() -> void:

## Makes this the active checkpoint.
func activate() -> void:
GameState.scene.activate_checkpoint()
GameState.scene.spawn_point = owner.get_path_to(spawn_point)
GameState.save()

Expand Down
10 changes: 10 additions & 0 deletions scenes/globals/game_state/per_scene_state.gd
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ extends Resource
## lights should turn on gradually.
signal lights_changed(lights_on: bool, immediate: bool)

## Emitted when the player activates a checkpoint.
signal checkpoint_activated

## The path to the current scene.
@export var path: String

Expand All @@ -24,6 +27,9 @@ signal lights_changed(lights_on: bool, immediate: bool)
## Set when any introductory dialogue has been played for the current scene.
@export var intro_dialogue_shown: bool

## Dictionary to store arbitrary persistent scene facts, such as enemy states and consumed tiles.
@export var facts: Dictionary = { }

## Current state of artificial lights. Set with [member set_lights_on]. This is
## not saved to disk since the lights are controlled programmatically.
var lights_on: bool
Expand All @@ -45,6 +51,10 @@ func set_lights_on(new_value: bool, immediate: bool = false) -> void:
lights_changed.emit(new_value, immediate)


func activate_checkpoint() -> void:
checkpoint_activated.emit()


func set_spawn_point(new_value: NodePath) -> void:
spawn_point = new_value
emit_changed()
Loading