feat(art): T3 演出密度终稿批——演出四项落码+fx_probe 新探针
死亡碎裂(standee mark_dead 增五碎块抛物线弹飞 0.45s——§4.6.4 灰化+碎裂禁瞬间消失+§8.4 冻结值);意图三档动画终稿(§4.6.4:档①普通攻击零表示不变/档②特殊准备=弹入+二次弹跳高举/档③重击蓄力=缓推 1.22+定格微顿+回落——三档运动签名互异灰度可辨);胜利纹章(battle_end open_victory 蜡封落定后十碎砖自四周拼纹章环,驻留至 close——§2.4);失败掸灰(open_defeat 八尘絮飘落——§2.5)+既有旗光收回挂探针;结算轮光带终稿(加 2px 亮金前导缘线——§2.2 逐行横扫方向可读)。全部程序化 tween 零新帧资产,确定性散布禁全局随机,时长全走 UiKit.motion 减动效包装。 新探针 smoke/fx_probe.tscn+gd:死亡碎裂5/意图三档+零表示/纹章10/掸灰8+旗光8/光带+缘线 五断言全 PASS。 连带:图例旗条目名回退『旗』(click_probe 图例基线精确匹配;T1 曾改旗(封印印记)——封印语义移描述列,色 #000000 保留)。轻闸=冒烟 557/0+五探针(ui_input/ui_probe/click/tune/fx)全 PASS。
This commit is contained in:
1 parent
0ad16747fc
commit
d458e393de
6 files changed
+203
-3
No files matched your search
@@ -63,6 +63,7 @@ func open_victory(verdict_line: String, continue_label: String, board_rect: Rect
|
||||
_board_rect = board_rect
|
||||
visible = true
|
||||
_play_seal_fall()
|
||||
_play_crest_assemble() # 碎砖拼纹章(§2.4——样张 v2 冻结批 T3 终稿)
|
||||
_play_flag_recall(0) # 胜利不放旗收回
|
||||
|
||||
|
||||
@@ -75,12 +76,14 @@ func open_defeat(verdict_line: String, continue_label: String, board_rect: Rect2
|
||||
_board_rect = board_rect
|
||||
visible = true
|
||||
_seal.visible = false
|
||||
_play_dust_sadle() # 坐砖掸灰(§2.5——终稿)
|
||||
_play_flag_recall(8)
|
||||
|
||||
|
||||
func close() -> void:
|
||||
visible = false
|
||||
_seal.visible = false
|
||||
_clear_crest_bricks()
|
||||
|
||||
|
||||
func is_open() -> bool:
|
||||
@@ -105,6 +108,7 @@ func _play_seal_fall() -> void:
|
||||
|
||||
# 旗化光点收回(失败——插过的旗逐面化光点收回占位,§2.5)
|
||||
func _play_flag_recall(count: int) -> void:
|
||||
_fx_flag_dots = count
|
||||
for i in count:
|
||||
var dot := ColorRect.new()
|
||||
dot.color = COL_GOLD
|
||||
@@ -119,3 +123,62 @@ func _play_flag_recall(count: int) -> void:
|
||||
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)
|
||||
|
||||
|
||||
# ---- 演出密度终稿(样张 v2 冻结批 T3——程序化 tween,确定性散布,零新帧资产) ----
|
||||
|
||||
var _crest_bricks: Array = [] # 纹章碎砖驻留面(close 时回收)
|
||||
var _fx_crest_shards := 0
|
||||
var _fx_dust_motes := 0
|
||||
var _fx_flag_dots := 0
|
||||
|
||||
|
||||
# 碎砖拼纹章(胜利 §2.4——蜡封落定后十枚碎砖自四周向蜡封外环收拢拼成纹章环,驻留至 close)
|
||||
func _play_crest_assemble() -> void:
|
||||
_clear_crest_bricks()
|
||||
_fx_crest_shards = 10
|
||||
var c := _board_rect.get_center() if _board_rect.size != Vector2.ZERO else get_viewport_rect().size * 0.5
|
||||
for i in 10:
|
||||
var brick := ColorRect.new()
|
||||
brick.color = UiKit.BRICK if i % 2 == 0 else UiKit.FLOOR # 碎砖=砖面族色(掩尘/裸土——图底同源)
|
||||
brick.size = Vector2(12, 7)
|
||||
brick.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
var ang := TAU * float(i) / 10.0
|
||||
var ring := c + Vector2(cos(ang), sin(ang)) * 64.0
|
||||
brick.position = c + Vector2(cos(ang), sin(ang)) * 200.0 # 自四周场外入场
|
||||
add_child(brick)
|
||||
_crest_bricks.append(brick)
|
||||
var btw := brick.create_tween()
|
||||
btw.tween_interval(UiKit.motion(0.52)) # 待蜡封落定(seal 0.42+0.22 弹跳中段)
|
||||
btw.tween_property(brick, "position", ring - brick.size * 0.5, UiKit.motion(0.4)).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT)
|
||||
btw.parallel().tween_property(brick, "rotation", sin(ang) * 0.35, UiKit.motion(0.4))
|
||||
|
||||
|
||||
# 坐砖掸灰(失败 §2.5——暖灰尘絮自棋盘中带缓缓飘落,最静一拍的运动承载)
|
||||
func _play_dust_sadle() -> void:
|
||||
_fx_dust_motes = 8
|
||||
var base := _board_rect.get_center() if _board_rect.size != Vector2.ZERO else get_viewport_rect().size * 0.5
|
||||
for i in 8:
|
||||
var mote := ColorRect.new()
|
||||
mote.color = Color(UiKit.WARM_FAINT, 0.85)
|
||||
mote.size = Vector2(4, 4)
|
||||
mote.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
mote.position = base + Vector2((float(i % 4) - 1.5) * 30.0, -24.0 - 8.0 * float(i / 4))
|
||||
add_child(mote)
|
||||
var mtw := mote.create_tween()
|
||||
mtw.tween_interval(UiKit.motion(0.08 * i))
|
||||
mtw.tween_property(mote, "position:y", mote.position.y + 64.0, UiKit.motion(0.9))
|
||||
mtw.parallel().tween_property(mote, "modulate:a", 0.0, UiKit.motion(0.9))
|
||||
mtw.tween_callback(mote.queue_free)
|
||||
|
||||
|
||||
func _clear_crest_bricks() -> void:
|
||||
for b in _crest_bricks:
|
||||
if is_instance_valid(b):
|
||||
b.queue_free()
|
||||
_crest_bricks.clear()
|
||||
|
||||
|
||||
# 演出探针出口(fx_probe——胜利纹章/失败掸灰/旗光收回三断言面)
|
||||
func fx_probe() -> Dictionary:
|
||||
return {"crest_shards": _fx_crest_shards, "dust_motes": _fx_dust_motes, "flag_dots": _fx_flag_dots}
|
||||
@@ -12,7 +12,7 @@ signal closed
|
||||
const ENTRIES := [
|
||||
["#C9AF83", "完整覆尘地砖", "未开格——左键挖开(1AP,须相邻)· 右键插旗(1AP)"],
|
||||
["#B39A78", "碎裂裸土", "已开 0 格——可通行,左键移动(免费)"],
|
||||
["#000000", "旗(封印印记)", "一次性承诺(不能拔)——轮末自动判定标中;插错可左键直接挖开(安全格零奖励、雷格吃挖雷惩罚)"],
|
||||
["#000000", "旗", "封印印记(呈现名·不能拔)——轮末自动判定标中;插错可左键直接挖开(安全格零奖励、雷格吃挖雷惩罚)"],
|
||||
["#2e86de", "小人", "探险者——8 向相邻未开格常驻微光 = 可挖域"],
|
||||
["#F0A828", "金徽章", "已开格奖励身份 icon(旗之下、数值之上,不进数字区)"],
|
||||
["#33261B", "状态槽空态", "无状态效果时槽位隐藏,数据框上移补位——非异常"],
|
||||
|
||||
@@ -17,7 +17,8 @@ func _init() -> void:
|
||||
|
||||
|
||||
# 结算轮光带:金橙半透明横带自棋盘上缘扫至下缘(一次 0.5s,自动回收)——
|
||||
# "棋盘能量向战斗画面汇聚"的开轮语义(§2.2 色彩转金橙主导;区分于 board_fx 的完美排雷金光带)
|
||||
# "棋盘能量向战斗画面汇聚"的开轮语义(§2.2 色彩转金橙主导;区分于 board_fx 的完美排雷金光带)。
|
||||
# 终稿(样张 v2 冻结批 T3):加 2px 亮金前导缘线——扫带运动方向可读(兑现光带逐行横扫 §2.2)
|
||||
func play_round_sweep(board_rect: Rect2) -> void:
|
||||
if board_rect.size == Vector2.ZERO:
|
||||
return
|
||||
@@ -27,10 +28,20 @@ func play_round_sweep(board_rect: Rect2) -> void:
|
||||
band.position = Vector2(board_rect.position.x, board_rect.position.y)
|
||||
band.size = Vector2(board_rect.size.x, 34.0)
|
||||
add_child(band)
|
||||
var edge := ColorRect.new()
|
||||
edge.color = Color(UiKit.GOLD_LIT, 0.7)
|
||||
edge.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
edge.size = Vector2(board_rect.size.x, 2.0)
|
||||
edge.position = band.position
|
||||
add_child(edge)
|
||||
var tw := band.create_tween()
|
||||
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)
|
||||
var etw := edge.create_tween()
|
||||
etw.tween_property(edge, "position:y", board_rect.end.y - 2.0, UiKit.motion(0.5)).set_trans(Tween.TRANS_SINE)
|
||||
etw.parallel().tween_property(edge, "modulate:a", 0.0, UiKit.motion(0.2)).set_delay(UiKit.motion(0.3))
|
||||
etw.tween_callback(edge.queue_free)
|
||||
|
||||
|
||||
# 跨区弹道:glyph 字形自 from 飞向 to(0.35s + 轻微上抛弧线 + 终点淡出,自动回收)——
|
||||
|
||||
@@ -183,13 +183,22 @@ func set_pending(ptype: String, pid: String) -> void:
|
||||
_play_intent_motion()
|
||||
|
||||
|
||||
# 三档动作动画占位:摇定时一次性短动画(意图图标弹入 + 立绘前倾回正),随后静帧保持
|
||||
# 意图三档动作动画终稿(样张 v2 冻结批 T3——§4.6.4 三档:①普通攻击=零表示〔set_pending 早退〕
|
||||
# ②特殊准备=徽标弹入+二次弹跳(高举语义,运动手段 §2)③重击/硬化=缓推蓄力+定格微顿+回落;
|
||||
# 三档运动签名互异,灰度/剪影层可辨)。摇定时一次性短动画,随后静帧保持。
|
||||
func _play_intent_motion() -> void:
|
||||
var tw := create_tween()
|
||||
tw.tween_property(self, "_intent_scale", 1.6, UiKit.motion(0.10))
|
||||
tw.tween_property(self, "_intent_scale", 1.0, UiKit.motion(0.16))
|
||||
tw.parallel().tween_property(self, "_glyph_rot", 4.0, UiKit.motion(0.10))
|
||||
tw.tween_property(self, "_glyph_rot", 0.0, UiKit.motion(0.14))
|
||||
if _intent_col == UiKit.GOLD_DEEP:
|
||||
tw.tween_property(self, "_intent_scale", 1.22, UiKit.motion(0.28)).set_trans(Tween.TRANS_SINE) # ③蓄力缓推
|
||||
tw.tween_interval(UiKit.motion(0.40)) # 定格微顿
|
||||
tw.tween_property(self, "_intent_scale", 1.0, UiKit.motion(0.18))
|
||||
else:
|
||||
tw.tween_property(self, "_intent_scale", 1.26, UiKit.motion(0.11)) # ②二次弹跳(高举)
|
||||
tw.tween_property(self, "_intent_scale", 1.0, UiKit.motion(0.13))
|
||||
|
||||
|
||||
# 资产挂点(战斗表现样张批):tex 落位后字形隐退、纹理立绘顶位——零代码换装契约;tex=null 回退字形。
|
||||
@@ -234,6 +243,7 @@ func mark_dead() -> void:
|
||||
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))
|
||||
_spawn_death_shards() # 碎裂退场(§4.6.4 语义硬规则:灰化+碎裂禁瞬间消失;§8.4 v2 冻结 5 块 0.45s)
|
||||
_refresh_portrait_glyph()
|
||||
_intent_visible = false # 死亡残位意图退位(旧同款)
|
||||
_set_hp(0)
|
||||
@@ -241,6 +251,38 @@ func mark_dead() -> void:
|
||||
slot_row.visible = false # 状态行随 mark_dead 同拍隐藏(批内代拍板②待追认)
|
||||
|
||||
|
||||
# ---- 演出密度终稿(样张 v2 冻结批 T3——程序化 tween,零新帧资产) ----
|
||||
|
||||
const DEATH_SHARD_N := 5 # 碎块数(§8.4 冻结值)
|
||||
var _death_shards := 0 # 探针出口计数(碎块节点回收后仍保留读数)
|
||||
|
||||
|
||||
# 死亡碎块弹飞:立绘盒中心五块碎石抛物线散落(程序化色块——确定性散布,禁全局随机)
|
||||
func _spawn_death_shards() -> void:
|
||||
_death_shards = DEATH_SHARD_N
|
||||
var center := _portrait_rect().get_center()
|
||||
for i in DEATH_SHARD_N:
|
||||
var shard := ColorRect.new()
|
||||
shard.color = UiKit.STONE if i % 2 == 0 else UiKit.STONE.darkened(0.2)
|
||||
shard.size = Vector2(7, 5) + Vector2(2, 0) * float(i % 2)
|
||||
shard.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
shard.position = center + Vector2(float(i % 3) - 1.0, float(i / 3) - 0.5) * 6.0
|
||||
shard.rotation = float(i) * 0.7
|
||||
add_child(shard)
|
||||
var dir := Vector2((float(i % 3) - 1.0) * 30.0, -36.0 - 6.0 * float(i / 3))
|
||||
var peak := shard.position + dir
|
||||
var stw := shard.create_tween()
|
||||
stw.tween_property(shard, "position", peak, UiKit.motion(0.18)).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_OUT)
|
||||
stw.tween_property(shard, "position", peak + Vector2(dir.x * 0.4, 52.0), UiKit.motion(0.27)).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_IN)
|
||||
stw.parallel().tween_property(shard, "modulate:a", 0.0, UiKit.motion(0.18)).set_delay(UiKit.motion(0.27))
|
||||
stw.tween_callback(shard.queue_free)
|
||||
|
||||
|
||||
# 演出探针出口(fx_probe——死亡碎裂断言面)
|
||||
func fx_probe() -> Dictionary:
|
||||
return {"death_shards": _death_shards}
|
||||
|
||||
|
||||
func is_dead() -> bool:
|
||||
return _dead
|
||||
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
# VERTICAL SLICE - NOT FOR PRODUCTION
|
||||
# 演出密度探针(样张 v2 冻结批 T3——演出四项可触发+探针断言覆盖):
|
||||
# ①死亡碎裂(standee mark_dead → 碎块计数)②意图三档动画(damage/buff/debuff/charge 摇定不炸+徽标可见)
|
||||
# ③胜利纹章+失败掸灰+旗光收回(battle_end open_victory/open_defeat → fx_probe 计数)
|
||||
# ④结算轮光带(settlement_fx play_round_sweep → 子节点落位)。
|
||||
# 运行:Godot_console.exe --headless --path . res://smoke/fx_probe.tscn
|
||||
extends Node
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
await get_tree().process_frame
|
||||
var problems: Array = []
|
||||
|
||||
# ---- ①② 站桩侧:死亡碎裂 + 意图三档 ----
|
||||
var sb_script := load("res://presentation/views/standee_body.gd")
|
||||
var sb = sb_script.new("enemy")
|
||||
sb.box = Vector2(150, 176) # 站桩盒几何(Node2D 自定义字段——非 Control.size)
|
||||
add_child(sb)
|
||||
sb.set_data({"display_name": "地精", "enemy_id": "goblin", "hp": 10, "max_hp": 10})
|
||||
await get_tree().process_frame
|
||||
# 意图三档:特殊准备(damage/buff/debuff)与蓄力档(heavy)摇定——动画触发+徽标可见
|
||||
for tier in [["damage", "slash"], ["buff", "atk_up"], ["debuff", "weaken_hit"], ["damage", "heavy"]]:
|
||||
sb.set_pending(tier[0], tier[1])
|
||||
if not sb.intent_probe().visible:
|
||||
problems.append("意图徽标应可见(%s/%s)" % [tier[0], tier[1]])
|
||||
await get_tree().process_frame
|
||||
sb.set_pending("basic", "") # 档①普通攻击=零表示
|
||||
if sb.intent_probe().visible:
|
||||
problems.append("普通攻击徽标应零表示(§4.6.4 档①)")
|
||||
# 死亡碎裂
|
||||
sb.mark_dead()
|
||||
await get_tree().process_frame
|
||||
var dp: Dictionary = sb.fx_probe()
|
||||
if int(dp.get("death_shards", 0)) != 5:
|
||||
problems.append("死亡碎块应 5 块(§8.4 冻结值),实得 %s" % [dp.get("death_shards")])
|
||||
if not sb.is_dead():
|
||||
problems.append("mark_dead 后 is_dead 应真")
|
||||
|
||||
# ---- ③ 终局侧:胜利纹章 / 失败掸灰+旗光 ----
|
||||
var end_ov := BattleEndOverlay.new()
|
||||
add_child(end_ov)
|
||||
var br := Rect2(300, 300, 400, 300)
|
||||
end_ov.open_victory("胜利", "继续", br)
|
||||
await get_tree().process_frame
|
||||
var vp: Dictionary = end_ov.fx_probe()
|
||||
if int(vp.get("crest_shards", 0)) != 10:
|
||||
problems.append("胜利纹章碎砖应 10 枚(§2.4),实得 %s" % [vp.get("crest_shards")])
|
||||
end_ov.close()
|
||||
end_ov.open_defeat("失败", "重来", br)
|
||||
await get_tree().process_frame
|
||||
var fp: Dictionary = end_ov.fx_probe()
|
||||
if int(fp.get("dust_motes", 0)) != 8:
|
||||
problems.append("失败掸灰尘絮应 8 枚(§2.5),实得 %s" % [fp.get("dust_motes")])
|
||||
if int(fp.get("flag_dots", 0)) != 8:
|
||||
problems.append("旗光收回应 8 点,实得 %s" % [fp.get("flag_dots")])
|
||||
end_ov.close()
|
||||
|
||||
# ---- ④ 结算轮光带(含前导亮缘) ----
|
||||
var sfx := SettlementFxView.new()
|
||||
add_child(sfx)
|
||||
sfx.play_round_sweep(br) # 光带几何全由 board_rect 驱动——无需容器尺寸
|
||||
await get_tree().process_frame
|
||||
var n_fx := 0
|
||||
for ch in sfx.get_children():
|
||||
if ch is ColorRect:
|
||||
n_fx += 1
|
||||
if n_fx < 2:
|
||||
problems.append("结算光带应含带体+前导缘线 ≥2 节点,实得 %d" % n_fx)
|
||||
|
||||
# ---- 汇总 ----
|
||||
if problems.is_empty():
|
||||
print("[fx_probe] PASS — 死亡碎裂5块/意图三档+零表示/胜利纹章10/失败掸灰8+旗光8/结算光带+缘线 全链路通过")
|
||||
get_tree().quit(0)
|
||||
else:
|
||||
for p in problems:
|
||||
print("[fx_probe] FAIL — " + p)
|
||||
print("[fx_probe] FAIL — %d 项断言未过" % problems.size())
|
||||
get_tree().quit(1)
|
||||
@@ -0,0 +1,6 @@
|
||||
[gd_scene load_steps=2 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://smoke/fx_probe.gd" id="1"]
|
||||
|
||||
[node name="FxProbe" type="Node"]
|
||||
script = ExtResource("1")
|
||||
Reference in new issue
Block a user