Storing High Scores in a Database in Godot

Storing High Scores in a Database in Godot

This is more than is expected for a Project at NCEA Level 2 and 3

Storing high scores locally is a classic game dev Milestone, and SQLite is a fantastic choice for Godot – it’s lightweight, self-contained, and much more robust than just dumping text into a JSON file.

Since Godot doesn’t have SQLite built-in by default, we will use the highly popular Godot-SQLite plugin.

Here is your step-by-step Guide to setting up and using SQLite3 in Godot 4.

1. Setup & Installation

Before writing code, you Need to add the SQLite extension to your Project.

  1. Open your Godot Project.
  2. Click on the AssetLib tab at the top of the editor.
  3. Search for “Godot-SQLite” (by 2shady4u) and download it.
  4. In the installation window, Ensure it installs into your addons/ folder.
  5. Go to Project -> Project Settings -> Plugins and check the Enable box next to Godot-SQLite.

2. Creating the Database Manager (Autoload)

To make sure your database is accessible from any scene (MainMenu, GameOver, etc.), it’s best to create a singleton (Autoload).

  1. Create a new script named DatabaseManager.gd.
  2. Go to Project -> Project Settings -> Autoload, add this script, and name it DatabaseManager.

Here is the foundational code to initialize your database and create a highscores table:

extends Node

const SQLite = preload("res://addons/godot-sqlite/bin/gdsqlite.gd")
var db: SQLite
var db_path: String = "user://game_data.db" # 'user://' ensures it saves correctly on all platforms

func _ready() -> void:
	# Initialize the database
	db = SQLite.new()
	db.path = db_path
	db.open_db()
	
	# Create the high scores table if it doesn't exist
	create_highscore_table()

func create_highscore_table() -> void:
	var table_structure: Dictionary = {
		"id": {"Data_type": "int", "primary_key": true, "not_null": true, "auto_increment": true},
		"player_name": {"Data_type": "text", "not_null": true},
		"score": {"Data_type": "int", "not_null": true},
		"date": {"Data_type": "text"}
	}
	
	db.create_table("highscores", table_structure)

We use user:// for the database path. If you use res://, your game won’t be able to save Data once it’s exported into a finished executable!

3. Saving a New High Score

When a player dies or finishes a round, you’ll want to insert their name and score into the database. Add this Function to your DatabaseManager.gd:

func save_score(player_name: String, score: int) -> void:
	# Get current date/time to log when the score happened
	var current_time: String = Time.get_datetime_String_from_System(false, true)
	
	var Row_Data: Dictionary = {
		"player_name": player_name,
		"score": score,
		"date": current_time
	}
	
	# Insert into the 'highscores' table
	db.insert_Row("highscores", Row_Data)
	print("Score saved successfully!")

4. Retrieving the Top Scores

To display a Leaderboard, you Need to Query the database, sort the scores from highest to lowest, and limit the results (e.g., Top 10). Add this to DatabaseManager.gd:

func get_top_scores(limit: int = 10) -> Array:
	# Construct a Query to get top scores sorted descending
	# SQLite plugin allows custom queries via text
	var Query: String = "SELECT player_name, score, date FROM highscores ORDER BY score DESC LIMIT %d;" % limit
	
	db.Query(query)
	
	# db.Query_result holds an Array of Dictionaries representing the rows
	return db.Query_result

5. Putting It All Together (Example Usage)

Now that your manager is setup, using it anywhere else in your game is incredibly straightforward.

Saving a score from a “Game Over” screen:

extends Control

func _on_submit_score_button_pressed() -> void:
	var name_entered = $LineEdit.text
	var final_score = 15200 # Grab this from your global game manager
	
	if name_entered != "":
		DatabaseManager.save_score(name_entered, final_score)

Displaying the leaderboard on a “Main Menu” screen:

extends Control

@onready var score_list = $ItemList # An ItemList UI node to display rows

func _ready() -> void:
	display_leaderboard()

func display_leaderboard() -> void:
	score_list.clear()
	
	# Fetch top 5 scores
	var top_scores: Array = DatabaseManager.get_top_scores(5)
	
	# Loop through rows and add them to the UI
	var rank = 1
	for row in top_scores:
		var entry_text = "%d. %s - %d pts (%s)" % [rank, row["player_name"], row["score"], row["date"].split("T")[0]]
		score_list.add_item(entry_text)
		rank += 1

Staying Safe

Sanitization: The Godot-SQLite plugin handles Basic dictionary-to-Query conversions safely, but if you write raw SQL strings with user input, always be careful of SQL injection.

Closing the DB: It’s good practice to close the database connection when the game exits. You can add a func _exit_tree() -> void: db.close_db() into your DatabaseManager.gd.