Game Development with Godot & GDScript Module 1 – Godot, the editor and your first scene
Project files, UIDs and version control
Know where res://, user:// and uid:// point, commit the right Godot files, keep references working with .uid files, and set up Git, LFS and scene merges.
What you will learn
- Explain where res://, user:// and uid:// paths point, and which of them a game can write to
- Decide which project files to commit and which to ignore, and rebuild .godot/ after a clone
- Move files without breaking references by keeping their .uid and .import files with them
- Set up Git for a Godot project with .gitignore, .gitattributes and Git LFS for large assets
- Read a scene diff, resolve a conflict in a .tscn file and check that the scene still loads
Before you start
What you’ll learn · Before you start
What you will learn
- Explain where res://, user:// and uid:// paths point, and which of them a game can write to
- Decide which project files to commit and which to ignore, and rebuild .godot/ after a clone
- Move files without breaking references by keeping their .uid and .import files with them
- Set up Git for a Godot project with .gitignore, .gitattributes and Git LFS for large assets
- Read a scene diff, resolve a conflict in a .tscn file and check that the scene still loads
Before you start
On this page
A teammate moves one texture into a new folder outside the editor, commits, and the next morning your copy of the game
shows the coin with no picture. Three files decide whether that happens: the texture, its .import file and the scene
that refers to it, and only two of the three made it into the commit.
Here you learn where Godot’s res://, user:// and uid:// paths point, which files of a project belong in version
control, how UIDs keep references working when files move, how to set up Git with the files Godot itself writes, and
how to read a scene diff and resolve a conflict in one.
res:// is the project, user:// is the player’s, uid:// survives moves
Every path in Godot starts with one of three prefixes, and each points somewhere different:
| Path | Points to | Can the game write there? |
|---|---|---|
res:// |
the project’s folder, the one that holds project.godot |
only when run from the editor; an exported game’s files are read-only |
user:// |
a folder per project and user, outside the project | always: saves, settings and logs go here |
uid:// |
the file that owns a unique id, wherever it is now | a reference, not a place to write |
res://: The path prefix for the files of a project: res:// is the folder that holds project.godot. It can be written while the project runs from the editor, and is read-only in an exported game. is the project as you see it in the FileSystem dock. Once the game is exported, its
files are packed and read-only, so anything a game saves goes to user://: The path prefix for a game's writable data, such as saves, settings and logs, kept per project and user outside the project. On macOS it is ~/Library/Application Support/Godot/app_userdata/ and the project's name. instead.
Godot’s documentation gives user:// on Windows as %APPDATA%\Godot\app_userdata\[project_name], on macOS as
~/Library/Application Support/Godot/app_userdata/[project_name], and on Linux as
~/.local/share/godot/app_userdata/[project_name], where the folder name comes from the project’s name. This script
asks the engine for both folders and for the third kind of path, a UID:
extends SceneTree
# Where res://, user:// and uid:// point on this computer.
func _initialize() -> void:
print("res:// is ", ProjectSettings.globalize_path("res://"))
print("user:// is ", ProjectSettings.globalize_path("user://"))
var id := ResourceLoader.get_resource_uid("res://art/coin.svg")
print("res://art/coin.svg has the UID ", ResourceUID.id_to_text(id))
print("and that UID leads to ", ResourceUID.get_id_path(id))
var texture := load("uid://bf5btoh7rd06h") as Texture2D
print("Loaded by UID: a ", texture.get_class(), " of ", texture.get_size())
var file := FileAccess.open("user://best_score.txt", FileAccess.WRITE)
file.store_string("1200")
file.close()
print("user://best_score.txt saved: ", FileAccess.file_exists("user://best_score.txt"))
quit() from session import files, run
files("dodge=.")
run("""
godot --headless --quiet --import
godot --headless --no-header --script paths_report.gd
ls ~/Library/Application\\ Support/Godot/app_userdata/Dodge
""") Output
$ godot --headless --quiet --import $ godot --headless --no-header --script paths_report.gd res:// is ~/game/ user:// is ~/Library/Application Support/Godot/app_userdata/Dodge/ res://art/coin.svg has the UID uid://bf5btoh7rd06h and that UID leads to res://art/coin.svg Loaded by UID: a CompressedTexture2D of (16.0, 16.0) user://best_score.txt saved: true $ ls ~/Library/Application\ Support/Godot/app_userdata/Dodge best_score.txt logs objectdb_snapshots
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 paths_session.py
The project named Dodge has its user:// in app_userdata/Dodge, and the listing shows the saved best_score.txt
there beside folders that Godot writes itself, such as logs. The texture res://art/coin.svg has
the UID: A unique id that Godot gives every file, written as uid:// and a code. References store it beside the path, so they keep working when the file moves together with its .uid or .import file. uid://bf5btoh7rd06h, and load("uid://bf5btoh7rd06h") loads it without naming any folder. That is what makes UIDs useful: the next sections show
how they survive a move.
Renaming the project and losing its saves
user:// is named after application/config/name. Rename the project and the game looks in a new, empty folder, so
saves from before seem lost. Set a fixed folder name with application/config/use_custom_user_dir and
custom_user_dir_name before the first release.
Checkpoint
Check: res://, user:// and uid:// paths
Commit the project and its metadata, never the .godot/ cache
A Godot project is mostly text: scenes, resources, scripts and project.godot. Beside some files Godot writes
metadata files that must travel with them, and inside the project it keeps a cache that must not travel at all:
| File | What it is | Commit it? |
|---|---|---|
project.godot, .tscn, .tres, .gd, assets |
the project itself | yes |
*.import beside an asset |
its import settings and its UID | yes, with the asset |
*.uid beside a script or shader |
its UID (since 4.4) | yes, with the file |
export_presets.cfg |
export settings; 4.1 and later keep credentials elsewhere | yes |
| .godot/ folder: A project's cache: imported copies of its assets, the list of global classes and the UID cache. Godot rebuilds it from the project's files with one import, so it is never committed. | the cache: imported copies, the class list, the UID cache | never |
*.translation |
binary translations imported from CSV files | never |
| exported builds | the output of an export | never; publish them instead |
The cache can always be rebuilt from the committed files, which is exactly why it stays out of the repository. A fresh
clone therefore has no .godot/ folder, and that matters the first time you run a script without opening the editor.
Step through it:
The drawing shows the whole scene; its 5 steps are listed below.
Every step as text
- Clone. The clone brings every committed file, scripts, scenes, assets and their .uid and .import files, but no .godot/ folder, because .gitignore keeps the cache out of the repository.
- A script fails. uses_player.gd uses the class Player that player.gd declares with class_name. Run headless now, it fails to parse, because the list of global classes lives in the cache that does not exist yet.
- Import once. godot --headless --import scans the project as the editor does when it first opens it, imports the assets, registers the global classes and the UIDs, and quits.
- The cache is back. .godot/ now holds global_script_class_cache.cfg, uid_cache.bin and the imported assets. All of it can be rebuilt from the committed files, which is why it is never committed.
- The script runs. The same command now prints "A Player with 3 lives". Opening the project in the editor does the same import, so this step matters for build machines and scripts that never open the editor.
Here is that clone of the Dodge project, run for real:
from session import files, run
# The project as a fresh clone has it: scripts, scenes, assets and their .uid and .import files, but no .godot/ folder.
files("dodge=.")
run("""
ls -A
!godot --headless --no-header --script uses_player.gd
godot --headless --quiet --import
ls -A .godot
godot --headless --no-header --script uses_player.gd
""") Output
$ ls -A
art
check_coin.gd
check_coin.gd.uid
check_level.gd
check_level.gd.uid
coin.gd
coin.gd.uid
coin.tscn
paths_report.gd
paths_report.gd.uid
player.gd
player.gd.uid
project.godot
uses_player.gd
uses_player.gd.uid
$ godot --headless --no-header --script uses_player.gd
SCRIPT ERROR: Parse Error: Identifier "Player" not declared in the current scope.
at: GDScript::reload (res://uses_player.gd:5)
SCRIPT ERROR: Parse Error: Cannot infer the type of "player" variable because the value doesn't have a set type.
at: GDScript::reload (res://uses_player.gd:5)
ERROR: Failed to load script "res://uses_player.gd" with error "Parse error".
at: load (modules/gdscript/gdscript_resource_format.cpp:46)
$ godot --headless --quiet --import
$ ls -A .godot
.gdignore
editor
global_script_class_cache.cfg
imported
uid_cache.bin
$ godot --headless --no-header --script uses_player.gd
A Player with 3 lives
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 clone_session.py
Before the import, Player, a class that player.gd declares with class_name, was unknown, because the list of
global classes lives in .godot/global_script_class_cache.cfg. One godot --headless --import rebuilt the whole
cache, and the same script then ran. Opening the project in the editor does the same import, so this step is for build
machines and scripts that never open the editor.
Checkpoint
Check: What to commit and what to ignore
A UID keeps a reference working when its file moves
Every scene, resource, script, shader and imported asset of a project has a UID. A script or a shader keeps its UID in
a .uid file beside it; an imported asset keeps it in its .import file: The file Godot writes beside each imported asset, such as coin.svg.import, with the asset's import settings and its UID. It belongs in version control with the asset.; a scene or a
resource keeps it in its own first line. A scene stores both the UID and the path of each file it uses, and the UID
wins.
New in Godot 4.4 and 4.5
Godot 4.4 gave UIDs to the file types that had none, with the .uid files beside scripts and shaders, and a UID
upgrade tool for projects from 4.3. Since 4.5, holding Ctrl (Cmd on macOS) while dropping a file into a script writes
a preload() by UID.
Step through how coin.tscn finds its texture after two moves:
The drawing shows the whole scene; its 5 steps are listed below.
Every step as text
- The scene names both. coin.tscn refers to its texture with an ext_resource line that holds a UID, uid://bf5btoh7rd06h, and a path, res://art/coin.svg. The UID is the main reference; the path is a fallback.
- The UID lives in .import. An imported file keeps its UID in its .import file. When Godot scans the project, it records every UID and the file it belongs to in the UID cache.
- Loading by UID. To load the scene, Godot looks up the UID in the cache and loads the file it names, res://art/coin.svg, the first run of check_coin.gd.
- Moved with its .import. The texture moves to sprites/ together with coin.svg.import. The next scan finds the same UID at the new place, so the scene loads res://sprites/coin.svg although its own line still says art/.
- Moved without it. Moved again with its .import file deleted, the texture gets a new random UID at the next scan. The scene's UID is unknown, Godot warns and tries the path, and art/coin.svg is gone, so the texture is missing.
Both moves, run for real: the first carries the metadata files along, the second leaves them behind, as a file manager or a careless commit would:
from session import files, run
files("dodge=.")
run("""
godot --headless --quiet --import
godot --headless --no-header --script check_coin.gd
# Move the texture and the script with their metadata files, as the FileSystem dock does.
mkdir sprites scripts
mv art/coin.svg art/coin.svg.import sprites/
mv coin.gd coin.gd.uid scripts/
godot --headless --quiet --import
godot --headless --no-header --script check_coin.gd
grep ext_resource coin.tscn
# Move them again, this time leaving the metadata files behind.
mkdir icons
mv sprites/coin.svg icons/
mv scripts/coin.gd coin_logic.gd
rm sprites/coin.svg.import scripts/coin.gd.uid
godot --headless --quiet --import
!godot --headless --no-header --script check_coin.gd
""") Output
$ godot --headless --quiet --import
$ godot --headless --no-header --script check_coin.gd
Texture: res://art/coin.svg
Script: res://coin.gd
# Move the texture and the script with their metadata files, as the FileSystem dock does.
$ mkdir sprites scripts
$ mv art/coin.svg art/coin.svg.import sprites/
$ mv coin.gd coin.gd.uid scripts/
$ godot --headless --quiet --import
$ godot --headless --no-header --script check_coin.gd
Texture: res://sprites/coin.svg
Script: res://scripts/coin.gd
$ grep ext_resource coin.tscn
[ext_resource type="Texture2D" uid="uid://bf5btoh7rd06h" path="res://art/coin.svg" id="1_tex"]
[ext_resource type="Script" uid="uid://cmoaxedjfbo8h" path="res://coin.gd" id="2_script"]
# Move them again, this time leaving the metadata files behind.
$ mkdir icons
$ mv sprites/coin.svg icons/
$ mv scripts/coin.gd coin_logic.gd
$ rm sprites/coin.svg.import scripts/coin.gd.uid
$ godot --headless --quiet --import
$ godot --headless --no-header --script check_coin.gd
WARNING: res://coin.tscn:3 - ext_resource, invalid UID: uid://bf5btoh7rd06h - using text path instead: res://art/coin.svg
at: load (scene/resources/resource_format_text.cpp:501)
GDScript backtrace (most recent call first):
[0] _initialize (res://check_coin.gd:5)
ERROR: Resource file not found: res://art/coin.svg (expected type: Texture2D)
at: _load (core/io/resource_loader.cpp:325)
GDScript backtrace (most recent call first):
[0] _initialize (res://check_coin.gd:5)
WARNING: res://coin.tscn:4 - ext_resource, invalid UID: uid://cmoaxedjfbo8h - using text path instead: res://coin.gd
at: load (scene/resources/resource_format_text.cpp:501)
GDScript backtrace (most recent call first):
[0] _initialize (res://check_coin.gd:5)
ERROR: Attempt to open script 'res://coin.gd' resulted in error 'File not found'.
at: load_source_code (modules/gdscript/gdscript.cpp:1139)
GDScript backtrace (most recent call first):
[0] _initialize (res://check_coin.gd:5)
ERROR: Failed loading resource: res://coin.gd.
at: _load (core/io/resource_loader.cpp:317)
GDScript backtrace (most recent call first):
[0] _initialize (res://check_coin.gd:5)
ERROR: res://coin.tscn:7 - Parse Error: [ext_resource] referenced non-existent resource at: res://art/coin.svg.
at: _printerr (scene/resources/resource_format_text.cpp:41)
GDScript backtrace (most recent call first):
[0] _initialize (res://check_coin.gd:5)
ERROR: res://coin.tscn:8 - Parse Error: [ext_resource] referenced non-existent resource at: res://coin.gd.
at: _printerr (scene/resources/resource_format_text.cpp:41)
GDScript backtrace (most recent call first):
[0] _initialize (res://check_coin.gd:5)
Texture: missing
Script: missing
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 move_session.py
After the first move, coin.tscn still says art/coin.svg and coin.gd, yet the texture and the script loaded from
sprites/ and scripts/, found by their UIDs. After the second, the old UIDs had gone with the deleted metadata files:
Godot warned “invalid UID … using text path instead”, the old paths led nowhere, and the coin had no texture and no
script.
The FileSystem dock moves the metadata files for you and updates the scenes that refer to a file. Godot’s documentation
asks you to move, rename and delete assets there, and when a file must move outside the editor, to move its .uid file
too.
Key idea
A file’s UID lives in its .uid or .import file, so commit those files and move them with their file: then every
reference survives the move, even one whose path is out of date.
Checkpoint
Check: UIDs and moving files
Two small files set Git up for a Godot project
When you choose Git in the New Project dialog, or Project > Version Control > Generate Version Control Metadata later,
Godot writes two files. Its 4.7.2 source writes exactly these lines: a .gitignore with .godot/ and /android/, the
folder the Android build template is installed into, and a .gitattributes with * text=auto eol=lf, which stores text
files with LF line endings on every system. A real project adds a few lines, as this session does: the binary
translations, a folder for exported builds, and Git LFS: An extension of Git that stores large binary files, such as textures and sounds, on a separate server and keeps small text pointers to them in the repository. Each machine installs it once. for large binary assets:
from session import files, run, write
files("dodge=.")
# The two files of the Project Manager's Git option, each with a few lines added for a real project.
write(".gitignore", """# Godot 4+ specific ignores
.godot/
/android/
# Binary translations Godot imports from CSV files
*.translation
# Exported builds
/builds/
""")
write(".gitattributes", """# Normalize EOL for all files that Git considers text files.
* text=auto eol=lf
# Large binary assets go to Git LFS
*.png filter=lfs diff=lfs merge=lfs -text
*.wav filter=lfs diff=lfs merge=lfs -text
*.ogg filter=lfs diff=lfs merge=lfs -text
*.glb filter=lfs diff=lfs merge=lfs -text
""")
run("""
git --version
godot --headless --quiet --import
mkdir builds && touch builds/dodge.x86_64
git init -q
git check-ignore -v .godot/uid_cache.bin builds/dodge.x86_64
!git check-ignore -v player.gd.uid art/coin.svg.import
git check-attr filter eol -- art/coin.svg art/coin.png sounds/jump.wav
git add -A
git status --short
""") Output
$ git --version git version 2.54.0 (Apple Git-157) $ godot --headless --quiet --import $ mkdir builds && touch builds/dodge.x86_64 $ git init -q $ git check-ignore -v .godot/uid_cache.bin builds/dodge.x86_64 .gitignore:2:.godot/ .godot/uid_cache.bin .gitignore:9:/builds/ builds/dodge.x86_64 $ git check-ignore -v player.gd.uid art/coin.svg.import [exit status 1] $ git check-attr filter eol -- art/coin.svg art/coin.png sounds/jump.wav art/coin.svg: filter: unspecified art/coin.svg: eol: lf art/coin.png: filter: lfs art/coin.png: eol: lf sounds/jump.wav: filter: lfs sounds/jump.wav: eol: lf $ git add -A $ git status --short A .gitattributes A .gitignore A art/coin.svg A art/coin.svg.import A check_coin.gd A check_coin.gd.uid A check_level.gd A check_level.gd.uid A coin.gd A coin.gd.uid A coin.tscn A paths_report.gd A paths_report.gd.uid A player.gd A player.gd.uid A project.godot A uses_player.gd A uses_player.gd.uid
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 git_session.py
git check-ignore -v names the rule that ignores each path: line 2 of .gitignore for the cache and line 9 for the
build. For player.gd.uid and art/coin.svg.import it printed nothing and ended with status 1, which here means good
news: neither is ignored. git check-attr shows that PNG and WAV files would go through the lfs filter, while the SVG,
a text file, stays in Git with LF endings. The final git status lists what the first commit will hold: every script with
its .uid, the texture with its .import, and no .godot/.
Two rules from Godot’s documentation keep LFS painless. Set it up before you commit any large file, because files
committed earlier stay in the history and must be removed and added again. And everyone who clones the repository needs
Git LFS installed; run git lfs install once per machine. If a project has no .gitattributes, Windows users should
also set git config --global core.autocrlf input, so Git does not mark files as changed by their line endings.
Ignoring the .import or .uid files
A .gitignore line such as *.import or *.uid looks tidy and breaks every reference by UID on the next clone:
each file gets a new UID there, and scenes fall back to their paths, with a warning for each, or fail when a path has
changed.
Checkpoint
Check: .gitignore, .gitattributes and Git LFS
A scene change is a one-line diff, and a conflict is a choice
A scene in text form makes version control useful: move one coin in the editor, save, and the diff shows one changed
line. When two branches change the same line, Git cannot choose, and the scene holds conflict markers until you do. This
session moves Coin2 on two branches, right on main and up on raise-coin, merges them, and checks the scene with
Godot before and after the conflict is resolved:
from session import files, run, write
# level.tscn and three versions of it as the editor saves it after Coin2 is moved, kept outside the project in ~/edits.
files("dodge=.", "edits/level.tscn=level.tscn", "edits=../edits")
write(".gitignore", "# Godot 4+ specific ignores\n.godot/\n/android/\n")
run("""
godot --headless --quiet --import
git init -q
git add -A
git commit -q -m "Level with three coins"
git switch -q -c raise-coin
cp ~/edits/coin2_up.tscn level.tscn
git commit -q -am "Raise coin 2"
git switch -q main
cp ~/edits/coin2_right.tscn level.tscn
git diff
git commit -q -am "Move coin 2 right"
!git merge raise-coin
sed -n '9,15p' level.tscn
!godot --headless --no-header --script check_level.gd
cp ~/edits/coin2_right_and_up.tscn level.tscn
godot --headless --no-header --script check_level.gd
git add level.tscn
git commit -q -m "Merge raise-coin: coin 2 right and up"
git log --oneline --graph
""") Output
$ godot --headless --quiet --import
$ git init -q
$ git add -A
$ git commit -q -m "Level with three coins"
$ git switch -q -c raise-coin
$ cp ~/edits/coin2_up.tscn level.tscn
$ git commit -q -am "Raise coin 2"
$ git switch -q main
$ cp ~/edits/coin2_right.tscn level.tscn
$ git diff
diff --git a/level.tscn b/level.tscn
index 280e8db..15a4f6f 100644
--- a/level.tscn
+++ b/level.tscn
@@ -7,7 +7,7 @@
[node name="Coin1" parent="." unique_id=1628878745 instance=ExtResource("1_coin")]
[node name="Coin2" parent="." unique_id=403667776 instance=ExtResource("1_coin")]
-position = Vector2(40, 0)
+position = Vector2(48, 0)
[node name="Coin3" parent="." unique_id=2076929865 instance=ExtResource("1_coin")]
position = Vector2(80, 0)
$ git commit -q -am "Move coin 2 right"
$ git merge raise-coin
Auto-merging level.tscn
CONFLICT (content): Merge conflict in level.tscn
Automatic merge failed; fix conflicts and then commit the result.
[exit status 1]
$ sed -n '9,15p' level.tscn
[node name="Coin2" parent="." unique_id=403667776 instance=ExtResource("1_coin")]
<<<<<<< HEAD
position = Vector2(48, 0)
=======
position = Vector2(40, -16)
>>>>>>> raise-coin
$ godot --headless --no-header --script check_level.gd
ERROR: Parse Error: Parse error. [Resource file res://level.tscn:12]
at: _parse_node_tag (scene/resources/resource_format_text.cpp:293)
GDScript backtrace (most recent call first):
[0] _initialize (res://check_level.gd:5)
ERROR: Failed loading resource: res://level.tscn.
at: _load (core/io/resource_loader.cpp:317)
GDScript backtrace (most recent call first):
[0] _initialize (res://check_level.gd:5)
level.tscn did not load
[exit status 1]
$ cp ~/edits/coin2_right_and_up.tscn level.tscn
$ godot --headless --no-header --script check_level.gd
Coin1 at (0.0, 0.0)
Coin2 at (48.0, -16.0)
Coin3 at (80.0, 0.0)
$ git add level.tscn
$ git commit -q -m "Merge raise-coin: coin 2 right and up"
$ git log --oneline --graph
* a13c282 Merge raise-coin: coin 2 right and up
|\
| * 82e453c Raise coin 2
* | a0dbd94 Move coin 2 right
|/
* 8c45742 Level with three coins
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 conflict_session.py
The diff is one line: position = Vector2(40, 0) became (48, 0). The merge stopped with a conflict on that line, and
the scene then held both versions between <<<<<<<, ======= and >>>>>>>. Godot cannot load a scene in that state:
the check printed a parse error at line 12, inside the conflict. The fix is a choice per property. Here the coin
should move both right and up, so the resolved line is position = Vector2(48, -16), the scene loads again, and the
merge commit records the choice.
Three habits keep scene conflicts rare and small. Split big levels into scenes, so that two people rarely edit the same
file. Commit often, so that conflicts cover few lines. And after upgrading to 4.6 or later, save every scene once with
Project > Tools > Upgrade Project Files, so the new unique_id of every node arrives in one commit instead of in
everyone’s next diffs.
Checkpoint
Check: Scene diffs and conflicts
Practise: set up version control for a project
Use the Pixel Dodge project of the earlier lessons, or any project of your own:
- Open the project’s folder in a terminal and run
git init. If the project has no.gitignoreand.gitattributes, create them with Project > Version Control > Generate Version Control Metadata. - Add
/builds/and*.translationto.gitignore, and LFS lines for the binary types your project uses to.gitattributes, as in the session; rungit lfs installif you use LFS. - Run
git check-ignore -v .godot/uid_cache.binand expect line 2 of.gitignore; run it on one.uidand one.importfile and expect no output and status 1. - Commit, then clone the repository into a new folder, run
godot --headless --importthere, and open it. - Move one asset to another folder in the FileSystem dock and look at
git status: the asset and its.importfile move together, and the scenes that use it change their paths.
Then work through the self-check below. It covers the five parts of this page, asks about the part you are least sure of first, and gives the reason for every answer straight away:
Practice that adapts to you
Practise this lesson
10 questions about 5 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
Interview questions
Warm-up: which files of a Godot project do you commit, and which do you ignore?
Commit the project: project.godot, scenes and resources (.tscn, .tres), scripts, assets, export_presets.cfg,
and the metadata files Godot writes beside them, every .import file of an asset and every .uid file of a script or
shader, because they hold the UIDs that references depend on. Ignore the .godot/ folder, a cache of imported copies,
the list of global classes and the UID cache that Godot rebuilds with one import, and *.translation files, binary
translations imported from CSV files. Exported builds stay out too. Godot’s own Git metadata writes a .gitignore
with .godot/ and /android/, and Git LFS is worth setting up for large binary assets before their first commit.
Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.
-
Fresher to mid
A teammate reorganised the art folders and pushed. On their machine everything works; on yours, half the scenes show missing textures and Godot prints "invalid UID" warnings. What most likely happened, and how do you repair the project without redoing the reorganisation?
-
Mid to senior
Your four-person team gets merge conflicts in .tscn files every week, mostly in one big level scene, and twice a conflicted scene was committed and broke the build. What do you change in the project, the workflow and the checks?
Key takeaways
res://is the project’s folder and read-only in an exported game,user://is a writable folder per project and user, named after the project, anduid://points to a file wherever it has moved.- Commit
project.godot, scenes, scripts, assets and every.importand.uidfile; never commit.godot/, which onegodot --headless --importrebuilds after a clone. - A file’s UID lives in its
.uidor.importfile, so moving the file with its metadata keeps every reference working, and moving it without breaks references whose paths have changed. - Godot’s Git option writes a
.gitignorefor.godot/and/android/and a.gitattributesfor LF line endings; add your builds, the binary translations and Git LFS for large assets before their first commit. - A scene change is a small text diff, a conflict is one property to choose, and Godot loading the scene afterwards is the check that the resolution worked.
Terms in this lesson
- .godot/ folder
- A project's cache: imported copies of its assets, the list of global classes and the UID cache. Godot rebuilds it from the project's files with one import, so it is never committed.
- .import file
- The file Godot writes beside each imported asset, such as coin.svg.import, with the asset's import settings and its UID. It belongs in version control with the asset.
- Git LFS
- An extension of Git that stores large binary files, such as textures and sounds, on a separate server and keeps small text pointers to them in the repository. Each machine installs it once.
- res://
- The path prefix for the files of a project: res:// is the folder that holds project.godot. It can be written while the project runs from the editor, and is read-only in an exported game.
- UID
- A unique id that Godot gives every file, written as uid:// and a code. References store it beside the path, so they keep working when the file moves together with its .uid or .import file.
- user://
- The path prefix for a game's writable data, such as saves, settings and logs, kept per project and user outside the project. On macOS it is ~/Library/Application Support/Godot/app_userdata/ and the project's name.
References
- File paths in Godot projects (Godot Engine)
- File system (Godot Engine)
- Version control systems (Godot Engine)
- Project organization (Godot Engine)
- UID changes coming to Godot 4.4 (Godot Engine)
- Godot 4.4 release page (universal UID support) (Godot Engine)
- Godot 4.5 release page (preload by UID) (Godot Engine)
- Godot 4.6 release page (unique node IDs, Upgrade Project Files) (Godot Engine)
- OS class reference: get_user_data_dir (Godot Engine)
- ResourceUID class reference (Godot Engine)
- Godot 4.7.2 source: editor_vcs_interface.cpp (the Git metadata files) (Godot Engine)
- git-check-ignore documentation (Git project)
- gitattributes documentation (Git project)
- Git Branching: Basic Branching and Merging (Git project)
- Gradle builds for Android (the res://android/build folder) (Godot Engine)
- Git Large File Storage (Git LFS project)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress