mcpField
G
game-feel

game-feel

github.com/gamedev-skills设计与 UI5.4k10.7k 次安装1 天前更新
代码仓库

关于 game-feel

game-feel 是帮助游戏开发者为已有机制添加反馈效果的技能,适用于动作虽正常但感觉迟钝或缺乏冲击力的情况。它通过震屏、顿帧、缓动和多层反馈提升响应感,同时强调与核心模拟分离并按事件重要性调整强度。

主要功能

  1. 01设计震屏与顿帧反馈
  2. 02应用缓动、挤压和拉伸
  3. 03组合击退、闪光与粒子效果
  4. 04按事件重要性分级反馈
  5. 05提供 Godot 和 Unity 示例及注意事项

适用场景

  1. 01增强命中或拾取的反馈
  2. 02让跳跃和冲刺更有响应感
  3. 03为不同重要程度的事件设定效果
  4. 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 死亡。

核心工作流

  1. **确认事件钩子已存在。**反馈强化依附于离散事件:on_hit、on_land、on_pickup、on_death、on_fire。如果机制没有发出这些事件,请先添加。
  2. 为每个事件选择反馈通道,从以下选项中挑选:声音、粒子、震动、受击停顿、闪光、击退、补间、弹出数字。先从 2–3 种开始;逐步添加,直到效果足够明显,然后停止。
  3. **让动作带有缓动,而不是线性变化。**通过带缓动效果的补间来处理缩放/位置/UI 变化(“弹出”使用过冲,回归稳定状态使用缓出)。线性动作会显得很机械。
  4. **将受击停顿和震动留给冲击事件。**它们是最强、也最容易被滥用的工具——持续时间要短,强度要随重要程度调整,绝不能用于常规动作。
  5. **让反馈脱离关键模拟。**震动应移动相机/视觉效果,而不是角色本体;受击停顿应使用时间缩放或真实时间暂停,而不是阻塞游戏逻辑。
  6. **按重要程度分级调整。**定义轻微/中等/强烈的反馈预设,并将事件分配到对应等级,确保整个游戏的反馈强化保持一致且与事件成比例。
  7. **通过实际游玩和观察进行验证。**重复触发事件,确认反馈会触发并回归静止状态,且不会引起晕眩或阻塞输入。报告观察结果(震动会衰减吗?受击停顿期间输入仍会被处理吗?)。

模式

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 — 能通过此技能提升即时操作手感的游戏类型。

评论

支持 Markdown