Thank you all so much! I can finally sleep easy that the system is working (need to work on my level naming so it matches the level ids but nothing too complicated).
I’m still going to try to use this to save on the checkpoints in the levels as well, but that’s a challenge for a another day.
For reference so anyone want to know how it turned out here it is:
My Scene_manager global (still needs to be reworked with the level names):
extends Node
var scene_transition_screen = preload("res://UI/screen_transition/scene_transition_screen.tscn")
var scenes : Dictionary = {"Level 1": "res://Levels/level_1.tscn",
"Level 2": "res://Levels/level_2.tscn",
"Level 3": "res://Levels/level_3.tscn",
"Level 4": "res://Levels/level_4.tscn",
"Level 5": "res://Levels/level_5.tscn",
"Level 6": "res://Levels/level_6.tscn",
"Level 7": "res://Levels/level_7.tscn",
"Level 8": "res://Levels/level_8.tscn",
"Final Level": "res://Levels/Final_level.tscn",
"Lore Level": "res://Levels/lorelevel.tscn",
"office Level 1":"res://Levels/office_level_1tscn.tscn",
"Credits":"res://UI/credits.tscn",
"Final Cut Scene": "",
"Intro Cut Scene": "res://UI/CutScenes/beginning_cut_scene.tscn"
}
var level : String
signal on_trigger_player_spawn
signal transition_finished
var spawn_door_tag
func transiton_to_scene(level : String, destination_tag):
var scene_path : String = scenes.get(level)
if scene_path != null:
var scene_transition_screen_instance = scene_transition_screen.instantiate()
spawn_door_tag = destination_tag
get_tree().get_root().add_child(scene_transition_screen_instance)
await get_tree().create_timer(2.5).timeout
get_tree().change_scene_to_file(scene_path)
scene_transition_screen_instance.queue_free()
transition_finished.emit()
func trigger_player_spawn(position: Vector2, direction: String):
on_trigger_player_spawn.emit(position,direction)
# Called when the node enters the scene tree for the first time.
func _ready():
pass
My game_manager:
extends Node
var main_menu = preload("res://UI/MainMenu.tscn")
var pause_menu = preload("res://UI/PauseMenu.tscn")
const LEVEL_SAVES_FOLDER : String = "user://level_saves/"
var reset_xeno : int = 0
# Called when the node enters the scene tree for the first time.
func _ready():
RenderingServer.set_default_clear_color(Color(0,0,0,1))
func _toggle_fullscreen(toggle):
if DisplayServer.window_get_mode() == DisplayServer.WINDOW_MODE_WINDOWED:
DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_FULLSCREEN)
else:
DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED)
func start_game():
#if get_tree().paused:
if DirAccess.dir_exists_absolute(LEVEL_SAVES_FOLDER):
continue_game()
return
if not DirAccess.dir_exists_absolute(LEVEL_SAVES_FOLDER):
SceneManager.transiton_to_scene("Intro Cut Scene", "0")
func exit_game():
get_tree().quit()
func pause_game():
get_tree().paused = true
var pause_menu_instance = pause_menu.instantiate()
get_tree().get_root().add_child(pause_menu_instance)
func continue_game():
var save := ResourceLoader.load(get_level_save_file_path()) as LevelSaveData
print_debug(save)
if save == null:
return
var meta : Dictionary = save.data.get("meta", {})
print_debug(meta)
#var level_name = str("Level " + meta.get("level_id", ""))
#var door_id = meta.get("door_id", "")
print_debug("Level " + str((meta.get("level_id"))))
print_debug(meta.get("door_id"))
SceneManager.transiton_to_scene("Level " + str((meta.get("level_id"))),str(meta.get("door_id")))
#await SceneManager.transition_finished
print_debug(save.data.get("node_data", {}))
var scene := get_tree().current_scene
var node_data: Dictionary = save.data.get("node_data", {})
for path in node_data:
var saver := scene.get_node_or_null(path)
if saver is SaverNode:
saver.apply_save_dict(node_data[path])
get_tree().paused = false
func new_save():
if DirAccess.dir_exists_absolute(LEVEL_SAVES_FOLDER):
DirAccess.open("user://level_saves/")
DirAccess.remove_absolute("user://level_save")
CollectibleManager.total_award_amount = reset_xeno
CollectibleManager.xeno_collected.clear()
InventoryManager.inventory.clear()
get_tree().paused = false
SceneManager.transiton_to_scene("Intro Cut Scene", "0")
return
if not DirAccess.dir_exists_absolute(LEVEL_SAVES_FOLDER):
SceneManager.transiton_to_scene("Intro Cut Scene", "0")
func backtomain_menu():
var main_menu_instance = main_menu.instantiate()
get_tree().get_root().add_child(main_menu_instance)
#save system
func get_level_save_file_path()-> String:
return LEVEL_SAVES_FOLDER + str("level_save") + ".tres"
func save_level_data()-> void:
var new_save = LevelSaveData.new()
new_save.data = {
"meta": {
"scene_path": get_tree().current_scene.scene_file_path,
"level_id": get_tree().current_scene.level_id,
"door_id": get_tree().current_scene.level_id - 1,
},
"node_data": {},
}
for saver: SaverNode in get_tree().get_nodes_in_group(SaverNode.SAVER_NODE_GROUP):
var path = get_path_to(saver)
new_save.data["node_data"][path] = saver.get_save_dict()
ResourceSaver.save(new_save, get_level_save_file_path())
print("Saved Level data to",get_level_save_file_path())
func load_level_data()-> void:
var save := ResourceLoader.load(get_level_save_file_path()) as LevelSaveData
print_debug(save)
if save == null:
print_debug("no save")
return
var meta : Dictionary = save.data.get("meta", {})
print_debug(meta)
#var level_name = str("Level " + meta.get("level_id", ""))
#var door_id = meta.get("door_id", "")
#await SceneManager.transition_finished
print_debug(save.data.get("node_data", {}))
var scene := get_tree().current_scene
var node_data: Dictionary = save.data.get("node_data", {})
for path in node_data:
var saver := scene.get_node_or_null(path)
if saver is SaverNode:
saver.apply_save_dict(node_data[path])
My base level scene:
class_name Level extends Node2D
enum LevelIDs{
NONE = 0,
LEVEL_1 = 1,
LEVEL_2 = 2,
LEVEL_3 = 3,
OFFICE_LEVEL_1 = 4,
LEVEL_4 = 5,
LEVEL_5 = 6,
LEVEL_6 = 7,
LORE_LEVEL = 8,
LEVEL_7 = 9,
LEVEL_8 = 10,
FINAL_LEVEL = 11,
}
@export var level_id : LevelIDs
func _ready() -> void:
GlobalAudioManager.base_level_music()
GameManager.load_level_data()
GameManager.save_level_data()
if SceneManager.spawn_door_tag != null:
_on_level_spawn(SceneManager.spawn_door_tag)
func _on_level_spawn(destination_tag: String):
var door_path = "Door_" + destination_tag
var door = get_node(door_path) as Door
SceneManager.trigger_player_spawn(door.spawn.global_position, door.spawn_direction)
My player:
class_name player
extends CharacterBody2D
signal respawned()
var bullet = preload("res://Player/bullet.tscn")
var player_death_effect = preload("res://Player/player_death_effect/player_death_effect.tscn")
@onready var muzzle : Marker2D = $Muzzle
#@onready var hit_animation_player = $HitAnimationPlayer
@onready var animated_sprite_2d = $AnimatedSprite2D
@onready var jump_sound = $Sounds/Jump
@onready var run_sound = $Sounds/Run
@onready var death_sound = $Sounds/Death
@onready var damage_sound = $Sounds/Damage
@onready var respawn_sound = $Sounds/Respawn
@onready var actionable_finder = $ActionableFinder
const GRAVITY = 1000
const SPEED = 1000
@export var speed : int = SPEED
@export var max_horizontal_speed: int = 300
@export var slow_down_speed : int = 1500
@export var jump : int = -300
@export var jump_horizontal_speed : int = 1000
@export var max_jump_horizontal_speed : int = 300
@export var jump_height : float = -250
#change value below for multiple jumps
@export var jump_count : int = 1
@export var max_jump_count : int = 2
@export var character_body_2d : CharacterBody2D
@export var knockbackPower : int = 500
@export var enemy_position : Vector2
@export var dialogue_resource : DialogueResource
@export var dialogu_start : String = "Health_states"
#for save
@export var health_for_save : int = HealthManager.current_health
@export var xeno_for_save : Dictionary = CollectibleManager.xeno_collected
@export var inventory_for_save : Dictionary = InventoryManager.inventory
enum State {Idle, Run, Jump, Shoot}
var current_state : State
var muzzle_position
var current_jump_count : int
var character_sprite : Sprite2D
var coyote_jump : bool
var is_dead : bool
var checkpoint_manager
func _ready():
#current_state = State.Idle
muzzle_position = muzzle.position
checkpoint_manager = get_parent().get_node("CheckpointManager")
SceneManager.on_trigger_player_spawn.connect(_on_spawn)
func _on_spawn(position: Vector2, direction: String):
global_position = position
current_state = State.Run
HealthManager.change_health(health_for_save)
InventoryManager.inventory = inventory_for_save.duplicate(true)
CollectibleManager.xeno_collected = xeno_for_save.duplicate(true)
CollectibleManager.on_collectible_award_received.emit(xeno_for_save.size())
CollectibleManager.total_award_amount = xeno_for_save.size()
func _physics_process(delta: float):
player_falling(delta)
player_idle(delta)
player_run(delta)
player_jump(delta)
player_muzzle_position()
player_shoting(delta)
move_and_slide()
player_animations()
#print("State:", State.keys()[current_state])
func player_falling(delta: float):
if !is_on_floor():
velocity.y += GRAVITY * delta
func player_idle(delta: float):
var direction = Input_movement()
if is_on_floor() and direction == 0:
current_state = State.Idle
func player_run(delta: float):
var direction = Input_movement()
if direction:
velocity.x += direction * speed * delta
velocity.x = clamp(velocity.x, -max_horizontal_speed, max_horizontal_speed)
else:
velocity.x = move_toward(velocity.x, 0, slow_down_speed * delta)
if direction != 0 and HealthManager.current_health != 0:
current_state = State.Run
run_sound.play()
animated_sprite_2d.flip_h = false if direction > 0 else true
if direction == 0:
run_sound.stop()
func player_jump(delta: float):
var jump_input : bool = Input.is_action_just_pressed("jump")
if is_on_floor() and jump_input:
current_jump_count = 0
velocity.y = jump_height
coyote_jump = false
current_jump_count += 1
current_state = State.Jump
jump_sound.play()
if coyote_jump:
character_body_2d.velocity.y = jump_height
coyote_jump = false
current_jump_count += 1
# multiple jumps
if !is_on_floor() and jump_input and current_jump_count != max_jump_count:
character_body_2d.velocity.y = jump_height
current_jump_count += 1
current_state = State.Jump
jump_sound.play()
if !is_on_floor() and current_state == State.Jump:
var direction = Input_movement()
velocity.x += direction * jump_horizontal_speed * delta
velocity.x = clamp(velocity.x, -max_jump_horizontal_speed, max_jump_horizontal_speed)
func player_shoting(delta:float):
var direction = Input_movement()
if direction != 0 and Input.is_action_just_pressed("shoot"):
var bullet_instance = bullet.instantiate() as Node2D
bullet_instance.direction = direction
bullet_instance.global_position = muzzle.global_position
get_parent().add_child(bullet_instance)
current_state = State.Shoot
func player_muzzle_position():
var direction = Input_movement()
if direction > 0:
muzzle.position.x = muzzle_position.x
elif direction < 0 :
muzzle_position.x = -muzzle_position.x
func player_animations():
if current_state == State.Idle:
animated_sprite_2d.play("idle")
elif current_state == State.Run and animated_sprite_2d.animation != "Run_shoot":
animated_sprite_2d.play("Run")
elif current_state == State.Jump:
animated_sprite_2d.play("Jump")
elif current_state == State.Shoot:
animated_sprite_2d.play("Run_shoot")
func player_death():
var player_death_effect_instance = player_death_effect.instantiate() as Node2D
if is_dead == true:
print_debug("calling player death func")
player_death_effect_instance.global_position = global_position
get_parent().add_child(player_death_effect_instance)
death_sound.play()
DialogueManager.dialogue_ended
visible = false
set_process_input(false)
set_process_unhandled_input(false)
set_process(false)
set_physics_process(false)
await get_tree().create_timer(2).timeout
get_tree().paused = true
print_debug("BEFORE called respawn")
respawned.emit()
is_dead = false
HealthManager.increase_health(3)
velocity.x = 0
global_position = checkpoint_manager.last_location
respawn_sound.play()
get_tree().paused = false
#get_tree().reload_current_scene()
visible = true
set_process_input(true)
set_process_unhandled_input(true)
set_process(true)
set_physics_process(true)
if DialogueManager.dialogue_ended:
DialogueManager.show_dialogue_balloon(dialogue_resource, dialogu_start)
if DialogueManager.dialogue_started:
pass
if is_dead == false:
pass
func Input_movement():
var direction : float = Input.get_axis("move_left", "move_right")
return direction
func _on_hurtbox_body_entered(body : Node2D):
if body.is_in_group("Enemy"):
if is_dead == false:
enemy_position = body.global_position
var tween = get_tree().create_tween()
tween.tween_property(animated_sprite_2d, "material:shader_parameter/enabled", true, 0)
tween.tween_property(animated_sprite_2d, "material:shader_parameter/enabled", false, 0.2)
damage_sound.play()
HealthManager.decrease_health(body.damage_amount)
knockback(1,1500,300)
if is_dead == true:
pass
if body.is_in_group("ouchy"):
if is_dead == false:
var tween = get_tree().create_tween()
tween.tween_property(animated_sprite_2d, "material:shader_parameter/enabled", true, 0)
tween.tween_property(animated_sprite_2d, "material:shader_parameter/enabled", false, 0.2)
damage_sound.play()
HealthManager.decrease_health(1)
knockback(0.5,500,400)
if is_dead == true:
pass
if HealthManager.current_health == 0:
if is_dead == false:
is_dead = true
player_death()
if is_dead == true:
pass
func knockback(force: float, y_pos: float, up_force: float):
if (character_body_2d.position.y - enemy_position.y > 0):
character_body_2d.velocity = Vector2(-force * 2 * y_pos, -force * up_force)
print(enemy_position.x)
print(character_body_2d.global_position.x)
else:
character_body_2d.velocity = Vector2(force * 2 * y_pos, -force * up_force)