Files
GGJ26/addons/maaacks_game_template/extras/scripts/level_manager.gd
minimata 44f251ed66
All checks were successful
Create tag and build when new code gets to main / BumpTag (push) Successful in 6s
Create tag and build when new code gets to main / Export (push) Successful in 1m1s
Basic game template addon
2026-01-30 19:45:56 +01:00

184 lines
6.5 KiB
GDScript

class_name LevelManager
extends Node
## Manage level changes in games.
##
## A helper script to assign to a node in a scene.
## It works with a level loader and can open menus when players win or lose.
## It can either be assigned a starting level path or a scene lister.
## It can detect signals from levels to change levels in an open-world.
## With a scene lister, it will instead traverse through levels linearly.
## Required reference to a level loader in the scene.
@export var level_loader : LevelLoader
## Optional path to a starting level scene.
## Required if there is no scene lister.
@export_file var starting_level_path : String
## Optional reference to a scene lister in the scene.
## Required if there is no starting level path.
@export var scene_lister : SceneLister
## Whether to load the starting level when ready.
@export var auto_load : bool = true
@export_group("Scenes")
## Path to a main menu scene.
@export_file("*.tscn") var main_menu_scene_path : String
## Optional path to an ending scene.
@export_file("*.tscn") var ending_scene_path : String
## Optional screen to be shown after the game is won.
@export var game_won_scene : PackedScene
## Optional screen to be shown after the level is lost.
@export var level_lost_scene : PackedScene
## Optional screen to be shown after the level is won.
@export var level_won_scene : PackedScene
## If Maaack's Scene Loader is installed, then it will be used to change scenes.
@onready var scene_loader_node = get_tree().root.get_node_or_null(^"SceneLoader")
## Reference to the current level node.
var current_level : Node
var current_level_path : String : set = set_current_level_path
var checkpoint_level_path : String : set = set_checkpoint_level_path
func set_current_level_path(value : String) -> void:
current_level_path = value
func set_checkpoint_level_path(value : String) -> void:
checkpoint_level_path = value
func _try_connecting_signal_to_node(node : Node, signal_name : String, callable : Callable) -> void:
if node.has_signal(signal_name) and not node.is_connected(signal_name, callable):
node.connect(signal_name, callable)
func _try_connecting_signal_to_level(signal_name : String, callable : Callable) -> void:
_try_connecting_signal_to_node(current_level, signal_name, callable)
func get_main_menu_scene_path() -> String:
return main_menu_scene_path
func _load_main_menu() -> void:
if scene_loader_node:
scene_loader_node.load_scene(get_main_menu_scene_path())
else:
get_tree().change_scene_to_file(get_main_menu_scene_path())
func _find_in_scene_lister(level_path : String) -> int:
if not scene_lister: return -1
level_path = ResourceUID.ensure_path(level_path)
return scene_lister.files.find(level_path)
func is_on_last_level() -> bool:
var current_level_id = _find_in_scene_lister(current_level_path)
return current_level_id > -1 and current_level_id == scene_lister.files.size() - 1
func get_relative_level_path(offset : int = 1) -> String:
var current_level_id := _find_in_scene_lister(current_level_path)
if current_level_id > -1:
if current_level_id >= max(0, -(offset)) and current_level_id < scene_lister.files.size() - max(0, offset):
current_level_id += offset
return scene_lister.files[current_level_id]
return ""
func get_next_level_path() -> String:
return get_relative_level_path(1)
func get_prev_level_path() -> String:
return get_relative_level_path(-1)
func get_ending_scene_path() -> String:
return ending_scene_path
func _load_ending() -> void:
if not get_ending_scene_path().is_empty():
if scene_loader_node:
scene_loader_node.load_scene(get_ending_scene_path())
else:
get_tree().change_scene_to_file(get_ending_scene_path())
else:
_load_main_menu()
func _on_level_lost() -> void:
if level_lost_scene:
var instance = level_lost_scene.instantiate()
get_tree().current_scene.add_child(instance)
_try_connecting_signal_to_node(instance, &"restart_pressed", _reload_level)
_try_connecting_signal_to_node(instance, &"main_menu_pressed", _load_main_menu)
else:
_reload_level()
func get_checkpoint_level_path() -> String:
if checkpoint_level_path.is_empty():
if scene_lister:
return scene_lister.files.front()
if not starting_level_path.is_empty():
return starting_level_path
return checkpoint_level_path
func load_level(level_path : String) -> void:
current_level_path = level_path
level_loader.load_level(level_path)
func _load_checkpoint_level() -> void:
load_level(get_checkpoint_level_path())
func _reload_level() -> void:
load_level(current_level_path)
func _load_win_screen_or_ending() -> void:
if game_won_scene:
var instance = game_won_scene.instantiate()
get_tree().current_scene.add_child(instance)
_try_connecting_signal_to_node(instance, &"continue_pressed", _load_ending)
_try_connecting_signal_to_node(instance, &"restart_pressed", _reload_level)
_try_connecting_signal_to_node(instance, &"main_menu_pressed", _load_main_menu)
else:
_load_ending()
func _load_level_won_screen_or_checkpoint() -> void:
if level_won_scene:
var instance = level_won_scene.instantiate()
get_tree().current_scene.add_child(instance)
_try_connecting_signal_to_node(instance, &"continue_pressed", _load_checkpoint_level)
_try_connecting_signal_to_node(instance, &"restart_pressed", _reload_level)
_try_connecting_signal_to_node(instance, &"main_menu_pressed", _load_main_menu)
else:
_load_checkpoint_level()
func _on_level_won(next_level_path : String = ""):
if next_level_path.is_empty():
next_level_path = get_next_level_path()
if next_level_path.is_empty():
_load_win_screen_or_ending()
else:
checkpoint_level_path = next_level_path
_load_level_won_screen_or_checkpoint()
func _on_level_changed(next_level_path : String):
checkpoint_level_path = next_level_path
_load_checkpoint_level()
func _connect_level_signals() -> void:
_try_connecting_signal_to_level(&"level_lost", _on_level_lost)
_try_connecting_signal_to_level(&"level_won", _on_level_won)
_try_connecting_signal_to_level(&"level_changed", _on_level_changed)
func _on_level_loader_level_loaded() -> void:
current_level = level_loader.current_level
await current_level.ready
_connect_level_signals()
func _on_level_loader_level_load_started() -> void:
pass
func _on_level_loader_level_ready() -> void:
pass
func _auto_load() -> void:
if auto_load:
_load_checkpoint_level()
func _ready() -> void:
if Engine.is_editor_hint(): return
level_loader.level_loaded.connect(_on_level_loader_level_loaded)
level_loader.level_ready.connect(_on_level_loader_level_ready)
level_loader.level_load_started.connect(_on_level_loader_level_load_started)
_auto_load()