Files
mtgodot-poc/project/ui/mobile/mobile_hud.gd
T

343 lines
11 KiB
GDScript

# MobileHud —— landscape in-game HUD.
#
# This is deliberately a presentation-only node. NetPlay updates it through
# the same setter contract as hud.gd, while MobileUiRoot wires its buttons to
# the existing feature controllers.
extends Control
const TouchButton := preload("res://ui/mobile/mobile_touch_button.gd")
signal avatar_pressed
signal inventory_pressed
signal minimap_pressed
signal menu_pressed
signal quest_pressed
signal party_member_pressed(vid: int)
signal context_action_pressed
const AVATAR_SIZE := 78.0
const SAFE_MARGIN := 16.0
var client: Node
var _player_name: Label
var _avatar: TouchButton
var _hp_bar: ProgressBar
var _mp_bar: ProgressBar
var _hp_text: Label
var _mp_text: Label
var _level: Label
var _party_host: VBoxContainer
var _target_panel: Panel
var _target_name: Label
var _target_bar: ProgressBar
var _quest: TouchButton
var _context: TouchButton
var _mini_host: Control
var _mini_placeholder: Label
var _channel: Label
var _affects: HBoxContainer
var _client_bound := false
func setup() -> void:
set_anchors_preset(Control.PRESET_FULL_RECT)
mouse_filter = Control.MOUSE_FILTER_IGNORE
_build_player_card()
_build_top_right()
_build_target()
_build_quest()
_build_context_action()
func bind_client(m2client: Node) -> void:
if _client_bound and client == m2client:
_refresh_player()
_refresh_party()
return
client = m2client
_client_bound = client != null
if client and client.has_signal("party_changed"):
client.party_changed.connect(_refresh_party)
if client and client.has_signal("vitals_changed"):
client.vitals_changed.connect(func(_vid): _refresh_party())
_refresh_player()
_refresh_party()
func set_player_name(value: String) -> void:
if _player_name:
_player_name.text = value if value != "" else "冒险者"
func get_minimap_host() -> Control:
return _mini_host
func get_quest_button() -> Control:
return _quest
func get_context_button() -> Control:
return _context
func set_vitals(hp: int, max_hp: int, sp: int, max_sp: int) -> void:
if _hp_bar:
_hp_bar.max_value = maxi(1, max_hp)
_hp_bar.value = clampi(hp, 0, maxi(1, max_hp))
if _mp_bar:
_mp_bar.max_value = maxi(1, max_sp)
_mp_bar.value = clampi(sp, 0, maxi(1, max_sp))
if _hp_text:
_hp_text.text = "%d/%d" % [hp, max_hp]
if _mp_text:
_mp_text.text = "%d/%d" % [sp, max_sp]
func set_exp(xp: int, next_xp: int) -> void:
# The mobile card keeps the main HUD quiet; experience is shown in the
# level caption so it remains available without adding a bottom bar.
if _level and next_xp > 0:
_level.tooltip_text = "经验 %d / %d" % [xp, next_xp]
func set_level(value: int) -> void:
if _level:
_level.text = "Lv %d" % value
func set_energy(value: int, max_value: int = 100) -> void:
if _avatar:
_avatar.tooltip_text = "职业资源 %d/%d" % [value, max_value]
func set_stamina(value: int, max_value: int) -> void:
if _avatar:
_avatar.tooltip_text = "体力 %d/%d" % [value, max_value]
func set_affects(values: Array) -> void:
if _affects == null:
return
for child in _affects.get_children():
child.queue_free()
for value in values:
var chip := Label.new()
chip.text = "◆"
chip.add_theme_font_size_override("font_size", 11)
chip.add_theme_color_override("font_color", Color.from_hsv(
fmod(float(int(value.get("type", 0))) * 0.13, 1.0), 0.55, 0.95))
chip.tooltip_text = "状态 #%d" % int(value.get("type", 0))
_affects.add_child(chip)
func set_target(name: String, hp_pct: int) -> void:
if _target_panel == null:
_build_target()
_target_panel.visible = true
_target_name.text = name if name != "" else "目标"
_target_bar.value = clampi(hp_pct, 0, 100)
func clear_target() -> void:
if _target_panel:
_target_panel.visible = false
func set_channel(value: int) -> void:
if _channel:
_channel.text = "CH %d" % value
func set_dungeon_destination(active: bool, _world_pos: Vector3) -> void:
if _quest and active:
_quest.set_caption("副本目标")
func set_context_action(text: String, visible: bool) -> void:
if _context == null:
return
_context.set_caption(text)
_context.visible = visible
func _build_player_card() -> void:
var card := Control.new()
card.position = Vector2(SAFE_MARGIN, SAFE_MARGIN)
card.size = Vector2(110, 122)
add_child(card)
_player_name = Label.new()
_player_name.text = "冒险者"
_player_name.position = Vector2(0, 0)
_player_name.size = Vector2(AVATAR_SIZE + 10, 20)
_player_name.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
_player_name.add_theme_font_size_override("font_size", 13)
_player_name.add_theme_color_override("font_color", Color(0.98, 0.88, 0.61))
_player_name.add_theme_color_override("font_outline_color", Color(0.01, 0.02, 0.04, 0.95))
_player_name.add_theme_constant_override("outline_size", 4)
card.add_child(_player_name)
_avatar = TouchButton.new()
_avatar.setup("角\n色", Color(0.92, 0.72, 0.32, 0.95))
_avatar.position = Vector2(5, 20)
_avatar.size = Vector2(AVATAR_SIZE, AVATAR_SIZE)
_avatar.pressed.connect(func(): avatar_pressed.emit())
card.add_child(_avatar)
_hp_bar = _bar(Color(0.86, 0.24, 0.22), Vector2(AVATAR_SIZE, 9))
_hp_bar.position = Vector2(5, 100)
card.add_child(_hp_bar)
_hp_text = _bar_text(_hp_bar)
_mp_bar = _bar(Color(0.24, 0.48, 0.93), Vector2(AVATAR_SIZE, 7))
_mp_bar.position = Vector2(5, 111)
card.add_child(_mp_bar)
_mp_text = _bar_text(_mp_bar)
_level = Label.new()
_level.text = "Lv 1"
_level.position = Vector2(86, 27)
_level.add_theme_font_size_override("font_size", 11)
_level.add_theme_color_override("font_color", Color(0.78, 0.86, 0.98))
card.add_child(_level)
_party_host = VBoxContainer.new()
_party_host.position = Vector2(0, 126)
_party_host.size = Vector2(196, 180)
_party_host.add_theme_constant_override("separation", 4)
add_child(_party_host)
_affects = HBoxContainer.new()
_affects.position = Vector2(5, 121)
_affects.add_theme_constant_override("separation", 3)
card.add_child(_affects)
func _build_top_right() -> void:
var actions := Control.new()
actions.set_anchors_preset(Control.PRESET_TOP_RIGHT)
actions.position = Vector2(-250, SAFE_MARGIN)
actions.size = Vector2(234, 98)
add_child(actions)
var bag := TouchButton.new()
bag.setup("背包", Color(0.95, 0.72, 0.34, 0.95))
bag.position = Vector2(0, 18)
bag.size = Vector2(56, 56)
bag.pressed.connect(func(): inventory_pressed.emit())
actions.add_child(bag)
_mini_host = Control.new()
_mini_host.position = Vector2(62, 0)
_mini_host.size = Vector2(96, 96)
_mini_host.mouse_filter = Control.MOUSE_FILTER_IGNORE
actions.add_child(_mini_host)
_mini_placeholder = Label.new()
_mini_placeholder.text = "地图"
_mini_placeholder.position = Vector2(28, 38)
_mini_placeholder.add_theme_font_size_override("font_size", 12)
_mini_placeholder.add_theme_color_override("font_color", Color(0.8, 0.9, 1.0, 0.85))
_mini_placeholder.mouse_filter = Control.MOUSE_FILTER_IGNORE
_mini_host.add_child(_mini_placeholder)
var map_button := TouchButton.new()
map_button.setup("", Color(0.45, 0.7, 0.95, 0.95))
map_button.position = Vector2(62, 0)
map_button.size = Vector2(96, 96)
map_button.set_idle_modulate(Color(1, 1, 1, 0.03))
map_button.pressed.connect(func(): minimap_pressed.emit())
actions.add_child(map_button)
var menu := TouchButton.new()
menu.setup("菜单", Color(0.58, 0.74, 0.96, 0.95))
menu.position = Vector2(164, 18)
menu.size = Vector2(56, 56)
menu.pressed.connect(func(): menu_pressed.emit())
actions.add_child(menu)
_channel = Label.new()
_channel.text = "CH -"
_channel.position = Vector2(75, 98)
_channel.add_theme_font_size_override("font_size", 10)
_channel.add_theme_color_override("font_color", Color(0.64, 0.75, 0.9))
actions.add_child(_channel)
func _build_target() -> void:
_target_panel = Panel.new()
_target_panel.set_anchors_preset(Control.PRESET_CENTER_TOP)
_target_panel.position = Vector2(-112, 14)
_target_panel.size = Vector2(224, 48)
_target_panel.visible = false
_target_panel.mouse_filter = Control.MOUSE_FILTER_IGNORE
_target_panel.add_theme_stylebox_override("panel", _panel_style(Color(0.12, 0.05, 0.06, 0.84), Color(0.82, 0.3, 0.3)))
add_child(_target_panel)
_target_name = Label.new()
_target_name.position = Vector2(10, 4)
_target_name.size = Vector2(204, 18)
_target_name.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
_target_name.add_theme_font_size_override("font_size", 12)
_target_panel.add_child(_target_name)
_target_bar = _bar(Color(0.88, 0.24, 0.23), Vector2(204, 9))
_target_bar.position = Vector2(10, 28)
_target_bar.max_value = 100
_target_bar.value = 100
_target_panel.add_child(_target_bar)
func _build_quest() -> void:
_quest = TouchButton.new()
_quest.setup("任务 · 点击查看", Color(1.0, 0.72, 0.3, 0.9))
_quest.set_anchors_preset(Control.PRESET_TOP_RIGHT)
_quest.position = Vector2(-244, 126)
_quest.size = Vector2(224, 45)
_quest.pressed.connect(func(): quest_pressed.emit())
add_child(_quest)
func _build_context_action() -> void:
_context = TouchButton.new()
_context.setup("交互", Color(0.92, 0.68, 0.31, 0.95))
_context.set_anchors_preset(Control.PRESET_CENTER_BOTTOM)
_context.position = Vector2(110, -116)
_context.size = Vector2(74, 48)
_context.visible = false
_context.pressed.connect(func(): context_action_pressed.emit())
add_child(_context)
func _refresh_player() -> void:
if client == null or not client.has_method("get_main_vid") or not client.has_method("get_entity"):
return
var entity: Dictionary = client.get_entity(client.get_main_vid())
set_player_name(String(entity.get("name", "冒险者")))
set_level(int(entity.get("level", 1)))
func _refresh_party() -> void:
if _party_host == null:
return
for child in _party_host.get_children():
child.queue_free()
if client == null or not client.has_method("get_party"):
return
var members: Array = client.get_party()
for member in members:
var vid := int(member.get("vid", 0))
var row := TouchButton.new()
row.setup(" %s %d%%" % [String(member.get("name", "队员")), int(member.get("hp_pct", 0))],
Color(0.32, 0.52, 0.76, 0.8))
row.set_rect_style()
row.custom_minimum_size = Vector2(184, 29)
row.size = Vector2(184, 29)
row.pressed.connect(func(): party_member_pressed.emit(vid))
_party_host.add_child(row)
func _bar(color: Color, bar_size: Vector2) -> ProgressBar:
var bar := ProgressBar.new()
bar.custom_minimum_size = bar_size
bar.size = bar_size
bar.min_value = 0
bar.max_value = 100
bar.value = 100
bar.show_percentage = false
bar.mouse_filter = Control.MOUSE_FILTER_IGNORE
bar.add_theme_stylebox_override("background", _panel_style(Color(0.01, 0.02, 0.04, 0.78), Color(0.1, 0.14, 0.2, 0.6), 2))
bar.add_theme_stylebox_override("fill", _panel_style(color, color, 2))
return bar
func _bar_text(bar: ProgressBar) -> Label:
var label := Label.new()
label.set_anchors_preset(Control.PRESET_FULL_RECT)
label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
label.add_theme_font_size_override("font_size", 8)
label.add_theme_color_override("font_color", Color(0.96, 0.98, 1.0))
label.mouse_filter = Control.MOUSE_FILTER_IGNORE
bar.add_child(label)
return label
func _panel_style(bg: Color, border: Color, width := 1) -> StyleBoxFlat:
var style := StyleBoxFlat.new()
style.bg_color = bg
style.border_color = border
style.set_border_width_all(width)
style.set_corner_radius_all(6)
return style