Skip to content
Agentmart
Submit a tool

godot-engine

Godot 4 engine quirks, known bugs, workarounds, scene builder patterns, and material/lighting setup.

Skill by dragoscirjan

IndexedMIT
Write a review

Install

Works with Claude Code, Claude apps, Codex, Cursor, Gemini CLI, GitHub Copilot, VS Code, OpenCode, Goose, Windsurf. Read the repository before you give any tool access to your data.

npx agentmarket install skill/dragoscirjan-godot-engine

Asks which of your tools to add it to (Claude Code, Codex, Cursor, VS Code, Windsurf, Gemini CLI, Claude Desktop) and shows the change first. See https://agentmart.dev/cli.

Get the weekly diff

The weekly diff: one email a week with new agent tools, notable updates and grade changes, plus security alerts for tools you follow. Double opt-in, one-click unsubscribe.

We use your email only for this newsletter. Privacy

About

Godot 4 engine quirks, known bugs, workarounds, scene builder patterns, and material/lighting setup. Load before writing GDScript that interacts with the engine.

Use cases: writing

No reviews yet. Installed it? Reviews come from verified installers only. How reviews work

Permissions

What this skill can do and what it asks for, from its SKILL.md. Checked Oct 25, 2026.

Tools
No tools found
Secrets
None asked for
Install scripts
None found

Network hosts it talks to 1

  • codeload.github.com · found in install command

Files it touches

  • ~/.claude/skills/godot-engine

What's inside

Skill name
godot-engine
Description
Godot 4 engine quirks, known bugs, workarounds, scene builder patterns, and material/lighting setup. Load before writing GDScript that interacts with the engine.
License
MIT
Instructions
334 lines in SKILL.md · view on GitHub

Files (1)

  • SKILL.md

SKILL.md (first lines)

# Godot Engine — Quirks & Scene Generation

## Two Script Types

GDScript files in this pipeline fall into two categories. Confusing them is a major bug source.

### Scene Builders (build-time, headless)

- `extends SceneTree` with `_initialize()` entry point
- Run via `godot --headless --script build_scene.gd`
- Build node hierarchy, set owners, pack, validate, save `.tscn`, then `quit()`
- Do NOT use: `@onready`, `preload()`, signal connections, `look_at()`, `to_global()`
- `_process()` signature: `func _process(delta: float) -> bool:` (returns bool, not void)

### Runtime Scripts (game logic)

- `extends Node2D/3D`, `CharacterBody3D`, etc.
- Use `_ready()`, `_process()`, `_physics_process()`, signals, `@onready`
- Attached to scene nodes via `set_script()` in builders or editor

## Known Quirks

### Headless / Scene Builder Specific

- **RID leak errors on exit** — headless builders always produce these. Harmless; ignore.
- **`add_to_group()` in builders** — groups set at build-time persist in saved `.tscn` files.
- **`_ready()` skipped in `_initialize()`** — `_ready()` on instantiated scene nodes does NOT fire during `_initialize()`. Call init methods manually after `root.add_child()`.
- **Autoloads in SceneTree scripts** — cannot reference autoload singletons by name (compile error). Find via `root.get_children()` and match by `.name`.
- **`preload()` vs `load()` during generation** — do NOT use `preload()` for scenes/resources that may not exist yet. Use `load()`.
- **No spatial methods in `_initialize()`** — `look_at()`, `to_global()` fail because nodes aren't in the tree yet. Use `rotation_degrees` or compute transforms manually.

### Serialization

- **MultiMeshInstance3D + GLBs** — does NOT render after pack+save (mesh resource reference lost). Use individual GLB instances instead.
- **GLB `material_override` doesn't serialize** — setting `material_override` on GLB-internal MeshInstance3D nodes does NOT persist in `.tscn` because `set_owner_on_new_nodes()` skips GLB children. Use procedural ArrayMesh when custom material is required.
- **MultiMeshInstance3D `Mesh.duplicate()`** — needed before freeing the source GLB instance, otherwise the mesh resource is garbage-collected.

### Physics

- **`free()` vs `queue_free()` in test harnesses** — `queue_free()` leaves the node in `root.get_children()` until frame end, blocking name reuse. Use `free()` when immediately replacing scenes.
- **BoxShape3D on trimesh** — snags on collision edges (Godot/Jolt bug). Use CapsuleShape3D for objects that slide across trimesh surfaces.
- **Default collision mask misses non-default layers** — new bodies get `collision_mask = 1`. If terrain/walls use layer 2+, player falls through. Always set mask to include all needed layers.
- **Collision layer bitmask vs UI index** — `collision_layer`/`collision_mask` are bitmasks. UI Layer 1 = bitmask 1, Layer 2 = bitmask 2, Layer 3 = bitmask 4, Layer 4 = bitmask 8 (powers of 2).
- **Collision state changes in callbacks** — changing `.disabled` inside `body_entered`/`body_exited` → "Can't change state while flushing queries". Use `set_deferred("disabled", false)`.
- **`CharacterBody3D.MOTION_MODE_FLOATING`** — needed for non-platformer 3D movement (vehicles, snowboards). GROUNDED mode's `floor_stop_on_slope` fights slope movement.
- **`reset_physics_interpolation()`** — call when teleporting or switching cameras to prevent interpolation glitch.
- **2D collision shape sizing** — slightly smaller than tile (e.g., 48px in 64px grid) allows smooth cornering through 1-tile corridors.

### Camera

- **Camera2D has no `current` property** — use `make_current()`, only after node is in scene tree.
- **Camera lerp from origin** — cameras using `lerp()` in `_physics_process()` will swoop from (0,0,0) on first frame. Use an `_initialized` flag to snap position on first frame, then lerp.
- **Chase camera `current` re-assertion** — game cameras that set `current = true` in `_physics_process()` override the test harness camera every frame. Test harnesses must disable the game camera EVERY frame.

### Video/Movie Writer

- **`--write-movie` frame 0** — first frame renders before `_process()` runs. Camera position set in `_process()` won't appear until frame 1. Pre-position camera in `_initialize()` via `position`/`rotation_degrees` (NOT `look_at()`).
- **`await` during `--write-movie`** — `await get_tree().process_frame` advances the movie frame counter each tick. Use `_init_frames` counter in `_physics_process()` instead of await chains.

### Visual / Material

- **Frame-rate dependent drag** — `speed *= (1 - drag)` per tick is exponential decay tied to tick rate. Use `speed *= exp(-rate * delta)` for frame-rate independent damping.
- **ProceduralSkyMaterial sun disc** — uses DirectionalLight3D direction/color. Set `sky_mode = SKY_MODE_LIGHT_AND_SKY` on sun, `SKY_MODE_LIGHT_ONLY` on fill lights — otherwise multiple sun discs appear.
- **Smooth yaw tracking 360 spin** — `lerp()` on raw angles causes 360-degree spin-arounds. Wrap difference to [-PI, PI]: `var diff: float = fmod(target_yaw - current_yaw + 3.0 * PI, TAU) - PI`.
- **`StandardMaterial3D` with `no_depth_test = true` + `TRANSPARENCY_ALPHA`** → invisible. Use opaque + unshaded for overlays.
- **Z-fighting** between layered surfaces: offset 0.15-0.30m vertically + `render_priority = 1`.
- **`cull_mode = CULL_DISABLED`** as safety net on all procedural meshes until winding is confirmed correct.
- **UV tiling double-scaling** — do NOT use world-space UV coords AND `uv1_scale` together. Pick one.
- **MultiMeshInstance3D `custom_aabb`** — must cover the entire visible area. Without it, the MultiMesh gets frustum-culled when the camera moves to edges.
- **MultiMeshInstance3D materials** — has no `set_surface_override_material()`. Use `material_override` or keep materials from source mesh.

### Timing / Signals

- **Sibling signal timing in `_ready()`** — `_ready()` fires on children in tree order. If sibling A emits in its `_ready()`, sibling B hasn't connected yet. Fix: after connecting, check if the emitter already has data and call the handler manually.
- **`get_path()` is a built-in Node method** — returns NodePath. Cannot override. Name yours `get_track_path()`, `get_road_path()`, etc.
- **Spawn immunity for revealed items** — items spawned inside an active Area2D get `area_entered` immediately → destroyed same frame. Track `_alive_time` in `_process()`, ignore `area_entered` for ~0.8s.

## Type Inference Errors

Three common issues — applies in both scene builders and runtime scripts:

```gdscript
# WRONG — load() returns Resource, no instantiate():
var scene := load("res://assets/glb/car.glb")
var model := scene.instantiate()  # Error

# WRONG — := with instantiate() causes Variant inference error:
var scene: PackedScene = load("res://assets/glb/car.glb")
var model := scene.instantiate()  # Error

# CORRECT — type load() AND use = (not :=) for instantiate():
var scene: PackedScene = load("res://assets/glb/car.glb")
var model = scene.instantiate()  # Works

# WRONG — := with polymorphic math functions (return Variant):
var x := abs(speed)              # Error
# Affected: abs, sign, clamp, min, max, floor, ceil, round, lerp,
#   smoothstep, move_toward, wrap, snappedf, randf_range, randi_range

# WRONG — := with array/dictionary element access (returns Variant):
var pos := positions[i]          # Error

# CORRECT — explicit type or untyped:
var pos: Vector3 = positions[i]  # OK
var val = my_dict["key"]         # OK (untyped)
```

## Pass-by-Value Pitfall

`bool`, `int`, `float`, `Vector3`, `AABB`, `Transform3D` etc. are value types — assigning to a parameter inside a function does NOT update the caller:

```gdscript
# WRONG — result never updates caller:
func collect(node: Node, result: AABB) -> void:
    result = result.merge(child_aabb)  # lost at return

# CORRECT — use Array accumulator:
func collect(node: Node, out: Array) -> void:
    out.append(child_aabb)

From dragoscirjan/opencode-config, MIT.

More from dragoscirjan/opencode-config

Signals

Stars
0
Forks
0
Open issues
3
Last commit (this folder)
May 28, 2026 (5 months ago)
Last release
Jun 2, 2026 (4 months ago)

From GitHub (fetched Oct 24, 2026).

Details

License
MIT
First seen
Oct 24, 2026
Last checked
Oct 24, 2026

More from dragoscirjan

Cite this page

godot-engine skill. Agent Mart. https://agentmart.dev/skills/dragoscirjan-godot-engine (last updated 2026-10-24)