Godot scaffold
Design game architecture and produce a compilable Godot project skeleton in C# (.NET) or GDScript (project.godot, .csproj for C#, STRUCTURE.md, script stubs, scene-builder stubs). Works for fresh projects and for adding/reworking subsystems in an existing one.From its SKILL.md
npx -y skills add cnqdztp/godot-skills --skill godot-scaffoldAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
21.8 KB, as published. Nobody here has run it
Godot Scaffold Generator
Design game architecture and produce a compilable Godot project skeleton: project.godot, STRUCTURE.md, script stubs, and scene builder stubs (plus a .csproj for C# projects). Defines what exists and how it connects — not behavior.
Works for both fresh projects and incremental changes (adding scenes/scripts, reimplementing subsystems).
Language: C# (.NET) or GDScript?
Detect the project's language before scaffolding — the steps below fork by language:
- A
.csproj/.slnin the repo, or a[dotnet]section inproject.godot⇒ C# (.NET). - Otherwise ⇒ GDScript.
For a fresh project, follow the language the user asked for (default to GDScript if unspecified and there is no .NET toolchain). The shared parts (project.godot, STRUCTURE.md, the scene hierarchy / architecture design) are identical for both; only the script stubs, scene-builder stubs, and the build/verify commands differ. Each forking step below is tagged (C# / .NET) or (GDScript).
Workflow
- Check installed toolchain first — run
godot --version(always). [C# only] also rundotnet --versionbefore writing the.csproj. Record the detected versions inMEMORY.mdif the project is long-running. - Match templates to the local toolchain — never hardcode version-sensitive values from an example. At minimum, match:
project.godotconfig_version(both languages)- [C# only]
Project Sdk="Godot.NET.Sdk/..." - [C# only]
TargetFramework - any other engine-version-specific keys already present in an existing
project.godotIf a project already exists, preserve those version-sensitive values unless the user explicitly asked for a toolchain migration.
- Read
reference.png— understand the visual target: camera angle, distance, FOV, lighting direction, environment structure, scene layout. Use this to inform architecture decisions (node hierarchy, camera setup, lighting rig). - Read input — game description (fresh) or change request (incremental).
- Assess project state:
- No project → create from scratch.
- Existing project, fresh start requested → delete existing scenes/scripts.
- Existing project, incremental change → read existing
STRUCTURE.md,project.godot, scripts (and.csprojfor C#). Identify what to add or replace. Preserve unchanged files, especially version-sensitive settings.
- Design / update architecture — scenes, scripts, signals, input actions. (shared — language-agnostic)
- Write/update
project.godot— create or merge input mappings. [C# only] include[dotnet]section. Match the local engine's expectedconfig_version; do not guess. (shared; the[dotnet]line is the only C#-specific part) - (C# / .NET) Write
.csproj— create or verify the project file exists. Match the local Godot/.NET toolchain instead of hardcoding an SDK version from examples. (GDScript) Skip — there is no.csproj; GDScript compiles on import. - Write
STRUCTURE.md— always the complete architecture, not a diff. (shared — language-agnostic) - Write script stubs — for new scripts and any existing scripts the task explicitly asks to replace. (C# / .NET)
.csfiles inscripts/. (GDScript).gdfiles inscripts/. - Write the scene-builder base — shared base class (see the
godot-scene-builderskill). Create once per project; skip if it already exists. (C# / .NET)scenes/SceneBuilderBase.cs. (GDScript) the builders extendSceneTreedirectly, so there is no required base file — skip unless you factor out shared helpers intoscenes/scene_builder_base.gd. - (C# / .NET) Build .NET project —
timeout 60 dotnet build 2>&1. If this fails because the target framework or Godot SDK version does not match the installed toolchain, fix that first before changing game code. (GDScript) Skip — there is no compile step; GDScript is checked on import. - Import assets —
timeout 60 godot --headless --import 2>&1. If Godot fails here becauseproject.godothas the wrongconfig_versionor incompatible engine keys, fix the project file before proceeding. (shared — both languages) - Build scene stubs — for each new/changed scene, write a scene builder script, then run in dependency order (leaf scenes first). (C# / .NET)
scenes/BuildXxx.cs→timeout 60 godot --headless --script scenes/BuildXxx.cs. (GDScript)scenes/build_xxx.gd→timeout 60 godot --headless --script scenes/build_xxx.gd. - Verify —
timeout 60 godot --headless --quit 2>&1. NoERRORlines. RID warnings are benign. (shared — both languages) - Git commit — repo is already initialized before Claude starts:
git add -A && git commit -m "scaffold: project skeleton"
Version-Sensitive Files
Treat these as local-toolchain outputs, not static templates:
project.godotconfig_version(both languages)- [C# only]
.csprojProject Sdk="Godot.NET.Sdk/..." - [C# only]
.csprojTargetFramework
Rules:
- Existing project -> preserve the current values unless the user explicitly asked to migrate the toolchain.
- Fresh project -> detect the installed versions first and write matching values.
- Do not blindly copy
config_version=6,Godot.NET.Sdk/4.4.0, ornet9.0from examples. - If the first
godot --headless --importorgodot --headless --quitreports a project-version mismatch, stop and fix the version fields before doing any more work.
Output Files
1. project.godot (shared — both languages)
This example is schematic. Version-sensitive fields must match the installed local engine, not the literal values shown in an old template. The [dotnet] section is [C# only] — omit it for GDScript projects. Autoload paths point at .cs files for C# and .gd files for GDScript.
; Engine configuration file
; Do not edit manually
config_version={match local Godot}
[application]
config/name="{ProjectName}"
run/main_scene="res://scenes/main.tscn"
[display]
window/size/viewport_width=1280
window/size/viewport_height=720
window/stretch/mode="canvas_items"
window/stretch/aspect="expand"
; [C# only] — omit this whole section for GDScript projects:
[dotnet]
project/assembly_name="{ProjectName}"
[physics]
common/physics_ticks_per_second=120
common/physics_interpolation=true
; 3D only — omit for 2D projects:
3d/physics_engine="Jolt Physics"
[rendering]
; 3D games:
lights_and_shadows/directional_shadow/soft_shadow_filter_quality=3
anti_aliasing/quality/msaa_3d=2
; 2D pixel art (instead of above):
; textures/canvas_textures/default_texture_filter=0
; 2d/snap/snap_2d_transforms_to_pixel=true
[layer_names]
; Name collision layers used by the game:
2d_physics/layer_1="player"
2d_physics/layer_2="enemies"
; (add as needed)
[autoload]
; Singletons — point at the script for the project's language:
; C#: GameManager="*res://scripts/GameManager.cs"
; GDScript: GameManager="*res://scripts/game_manager.gd"
[input]
move_forward={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":87,"key_label":0,"unicode":119)]
}
Key physical keycodes: W=87, A=65, S=83, D=68, Up=4194320, Down=4194322, Left=4194319, Right=4194321, Space=32, Enter=4194309, Escape=4194305, Shift=4194325, Ctrl=4194326, Alt=4194328.
Mouse buttons use InputEventMouseButton with button_index (1=left, 2=right) and matching button_mask:
fire={
"deadzone": 0.2,
"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":1,"position":Vector2(0,0),"global_position":Vector2(0,0),"factor":1.0,"button_index":1,"canceled":false,"pressed":true,"double_click":false)]
}
2. .csproj — [C# only] (GDScript projects have no .csproj)
This example is schematic. Match the installed Godot and .NET versions on the machine running the project.
<Project Sdk="Godot.NET.Sdk/{match local Godot/.NET integration}">
<PropertyGroup>
<TargetFramework>{match installed dotnet}</TargetFramework>
<EnableDynamicLoading>true</EnableDynamicLoading>
</PropertyGroup>
</Project>
The file must be named {ProjectName}.csproj matching the assembly_name in project.godot.
3. STRUCTURE.md (shared — language-agnostic)
Complete architecture reference. Always written in full, even for incremental updates. The scene hierarchy, signal map, and asset hints are identical regardless of language; only the script file extensions in the paths differ (.cs for C#, .gd for GDScript).
# {Project Name}
## Dimension: {2D or 3D}
Decide the world's dimension and node family BEFORE listing scenes — pick by responsibility (see godot-scene-builder's "Choosing the node family"):
- **2D game world** → roots `Node2D` / `CharacterBody2D` / `Area2D`; art `Sprite2D`/`AnimatedSprite2D`; tiles `TileMapLayer`; camera `Camera2D`.
- **3D game world** → roots `Node3D` / `CharacterBody3D`; art `MeshInstance3D` / GLB; camera `Camera3D`.
- **UI / HUD** (either dimension) → `Control` on a `CanvasLayer` above the world (see godot-ui-tscn).
The worked STRUCTURE.md below is a **3D** example; a **2D** project is identical with the 2D roots — e.g. Main root `Node2D`, Player root `CharacterBody2D`, scripts extending `CharacterBody2D`, art `Sprite2D`.
## Input Actions
| Action | Keys |
|--------|------|
| move_forward | W, Up |
| jump | Space |
## Scenes
### Main
- **File:** res://scenes/main.tscn
- **Root type:** Node3D
- **Children:** Player, Enemy
### Player
- **File:** res://scenes/player.tscn
- **Root type:** CharacterBody3D
## Scripts
### PlayerController
- **File:** res://scripts/PlayerController.cs ; (GDScript: res://scripts/player_controller.gd)
- **Extends:** CharacterBody3D
- **Attaches to:** Player:Player
- **Signals emitted:** Died, Scored
- **Signals received:** HurtBox.AreaEntered -> OnHurtEntered
- **Instantiates:** Bullet
## Signal Map
- Player:HurtBox.AreaEntered -> PlayerController.OnHurtEntered
- Main:GoalArea.BodyEntered -> LevelManager.OnGoalReached
## Asset Hints
- Player character model (~1.8m tall humanoid)
- Ground texture (tileable grass, 2m repeat)
- Sky panorama (360° daytime sky)
Architecture graph plus asset hints for the asset planner. No descriptions, no requirements, no task ordering.
4. .gitignore
Assets, tools, and build artifacts stay out of git:
assets
screenshots
.godot
*.import
bin/
obj/
bin/ and obj/ are [C# only] build artifacts; harmless to leave in a GDScript .gitignore but unnecessary.
4b. screenshots/.gdignore
Create screenshots/ with an empty .gdignore so Godot's importer skips screenshot PNGs (they're not game textures):
mkdir -p screenshots && touch screenshots/.gdignore
Do NOT create .gdignore in assets/ or any subdirectory of it — Godot must import those files. .gdignore makes the importer skip the entire directory silently.
Runtime vs reference/debug separation
assets/ contains only files the running game loads — it is the exportable subtree. Generation inputs (reference images used only to seed image_to_model, pose frames that don't ship, debug grids), sidecars, and meta files go outside assets/ — either at repo root (like reference.png) or under a sibling like refs/ or _meta/ with its own .gdignore. Mixing them bloats APK exports and muddies what the asset planner considers "the asset set".
5. Script stubs
Write the stub in the project's language. Both versions encode the same architecture: correct base class, signal declarations, exported defaults, and empty lifecycle + handler methods.
(C# / .NET) — scripts/*.cs
using Godot;
/// res://scripts/PlayerController.cs
public partial class PlayerController : CharacterBody3D
{
[Signal] public delegate void DiedEventHandler();
[Signal] public delegate void ScoredEventHandler();
[Export] public float Speed = 7.0f;
[Export] public float JumpVelocity = -4.5f;
public override void _Ready()
{
}
public override void _PhysicsProcess(double delta)
{
}
private void OnHurtEntered(Area3D area)
{
}
}
Correct base class, signal delegate declarations, [Export] defaults, empty lifecycle and handler methods. All classes partial.
(GDScript) — scripts/*.gd
GDScript has no .csproj and no compile step — it is checked on import. Use extends <BaseClass>, @export var (instead of [Export]), and signal declarations (instead of [Signal] delegates). No partial, no EventHandler suffix.
# res://scripts/player_controller.gd
extends CharacterBody3D
signal died
signal scored
@export var speed: float = 7.0
@export var jump_velocity: float = -4.5
func _ready() -> void:
pass
func _physics_process(delta: float) -> void:
pass
func _on_hurt_entered(area: Area3D) -> void:
pass
Correct extends base class, signal declarations, @export var defaults, empty lifecycle and handler methods. Add class_name PlayerController only if the script needs a global type name.
6. Scene-builder base
(C# / .NET) — scenes/SceneBuilderBase.cs — create once; all builders inherit from this. See the godot-scene-builder skill for the full base class.
(GDScript) — there is no required base file. GDScript builders extends SceneTree directly and implement func _initialize(). Only factor out a shared scenes/scene_builder_base.gd if multiple builders share helper code.
7. Scene builder stubs
Write each scene builder in the project's language — replace all UPPER_CASE placeholders with concrete values, delete optional blocks (SCRIPT, CHILDREN) that don't apply.
(C# / .NET) — scenes/Build*.cs
using Godot;
/// Scene builder — run: dotnet build && timeout 60 godot --headless --script scenes/Build<Name>.cs
public partial class Build<Name> : SceneBuilderBase
{
public override void _Initialize()
{
var temp = new Node();
var root = new ROOT_TYPE(); // REPLACE ROOT_TYPE — e.g. CharacterBody3D
root.Name = "ROOT_NAME"; // REPLACE ROOT_NAME — e.g. "Player"
temp.AddChild(root);
// CHILDREN — delete block if none, duplicate per child
var childVar = GD.Load<PackedScene>("CHILD_PATH").Instantiate(); // REPLACE CHILD_PATH
childVar.Name = "CHILD_NAME"; // REPLACE CHILD_NAME
root.AddChild(childVar);
// SCRIPTS — set LAST (SetScript disposes C# wrapper — see the godot-quirks skill)
root.SetScript(GD.Load("SCRIPT_PATH")); // REPLACE — e.g. "res://scripts/PlayerController.cs"
// Re-obtain root (old wrapper is disposed)
var rootNode = temp.GetChild(0);
temp.RemoveChild(rootNode);
temp.Free();
PackAndSave(rootNode, "OUTPUT_PATH"); // REPLACE — e.g. "res://scenes/player.tscn"
}
}
(GDScript) — scenes/build_*.gd
GDScript scene builders extends SceneTree and run via godot --headless --script scenes/build_xxx.gd. No dotnet build precedes them — they run straight after --import. Assigning the script does not dispose a wrapper (that C# quirk does not apply), so the owner/pack flow is simpler.
# Scene builder — run: timeout 60 godot --headless --script scenes/build_<name>.gd
extends SceneTree
func _initialize() -> void:
var root := ROOT_TYPE.new() # REPLACE ROOT_TYPE — e.g. CharacterBody3D
root.name = "ROOT_NAME" # REPLACE ROOT_NAME — e.g. "Player"
# CHILDREN — delete block if none, duplicate per child
var child := load("CHILD_PATH").instantiate() # REPLACE CHILD_PATH
child.name = "CHILD_NAME" # REPLACE CHILD_NAME
root.add_child(child)
child.owner = root
# SCRIPT — delete if none
root.set_script(load("SCRIPT_PATH")) # REPLACE — e.g. "res://scripts/player_controller.gd"
var packed := PackedScene.new()
packed.pack(root)
ResourceSaver.save(packed, "OUTPUT_PATH") # REPLACE — e.g. "res://scenes/player.tscn"
quit(0)
Every child's owner must be set to root (directly or transitively) or it will be dropped from the packed scene. Always quit(0) at the end so the headless run exits.
CRITICAL: Build order is specified in STRUCTURE.md. The ## Build Order section lists the exact sequence. Follow it mechanically — do not infer or reorder.
UI Overlay Architecture
For HUD/menus, add to the main scene:
Main (Node3D or Node2D)
├── ... game nodes ...
└── CanvasLayer (layer=1)
└── Control (anchors_preset=15, full rect)
├── VBoxContainer or HBoxContainer
│ ├── Label (score)
│ ├── ProgressBar (health)
│ └── Button (pause)
└── ...
Layout containers:
VBoxContainer— vertical stack;HBoxContainer— horizontalGridContainer— grid (setColumnsproperty)MarginContainer— padding;CenterContainer— centering;PanelContainer— with backgroundSizeFlagsHorizontal/Vertical = SizeFlags.ExpandFill(C#) /size_flags_horizontal = SIZE_EXPAND_FILL(GDScript)CustomMinimumSize(C#) /custom_minimum_size(GDScript) for fixed dimensions
For pause menus, set the CanvasLayer to run during pause: ProcessMode = ProcessModeEnum.Always (C#) / process_mode = Node.PROCESS_MODE_ALWAYS (GDScript).
Architecture Rules
- Explicit 2D or 3D — never mix dimensions in one hierarchy; when a scene needs both, render the secondary family in a
SubViewportand composite it back (see godot-scene-builder). UI isControlon aCanvasLayerabove the world. - Declare all input actions — anything used by scripts must appear in input table and project.godot.
- Signal contracts — if script A emits signal X, receivers must list it in the signal map.
Common Built-in Signals
- Area2D/3D — BodyEntered, BodyExited, AreaEntered, AreaExited
- Button — Pressed
- Timer — Timeout
- AnimationPlayer — AnimationFinished
- RigidBody2D/3D — BodyEntered (ContactMonitor required)
Common Errors
(C# / .NET)
CS0260: Missing partial modifier— all Godot C# classes MUST be declaredpartial. Addpartialkeyword.- [C# only]
dotnet buildmust succeed beforegodot --headless --script— scene builders and runtime scripts compile together; a build failure means none of the script changes take effect. Always run and passdotnet buildfirst. - [C# only] C# enum names — don't guess, look up — LLM training data is mostly GDScript, so C# enum member names (e.g.
ProcessModeEnum.Always,SizeFlags.ExpandFill,Key.Space) are easy to hallucinate. Verify the exact C# spelling (via thegodot-apiskill) rather than translating a GDScript constant. GD.Load()returns null — asset not imported yet. Rungodot --headless --importfirst.- Scene builder hangs — missing
Quit()call. The template includesQuit(0)— never remove it. - Signal delegate wrong name — must end in
EventHandler.[Signal] public delegate void Died();fails silently; useDiedEventHandler.
(GDScript)
load()returns null — asset not imported yet. Rungodot --headless --importfirst.- Scene builder hangs — missing
quit()call. The template includesquit(0)— never remove it. - Child missing from packed scene — every child's
ownermust be set to the scene root (directly or transitively) beforePackedScene.pack(), or it is silently dropped. - Parse error on import — GDScript is validated at import, not compiled ahead of time; a typo surfaces as an
ERRORduring--import/--quit, not at a separate build step.
Asset Hints in STRUCTURE.md
Assets are generated AFTER scaffold. Include an ## Asset Hints section at the end of STRUCTURE.md listing what visual assets the architecture needs. The asset planner uses these to decide what to generate.
## Asset Hints
- Player character model (~1.8m tall humanoid)
- Ground texture (tileable grass, 2m repeat)
- Sky panorama (360° daytime sky)
- Enemy model (~1m tall creature)
Be specific about type (model, texture, background, sprite), approximate size, and visual role. Don't describe style — the asset planner chooses that.
Build Order
The scaffold emits an explicit build order in STRUCTURE.md based on scene dependency analysis. Leaf scenes (no child scene references) first, parents after.
(C# / .NET) — dotnet build is always step 1:
## Build Order
1. dotnet build
2. scenes/BuildPlayer.cs → scenes/player.tscn
3. scenes/BuildEnemy.cs → scenes/enemy.tscn
4. scenes/BuildMain.cs → scenes/main.tscn (depends: player.tscn, enemy.tscn)
(GDScript) — no dotnet build; the first step is the import (GDScript is checked then):
## Build Order
1. godot --headless --import
2. scenes/build_player.gd → scenes/player.tscn
3. scenes/build_enemy.gd → scenes/enemy.tscn
4. scenes/build_main.gd → scenes/main.tscn (depends: player.tscn, enemy.tscn)
The task executor follows this order mechanically. Do not rely on the executor to infer dependencies.
What NOT to Include
- Implementation details or behavior descriptions
- Task ordering
- Lighting, environment, tonemapping, post-processing
- Any logic beyond empty method stubs — real scripts are written in a later stage