merge: 反馈与散项微批落籍——tooltip 防出屏/减动效开关全链包扎/HUD 义务清单补关卡节点地图行

This commit is contained in:
DKChan committed 2026-09-20 15:03:10 +08:00
commit ca6465aa26
11 files changed
+72 -40

No files matched your search

@@ -170,6 +170,7 @@ HUD框架站在玩家与全部玩法系统之间:挖掘期它是输入的翻
| [排雷玩法](../排雷循环/排雷玩法.md) | 已开 / 未开格态一眼可辨;悬停预承诺与可挖域常驻指示;移动路径预览即合法性指示;每次拒绝可感知且棋盘面细因可区分;停手被改判可感知 |
| [行动经济](../排雷循环/行动经济.md) | 行动点与储备常显;储备清零反馈;棋盘面与资源面拒绝可区分;刷新入口与代价可读 |
| [战斗结算](../战斗循环/战斗结算.md) | 未生效类明细行(奖励未生效)的呈现;高光行强调 |
| [关卡节点地图](../冒险循环/关卡节点地图.md) | 节点链进度常显(A 区顶栏:当前节点奖励金高亮、已过暗金、Boss 旗朱描边、候选下一跳亮金);选路覆盖层只读呈现(零自动推进停点语义) |
| [敌人系统](../战斗循环/敌人系统.md) | 意图线索映射成文且全局恒定;单位顺位与血条可读;敌方共享层数各卡同值 |
| [增益状态](../战斗循环/增益状态.md) | 状态栏常显;无状态时整排槽隐藏;有利 / 有害极性呈现不看数值符号 |
| [地图战斗资源](../战斗循环/地图战斗资源.md) | 已开格的奖励身份常读 |
@@ -249,3 +250,4 @@ HUD框架站在玩家与全部玩法系统之间:挖掘期它是输入的翻
| v0.3 | 2026-09-16 | 格面资源批 UI 欠账收口(可选类道具与其轮末窗口已随 2026-09-15 代码批删除):战斗屏布局五区 → 四区(删 E 道具栏行,D 区加宽至约 1036px 并同步装板校验变量);删道具窗口弹层条款(原位点亮、非模态不入栈)、P0 闭环窗口锚点条目、「道具窗口原位点亮 / 至多选一件 / 禁用件带原因 / 空库存不弹窗」验收行;依赖与输出表的窗口选择提交、道具使用窗口呈现、库存查询义务删除或改述为现行口径(奖励身份投影经 `query_identity` 只读回调,零直读);关卡末清库反馈义务改载魔法道具背包,随魔法道具 P0 落地。魔法道具预留义务行与「待拍板」登记保持 |
| v0.5 | 2026-09-17 | 数值纪律收口批:调参表标注文档自持口径(无表承载常量的唯一事实源声明) |
| v0.6 | 2026-09-20 | **敌人卡形态裁决收口批(与 battle.md / 战斗表现 / 06 清单同批,台账 99)**:全站桩形态拍板——B 区主角与敌人均为无框立绘+底座、状态小图标挂血条正下方,英雄卡/敌人卡退役;B 区行、敌人呈现义务行(载体由卡改立绘,意图占位/共享层数各立绘同值/死亡灰化退场等义务面平移)、下游战斗表现交付行、就地更新示例措辞四处同步;攻击值等规划数据仍归 C 区数据框(C 区不动)。呈现快照数据面零改动(消费字段不变,只换呈现载体) |
| v0.7 | 2026-09-20 | **表现层三批落地 + 义务清单补行(代码同日三批:样式收敛 / 站桩改造 / 结算演出链)**:呈现义务来源清单补「关卡节点地图」行(节点链进度常显 + 选路覆盖层只读呈现——此前漏登记);新增结算演出覆盖层(跨区弹道/光带载体,兑现演出因果可视);减动效承诺落地(UiKit.motion 单点包装全部呈现层 tween 时长,开启时长减半、可挖域呼吸停格;切片期入口 = TunePanel 开关,正式设置层归设置批)。代码侧站桩载体(StandeeView)与卡片退役随 v0.6 裁决执行 |
@@ -171,8 +171,8 @@ func _disarm() -> void:
# 资源面拒绝反馈(pattern 1 resource_face 腿):AP 数字脉冲
func pulse_ap() -> void:
var tw := create_tween()
tw.tween_property(_ap_label, "modulate", Color(1.0, 0.4, 0.3), 0.1)
tw.tween_property(_ap_label, "modulate", Color(1, 1, 1), 0.25)
tw.tween_property(_ap_label, "modulate", Color(1.0, 0.4, 0.3), UiKit.motion(0.1))
tw.tween_property(_ap_label, "modulate", Color(1, 1, 1), UiKit.motion(0.25))
func _make_dot() -> ColorRect:
@@ -99,8 +99,8 @@ func _play_seal_fall() -> void:
_seal.position = Vector2(target.x, -100.0)
_seal.visible = true
var tw := _seal.create_tween()
tw.tween_property(_seal, "position:y", target.y - 26.0, 0.42).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_IN)
tw.tween_property(_seal, "position:y", target.y, 0.22).set_trans(Tween.TRANS_BOUNCE).set_ease(Tween.EASE_OUT)
tw.tween_property(_seal, "position:y", target.y - 26.0, UiKit.motion(0.42)).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_IN)
tw.tween_property(_seal, "position:y", target.y, UiKit.motion(0.22)).set_trans(Tween.TRANS_BOUNCE).set_ease(Tween.EASE_OUT)
# 旗化光点收回(失败——插过的旗逐面化光点收回占位,§2.5)
@@ -115,7 +115,7 @@ func _play_flag_recall(count: int) -> void:
dot.mouse_filter = Control.MOUSE_FILTER_IGNORE
add_child(dot)
var tw := dot.create_tween()
tw.tween_interval(0.1 * i)
tw.tween_property(dot, "position:y", dot.position.y - 120.0, 0.8)
tw.parallel().tween_property(dot, "modulate:a", 0.0, 0.8)
tw.tween_interval(UiKit.motion(0.1 * i))
tw.tween_property(dot, "position:y", dot.position.y - 120.0, UiKit.motion(0.8))
tw.parallel().tween_property(dot, "modulate:a", 0.0, UiKit.motion(0.8))
tw.tween_callback(dot.queue_free)
@@ -32,7 +32,7 @@ func play_clear_burst() -> void:
var fountain := CPUParticles2D.new()
fountain.position = size * 0.5
fountain.amount = 90
fountain.lifetime = 1.6
fountain.lifetime = UiKit.motion(1.6)
fountain.one_shot = true
fountain.explosiveness = 0.2
fountain.direction = Vector2(0, -1)
@@ -48,7 +48,7 @@ func play_clear_burst() -> void:
var confetti := CPUParticles2D.new()
confetti.position = Vector2(size.x * 0.5, -8.0)
confetti.amount = 60
confetti.lifetime = 3.2
confetti.lifetime = UiKit.motion(3.2)
confetti.one_shot = true
confetti.explosiveness = 0.9
confetti.direction = Vector2(0, 1)
@@ -62,7 +62,7 @@ func play_clear_burst() -> void:
confetti.emitting = true
add_child(confetti)
var tw := create_tween()
tw.tween_interval(3.5)
tw.tween_interval(UiKit.motion(3.5))
tw.tween_callback(func():
for n in [fountain, confetti]:
if is_instance_valid(n):
@@ -85,8 +85,8 @@ func play_boss_shadow() -> void:
shadow.mouse_filter = Control.MOUSE_FILTER_IGNORE
add_child(shadow)
var tw := shadow.create_tween()
tw.tween_property(shadow, "position:y", size.y, 1.1).set_trans(Tween.TRANS_SINE)
tw.tween_property(shadow, "modulate:a", 0.0, 0.5)
tw.tween_property(shadow, "position:y", size.y, UiKit.motion(1.1)).set_trans(Tween.TRANS_SINE)
tw.tween_property(shadow, "modulate:a", 0.0, UiKit.motion(0.5))
tw.tween_callback(shadow.queue_free)
@@ -100,7 +100,7 @@ func play_perfect_sweep() -> void:
band.mouse_filter = Control.MOUSE_FILTER_IGNORE
add_child(band)
var tw := band.create_tween()
tw.tween_property(band, "position:x", size.x + 8.0, 0.5 + 0.15 * i)
tw.tween_property(band, "position:x", size.x + 8.0, UiKit.motion(0.5 + 0.15 * i))
tw.tween_callback(band.queue_free)
_show_banner("完美排雷 ×2")
@@ -115,8 +115,8 @@ func play_flag_hit_projectile(heavy: bool) -> void:
shot.mouse_filter = Control.MOUSE_FILTER_IGNORE
add_child(shot)
var tw := shot.create_tween()
tw.tween_property(shot, "position", Vector2(size.x * 0.75, -30.0), 0.35)
tw.parallel().tween_property(shot, "modulate:a", 0.0, 0.35)
tw.tween_property(shot, "position", Vector2(size.x * 0.75, -30.0), UiKit.motion(0.35))
tw.parallel().tween_property(shot, "modulate:a", 0.0, UiKit.motion(0.35))
tw.tween_callback(shot.queue_free)
@@ -125,7 +125,7 @@ func _show_banner(text: String) -> void:
_banner.visible = true
_banner.modulate.a = 0.0
var tw := create_tween()
tw.tween_property(_banner, "modulate:a", 1.0, 0.2)
tw.tween_interval(1.6)
tw.tween_property(_banner, "modulate:a", 0.0, 0.4)
tw.tween_property(_banner, "modulate:a", 1.0, UiKit.motion(0.2))
tw.tween_interval(UiKit.motion(1.6))
tw.tween_property(_banner, "modulate:a", 0.0, UiKit.motion(0.4))
tw.tween_callback(func(): _banner.visible = false)
@@ -84,7 +84,7 @@ func set_desaturated(on: bool) -> void:
_desat_tween = create_tween()
var target := 1.0 if on else 0.0
_desat_tween.tween_method(func(v: float): _desat_mat.set_shader_parameter("strength", v),
float(_desat_mat.get_shader_parameter("strength")), target, 0.15)
float(_desat_mat.get_shader_parameter("strength")), target, UiKit.motion(0.15))
for c in _cells:
(_cells[c] as Button).material = _desat_mat if on else null
@@ -372,6 +372,8 @@ func _process(delta: float) -> void:
if not _dig_glow or _diggable.is_empty():
return
_breath_t += delta
if UiKit.motion_scale < 1.0:
return # 减动效:呼吸停格(保留当前微光边框——静态可辨、无持续运动)
var k := 0.5 + 0.5 * sin(_breath_t * 3.0) # 3 rad/s≈0.48Hz——无 >3 次/秒频闪(AC 基线)
var col := COL_DIG_EDGE.lerp(UiKit.DIG_EDGE_DEEP, 0.55 - 0.35 * k)
for c in _diggable:
@@ -97,8 +97,8 @@ func set_rap(rap: int, cap_hint: int) -> void:
# battle_over RAP 清零可见反馈(行动经济 D1 义务):数字滚动归零 + 微文案占位
_rap_feedback.text = "随战斗结束消散"
var tw := create_tween()
tw.tween_property(_rap_label, "modulate", Color(1.4, 1.2, 0.7), 0.12)
tw.tween_property(_rap_label, "modulate", Color(1, 1, 1), 0.3)
tw.tween_property(_rap_label, "modulate", Color(1.4, 1.2, 0.7), UiKit.motion(0.12))
tw.tween_property(_rap_label, "modulate", Color(1, 1, 1), UiKit.motion(0.3))
elif rap > 0:
_rap_feedback.text = ""
@@ -135,8 +135,8 @@ func rap_text() -> String:
# 资源面拒绝反馈(pattern 1 resource_face 腿):RAP 数字脉冲
func pulse_rap() -> void:
var tw := create_tween()
tw.tween_property(_rap_label, "modulate", Color(1.0, 0.4, 0.3), 0.1)
tw.tween_property(_rap_label, "modulate", Color(1, 1, 1), 0.25)
tw.tween_property(_rap_label, "modulate", Color(1.0, 0.4, 0.3), UiKit.motion(0.1))
tw.tween_property(_rap_label, "modulate", Color(1, 1, 1), UiKit.motion(0.25))
func _render_rap_dots(rap: int) -> void:
@@ -28,8 +28,8 @@ func play_round_sweep(board_rect: Rect2) -> void:
band.size = Vector2(board_rect.size.x, 34.0)
add_child(band)
var tw := band.create_tween()
tw.tween_property(band, "position:y", board_rect.end.y - 34.0, 0.5).set_trans(Tween.TRANS_SINE)
tw.parallel().tween_property(band, "modulate:a", 0.0, 0.5).set_delay(0.15)
tw.tween_property(band, "position:y", board_rect.end.y - 34.0, UiKit.motion(0.5)).set_trans(Tween.TRANS_SINE)
tw.parallel().tween_property(band, "modulate:a", 0.0, UiKit.motion(0.5)).set_delay(UiKit.motion(0.15))
tw.tween_callback(band.queue_free)
@@ -49,8 +49,8 @@ func play_projectile(from: Vector2, to: Vector2, glyph: String, col: Color, crit
var mid := (from + to) * 0.5 + Vector2(0, -36.0) # 轻微上抛弧线
var tw := shot.create_tween()
if crit:
tw.tween_property(shot, "scale", Vector2(1.5, 1.5), 0.08)
tw.tween_property(shot, "position", mid, 0.16).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_OUT)
tw.tween_property(shot, "position", to - shot.size * 0.5, 0.19).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN)
tw.parallel().tween_property(shot, "modulate:a", 0.0, 0.12).set_delay(0.23)
tw.tween_property(shot, "scale", Vector2(1.5, 1.5), UiKit.motion(0.08))
tw.tween_property(shot, "position", mid, UiKit.motion(0.16)).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_OUT)
tw.tween_property(shot, "position", to - shot.size * 0.5, UiKit.motion(0.19)).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN)
tw.parallel().tween_property(shot, "modulate:a", 0.0, UiKit.motion(0.12)).set_delay(UiKit.motion(0.23))
tw.tween_callback(shot.queue_free)
@@ -143,7 +143,7 @@ func _set_hp(hp: int) -> void:
if _hp_tween != null and _hp_tween.is_valid():
_hp_tween.kill()
_hp_tween = create_tween()
_hp_tween.tween_property(_hp_bar, "value", target, 0.15)
_hp_tween.tween_property(_hp_bar, "value", target, UiKit.motion(0.15))
func set_idle() -> void:
@@ -203,10 +203,10 @@ func _play_intent_motion() -> void:
_intent_badge.pivot_offset = _intent_badge.size * 0.5
_portrait.pivot_offset = _portrait.size * 0.5
var tw := create_tween()
tw.tween_property(_intent_badge, "scale", Vector2(1.6, 1.6), 0.10)
tw.tween_property(_intent_badge, "scale", Vector2.ONE, 0.16)
tw.parallel().tween_property(_portrait, "rotation_degrees", 4.0, 0.10)
tw.tween_property(_portrait, "rotation_degrees", 0.0, 0.14)
tw.tween_property(_intent_badge, "scale", Vector2(1.6, 1.6), UiKit.motion(0.10))
tw.tween_property(_intent_badge, "scale", Vector2.ONE, UiKit.motion(0.16))
tw.parallel().tween_property(_portrait, "rotation_degrees", 4.0, UiKit.motion(0.10))
tw.tween_property(_portrait, "rotation_degrees", 0.0, UiKit.motion(0.14))
# 资产挂点(战斗表现样张批):tex 落位后字形隐退、纹理立绘顶位——零代码换装契约;tex=null 回退字形
@@ -231,9 +231,9 @@ func mark_dead() -> void:
_dead = true
modulate = Color(0.62, 0.62, 0.62, 0.9)
var tw := create_tween()
tw.tween_property(self, "rotation_degrees", 2.5, 0.12)
tw.tween_property(self, "rotation_degrees", -1.5, 0.1)
tw.tween_property(self, "rotation_degrees", 0.0, 0.08)
tw.tween_property(self, "rotation_degrees", 2.5, UiKit.motion(0.12))
tw.tween_property(self, "rotation_degrees", -1.5, UiKit.motion(0.1))
tw.tween_property(self, "rotation_degrees", 0.0, UiKit.motion(0.08))
_refresh_portrait_glyph()
if _intent_badge != null:
_intent_badge.visible = false
@@ -247,8 +247,8 @@ func is_dead() -> bool:
# 受击闪白(结算演出占位——§2.2 受击反馈)
func flash_hit() -> void:
var tw := create_tween()
tw.tween_property(self, "modulate", Color(1.6, 1.4, 1.2), 0.08)
tw.tween_property(self, "modulate", Color(1, 1, 1) if not _dead else Color(0.62, 0.62, 0.62, 0.9), 0.2)
tw.tween_property(self, "modulate", Color(1.6, 1.4, 1.2), UiKit.motion(0.08))
tw.tween_property(self, "modulate", Color(1, 1, 1) if not _dead else Color(0.62, 0.62, 0.62, 0.9), UiKit.motion(0.2))
func _revive() -> void:
@@ -144,7 +144,18 @@ func _on_slot_hover(slot: Control) -> void:
int(eff.get("remaining_settlements", 0)), src, int(eff.get("round_index", 0))]
if not _tip.is_inside_tree():
add_child(_tip)
_tip.position = Vector2(0, -70)
_tip.reset_size()
# 防出屏定位(缺陷①家族回归——边缘格 tooltip 硬编码上偏移会被视口裁切):
# 默认悬浮槽位上方,越上缘翻转到槽位下方,再钳制在视口内;top_level 脱离容器布局管理
_tip.top_level = true
var slot_rect := slot.get_global_rect()
var desired := slot_rect.position + Vector2(0.0, -_tip.size.y - 6.0)
var vp := get_viewport_rect().size
desired.x = clampf(desired.x, 4.0, maxf(4.0, vp.x - _tip.size.x - 4.0))
if desired.y < 4.0:
desired.y = slot_rect.end.y + 6.0
desired.y = clampf(desired.y, 4.0, maxf(4.0, vp.y - _tip.size.y - 4.0))
_tip.global_position = desired
_tip.visible = true
@@ -159,6 +159,14 @@ func _build() -> void:
var reset_btn := UiKit.button("重置为 0")
reset_btn.pressed.connect(_reset_btn_pressed)
btn_row.add_child(reset_btn)
# 减动效开关(battle.md §Transitions 内建承诺——切片期入口;正式设置层归设置批,两分制)
var motion_row := HBoxContainer.new()
box.add_child(motion_row)
motion_row.add_child(UiKit.label("减动效(动画时长减半——运动敏感/演示用)", 14, false))
var motion_btn := CheckButton.new()
motion_btn.button_pressed = UiKit.motion_scale < 1.0
motion_btn.toggled.connect(func(on: bool): UiKit.motion_scale = 0.5 if on else 1.0)
motion_row.add_child(motion_btn)
box.add_child(HSeparator.new())
var sim_row := HBoxContainer.new()
@@ -65,6 +65,15 @@ const HEX_ON_GOLD := "3a2a08"
const DOT_OFF := PANEL_BORDER
# ── 减动效(battle.md §Transitions 内建承诺:开启时时长减半;结算逐行兑现为信息载体降速不跳过)──
# 切片期入口 = TunePanel 开关(设置独立于存档两分制——正式设置层载体随设置批落位)
static var motion_scale := 1.0 # 1.0 常态 / 0.5 减动效
# 全部呈现层 tween 时长经本函数包装——新增动效一律 UiKit.motion(秒),禁再写裸时长字面量
static func motion(dur: float) -> float:
return dur * motion_scale
# ── StyleBox 工厂 ──
# margin = (左, 上, 右, 下);border_w = 0 即无边框(根底/遮罩用)
static func panel_flat(bg: Color, border: Color, corner: int = 0, border_w: int = 1, margin: Vector4 = Vector4.ZERO) -> StyleBoxFlat: