Add highscores saves and level selection. The levels are not played sequentially, but the level played can be choosen at start.

This commit is contained in:
Feufochmar 2021-11-07 21:16:26 +01:00
parent 0d6809d1fb
commit fc707b23f7
8 changed files with 266 additions and 40 deletions

36
main.gd
View File

@ -1,22 +1,31 @@
extends Node
var StartScreen = preload("res://screens/start_screen.tscn")
var LevelSelectScreen = preload("res://screens/level_select_screen.tscn")
var GameScreen = preload("res://screens/game_screen.tscn")
var GameOverScreen = preload("res://screens/game_over_screen.tscn")
var PauseScreen = preload("res://screens/pause_screen.tscn")
var HighScores = preload("res://tools/highscores.gd")
var Levels = preload("res://tools/levels.gd")
# Variables
var current_screen = null # Current displayed screen
var current_state = "" # Current state
var game_screen = null # To store the game screen while in pause
var highscores = HighScores.new() # Object to keep the scores
var levels = Levels.new() # Object to keep the levels
func _ready():
# Read the high scores
highscores.load_scores()
# Go to the start screen
goToStartScreen()
func _on_next_screen(next):
if next == "start":
goToStartScreen()
if next == "select":
goToLevelSelectScreen()
elif next == "game":
goToGameScreen()
elif next == "game_won":
@ -38,8 +47,20 @@ func goToStartScreen():
current_screen.connect("next_screen", self, "_on_next_screen")
add_child(current_screen)
func goToLevelSelectScreen():
# Clean up current scene
if current_screen != null:
current_screen.queue_free()
if game_screen != null:
game_screen.queue_free()
game_screen = null
current_state = "select"
current_screen = LevelSelectScreen.instance()
current_screen.init(levels, highscores)
current_screen.connect("next_screen", self, "_on_next_screen")
add_child(current_screen)
func goToGameScreen():
var start_level = 0
if current_screen != null:
current_screen.queue_free()
current_state = "game"
@ -49,27 +70,34 @@ func goToGameScreen():
game_screen = null
else:
current_screen = GameScreen.instance()
current_screen.init(start_level)
current_screen.init(levels.get_current())
current_screen.connect("next_screen", self, "_on_next_screen")
add_child(current_screen)
func goToGameOverScreen(hasWon):
# Retrieve score
var score = 0
var level_name = ""
if current_state == "game":
score = current_screen.get_score()
level_name = current_screen.get_node("LevelName").text
elif game_screen != null:
score = game_screen.get_score()
level_name = game_screen.get_node("LevelName").text
# Clean up current scene
if current_screen != null:
current_screen.queue_free()
if game_screen != null:
game_screen.queue_free()
game_screen = null
# Highscores are updated only when a level is won (terminated)
var is_new_high = false
if hasWon:
is_new_high = highscores.set_score(level_name, score)
# Prepare screen
current_state = "game_over"
current_screen = GameOverScreen.instance()
current_screen.init(hasWon, score)
current_screen.init(hasWon, score, is_new_high)
current_screen.connect("next_screen", self, "_on_next_screen")
add_child(current_screen)

View File

@ -8,8 +8,10 @@ signal next_screen(screen_name)
func _ready():
pass
func init(hasWon, score):
if hasWon:
func init(hasWon, score, isNewHigh):
if hasWon && isNewHigh:
$Status.text = "New High Score !"
elif hasWon:
$Status.text = "Victory !"
else:
$Status.text = "Game Over !"

View File

@ -3,31 +3,9 @@ extends Node
signal next_screen(screen_name)
var Ball = preload("res://ball/ball.tscn")
# List of levels
var Levels = [
# - Classic - #
preload("res://levels/level_01_01.tscn"), # "Breakout" - Disposition from the 1st breakout game
preload("res://levels/level_01_02.tscn"), # "Behind the wall" - Wall block
preload("res://levels/level_01_03.tscn"), # "Building" - Hard blocks
preload("res://levels/level_01_04.tscn"), # "Heart" - Half blocks
preload("res://levels/level_01_05.tscn"), # "Paw" - (no new block)
# - Hex - #
preload("res://levels/level_02_01.tscn"), # "Hive" - Hexagonal blocks
preload("res://levels/level_02_02.tscn"), # "Flowers" - Non-regular hexagons
preload("res://levels/level_02_03.tscn"), # "Leaves" - (no new block)
preload("res://levels/level_02_04.tscn"), # "Aquarium" - Fish blocks (+ Leaves)
preload("res://levels/level_02_05.tscn"), # "Stars" - Star3 blocks
# - Moving/Timing blocks - #
# "Invasion" - Left-right movement
# "Ghosts" - Blinking blocks : block appear / disappear
# "Gems" - Gravity blocks - cf. Boulder Dash
# "Many-Eyed Angel" - Blinking blocks 2 : wall / non-wall
# "Tris" - Falling blocks / appearing at random on the top
]
# Declare member variables here.
var ball = null # Ball on the field
var current_level = 0 # Index of the current levels
var level = null # Current level
var total_score = 0 # Score
var paused = false
@ -36,11 +14,10 @@ var paused = false
func _ready():
pass
func init(level_index):
current_level = level_index
func init(level_type):
total_score = 0
update_score()
new_level()
new_level(level_type)
new_ball()
# Called every frame. 'delta' is the elapsed time since the previous frame.
@ -63,8 +40,8 @@ func unpause():
$BGM.play()
paused = false
func new_level():
level = Levels[current_level].instance()
func new_level(level_type):
level = level_type.instance()
level.connect("level_ended", self, "_on_level_ended")
level.connect("score_increased", self, "_on_score_increased")
$LevelName.text = level.level_name
@ -105,9 +82,4 @@ func _on_EndLevelTimer_timeout():
ball.disconnect("ball_lost", self, "_on_ball_lost")
level.queue_free()
ball.queue_free()
current_level = current_level + 1
if current_level >= Levels.size():
emit_signal("next_screen", "game_won")
else:
new_level()
new_ball()
emit_signal("next_screen", "game_won")

View File

@ -0,0 +1,43 @@
extends Node
signal next_screen(screen_name)
var level_list = null
var highscore_list = null
var shown_level = null
# Called when the node enters the scene tree for the first time.
func _ready():
pass
# Init with the level list
func init(levels, highscores):
level_list = levels
highscore_list = highscores
display_level()
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(_delta):
if Input.is_action_just_pressed('ui_accept'):
emit_signal("next_screen", "game")
if Input.is_action_just_pressed("ui_menu"):
emit_signal("next_screen", "start")
if Input.is_action_just_pressed("ui_left"):
level_list.go_to_prev()
display_level()
if Input.is_action_just_pressed("ui_right"):
level_list.go_to_next()
display_level()
# Display the level
func display_level():
if shown_level != null:
shown_level.queue_free()
shown_level = level_list.get_current().instance()
$LevelName.text = shown_level.level_name
var high = highscore_list.get_score(shown_level.level_name)
if high == null:
$Score.text = "-no highscore-"
else:
$Score.text = str(high)
$Level.call_deferred("add_child", shown_level)

View File

@ -0,0 +1,69 @@
[gd_scene load_steps=7 format=2]
[ext_resource path="res://screens/NovaOval.ttf" type="DynamicFontData" id=1]
[ext_resource path="res://empty_level/empty_level.tscn" type="PackedScene" id=2]
[ext_resource path="res://screens/level_select_screen.gd" type="Script" id=3]
[sub_resource type="DynamicFont" id=1]
size = 24
font_data = ExtResource( 1 )
[sub_resource type="DynamicFont" id=2]
size = 24
font_data = ExtResource( 1 )
[sub_resource type="DynamicFont" id=3]
size = 50
font_data = ExtResource( 1 )
[node name="LevelSelectScreen" type="Node"]
script = ExtResource( 3 )
[node name="EmptyLevel" parent="." instance=ExtResource( 2 )]
[node name="Score" type="Label" parent="."]
anchor_left = 1.0
anchor_right = 1.0
margin_left = -440.0
margin_right = -72.0
margin_bottom = 32.0
custom_fonts/font = SubResource( 1 )
custom_colors/font_color = Color( 0.552941, 0.960784, 0.505882, 1 )
text = "SCORE"
align = 2
valign = 1
__meta__ = {
"_edit_use_anchors_": false
}
[node name="LevelName" type="Label" parent="."]
margin_left = 72.0
margin_right = 440.0
margin_bottom = 32.0
custom_fonts/font = SubResource( 2 )
custom_colors/font_color = Color( 0.552941, 0.960784, 0.505882, 1 )
text = "NAME"
valign = 1
__meta__ = {
"_edit_use_anchors_": false
}
[node name="Level" type="Node" parent="."]
[node name="Instructions" type="Label" parent="."]
anchor_left = 0.5
anchor_top = 1.0
anchor_right = 0.5
anchor_bottom = 1.0
margin_left = -512.0
margin_top = -125.0
margin_right = 512.0
custom_fonts/font = SubResource( 3 )
text = "Select level with next and right,
then press space"
align = 1
valign = 1
autowrap = true
__meta__ = {
"_edit_use_anchors_": false
}

View File

@ -15,7 +15,7 @@ func init():
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(_delta):
if Input.is_action_just_pressed('ui_accept'):
emit_signal("next_screen", "game")
emit_signal("next_screen", "select")
elif Input.is_action_just_pressed("ui_menu"):
get_tree().notification(MainLoop.NOTIFICATION_WM_QUIT_REQUEST)

58
tools/highscores.gd Normal file
View File

@ -0,0 +1,58 @@
extends Reference
# Score list. A map level_name -> value
var scores = {}
# Path to score file
const score_file = "user://highscores.json"
# Called when the node enters the scene tree for the first time.
func _ready():
pass # Replace with function body.
# Return the score or null if unset
func get_score(level_name: String):
return scores.get(level_name, null)
# Set score, return true if a new high score was set, false otherwise
func set_score(level_name: String, new_score: int):
var old_score = get_score(level_name)
if (old_score == null) or (new_score > old_score):
scores[level_name] = new_score
save_scores()
return true
else:
return false
func save_scores():
# Serialize the scores in JSON
var serialized = JSON.print(scores)
# Write them to score file
var file = File.new()
var err = file.open(score_file, File.WRITE)
if err == OK:
file.store_string(serialized)
file.close()
else:
print("Error opening file " + score_file + " - Unable to save scores")
func load_scores():
# Clear scores
scores.clear()
# Read score file, if it exists
var file = File.new()
if file.file_exists(score_file):
var err = file.open(score_file, File.READ)
if err == OK:
var serialized = file.get_as_text()
file.close()
# Parse JSON
var parsed = JSON.parse(serialized)
# Check if OK
if (parsed.error == OK) && (typeof(parsed.result) == TYPE_DICTIONARY):
# Numeric values read from JSON are float type : must be converted to int
for k in parsed.result.keys():
scores[k] = int(parsed.result[k])
else:
print("Error reading file " + score_file + " - Unexpected format")
else:
print("Error opening existing file " + score_file)

54
tools/levels.gd Normal file
View File

@ -0,0 +1,54 @@
extends Reference
# List of levels
var list = [
# - Classic - #
preload("res://levels/level_01_01.tscn"), # "Breakout" - Disposition from the 1st breakout game
preload("res://levels/level_01_02.tscn"), # "Behind the wall" - Wall block
preload("res://levels/level_01_03.tscn"), # "Building" - Hard blocks
preload("res://levels/level_01_04.tscn"), # "Heart" - Half blocks
preload("res://levels/level_01_05.tscn"), # "Paw" - (no new block)
# - Hex - #
preload("res://levels/level_02_01.tscn"), # "Hive" - Hexagonal blocks
preload("res://levels/level_02_02.tscn"), # "Flowers" - Non-regular hexagons
preload("res://levels/level_02_03.tscn"), # "Leaves" - (no new block)
preload("res://levels/level_02_04.tscn"), # "Aquarium" - Fish blocks (+ Leaves)
preload("res://levels/level_02_05.tscn"), # "Stars" - Star3 blocks
# - Moving/Timing blocks - #
# "Invasion" - Left-right movement
# "Ghosts" - Blinking blocks : block appear / disappear
# "Gems" - Gravity blocks - cf. Boulder Dash
# "Many-Eyed Angel" - Blinking blocks 2 : wall / non-wall
# "Tris" - Falling blocks / appearing at random on the top
]
# Index for moving in the list
var current_level = 0
# Called when the node enters the scene tree for the first time.
func _ready():
pass
# Get current level
func get_current():
return list[current_level]
# Go to next level (list is circulary)
func go_to_next():
current_level = current_level + 1
if current_level >= list.size():
current_level = 0
# Go to previous level (list is circulary)
func go_to_prev():
current_level = current_level - 1
if current_level < 0:
current_level = list.size() - 1
# Check if current level is last
func current_is_last():
return current_level == list.size() - 1
# Check if current level is first
func current_is_first():
return current_level == 0