Skip to content

M1.1 — Project Settings & Window

What you'll learn

  • Configure a 2D project's renderer, base resolution (1280×720), and canvas_items/keep stretch.
  • Set Nearest texture 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_items re-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.godot is 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 Pixel and Snap 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.

  1. Open Project → Project Settings….
  2. Application → Config: set Name to your project's title (suggested: Emberdelve). It shows in the window title bar.
  3. Display → Window → Size: set Viewport Width 1280, Viewport Height 720. These are your base-resolution numbers.
  4. Display → Window → Stretch: set Mode canvas_items, Aspect keep.
  5. If your art is pixel art: Rendering → Textures → Canvas Textures → Default Texture FilterNearest. Then Rendering → 2D → Snap → enable Snap 2D Transforms To Pixel and Snap 2D Vertices To Pixel. (Skip this step for smooth/high-res art.)
  6. Close the dialog. The values are written to project.godot automatically.
  7. There is nothing to run yet — no main scene exists. Pressing F5 now 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.