# mobile_gesture_test —— 移动端长按、瞄准、快捷栏交换和物品触摸拖放。 # godot --display-driver headless --audio-driver Dummy --rendering-method gl_compatibility --path project --script mobile_gesture_test.gd extends SceneTree const TouchButton = preload("res://ui/mobile/mobile_touch_button.gd") const Quickbar = preload("res://ui/quickbar.gd") const SkillTable = preload("res://ui/skill_table.gd") const SkillUI = preload("res://ui/skill_ui.gd") const UiManager = preload("res://ui/ui_manager.gd") const InventoryUI = preload("res://ui/inventory_ui.gd") const MouseController = preload("res://ui/mouse_controller.gd") class FakeClient extends Node: var inventory := [{"cell": 5, "vnum": 19, "count": 2}] var skills := [{"id": 1, "level": 5, "master": 0}, {"id": 16, "level": 3, "master": 0}] var quickslots := [] var calls := [] var casts := [] var main := 1000 func get_main_vid() -> int: return main func get_entity(_vid: int) -> Dictionary: return {"vid": _vid, "kind": 2, "race": 0} func get_target() -> Dictionary: return {"vid": 2000} func get_inventory() -> Array: return inventory func get_equipment() -> Array: return [] func get_skills() -> Array: return skills func get_skill_group() -> int: return 1 func get_points() -> Dictionary: return {"level": 30, "skill_active": 5, "skill_support": 3, "skill_horse": 2} func get_quickslots() -> Array: return quickslots func use_skill(id: int, target: int) -> bool: calls.append(["use_skill", id, target]); return true func cast_skill(motion: int, heading: float, x: int, y: int) -> bool: casts.append([motion, heading, x, y]); return true func quickslot_add(pos: int, kind: int, ref: int) -> bool: calls.append(["quickslot_add", pos, kind, ref]); return true func quickslot_swap(a: int, b: int) -> bool: calls.append(["quickslot_swap", a, b]); return true func use_item(window: int, cell: int) -> bool: calls.append(["use_item", window, cell]); return true func move_item(fw: int, fc: int, tw: int, tc: int, count: int) -> bool: calls.append(["move_item", fw, fc, tw, tc, count]); return true class FakeProto extends Node: func item(vnum: int) -> Dictionary: return {"vnum": vnum, "name": "Item%d" % vnum, "locale_name": "物品%d" % vnum, "type": 1, "sub_type": 0, "anti_flags": 0, "wear_flags": 1 << 4, "limits": []} var _fail := 0 func _ck(condition: bool, message: String) -> void: if not condition: _fail += 1 printerr("FAIL: " + message) func _init() -> void: await _run() if _fail == 0: print("PASS: mobile_gesture_test (long press + aim + quickbar swap + item touch drag)") quit(0) else: printerr("%d check(s) failed" % _fail) quit(1) func _run() -> void: await process_frame # 1) 长按按钮必须保留同一 touch index,并在拖动松手时发出完成信号。 var gesture_host := Control.new() get_root().add_child(gesture_host) var gesture := TouchButton.new() gesture.setup("技能", Color(0.4, 0.7, 1.0)) gesture.set_gesture_enabled(true) gesture.size = Vector2(72, 72) gesture_host.add_child(gesture) var press_count := [0] var long_count := [0] var release_count := [0] gesture.pressed.connect(func(): press_count[0] += 1) gesture.long_pressed.connect(func(): long_count[0] += 1) gesture.drag_released.connect(func(_delta: Vector2): release_count[0] += 1) var down := InputEventScreenTouch.new() down.index = 41 down.pressed = true down.position = Vector2(30, 30) gesture._gui_input(down) await create_timer(0.65).timeout var drag := InputEventScreenDrag.new() drag.index = 41 drag.position = Vector2(52, 12) gesture._gui_input(drag) var up := InputEventScreenTouch.new() up.index = 41 up.pressed = false up.position = drag.position gesture._gui_input(up) _ck(press_count[0] == 1, "长按手势只触发一次按下") _ck(long_count[0] == 1, "超过阈值后触发 long_pressed") _ck(release_count[0] == 1 and not gesture.active, "拖动松手发出 drag_released 并清理状态") # 2) 快捷栏保持服务器槽位交换,同时瞄准方向进入 cast_skill heading。 var assets := AssetRoot.path() var table := SkillTable.new() var loaded := false for lang in ["en", "common"]: if table.load_file(assets.path_join("locale/locale/%s/skilldesc.txt" % lang)): loaded = true break if not loaded: print(" (skip: no skilldesc.txt)") return var ui: CanvasLayer = UiManager.new() get_root().add_child(ui) ui._ready() ui.set_mobile_mode(true) var client := FakeClient.new() get_root().add_child(client) var player := Node3D.new() get_root().add_child(player) var quickbar: Node = Quickbar.new() get_root().add_child(quickbar) quickbar.setup(client, table, ui, func() -> Node: return player) var skill_ui: Node = SkillUI.new() get_root().add_child(skill_ui) skill_ui.setup(client, table, ui) skill_ui.set_mobile_mode(true) skill_ui.set_mobile_quickbar(quickbar) skill_ui.open() await process_frame _ck(skill_ui._mobile_drop_slots.size() == 8, "技能页创建 8 个移动快捷栏落点") # 用真实技能行按钮的拖放结束坐标,验证技能可放入指定槽位。 var source: TouchButton = null for row in skill_ui._win.get_node("SkillScroll/list").get_children(): if row.get_child_count() > 0 and row.get_child(0) is TouchButton: source = row.get_child(0) break if source: var target_pos: Vector2 = skill_ui._mobile_drop_slots[2].get_global_rect().get_center() source._last_position = target_pos skill_ui._mobile_begin_skill_drag(1, source) skill_ui._mobile_end_skill_drag(1, source) _ck(quickbar.mobile_slot_state(2).get("id", 0) == 1, "长按技能拖放到第 3 格快捷栏") quickbar.assign(0, "skill", 16) quickbar.mobile_swap(0, 2) _ck(quickbar.mobile_slot_state(0).get("id", 0) == 1 and quickbar.mobile_slot_state(2).get("id", 0) == 16, "移动快捷栏交换更新本地槽位") _ck(client.calls.has(["quickslot_swap", 0, 2]), "移动快捷栏交换沿用 CG_QUICKSLOT_SWAP") quickbar.activate_aimed(0, Vector2(1, 0)) _ck(client.casts.size() == 1 and client.calls.has(["use_skill", 1, 2000]), "技能长按瞄准仍走 use_skill + cast_skill") # 3) 背包长按详情后继续拖动,松手由 MouseController 落到目标格。 var proto := FakeProto.new() get_root().add_child(proto) var mouse: Node = MouseController.new() get_root().add_child(mouse) mouse.setup(ui) var inventory: Node = InventoryUI.new() inventory.item_mouse = mouse get_root().add_child(inventory) inventory.setup(ui, client, proto, assets) inventory.set_mobile_mode(true) inventory.open() await process_frame var source_cell: Panel = inventory._cells.get(5) var target_cell: Panel = inventory._cells.get(8) if source_cell and target_cell: var item_down := InputEventScreenTouch.new() item_down.index = 52 item_down.pressed = true item_down.position = source_cell.get_global_rect().get_center() inventory._on_cell_input(5, item_down) await create_timer(0.65).timeout _ck(inventory._mobile_detail_dialog != null, "物品长按先打开详情卡") var item_drag := InputEventScreenDrag.new() item_drag.index = 52 item_drag.position = target_cell.get_global_rect().get_center() inventory._handle_mobile_cell_drag(5, item_drag) _ck(mouse.is_attached(), "继续拖动后进入全局物品拖放状态") var item_up := InputEventScreenTouch.new() item_up.index = 52 item_up.pressed = false item_up.position = item_drag.position inventory._input(item_up) mouse._input(item_up) _ck(client.calls.has(["move_item", 1, 5, 1, 8, 2]), "物品触摸松手落到目标格并保留堆叠数量") else: _ck(false, "inventory source/target cells exist")