M1.1 — Project Settings & Window¶
What you'll learn
- Configure a 2D project's renderer, base resolution (1280×720), and
canvas_items/keepstretch. - Set
Nearesttexture filtering and 2D pixel snap for crisp pixel art — and recognize when not to. - Distinguish project settings (shipped) from user settings (per-machine) to avoid "works on my machine" bugs.
How it applies
- Hardware spread. Players run the game on 1366×768 laptops, 4K desktops, ultrawide 21:9 panels, and Steam Deck. The stretch settings make one base-resolution layout look right across all of them without per-device tuning. Skip the choice and the HUD lands off-screen on half of them.
- Readable combat at speed. An ARPG asks the player to read health bars, damage numbers, and
loot beams while moving and dodging.
canvas_itemsre-rasterizes everything sharp at the output size; the alternative (viewport) blits a soft pre-scaled image where small numbers smear. In a genre where the player's eyes are darting, soft text is a real defect. - Pixel-art integrity. If your sprites are pixel art, the default linear texture filter blurs them the moment the camera lands on a half-pixel. Setting Nearest filtering and pixel snap is the difference between crisp sprites and a smeared mess the first time the player walks diagonally.
- QA test-matrix size. With
canvas_items, a UI bug found at the base resolution stays fixed at every resolution, because the engine scales uniformly. Without it, every UI bug must be retested at every common resolution — a multiplicative explosion that crushes a small QA effort. This is equivalence partitioning applied to the resolution axis: one base case covers the class. project.godotis in source control. Every teammate (and future-you) who clones the repo inherits these settings. Setting them once prevents the class of bug where a setting lives only on the original developer's machine.
Concepts¶
The Project Settings dialog and project.godot¶
Every Godot project has one configuration file at its root, project.godot, an INI-style text file
checked into source control. You rarely edit it by hand; you edit it through the Project Settings
dialog (Project → Project Settings…), a tree of categories on the left and an inspector on the
right, with a search box at the top. The search box beats tree-walking — there are several hundred
settings.
Throughout this book, a menu path written Project → Project Settings → Display → Window → Stretch →
Mode means: open the dialog, navigate to the Display/Window page, find the Stretch sub-heading,
set the Mode field. Typing the last segment into the search box usually finds it faster.
Renderer — Forward+ is fine, and invisible here¶
When you create the project, Godot asks for a renderer: Forward+ (Vulkan, high-end desktop), Mobile, or Compatibility (GLES3, widest reach). For a 2D game drawing sprites and Control nodes, none of the renderer's differences are visible — there is no PBR, no dynamic 3D lighting in the scene. Pick Forward+ and move on. The one practical reason to prefer Compatibility is shipping to old hardware or the web; if that is a goal, choose it at creation, because switching later can invalidate shader caches.
Example
A 3D dungeon crawler with normal-mapped walls and many dynamic lights would care which renderer
it uses — Forward+ runs a clustered path built for that. Emberdelve draws Sprite2D, TileMap,
and Control nodes, so the renderer choice changes nothing the player sees. This is the last time
the book mentions it.
Base resolution and stretch¶
Two ideas decide how your game looks on a window that is not exactly your design size.
The base resolution is the width×height you design against. This book uses 1280×720 — a clean 16:9 canvas, large enough for a HUD with room and small enough to scale up sharply. Every camera zoom and every HUD anchor you set later is reasoned against these numbers. Pick once; changing it midway means re-checking every UI placement.
The stretch mode decides how the engine maps that base canvas onto the real window:
disabled— render to the window's real pixels; anchors recompute against the live window size. Fine for a desktop tool, wrong for a game whose layout you reasoned about at one size.viewport— render the whole game once into a fixed buffer, then blit that buffer to the window like a sprite. Pixel-exact, but text and 2D edges lose sub-pixel sharpness.canvas_items— render 2D nodes against the base resolution, then scale the result before raster, so glyphs and edges re-rasterize crisply at the output size. This is the one to use.
And the aspect setting handles a window whose shape differs from your 16:9 base:
ignore— stretch to fit; the image squashes.keep— letterbox or pillarbox with black bars; the base aspect is preserved without distortion.keep_width/keep_height— lock one axis and reveal more world on the other.
For a top-down ARPG, keep is the safe default: the HUD stays where you put it and no one sees a
squashed character. (Once the camera and world are in, some developers switch to expand to show more
of the dungeon on wider monitors; that is a deliberate later decision, not the starting point.)
Example
Base 1280×720, Mode = canvas_items, Aspect = keep:
- Resize to
1920×1080(same 16:9): the whole game scales 1.5× cleanly, no bars, sprites and HUD sharp. - Resize to
1920×1200(taller 8:5): scales to fit width with thin letterbox bars top and bottom. - Resize to
2560×720(ultrawide): scales to fit height with pillarbox bars left and right.
In every case the layout you designed at 1280×720 is what's on screen, just scaled. You never have
to reason about 1920×1200 specifically — the engine handles it.
Texture filter and pixel snap¶
Two settings matter the moment the camera moves, and they depend on your art:
- Default texture filter (
Rendering → Textures → Canvas Textures → Default Texture Filter).Linear(the default) smooths textures when sampled off-grid — correct for high-resolution or hand-painted art, blurry for pixel art. If your sprites are pixel art, set this to Nearest. - 2D pixel snap (
Rendering → 2D → Snap → Snap 2D Transforms To PixelandSnap 2D Vertices To Pixel). With the camera following the player, positions land on fractional pixels constantly; snapping rounds them to whole pixels so pixel-art rows don't shimmer.
If you are using smooth, high-resolution art instead of pixel art, leave Linear filtering on and skip
pixel snap. Decide based on the art, and write the decision down — it affects how every sprite reads.
Example
A pixel-art skeleton walks diagonally with the camera following. Under Linear filtering and no
snap, its outline shimmers and softens on every frame because it is sampled between texels. Under
Nearest + snap, each frame lands on whole pixels and the outline stays razor-crisp. The setting
is invisible while standing still and obvious the instant anything moves — which, in an ARPG, is
always.
Project vs user settings¶
Settings written to project.godot ship with the game and live in source control: base resolution,
stretch, filtering. Settings a player changes at runtime (volume, key rebinds, windowed/fullscreen)
must be written to a file under user:// instead — the only location an exported game may write to.
Confusing the two is the "works on my machine" bug: a value that exists only on your machine and is
missing on every clone or every player's install. M8 returns to user:// for saves; for now, just
hold the distinction.
Walkthrough¶
Perform these in your own empty Godot 4.6 project.
- Open
Project → Project Settings…. Application → Config: set Name to your project's title (suggested:Emberdelve). It shows in the window title bar.Display → Window → Size: set Viewport Width1280, Viewport Height720. These are your base-resolution numbers.Display → Window → Stretch: set Modecanvas_items, Aspectkeep.- If your art is pixel art:
Rendering → Textures → Canvas Textures → Default Texture Filter→ Nearest. ThenRendering → 2D → Snap→ enable Snap 2D Transforms To Pixel and Snap 2D Vertices To Pixel. (Skip this step for smooth/high-res art.) - Close the dialog. The values are written to
project.godotautomatically. - There is nothing to run yet — no main scene exists. Pressing
F5now will prompt you to choose one; cancel that. You set the main scene in M1.4.
Optional sanity check
Close and reopen Project → Project Settings…, navigate back to Display → Window, and confirm
the values held. If a field reverted, the dialog rejected your entry (usually a typo). Re-enter and
re-close.
Self-check quiz¶
Q1 — Which stretch Mode keeps small HUD numbers sharp at non-native window sizes?
A. disabled
B. viewport
C. canvas_items
D. expand
Reveal answer
C — canvas_items. It re-rasterizes 2D nodes at the scaled output size, so glyphs are drawn
at the real output resolution. viewport renders once at the base size then blit-scales the
image, softening text. disabled doesn't scale at all (anchors recompute against the live
window). D is a distractor: expand is an aspect value, not a mode — it answers a different
question (how to handle aspect drift), not how to resample.
Q2 — Your sprites are pixel art. The camera follows the player. Outlines shimmer when moving. Which pair of settings fixes it?
A. Aspect = keep and Mode = viewport.
B. Default Texture Filter = Nearest and 2D pixel snap enabled.
C. Default Texture Filter = Linear and a higher base resolution.
D. Increase the physics tick rate.
Reveal answer
B. Shimmer comes from sampling pixel-art texels off-grid as the camera lands on fractional positions. Nearest filtering stops the blur between texels; pixel snap rounds node positions to whole pixels so rows don't crawl. A confuses stretch settings with filtering. C keeps the blurring filter and only changes scale. D affects simulation timing, not rasterization.
Q3 — A player changes the music volume in your options menu. Where must that value be written, and why?
A. project.godot, so it ships with the game.
B. A file under user://, because an exported game can only write there and the value is per-player.
C. The scene file (.tscn) that holds the options menu.
D. It does not need to persist; re-derive it each launch.
Reveal answer
B. project.godot and scene files live under res://, which is read-only in an exported
build, and they are shared by all players — wrong place for a per-player runtime choice. The
per-user writable location is user://. Writing player preferences to res:// is the
"works on my machine" bug: it appears to work in the editor (where res:// is writable) and
fails in the exported game. D is wrong because the player expects their volume to stick.
Integration question¶
Q4 — open
You set base resolution 1280×720, canvas_items, keep, and (for pixel art) Nearest filtering
plus pixel snap — all before there is a single sprite in the project. In the next chapter you set up
the folder layout. Why do both the stretch decision and the folder-layout decision belong in M1,
before any gameplay exists, rather than later when you have art and scenes to organize?
Reveal expected answer
Both lock a coordinate system that every later chapter is reasoned against. Stretch + base
resolution fix the meaning of every camera zoom and HUD anchor you will choose from M2 onward;
the folder layout fixes the meaning of every res:// path you will type from M2 onward.
Choosing either one after code depends on it forces you to rewrite the chapters that assumed
the old value. M1 is the "lock the coordinate systems" module: do it once, up front, so M2–M8
never have to revisit it.