feat: complete mobile UI implementation
This commit is contained in:
+57
-1
@@ -8,7 +8,9 @@
|
||||
# # 输入:数字 1..4 -> 本页 0..3,F1..F4 -> 本页 4..7。
|
||||
#
|
||||
# 技能施放:先发送 M2Client.use_skill(skill_id, target_vid),再同步
|
||||
# M2Client.cast_skill(motion_idx, heading, x_cm, y_cm)(朝向 = 玩家 yaw)。
|
||||
# M2Client.cast_skill(motion_idx, heading, x_cm, y_cm)(默认朝向 = 玩家 yaw)。
|
||||
# 移动端按住技能按钮时,activate_aimed() 会把拖动方向转换为相同的
|
||||
# server heading;校验、冷却和目标选择仍走 activate() 的同一条链路。
|
||||
# 冷却:本地预测时长由 SkillTable 提供;GC_SKILL_COOLTIME_END 可提前解锁。
|
||||
extends Node
|
||||
|
||||
@@ -35,6 +37,7 @@ var _slots := [] # 当前页 UI:[{btn, cd, lbl}]
|
||||
var _state := [] # 36 个服务端槽:[{kind, id, cd_end:float}]
|
||||
var _page := 0
|
||||
var _move_from := -1
|
||||
var _mobile_mode := false
|
||||
|
||||
func setup(m2client: Node, skill_table: RefCounted, parent: Node, player_getter: Callable) -> void:
|
||||
client = m2client
|
||||
@@ -84,7 +87,38 @@ func assign(slot: int, kind: String, id: int, persist := true) -> bool:
|
||||
_refresh_slot(slot)
|
||||
return true
|
||||
|
||||
# Mobile skill pages cannot rely on drag-and-drop into the small PC quickbar.
|
||||
# Put a selected skill/item/emote into the first free slot on the current page
|
||||
# while keeping the same server-backed CG_QUICKSLOT_ADD path.
|
||||
func assign_mobile(kind: String, id: int) -> bool:
|
||||
for slot in SLOTS_PER_PAGE:
|
||||
if _state[_global_slot(slot)].kind == "":
|
||||
return assign(slot, kind, id)
|
||||
return false
|
||||
|
||||
func mobile_slot_state(slot: int) -> Dictionary:
|
||||
if slot < 0 or slot >= SLOTS_PER_PAGE:
|
||||
return {}
|
||||
return _state[_global_slot(slot)].duplicate(true)
|
||||
|
||||
func mobile_swap(local_a: int, local_b: int) -> bool:
|
||||
if local_a < 0 or local_a >= SLOTS_PER_PAGE or local_b < 0 or local_b >= SLOTS_PER_PAGE:
|
||||
return false
|
||||
if local_a == local_b:
|
||||
return true
|
||||
_swap(local_a, local_b)
|
||||
return true
|
||||
|
||||
func activate(slot: int) -> void:
|
||||
_activate_slot(slot, Vector2.ZERO)
|
||||
|
||||
## Mobile long-press skill release. `screen_direction` uses screen space:
|
||||
## right is +X and up is -Y. A zero vector intentionally falls back to the
|
||||
## player's current facing, so a long press without a drag is still usable.
|
||||
func activate_aimed(slot: int, screen_direction: Vector2) -> void:
|
||||
_activate_slot(slot, screen_direction)
|
||||
|
||||
func _activate_slot(slot: int, screen_direction: Vector2) -> void:
|
||||
if slot < 0 or slot >= SLOTS_PER_PAGE:
|
||||
return
|
||||
var global := _global_slot(slot)
|
||||
@@ -101,6 +135,8 @@ func activate(slot: int) -> void:
|
||||
if p:
|
||||
yaw = fposmod(90.0 - rad_to_deg(p.rotation.y), 360.0)
|
||||
xy = Vector2(p.position.x * CM, -p.position.z * CM)
|
||||
if screen_direction.length() > 0.01:
|
||||
yaw = _aim_heading(p, screen_direction, yaw)
|
||||
var target_vid := 0
|
||||
if client.has_method("get_target"):
|
||||
target_vid = int(client.get_target().get("vid", 0))
|
||||
@@ -159,6 +195,18 @@ func activate(slot: int) -> void:
|
||||
elif s.kind == "emote" and client.has_method("send_emoticon"):
|
||||
client.send_emoticon(s.id)
|
||||
|
||||
func _aim_heading(player_node: Node3D, screen_direction: Vector2, fallback: float) -> float:
|
||||
# Keep the historical POC heading convention as the base and add the
|
||||
# camera-relative drag angle. This makes an upward drag equal to the
|
||||
# existing one-tap cast, while right/left drags rotate it predictably.
|
||||
var angle_from_up := rad_to_deg(atan2(screen_direction.x, -screen_direction.y))
|
||||
var camera_heading := player_node.rotation.y
|
||||
if net_play and "camera" in net_play and net_play.camera \
|
||||
and net_play.camera.has_method("heading"):
|
||||
camera_heading = float(net_play.camera.heading())
|
||||
var camera_delta := rad_to_deg(camera_heading - player_node.rotation.y)
|
||||
return fposmod(fallback + camera_delta + angle_from_up, 360.0)
|
||||
|
||||
# net_play 的 MODE_USE_SKILL 预约进射程后回调(use_skill_hook)——slot 为全局快捷栏
|
||||
# 索引。返回是否确实发起了施法(对齐参考端 `if (__UseSkill(slot)) __ClearReservedAction()`)。
|
||||
func activate_reserved(global_slot: int) -> bool:
|
||||
@@ -193,6 +241,14 @@ func toggle_visible() -> void:
|
||||
if _root:
|
||||
_root.visible = not _root.visible
|
||||
|
||||
func set_mobile_mode(enabled: bool) -> void:
|
||||
_mobile_mode = enabled
|
||||
if _root:
|
||||
_root.visible = not enabled
|
||||
|
||||
func is_mobile_mode() -> bool:
|
||||
return _mobile_mode
|
||||
|
||||
func is_visible() -> bool:
|
||||
return _root != null and _root.visible
|
||||
|
||||
|
||||
Reference in New Issue
Block a user