Add a savegame system using slots and a main menu, allow player to move RigidBody2Ds using apply_impulse(), increased physics tick, enabled FXAA, and more.
This commit is contained in:
parent
35ebe26340
commit
5a67d46d6f
13 changed files with 291 additions and 25 deletions
87
core/globals/gamestate.gd
Normal file
87
core/globals/gamestate.gd
Normal file
|
@ -0,0 +1,87 @@
|
|||
extends Node
|
||||
|
||||
# gamestate
|
||||
|
||||
var last_entrypoint: String
|
||||
|
||||
func reset_gamestate():
|
||||
# defaults
|
||||
last_entrypoint = "intro_start"
|
||||
|
||||
# save/load slot
|
||||
|
||||
const slot_min: int = 0
|
||||
const slot_max: int = 3
|
||||
|
||||
var current_slot: int = -1
|
||||
var current_slot_locked: bool = false
|
||||
|
||||
func _check_slot_idx(idx: int) -> bool:
|
||||
# returns if slot index is valid (in bounds)
|
||||
if idx < slot_min or idx > slot_max:
|
||||
push_error("Slot index %s is invalid." % current_slot)
|
||||
return false
|
||||
return true
|
||||
|
||||
func save_slot():
|
||||
# flush game state to slot
|
||||
if _check_slot_idx(current_slot):
|
||||
var slot_path: String = "user://slot%s.save" % current_slot
|
||||
var f = FileAccess.open(slot_path, FileAccess.WRITE)
|
||||
if f == null:
|
||||
push_error("Couldn't open %s" % slot_path)
|
||||
quit(1)
|
||||
return
|
||||
# create serializable data structure
|
||||
var data: Dictionary = {
|
||||
"last_entrypoint": last_entrypoint
|
||||
}
|
||||
var data_serialized = JSON.stringify(data)
|
||||
if data_serialized == null:
|
||||
push_error("Couldn't serialize save data for slot %s" % current_slot)
|
||||
quit(1)
|
||||
return
|
||||
f.store_string(data_serialized)
|
||||
|
||||
func load_slot():
|
||||
# load gamestate from slot
|
||||
if _check_slot_idx(current_slot):
|
||||
reset_gamestate() # init gamestate with defaults
|
||||
var slot_path: String = "user://slot%s.save" % current_slot
|
||||
if FileAccess.file_exists(slot_path):
|
||||
var f = FileAccess.open(slot_path, FileAccess.READ)
|
||||
if f == null:
|
||||
push_error("Couldn't open %s" % slot_path)
|
||||
quit(1)
|
||||
return
|
||||
var data_serialized = f.get_as_text()
|
||||
var data = JSON.parse_string(data_serialized)
|
||||
if data == null:
|
||||
push_error("Couldn't deserialize slot %s" % slot_path)
|
||||
quit(1)
|
||||
return
|
||||
# read values
|
||||
last_entrypoint = data["last_entrypoint"]
|
||||
else:
|
||||
save_slot() # new game; save defaults
|
||||
|
||||
func reset_slot(idx: int):
|
||||
# reset a save slot
|
||||
if _check_slot_idx(idx):
|
||||
if current_slot == idx:
|
||||
reset_gamestate()
|
||||
# delete slot file
|
||||
var slot_path: String = "user://slot%s.save" % idx
|
||||
if FileAccess.file_exists(slot_path):
|
||||
DirAccess.remove_absolute(slot_path)
|
||||
|
||||
#
|
||||
|
||||
func quit(code: int):
|
||||
# defer get_tree().quit() to not lose the last error/stdout
|
||||
# see https://github.com/godotengine/godot/issues/90667
|
||||
await get_tree().create_timer(0.1).timeout
|
||||
get_tree().quit(code)
|
||||
|
||||
func _ready() -> void:
|
||||
reset_gamestate()
|
|
@ -1,59 +1,79 @@
|
|||
extends Node
|
||||
|
||||
const mainmenu_player_pos: Vector2 = Vector2(0, 128 + 32)
|
||||
|
||||
class Entrypoint extends Object:
|
||||
var scene_name: String
|
||||
var player_position: Vector2
|
||||
var reset_physics: bool
|
||||
var initial_velocity: Vector2
|
||||
func _init(
|
||||
scene_name_: String,
|
||||
player_position_: Vector2,
|
||||
reset_physics_: bool
|
||||
initial_velocity_: Vector2 = Vector2.ZERO
|
||||
) -> void:
|
||||
self.scene_name = scene_name_
|
||||
self.player_position = player_position_
|
||||
self.reset_physics = reset_physics_
|
||||
self.initial_velocity = initial_velocity_
|
||||
|
||||
const SCENES = {
|
||||
"intro": "uid://c6w7lrydi43ts"
|
||||
"intro": "uid://c6w7lrydi43ts",
|
||||
"test": "uid://dqf665b540tfg",
|
||||
}
|
||||
|
||||
var ENTRYPOINTS = {
|
||||
"intro_start": Entrypoint.new("intro", Vector2(0, 0), true)
|
||||
"intro_start": Entrypoint.new("intro", Vector2.ZERO),
|
||||
"test": Entrypoint.new("test", Vector2.ZERO),
|
||||
}
|
||||
|
||||
var MENU_SCENE: PackedScene = preload("res://menu/menu.tscn")
|
||||
|
||||
# load that stuff
|
||||
|
||||
var level_root: Node
|
||||
var player: Node2D
|
||||
func _pre_load_checks() -> bool:
|
||||
if NodeRegistry.level_root_container == null:
|
||||
push_error("Can't load level, level_root is not registered yet.")
|
||||
return false
|
||||
if NodeRegistry.player == null:
|
||||
push_error("Can't load entrypoint, player is not registered yet.")
|
||||
return false
|
||||
return true
|
||||
|
||||
func load_scene(scn_name: String) -> bool: # returns true on success
|
||||
if level_root == null:
|
||||
push_error("Can't load level, level_root is not registered yet.")
|
||||
if not _pre_load_checks():
|
||||
return false
|
||||
if not scn_name in SCENES:
|
||||
push_error("Level " + scn_name + " doesn't exist.")
|
||||
return false
|
||||
unload_scene()
|
||||
var scn = load(SCENES[scn_name])
|
||||
level_root.add_child(scn.instantiate())
|
||||
NodeRegistry.level_root_container.add_child(scn.instantiate())
|
||||
return true
|
||||
|
||||
func unload_scene():
|
||||
for c in level_root.get_children():
|
||||
c.queue_free()
|
||||
if NodeRegistry.level_root_container != null:
|
||||
for c in NodeRegistry.level_root_container.get_children():
|
||||
c.queue_free()
|
||||
|
||||
func load_entrypoint(ep_name: String) -> bool: # returns true on success
|
||||
if not ep_name in ENTRYPOINTS:
|
||||
push_error("Entrypoint " + ep_name + " doesn't exist.")
|
||||
return false
|
||||
if player == null:
|
||||
push_error("Can't load entrypoint, player is not registered yet.")
|
||||
if not _pre_load_checks():
|
||||
return false
|
||||
var e: Entrypoint = ENTRYPOINTS[ep_name]
|
||||
if load_scene(e.scene_name):
|
||||
player.position = e.player_position
|
||||
if e.reset_physics:
|
||||
player.reset_physics()
|
||||
NodeRegistry.player.position = e.player_position
|
||||
NodeRegistry.player.velocity = e.initial_velocity
|
||||
Gamestate.last_entrypoint = ep_name
|
||||
Gamestate.save_slot() # save game
|
||||
return true
|
||||
else:
|
||||
return false
|
||||
|
||||
func load_menu():
|
||||
if not _pre_load_checks():
|
||||
return false
|
||||
unload_scene()
|
||||
NodeRegistry.level_root_container.add_child(MENU_SCENE.instantiate())
|
||||
NodeRegistry.player.position = mainmenu_player_pos
|
||||
return true
|
||||
|
|
4
core/globals/node_registry.gd
Normal file
4
core/globals/node_registry.gd
Normal file
|
@ -0,0 +1,4 @@
|
|||
extends Node
|
||||
|
||||
var player: CharacterBody2D
|
||||
var level_root_container: Node
|
Reference in a new issue