Home Storing Data Efficiently using Test-Driven-Development in Godot
Post
Cancel
Preview Image

Storing Data Efficiently using Test-Driven-Development in Godot

Intro

Decoupling is a software design term for removing dependencies between modules. According to this concept, the fewer modules depend on other modules, the better the overall management of the codebase.
I will try to show my approach to storing data efficiently in Godot using this concept.
My goal: load data from a JSON file and store it for easy access in the game.

Wikipedia about Coupling.


The Process

Creating a new resource

First, I created a new resource and gave it the name JSON_DB.tres In Godot, resources are great for storing data since the engine only loads them once. Any copy resource will point to the same original resource and will not create a new one in memory.

Creating a new resource. Creating a new resource.

Giving the resource a name. Giving the resource a name.

Now, adding a script to the resource:

Adding a script to the resource. Adding a script to the resource.


The Script

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
extends Resource
class_name JSON_DB

# ======== PRIVATE ========
var _database : Dictionary = {}
var _string_to_var : Array = []


# ======== Constructor ======== 
func _init(json_path="", json_strings_to_var=[]):
	# Load JSON from path
	var file = File.new()
	file.open(json_path, file.READ)
	_database = parse_json(file.get_as_text())
	file.close()
	
	# Which JSON's strings should be converted to variables
	_string_to_var = json_strings_to_var


# ======== PUBLIC ========
func get_dict():
	return _database


func get_values_first_key():
	var dict_all_keys = _database.keys()
	return _database.get(dict_all_keys.front())


func get_values(key):
	if _database.keys().has(key):
		return _database[key]
	return null


func get_nested_values(key, val):
	if _database.keys().has(key):  # Key (country) exists?
		if _database[key].has(val):  # Value exists?
			# If variable, convert from string
			for variable in _string_to_var:
				if String(_database[key][val]).find(variable) != -1:
					return str2var(_database[key][val])
			return _database[key][val]
	return null

Some points:

1) The database is a dictionary. It will contain unique values for each country.

2) The Constructor loads the JSON to this dictionary and converts variables specified in _string_to_var, such as Color(), to variables, as I will show below.

3) In the Public section there are several methods to access this dictionary. For now, I assume all the data I need is in this JSON file. Later I will add functions to set and change data.

Since JSON_DB will be used as a base for various data structures, I will create a dedicated database for the countries. Here’s my Countries_DB.gd:

1
2
3
4
5
extends "res://Resources/Database/JSON_DB/JSON_DB.gd"
class_name Countries_DB

func _init(json_path="", json_strings_to_var=[]).(json_path, json_strings_to_var):
	pass

The syntax for _init is derived from inner classes in Godot.


The JSON File (countries.json)

1
2
3
4
5
6
7
8
9
10
{ 
    "Brazil": {
      "color": "Color(0,1,0)",
      "stat": 1
    }, 
    "Chile": {
      "color": "Color(1,1,1)",
      "stat": 2
     }
}

In this JSON file, there’s a color variable. In the script earlier, get_nested_values will check if we specified any variables in _string_to_var (actually, the constructor does it) and if so, it will convert them to variables using the built-in str2var.


Testing using test-driven-development.

GUT is a test-driven development tool for Godot. It works well, and I will use it to test this script.

The test-driven script:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
extends "res://addons/gut/test.gd"

var _file_path = "res://JSON_FILES/countries.json"
var _countries_DB = null

var _values_first_line = null
var _key_first_country = null
var _key_second_country = null
var _values_second_country = null
var _color_first_country = null
var _color_second_country = null

func before_each():	
	_values_first_line = "{color:Color(0,1,0), stat:1}"
	_key_first_country = "Brazil"
	_key_second_country = "Chile"
	_values_second_country = "{color:Color(1,1,1), stat:2}"
	_color_first_country = Color(0,1,0)
	_color_second_country = Color(1,1,1)
	
        # Here I specifies the variables which should be converted (Color)
	_countries_DB = Countries_DB.new(_file_path, ["Color",])
	
func after_each():
	pass



func test_creating_Countries_instance():
	assert_not_null(_countries_DB, "Should not be null")


func test_get_dict_not_null():
	assert_not_null(_countries_DB.get_dict())


func test_get_dict_returns_dict():
	assert_typeof(_countries_DB.get_dict(), TYPE_DICTIONARY)


func test_DB_first_line():
	var values_first_line = String(_countries_DB.get_values_first_key())
	assert_eq(values_first_line, _values_first_line)


func test_DB_second_line():
	var values_second_country = _countries_DB.get_values(_key_second_country)
	values_second_country = String(values_second_country)
	assert_eq(values_second_country, _values_second_country)


func test_DB_first_line_nested_value():
	var color_first_country = _countries_DB.get_nested_values(_key_first_country, "color")
	assert_eq(color_first_country, _color_first_country)


func test_DB_second_line_nested_value():
	var color_second_country = _countries_DB.get_nested_values(_key_second_country, "color")
	assert_eq(color_second_country, _color_second_country)

All trademarks, registered trademarks, games' footage, and referenced code/materials are the property of their respective owners.

Designing Initial User-Interface for a Grand Strategy Game, Considering UX.

Game Balance in a Grand Strategy Game - Basic Economy Approach