- schema两件: CampSceneLayerSpec(层键+贴图矩形+命中矩形+命中键)+CampSceneLayoutTable(canvas/hud_band_h/gate_gap/wall_band/layers+validate()按面拆四查:身份贴图/命中边界/命中互斥/围墙带包络) - 数据表camp_scene_layout.tres: 10层坐标唯一事实源(布局草案值+§5已裁:布告板(845,268,130,140)/门扇(520,250,240,220)门洞对齐/帐篷300x170;贴图矩形=contain终稿实测尺寸锚定:底中心/顶锚/底锚按物件语义,campfire命中(560,640,160,80)只取下半) - ConfigDB接线: 表注册+表现域容器camp_scene_layout字段+装载期validate()逐问题_fatal(fail-fast红线:命中区越画面/相交/侵顶栏=数据致命) - CampSceneView: setup_layers/bind_hotspots/get_hotspot_rect/set_feedback_state/set_hud_band五公开口+object_pressed信号; Control根+TextureRect层子节点序=z序(禁CanvasLayer/z_index/Sprite2D); 根单点_gui_input命中判定(层全IGNORE); 四态反馈:悬停=Candle轮廓+名牌(FS_HINT/INK_ON_DEEP/BG_ROOT板,画面内钳制)+Tween逐级整像素浮动/按下=位移1px/禁用=ShaderMaterial去饱和+降明度(零新素材,modulate只有明度轴); 素材缺位程序化占位冒烟安全; 命中键=七物件绑定(gate/forge/dummy/chest/notebook/board/campfire) - V14单测camp_scene_layout_test.gd(11断言): 校验函数通过/七键集/两两不相交/不越画面不侵顶栏/篝火只下半/门扇对齐缺口/布告板挂墙带/10层序/ConfigDB装载链——全表关系谓词零坐标字面量; run_all manifest登记expect=0 - 验证: --editor --quit刷类缓存后V14孤跑绿(11项)+视图孤检绿(9项:子节点序/合成点击信号/悬停名牌/拖出取消/禁用不发/HUD槽——临时件不入库,T5探针承载正式验收); S9/S13归基线(gdstyle新违规4条已按职责轴拆函数清偿:validate按面四拆/_gui_input三拆/setup_layers构建段三拆/循环内分配提_build_layer_node)
68 lines
3.0 KiB
GDScript
68 lines
3.0 KiB
GDScript
## 营地场景布局表 schema(camp_scene_layout.tres):10 层贴图矩形/命中矩形/门洞缺口/围墙带包络/顶栏禁区。
|
||
## 坐标数值唯一事实源=数据行本表——视图、探针、单测一律读表取值,禁另写坐标字面量。
|
||
class_name CampSceneLayoutTable
|
||
extends Resource
|
||
|
||
@export var canvas := Vector2(1280, 800) # 终稿画布(工程档同幅面)
|
||
@export var hud_band_h := 48 # 顶栏禁区高度(命中不侵)
|
||
@export var gate_gap := Vector2i(520, 760) # 门洞缺口 x 范围(透出背景层小路,禁填死)
|
||
@export var wall_band := Rect2(0, 240, 1280, 70) # 围墙带包络(门柱顶 240—桩底 310;围墙贴图须落在其内)
|
||
@export var layers: Array[CampSceneLayerSpec] = []
|
||
|
||
|
||
## 加载期 fail-fast 校验(ConfigDB 装载即查,问题非空=数据致命)。
|
||
## 校验面按件拆分:层身份与贴图矩形/命中区边界/命中区互斥/围墙带包络。
|
||
func validate() -> Array[String]:
|
||
var problems: Array[String] = []
|
||
var clickables: Array[CampSceneLayerSpec] = _collect_clickables(problems)
|
||
_check_hotspot_bounds(clickables, problems)
|
||
_check_hotspot_disjoint(clickables, problems)
|
||
_check_wall_band(problems)
|
||
return problems
|
||
|
||
|
||
func _collect_clickables(problems: Array[String]) -> Array[CampSceneLayerSpec]:
|
||
var seen: Dictionary[StringName, bool] = {}
|
||
var clickables: Array[CampSceneLayerSpec] = []
|
||
for layer in layers:
|
||
if layer.key == &"":
|
||
problems.append("层键为空")
|
||
continue
|
||
if seen.has(layer.key):
|
||
problems.append("层键重复: %s" % layer.key)
|
||
seen[layer.key] = true
|
||
if _out_of_canvas(layer.texture_rect):
|
||
problems.append("贴图矩形越画面: %s %s" % [layer.key, layer.texture_rect])
|
||
if layer.is_clickable():
|
||
if layer.hotspot_key == &"":
|
||
problems.append("可点层缺命中键: %s" % layer.key)
|
||
clickables.append(layer)
|
||
elif layer.hotspot_key != &"":
|
||
problems.append("不可点层带命中键: %s" % layer.key)
|
||
return clickables
|
||
|
||
|
||
func _check_hotspot_bounds(clickables: Array[CampSceneLayerSpec], problems: Array[String]) -> void:
|
||
for h in clickables:
|
||
if h.hotspot.position.x < 0.0 or h.hotspot.position.y < hud_band_h \
|
||
or _out_of_canvas(h.hotspot):
|
||
problems.append("命中区越画面或侵顶栏禁区: %s %s" % [h.key, h.hotspot])
|
||
|
||
|
||
func _check_hotspot_disjoint(clickables: Array[CampSceneLayerSpec], problems: Array[String]) -> void:
|
||
for i in clickables.size():
|
||
for j in range(i + 1, clickables.size()):
|
||
if clickables[i].hotspot.intersects(clickables[j].hotspot):
|
||
problems.append("命中区相交: %s × %s" % [clickables[i].key, clickables[j].key])
|
||
|
||
|
||
func _check_wall_band(problems: Array[String]) -> void:
|
||
for layer in layers:
|
||
if layer.key == &"wall" and not wall_band.encloses(layer.texture_rect):
|
||
problems.append("围墙贴图越围墙带包络: %s" % layer.texture_rect)
|
||
|
||
|
||
func _out_of_canvas(rect: Rect2) -> bool:
|
||
return rect.position.x < 0.0 or rect.position.y < 0.0 \
|
||
or rect.end.x > canvas.x or rect.end.y > canvas.y
|