Data Sources — GDScript
VCE scope only. The study design names exactly three data sources: plain text (TXT), delimited (CSV) and XML files. JSON, databases (SQLite), REST APIs and Godot's own .tres/.res resources are out of scope — they do not earn source codes, however natural they feel in Godot.
Skill codes on this page
| Code | Level | Skill |
|---|---|---|
| C644 | 7–8 | uses appropriate data types, data structures and data sources — "including plain text, delimited and XML files" |
| C645 | 7–8 | describes, in the internal documentation, why the selected data sources were used |
| C657 | 9–10 | uses a range of data types, data structures and data sources |
| C658 | 9–10 | explains, in the internal documentation, why the selected data types, data structures and data sources were used |
Before you build: a data layer built on JSON, SQLite or .tres earns none of C644–C658, however natural those feel in Godot — and graders (human or AI) apply that rule mechanically. The sources you demonstrate must be TXT, CSV and/or XML.
The three sources
Definitions.
Plain text (TXT) file: a structured file that contains characters of readable data.
CSV: a comma-separated value file — a delimited file separated by commas. The delimiter is the character separating the values. A CSV is a flat table: rows, each row the same fields.
XML: Extensible Markup Language — data in elements marked by opening and closing tags. Elements can hold other elements, so the data is nested rather than flat: a tree with a root element at the top, and a prolog (version, encoding) before the content.
Know the difference: res:// is your project's read-only data; user:// is the writable save location. Naming that in validation is easy marks. club-ladder runs on exactly this split — the bundled season ships in res://seasons/, the player's copy lives at user://ladder.xml, and load_season() picks between them with one if FileAccess.file_exists(save_path).
TXT in Godot
# C644 — TXT source: write var file = FileAccess.open("user://balance.txt", FileAccess.WRITE) file.store_line(str(balance))
# C644 — TXT source: read var file = FileAccess.open("user://balance.txt", FileAccess.READ) balance = float(file.get_line())
CSV in Godot
FileAccess speaks CSV natively — get_csv_line() and store_csv_line() handle the delimiter for you.
# C644 — CSV source: read journeys.csv, one journey per row (zone,fare) var journeys: Array[Dictionary] = [] var file = FileAccess.open("user://journeys.csv", FileAccess.READ) file.get_csv_line() # skip the header row while not file.eof_reached(): var row: PackedStringArray = file.get_csv_line() if row.size() >= 2: journeys.append({"zone": int(row[0]), "fare": float(row[1])})
# C644 — CSV source: write journeys.csv var file = FileAccess.open("user://journeys.csv", FileAccess.WRITE) file.store_csv_line(PackedStringArray(["zone", "fare"])) # header for journey in journeys: file.store_csv_line(PackedStringArray([str(journey["zone"]), str(journey["fare"])]))
In tile-matching-game (deck.gd) — the whole game is driven by one CSV read; each row becomes a record in an array:
# tile-matching-game — deck.gd var file := FileAccess.open(path, FileAccess.READ) # C644 — CSV source var header := file.get_csv_line() # skip the pair,text,image header while not file.eof_reached(): var row := file.get_csv_line() if row.size() < 2: continue cards.append({"pair": row[0].strip_edges(), "text": row[1].strip_edges()})
The real file (decks/spanish.csv):
csv pair,text,image dog,perro, dog,dog, cat,gato, cat,cat,
Defensive touches worth copying: check FileAccess.file_exists(path) first, skip short rows, strip_edges() every field. And a Godot trap: .csv imports as a translation file by default — select the file, Import tab → Keep File (exported as is) → Reimport, or deck loading breaks in exported builds.
XML in Godot
Read XML with the built-in XMLParser. There is no built-in XML writer — either write your outputs as TXT/CSV, or build the XML text yourself as club-ladder does below.
<?xml version="1.0" encoding="UTF-8"?> <fares> <fare zone="1" cost="5.30"/> <fare zone="2" cost="8.00"/> </fares>
# C644 — XML source: read fares.xml into a Dictionary (zone → cost) var fares: Dictionary = {} var parser = XMLParser.new() parser.open("res://data/fares.xml") while parser.read() == OK: if parser.get_node_type() == XMLParser.NODE_ELEMENT \ and parser.get_node_name() == "fare": var zone: int = int(parser.get_named_attribute_value("zone")) fares[zone] = float(parser.get_named_attribute_value("cost"))
Here <fares> is the root element and each <fare> is a child element carrying its data in attributes.
In club-ladder (season.gd) — XML both ways, in a studied project. Reading, with a _attribute() helper that falls back when a value is missing ("real files are often missing things"):
# club-ladder — season.gd while parser.read() == OK: # C644 — XML source if parser.get_node_type() != XMLParser.NODE_ELEMENT: continue match parser.get_node_name(): "season": season.title = _attribute(parser, "title", "Season") "team": var team_name := _attribute(parser, "name", "") if team_name == "": continue # a team with no name is not worth keeping season.teams.append(Team.new(team_name, int(_attribute(parser, "wins", "0")), int(_attribute(parser, "draws", "0")), int(_attribute(parser, "losses", "0"))))
And writing — the season builds the text line by line, escaping XML's five special characters first:
# club-ladder — season.gd lines.append('<season title="%s">' % _escape(title)) # C644 — XML written by hand for team in teams: lines.append('\t<team name="%s" wins="%d" draws="%d" losses="%d"/>' % [_escape(team.team_name), team.wins, team.draws, team.losses]) lines.append("</season>") file.store_string("\n".join(lines) + "\n")
A team called "Smith & Sons" would corrupt the file without _escape() — and the & must be replaced first, or it would mangle the entities just added.
C644 — Uses appropriate types, structures and sources
Level 7–8 is the three layers working together: a file (source) parsed into a structure of typed values — like the CSV reader above (journeys.csv → Array[Dictionary] → int and float fields), labelled:
# C644 — CSV source → Array[Dictionary] structure → typed values
C645 — Why these data sources
Level 7–8 asks you to describe the reason — a sentence or two per source, in internal documentation:
# C645 — CSV for journeys: one journey per row with the same two fields; # a flat table that also opens in Excel for checking
Described in the wild: tile-matching-game's README describes its source choice at exactly this level — the game "reads its cards from a CSV file, so the same game can be re-skinned for any subject … without changing a line of code." One sentence that says why CSV earns its place.
C657 and C658 — Range, and the full explanation
C657 (9–10): a range — several types, several structures, several sources, each doing a job the others could not. A Myki-app spread using only VCE sources: XML fare table in res:// (nested: zones × day types), CSV journey log in user:// (flat table), TXT save file in user:// (one balance value).
C658 (9–10): explains why — reasons that weigh alternatives:
# C658 — chose CSV for the journey log: flat table, one row per journey; # XML would wrap every value in tags for no gain. Kept the fare table in # XML: fares nest by zone then day type, and nesting is what XML does # that CSV cannot. TXT for the save file: one value needs no structure.
Explained in the wild: club-ladder/season.gd's header comment explains its XML design by weighing the alternative — every value is an attribute because "one call gets each one", whereas nested <name> tags would mean "tracking which tag you are inside while text arrives separately". That is C658-grade reasoning, written where the marker will find it.
The verb ladder (outlines → identifies → describes → explains) is in the C06 home page.
Check Your Understanding
- Which sources earn C644–C658?
Answer
Only TXT, CSV and XML. JSON, SQLite and .tres earn nothing here.
- What reads XML in Godot, and what is missing?
Answer
XMLParser reads it; there is no built-in writer — build the text yourself, escaping & first.
res://vsuser://?
Answer
res:// is the project's read-only data; user:// is the writable save location.
- You add
fares.csvto your project and loading breaks in the exported build. First thing to check?
Answer
The Import setting — Godot treats .csv as a translation file by default. Set Import As: Keep File (exported as is) and reimport.
