Game designers on our team live in spreadsheets. Enemy stats, item definitions, wave configs. It all gets maintained in Google Sheets and exported as CSV. Our Godot project uses custom Resources for most of this, and for a while someone (usually me) was manually keeping .tres files in sync with whatever changed in the sheet.
So I wrote a script to kill that workflow. Point it at a CSV with a config dict describing the column-to-property mapping, and it generates one .tres file per row. New row means new file. Changed value means updated file. Handles basic type coercion: ints, floats, bools, plain strings. Filenames come from a designated slug column.
import csv, sys
from pathlib import Path
RESOURCE_TYPE = 'EnemyData'
COLUMNS = {
'id': ('id', 'String'),
'max_hp': ('max_hp', 'int'),
'speed': ('move_speed', 'float'),
'boss': ('is_boss', 'bool'),
}
def coerce(value, typ):
if typ == 'int': return str(int(value))
if typ == 'float': return str(float(value))
if typ == 'bool': return 'true' if value.lower() in ('1', 'true', 'yes') else 'false'
return '"{}"'.format(value)
def write_tres(row, out_path):
header = '[gd_resource type="{}" format=3]'.format(RESOURCE_TYPE)
lines = [header, '', '[resource]']
for col, (prop, typ) in COLUMNS.items():
lines.append('{} = {}'.format(prop, coerce(row[col], typ)))
Path(out_path).write_text('\n'.join(lines) + '\n')
if __name__ == '__main__':
src, out_dir = sys.argv[1], Path(sys.argv[2])
out_dir.mkdir(parents=True, exist_ok=True)
rows = list(csv.DictReader(open(src)))
for row in rows:
write_tres(row, out_dir / '{}.tres'.format(row['id']))
print('wrote {} .tres files to {}'.format(len(rows), out_dir))
Works fine for flat data. The part I haven't solved yet is sub-resources. If an EnemyData has a nested LootTable resource, there's no clean way to express that in a flat CSV. I'm thinking about a convention where a column references another .tres by ID and the script writes an ExtResource pointer, but haven't actually built that yet.
Curious if anyone's solved the nested resource problem without making the CSV completely unreadable. Also open to "just use SQLite" arguments. I know the option exists, I just don't want to add another dependency to what's currently a 60-line script.