关于 game-feel
game-feel 是帮助游戏开发者为已有机制添加反馈效果的技能,适用于动作虽正常但感觉迟钝或缺乏冲击力的情况。它通过震屏、顿帧、缓动和多层反馈提升响应感,同时强调与核心模拟分离并按事件重要性调整强度。
主要功能
- 01设计震屏与顿帧反馈
- 02应用缓动、挤压和拉伸
- 03组合击退、闪光与粒子效果
- 04按事件重要性分级反馈
- 05提供 Godot 和 Unity 示例及注意事项
适用场景
- 01增强命中或拾取的反馈
- 02让跳跃和冲刺更有响应感
- 03为不同重要程度的事件设定效果
- 04检查反馈是否过强或影响输入
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ gamedev-skills/ awesome-gamedev-agent-skills --skill game-feel安装后在 Claude Code 中输入/game-feel确认已加载。
附带文件references/feedback-recipes.md
详细介绍
游戏手感(反馈强化)
机制能正常运作和让人感觉很棒之间的区别,在于反馈:动作触发的多层、略带夸张的响应。本技能涵盖与引擎无关的技巧——屏幕震动、受击停顿、缓动、挤压与拉伸、击退和叠加反馈,并说明如何应用它们,同时不掩盖底层模拟。它会在现有机制的基础上增添润色效果,而不是实现该机制。
适用场景
- 当某个动作(命中、跳跃、冲刺、拾取、死亡、按下按钮)在机制上正确无误,但感觉软弱、轻飘或不够畅快,而你希望它更灵敏、更有冲击力时使用。
- 添加屏幕震动、受击停顿/冻结帧、缓动动作、挤压与拉伸、击退、闪光,或为同一事件叠加多种反馈通道时使用。
- 判断反馈强化的程度是否足够,以及何时会变成噪声。
**不适用场景:**原始控制器计算(跳跃高度、土狼时间)请使用对应游戏类型的 platformer 和引擎移动技能。相机的跟随/死区/环绕构图请使用 camera-systems(本技能只触发震动)。混音、闪避和自适应音乐请使用 audio-design。基于着色器的溶解/闪光效果请使用 shader-programming 和对应的引擎着色器技能。具体的补间/粒子节点 API 请使用引擎动画技能(godot-animation、unity-animation)。
核心原则:反馈是多层且夸张的
一次令人满足的命中,通常会在约 100 ms 内同时触发 5–8 种细微响应:声音、粒子爆发、短暂的受击停顿、闪光、击退、轻微的屏幕震动,以及弹出的数字。每种效果单独看都很轻量;叠加起来,就能呈现出“冲击感”。两条规则能避免效果变得杂乱:**(1)短暂地夸张表现,然后回归静止状态(反馈强化是暂时的,而不是一种新的静止状态);(2)**根据事件的重要程度调整反馈强度——脚步声不等于 Boss 死亡。
核心工作流
- **确认事件钩子已存在。**反馈强化依附于离散事件:
on_hit、on_land、on_pickup、on_death、on_fire。如果机制没有发出这些事件,请先添加。 - 为每个事件选择反馈通道,从以下选项中挑选:声音、粒子、震动、受击停顿、闪光、击退、补间、弹出数字。先从 2–3 种开始;逐步添加,直到效果足够明显,然后停止。
- **让动作带有缓动,而不是线性变化。**通过带缓动效果的补间来处理缩放/位置/UI 变化(“弹出”使用过冲,回归稳定状态使用缓出)。线性动作会显得很机械。
- **将受击停顿和震动留给冲击事件。**它们是最强、也最容易被滥用的工具——持续时间要短,强度要随重要程度调整,绝不能用于常规动作。
- **让反馈脱离关键模拟。**震动应移动相机/视觉效果,而不是角色本体;受击停顿应使用时间缩放或真实时间暂停,而不是阻塞游戏逻辑。
- **按重要程度分级调整。**定义轻微/中等/强烈的反馈预设,并将事件分配到对应等级,确保整个游戏的反馈强化保持一致且与事件成比例。
- **通过实际游玩和观察进行验证。**重复触发事件,确认反馈会触发并回归静止状态,且不会引起晕眩或阻塞输入。报告观察结果(震动会衰减吗?受击停顿期间输入仍会被处理吗?)。
模式
1. 通过衰减的“创伤值”实现屏幕震动(平滑,而非随机抖动)
# Godot 4.7. Store trauma 0..1; shake = trauma^2 so small hits barely move, big hits punch.
# Drives a Camera2D OFFSET (the visual), never the player body. Decays every frame.
@export var decay := 1.2 # trauma lost per second
@export var max_offset := Vector2(12, 8)
@export var max_roll := 0.1 # radians
var trauma := 0.0
var _t := 0.0
func add_trauma(amount: float) -> void:
trauma = clampf(trauma + amount, 0.0, 1.0) # hits ADD; they don't reset
func _process(dt: float) -> void:
if trauma <= 0.0: return
trauma = maxf(trauma - decay * dt, 0.0)
var shake := trauma * trauma # quadratic: gentle low, sharp high
_t += dt * 30.0
# Smooth pseudo-random via sampled noise/sin, NOT rand each frame (that buzzes).
offset = Vector2(max_offset.x * shake * sin(_t * 1.7),
max_offset.y * shake * sin(_t * 2.3))
rotation = max_roll * shake * sin(_t * 1.1)
# Unity 6.3 LTS: identical model on a CinemachineCamera via CinemachineBasicMultiChannelPerlin
# (set AmplitudeGain/FrequencyGain from trauma^2) — see camera-systems.
2. 受击停顿/冻结帧(通过短暂停止时间来强化冲击感)
# Godot 4.7. Drop time scale, then restore after a REAL-TIME delay (unaffected by time_scale).
func hit_stop(duration := 0.08, scale := 0.05) -> void:
Engine.time_scale = scale
# 4th arg ignore_time_scale=true → the timer still fires while the game is frozen.
await get_tree().create_timer(duration, true, false, true).timeout
Engine.time_scale = 1.0
// Unity 6.3 LTS (C#). WaitForSecondsRealtime ignores Time.timeScale, so the timer still elapses.
IEnumerator HitStop(float duration = 0.08f, float scale = 0.05f) {
Time.timeScale = scale;
yield return new WaitForSecondsRealtime(duration);
Time.timeScale = 1f; // RIGHT: real-time wait. WRONG: WaitForSeconds (never resumes at scale 0)
}
3. 使用缓动补间实现挤压与拉伸 + 过冲(“弹出”效果)
# Godot 4.7. Conserve volume: stretch one axis, squash the other, then spring back with overshoot.
func pop(node: Node2D) -> void:
node.scale = Vector2(1.3, 0.7) # instant squash on the event
var tw := create_tween()
tw.tween_property(node, "scale", Vector2.ONE, 0.18) \
.set_trans(Tween.TRANS_BACK).set_ease(Tween.EASE_OUT) # BACK = overshoots past 1, settles
# RIGHT: ease back (TRANS_BACK/ELASTIC) for life. WRONG: linear tween → mechanical, dead.
4. 按重要程度调整反馈组合(让反馈强化保持适度)
# One call per event; the tier decides intensity so the whole game stays consistent.
func feedback(event_pos: Vector2, tier: String) -> void:
match tier:
"small": AudioBus.play("tick"); Camera.add_trauma(0.15)
"medium": AudioBus.play("hit"); Camera.add_trauma(0.4); hit_stop(0.05); spawn_particles(event_pos, 6)
"large": AudioBus.play("boom"); Camera.add_trauma(0.8); hit_stop(0.12); spawn_particles(event_pos, 30); flash_white(0.06)
常见问题
- 抖动玩家/角色本身,而不是摄像机偏移量会导致碰撞与瞄准不同步。抖动摄像机(或视觉枢轴),切勿抖动模拟变换。
- 每帧随机偏移会像静电一样发出嗡嗡声。用采样噪声/正弦波和逐渐衰减的创伤值来驱动抖动,让它平滑地自行停止。
- 使用
WaitForSeconds/受缩放影响的计时器实现受击停顿会导致游戏无法恢复(时间缩放为 0 时,计时器永远不会前进)。使用实时等待(WaitForSecondsRealtime,或 Godot 中的ignore_time_scale计时器)。 - 持续攻击按住期间的每一帧都触发受击停顿会锁住游戏。每次造成冲击时只触发一次。
- 到处都使用线性补间会显得像机器人。几乎所有动画都应使用缓动;仅在需要“弹出”时使用过冲(BACK/ELASTIC),在需要“稳定”时使用缓出。
- 夸张效果永久持续(缩放不恢复、抖动不衰减)会成为新的常态,也就不再像反馈。增强效果必须恢复到静止状态。
- 对日常操作过度增强效果(每一步都加满屏幕抖动和受击停顿)会引发恶心感,还会掩盖真正的冲击。根据重要程度调整效果;添加“减少屏幕抖动”/“减少闪烁”辅助功能选项。
- 阻碍输入的反馈(长时间冻结、无法取消的动画)会损害响应性。增强效果应保持短促,并让输入能够在效果期间进入缓冲区。
参考资料
- 关于创伤抖动数学、缓动曲线速查表(弹出与稳定分别使用哪种缓动)、击退 + 闪光 + 数字弹出配方、重要性分级预设,以及各引擎的补间/粒子绑定,请阅读
references/feedback-recipes.md。
相关技能
camera-systems— 负责摄像机跟随/死区/环绕;此技能只为其提供抖动创伤值。godot-animation、unity-animation— 增强效果所依托的具体补间/AnimationPlayer/粒子 API。audio-design— 每套反馈组合中的声音层;包括音量闪避和音效变化。physics-tuning— 击退力以及必须确保不会被增强效果破坏的时间步长。platformer、fps-shooter、roguelike— 能通过此技能提升即时操作手感的游戏类型。