Skip to main content
game-feel is a skill from the Summer skill library: a guide your AI agent reads when the task matches, so it builds the good version on the first try. Add juice: hit-flash, trauma camera shake and audio ducking, one hit fires all three.

When your agent uses it

  • A Summer game feels flat, lacks impact, needs juice or punch
  • The user asks for hit feedback, screen shake, camera shake, or audio ducking

Install

summer setup installs every skill for your agent. To add only this one:
Swap claude-code for your agent, such as codex or cursor. See Skills for every option.

The skill

Particles do not make a game feel good. Screen shake alone does not either. What makes a hit land is three systems firing on the same frame: a sub-frame hit flash on the body that got hit, trauma-based camera shake with quadratic falloff so big hits saturate, and audio ducking that briefly squashes Music/Ambient so the impact SFX punches through. This skill installs the canonical Summer Engine stack as three small systems wired to one signal.

Step 1 — Ask what feels flat

Open with one focused question. Wait for the answer.
What feels flat? Pick one to start: combat hits / pickups / explosions / spell casts / something else.
If the user says “all of it”, anchor on combat hits — the hit-flash + shake + duck trio is the loudest perceived improvement per minute, and pickups/explosions reuse the same building blocks afterwards.

Step 2 — Read the current scene state

Before adding anything, see what’s there. Preferred (Summer MCP):
You’re looking for: a MeshInstance3D on the hurt body (for the flash), an existing Camera3D on the player (don’t spawn a new one), and the names of the audio buses already present (Master + at least one of Music, Ambient, SFX). If there’s no Camera3D under the player, stop and ask before adding one — there may be a level camera elsewhere you’d be fighting. If the only bus is Master, ducking will do nothing useful. See Common mistakes below. Fallback (no MCP): ask the user to paste the relevant .tscn and the project’s default_bus_layout.tres (or describe the bus list).

Step 3 — Propose the bundle

Don’t drip the systems in one at a time. The whole point is the trio fires together — install all three, wire one signal, see it land.
I’m about to add: HitFlash on ./World/Enemy, CameraShake script on ./World/Player/Camera3D, AudioDucker autoload, and connect the damaged signal so one hit fires all three. About 130 lines of GDScript across 3 files. May I proceed?

Step 4 — Install Section 1 (Hit Flash)

Goal: when a body takes damage, its mesh pulses to white for ~0.08s, then tweens back. Reads instantly even at 60 fps. Approach: keep a reference to the original albedo_color. On flash(), write white directly to the mesh’s material_override (or build a flash override the first time), then create_tween() back to the original over duration seconds. Tween, not await + sleep — tweens auto-cancel if flash() is re-called mid-flash, which matters during multi-hit combos. Node tree:
MCP path (every scene-mutating call takes an explicit scenePath):
Then write scripts/vfx/hit_flash.gd (via summer_write_file, which requires exactly one of create_only or expected_sha256):
Tunable knobs: duration 0.06–0.12s — under 0.06 reads as a flicker, over 0.12 looks slow. emission_energy_multiplier 1.5–3.0.
Note: summer_set_prop(scenePath="res://levels/world.tscn", path="./World/Enemy/Mesh", key="material_override", value="StandardMaterial3D") creates an inline StandardMaterial3D sub_resource that summer_set_resource_property(scenePath="res://levels/world.tscn", nodePath="./World/Enemy/Mesh", resourceProperty="material_override", subProperty="albedo_color", value="Color(r, g, b, a)") then edits and summer_save_scene persists — so the inline route, the script above, and a saved materials/flash.tres are all valid. What breaks is the value shape: a JSON object instead of a Godot literal string, a misspelled key/subProperty, or a wrong-typed value returns ok:true yet silently no-ops or destructively coerces on current engines (dict → material cleared; dict → Color(0,0,0,1); newer engines reject these with unknown_property / bad_value_shape / type_mismatch). Always pass class names and Color(...)/Vector3(...) strings, and confirm the result in the saved .tscn or a snapshot diff — never from ok alone. See summer_docs_read id reference/mcp-tools-reference.

Step 5 — Install Section 2 (Trauma Camera Shake)

Goal: any system can add_trauma(amount) to the camera. The camera accumulates trauma 0–1, decays it every frame, and shakes by trauma² × random_offset. The square is the trick: a 0.3 hit shakes very little (0.09), a 0.8 hit shakes a lot (0.64). Big hits feel huge, small hits don’t pollute the screen. Approach: put the shake on the existing Camera3D. Two variables — _trauma (0–1 accumulator) and _shake_offset (last frame’s applied rotation). Every frame, subtract last frame’s offset first so the next offset doesn’t drift, then apply the new one. This is the non-obvious trick. Node tree: the script attaches to the existing ./World/Player/Camera3D. No new nodes.
scripts/vfx/camera_shake.gd:
Calling it:
Tunable knobs: trauma_decay 3.0 (long-tail, cinematic) ↔ 6.0 (snappy, twitch-shooter feel). max_shake_angle_deg 2.0 (subtle) ↔ 4.0 (action-game). Keep yaw_factor ≤ 0.5 — equal pitch/yaw shake feels uniform and wrong; eyes are far more sensitive to vertical motion.

Step 6 — Install Section 3 (Audio Ducking)

This is the system most projects skip and the one that makes the biggest perceived difference. On a hit, briefly attenuate Music + Ambient + Announcer (whatever non-impact buses you have) so the SFX bus stabs through the mix. Then smoothly tween volumes back. Crucially: store original bus volumes once at startup, and apply ducking as an offset. Snapping volumes to absolute values destroys the user’s volume preferences. Approach: an autoload (AudioDucker) so any system can call AudioDucker.duck(0.5, 0.4) from anywhere. Internally:
  • _initial_volumes: Dictionary[StringName, float] captured once in _ready() from AudioServer.get_bus_volume_db() for each named bus.
  • duck(amount, duration) lowers each non-impact bus by amount × per_bus_weight, scheduled via Tween, and tweens back over duration.
  • Per-bus weights let Music duck more than Ambient (Music 0.9 = -90% intensity, Ambient 0.7, Announcer 0.5).
  • Kill any in-flight tween before starting a new one so rapid-fire hits don’t compound the duck and silence the mix.
Node tree: none — register the script as an autoload. scripts/vfx/audio_ducker.gd:
Register it as an autoload:
(Equivalently, by hand: Project Settings → Autoload → Add scripts/vfx/audio_ducker.gd as AudioDucker.) Calling it:
Why offset-not-absolute matters: if the user has Music slider at -10 dB in your settings menu, and ducking writes set_bus_volume_db("Music", -24.0) directly, the restore tween will pull Music up to -24’s “neutral” which is louder than the user wanted. Capturing _initial_volumes at autoload _ready() and only ever offsetting from those values preserves the user’s mix forever.

Step 7 — Wire the trio to one signal

The whole point: one hit, all three fire on the same frame. Keep this in the hurt body’s damage handler — not on a global event bus, because you want the trauma amount and flash to scale per-hit.
The camera joins player_camera from its own _ready() (Step 5). Node has no groups property, so summer_set_prop(key="groups", ...) cannot do this — and summer_set_prop only accepts a string, number, or boolean anyway, never an array. The two ways that do work are the add_to_group() call in _ready() or a groups = ["player_camera"] line written into the .tscn node header. Or wire the signal explicitly via MCP if you prefer (emitter/receiver, not from/to, and scenePath is required):

Step 8 — Scaling intensity per hit weight

The same code makes a glancing parry feel different from a critical hit. Calibrate the curve once: Because trauma is squared inside the camera, the felt difference between 0.35 (→ 0.12 shake) and 0.75 (→ 0.56 shake) is roughly 5×. That’s the whole point — small hits stay clean, big hits saturate the screen.

Step 9 — Verify

Tune one knob at a time:
  • Flash invisible → emission_energy_multiplier 1.5 → 3.0, or check that the mesh actually has a material to override.
  • Shake feels jittery → reduce max_shake_angle_deg, or check yaw_factor is ≤ 0.5.
  • Ducking inaudible → check _initial_volumes got populated (autoload running before the first hit?), and that Music/Ambient bus names actually match your default_bus_layout.tres.
  • Ducking too loud → reduce per-bus weight, not the amount argument. Weights are per-project; amount is per-hit.

Common mistakes

Collaborative protocol

This skill writes 3 GDScript files and adds 1 node + 1 autoload entry. Always ask before each section is applied. Group related writes into one ask: “I’m about to add HitFlash + CameraShake + AudioDucker autoload, wire one signal. OK?” See summer_docs_read id reference/collaborative-protocol.

When NOT to use this skill

  • 2D games — the trauma curve and dB ducking still apply, but the camera-shake script needs a Camera2D rewrite (offset instead of rotation). Use this skill as a reference, not a drop-in.
  • Turn-based / strategy — game-feel here lives in UI animation, not body-hit feedback. Skip the trio; reach for tween-based UI polish instead.
  • No audio buses configured — ducking with one Master bus is a no-op. Set up Music / Ambient / SFX first via the audio-direction skill, then come back.
  • Networked games where the hurt body is remote — the flash should run client-side on visual hit confirm, not on damage authority. Hook to a hit_visual_received signal, not the authoritative damage signal.

See also

  • summer_docs_read id reference/mcp-tools-reference — full MCP tool list, especially summer_set_resource_property on inline sub_resources and which failures are loud vs. silent
  • summer_docs_read id reference/godot-version — Summer compatibility and version-sensitive API notes
  • summer_docs_read id reference/collaborative-protocol — “May I write” pattern
  • summer_docs_read id reference/gd-style — typed GDScript conventions
  • audio-direction — bus layout setup (prerequisite for ducking)
  • vfx-hit-spark — particles to layer on top of the trio
  • debugging-game-feel — when the trio is installed and it still feels wrong

From a shell

No Summer MCP tools in this session? Run each one as a command; when summer --version is missing or below 4.0, use npx -y summer-engine@latest in place of summer. Fields are flags in kebab-case (--scene-path res://main.tscn) or one --input '<json>', and summer ops describe <op> prints the exact input. The summer-cli skill has the rest.
  • audio-direction: Define the game’s sound (music style, SFX vocabulary, dynamic music) as .summer/audio-bible.md.

Source: library/skills/game-feel/SKILL.md, rendered from summer-engine@4.3.0 on npm.