今日速览

  • 打击感不是「特效多」,而是反馈的时序:命中判定成功的同一帧(16ms 内)必须出现视觉/听觉/触觉变化,晚 2 帧玩家就会觉得「打空气」。
  • 四层反馈按成本从低到高排:顿帧 → 闪白 → 屏幕震动 → 粒子/数字。前两个各花 20 行代码,收益却最大。
  • Engine.time_scale 是全局的,用错会把 UI、暂停菜单、音频计时一起拖慢——顿帧必须「立刻恢复 + 异常兜底」。
  • ShaderMaterial 默认是共享资源,直接改 flash_amount 会让全场敌人一起闪白,这是新手最常踩的坑。
  • 屏幕震动要用「创伤值(trauma)」平方衰减 + 噪声偏移,不要用随机数直接怼 position,否则镜头会漂移且抖动廉价。

核心学习内容

一、顿帧(Hit Stop):0.06 秒的「时间凝固」

命中瞬间把全局时间放慢到 5%,持续 40–100ms 再恢复。这是最廉价也最有效的重量感来源:轻击 0.04s、重击 0.10s、处决 0.15s 以上。

# HitFeel.gd —— 挂在 autoload 上
var _stopping := false

func hit_stop(duration := 0.06, scale := 0.05) -> void:
	if _stopping:
		return                      # 连击时不要叠加,避免时间被越拖越慢
	_stopping = true
	Engine.time_scale = scale
	# 第 4 个参数 ignore_time_scale = true,否则计时器自己也被冻住,永远醒不过来
	await get_tree().create_timer(duration, true, false, true).timeout
	Engine.time_scale = 1.0
	_stopping = false

三个必须注意的点:

  1. ignore_time_scale 一定要开,这是 Godot 4.3+ 的 create_timer 第 4 个参数;否则计时器跟着 0.05 倍速走,0.06 秒变成 1.2 秒。
  2. 加锁 _stopping:三段连击同一帧触发三次顿帧,时间会被反复压低。
  3. 兜底恢复:_exit_tree()、场景切换、暂停菜单打开时强制 Engine.time_scale = 1.0。玩家最恨的是「游戏卡住了」,不是「不够爽」。

二、屏幕震动:用创伤值,不要用随机数

extends Camera2D

@export var decay := 1.8          # 每秒衰减速度
@export var max_offset := 12.0    # 最大位移(像素)
@export var max_roll := 0.03      # 最大旋转(弧度)

var _trauma := 0.0
var _noise := FastNoiseLite.new()
var _t := 0.0

func add_trauma(amount: float) -> void:
	_trauma = clampf(_trauma + amount, 0.0, 1.0)

func _process(delta: float) -> void:
	_trauma = maxf(_trauma - decay * delta, 0.0)
	var s := _trauma * _trauma          # 平方:小震动细腻,大震动才猛
	_t += delta * 40.0
	offset = Vector2(
		_noise.get_noise_2d(_t, 0.0),
		_noise.get_noise_2d(0.0, _t)
	) * max_offset * s
	rotation = _noise.get_noise_2d(_t, 99.0) * max_roll * s

关键:动的是 offset 而不是 position。position 会和跟随逻辑打架、还会累积漂移;offset 只是渲染层的临时偏移,停手就自然归零。震动强度用手感调:普攻 add_trauma(0.25)、暴击 0.5、Boss 落地 0.8。

三、受击闪白:一个 8 行的 canvas_item shader

shader_type canvas_item;
uniform vec4 flash_color : source_color = vec4(1.0, 1.0, 1.0, 1.0);
uniform float flash_amount : hint_range(0.0, 1.0) = 0.0;

void fragment() {
	vec4 tex = texture(TEXTURE, UV);
	COLOR = vec4(mix(tex.rgb, flash_color.rgb, flash_amount), tex.a);
}

用 mix 而不是加法:加色会让本来就亮的像素过曝成一坨白;mix 能保住轮廓。调用侧:

func flash() -> void:
	var mat := $Sprite2D.material as ShaderMaterial   # 已在编辑器里设为 local_to_scene 或 duplicate 过
	var tw := create_tween()
	tw.tween_method(func(v: float): mat.set_shader_parameter("flash_amount", v),
		1.0, 0.0, 0.12)

材质共享坑:在 Inspector 里给 ShaderMaterial 勾上 Resource → Local to Scene,或者在 _ready() 里写 material = material.duplicate()。否则一个敌人受击,全场同款敌人一起闪。

四、粒子与数字:最后一层「确认感」

GPUParticles2D 做一次性爆发:one_shot = true、explosiveness = 1.0、amount = 12~20、lifetime = 0.3、emitting = true 触发,配 finished 信号 queue_free()。方向用 ParticleProcessMaterial 的 direction + spread = 45,速度 80~160。伤害数字则建议用 Label + Tween 向上飘 40px 淡出,字号随伤害量放大(暴击 1.4 倍),颜色区分属性。

四层叠加的正确顺序是:判定成功 → 冻结双方动画 2 帧 → 闪白 + 粒子同帧出现 → 顿帧 → 音效(同音效随机 pitch 0.9~1.1)→ 手柄 Input.start_joy_vibration()。Steam Deck 玩家对震动很敏感,这一步直接关系到「Verified」的手感评价。

🎯 DeepSeek 提示词

提示词 1:生成 Godot 4 打击感管理器(顿帧 + 震动 + 手柄)

你是一位有 8 年动作游戏开发经验的 Godot 4 工程师。请为我写一个可复用的「打击感管理器」GDScript 单例(autoload 名 HitFeel),只用 Godot 4.3+ 内置 API,不依赖任何插件。

必须实现三个方法:
1. hit_stop(duration: float = 0.06, scale: float = 0.05):通过 Engine.time_scale 实现顿帧,用 await get_tree().create_timer(..., ignore_time_scale=true) 恢复;同帧重复调用不得叠加;提供 force_reset() 兜底。
2. shake(amount: float, duration: float = 0.25):基于 trauma 值(0~1)和 FastNoiseLite 的屏幕震动,作用于 Camera2D 的 offset 与 rotation,trauma 随时间平方衰减。
3. rumble(weak: float, strong: float, duration_ms: int):调用 Input.start_joy_vibration,并在结束后 Input.stop_joy_vibration。

输出格式:
- 第一部分:完整可运行代码(带中文注释)
- 第二部分:参数推荐表(普攻/重击/暴击/Boss 落地 四档)
- 第三部分:3 条「容易踩的坑」清单

约束:
- 不要使用全局变量污染,不要用 @tool
- 震动逻辑不要写进 Camera2D 里,管理器只发出信号,Camera 自己订阅
- 代码必须能在 Godot 4.3 直接粘贴运行,不要出现 4.4+ 才有的 API

用法:整段复制给 DeepSeek,拿到代码后贴进 hit_feel.gd 并在项目设置里注册为 autoload。参数表可以直接当调参起点。

提示词 2:受击闪白 Shader + 调用脚本(含材质共享处理)

你是 Godot 4 的 Shader 专家。请给一个 2D 精灵的「受击闪白」效果,要求:

1. 写一个 canvas_item shader(文件名 hit_flash.gdshader),uniform 包含:
   - flash_color: vec4(默认白色,带 source_color 提示)
   - flash_amount: float(0~1,带 hint_range)
   - 可选:dissolve 用的 noise 阈值,暂不实现但预留注释位置
2. 在 fragment() 中用 mix 混合原图与闪白色,必须保留 alpha。
3. 写配套的 GDScript 组件 HitFlash(extends Node,可挂到敌人根节点),提供 flash(duration := 0.12, color := Color.WHITE) 方法,内部用 create_tween + tween_method 驱动 uniform。
4. 必须处理「多个敌人共用同一个 ShaderMaterial 导致一起闪白」的问题:给出两种方案(Resource 的 local_to_scene、以及 _ready() 中 duplicate()),并说明各自的适用场景和内存代价。
5. 说明为什么用 mix 而不是 COLOR.rgb += flash_color.rgb。

输出:shader 代码 + GDScript 代码 + 一段 100 字以内的接入步骤(1/2/3 步)。

用法:一次生成两种材质方案,避免上线后才发现「全场一起闪」;同时它会解释 mix 与加色的差别,方便你自己改颜色。

提示词 3:给现有战斗做「打击感体检」

你是一位动作游戏手感调校师(做过 3 款以上 Steam 上线的 2D 动作游戏)。我将描述我的战斗反馈现状,请你做一次体检。

我的现状:
- 引擎:Godot 4.3,2D,60 FPS
- 攻击:玩家挥剑,判定用 Area2D,命中后敌人扣血 + 播放 hurt 动画
- 目前有:命中音效(每帧可能触发多次)、敌人闪红(直接改 modulate)
- 目前没有:顿帧、屏幕震动、粒子、伤害数字、手柄震动
- 问题:玩家反馈「打上去像打纸片」「连击没有节奏感」

请输出:
1. 诊断表(Markdown 表格,列:反馈层 / 现状 / 问题 / 优先级 P0-P2 / 预估工作量)
2. 一个「最小改动清单」——只用 1 天时间能把手感提升最明显的 3 件事,按顺序列出,每件给出具体实现要点(不要完整代码,给 API 和关键参数)
3. 一个「连击节奏设计」建议:三段连击的顿帧时长、震动强度、音效 pitch 应该如何递进
4. 明确列出「不要做的事」——哪些加料反而会毁掉手感(例如每帧触发音效、震动幅度过大)

约束:只针对 Godot 4,不要推荐 Unity/Unreal 方案;不要泛泛说「多测试」,每条建议必须可执行。

用法:适合在项目中期用,把你实际的节点结构、参数贴进去,它会按 P0/P1/P2 排优先级,避免你在美术特效上花三天却忘了加顿帧。

学习心得

今天最大的收获是:打击感是排序问题,不是预算问题。顿帧 20 行代码、闪白 8 行 shader、震动一个 trauma 变量,三样加起来不到一小时,却能把「打纸片」直接变成「打得进去」。真正贵的粒子、特效、动画反而是最后一层点缀。对「用 DeepSeek 做 Steam 游戏」这件事来说,这意味着我不需要美术团队,我需要的是知道自己要什么参数——而参数是可以让 DeepSeek 给出推荐表、再自己微调的。这也是我一直在攒提示词的原因:把「我不知道该怎么描述手感」变成「我有一份体检清单」。

明日关注

  • 把顿帧接到实际的攻击判定上,实测 0.04 / 0.06 / 0.10 三档手感差异,记录主观感受
  • 检查项目里所有 ShaderMaterial 是否有共享问题(local_to_scene 逐个确认)
  • 补一节「受击音效随机化 + 手柄震动」,凑齐四层反馈,再看连击节奏是否成立

本文由自动化学习流水线每日生成,坚持即学即记。