forked from xiangwang25/whale-town-front-v2
682 lines
26 KiB
GDScript
682 lines
26 KiB
GDScript
extends Control
|
||
|
||
# ============================================================================
|
||
# MapPanel.gd - 右上角小镇地图弹窗
|
||
# ============================================================================
|
||
# 监听 HUD 地图入口,展示当前区域全图与关键建筑标识。
|
||
# ============================================================================
|
||
|
||
const PANEL_SIZE: Vector2 = Vector2(548, 452)
|
||
const PANEL_MARGIN_TOP: float = 122.0
|
||
const PANEL_MARGIN_RIGHT: float = 56.0
|
||
const MAP_VIEW_SIZE: Vector2 = Vector2(500, 375)
|
||
const MAP_WORLD_SIZE: Vector2 = Vector2(2560, 1920)
|
||
const MAP_CAMERA_ZOOM: Vector2 = Vector2(MAP_VIEW_SIZE.x / MAP_WORLD_SIZE.x, MAP_VIEW_SIZE.y / MAP_WORLD_SIZE.y)
|
||
const MINIMAP_EXCLUDED_NODE_NAMES: Array[String] = [
|
||
"Characters",
|
||
"MultiplayerController",
|
||
]
|
||
|
||
const TEXT_COLOR: Color = Color(0.188, 0.294, 0.424)
|
||
const MUTED_COLOR: Color = Color(0.560, 0.639, 0.733)
|
||
const ACCENT_COLOR: Color = Color(0.258824, 0.627451, 0.913725)
|
||
const SOFT_BLUE: Color = Color(0.918, 0.961, 0.992, 0.94)
|
||
const PIN_BLUE: Color = Color(0.244, 0.572, 0.862)
|
||
|
||
const MAP_CONFIGS: Dictionary = {
|
||
"Square": {
|
||
"title": "小镇地图",
|
||
"sourceNodes": [
|
||
"HDGrassBase",
|
||
"GroundGrassLayer",
|
||
"PathBaseLayer",
|
||
"PathEdgeLayer",
|
||
"PathEdgeCornerLayer",
|
||
"PathEdgeJoinLayer",
|
||
"PathCurbLayer",
|
||
"YSortWorld",
|
||
],
|
||
"markers": [
|
||
{"label": "码头", "destinationId": "square_dock", "icon": "anchor", "pos": Vector2(0.160, 0.395), "side": "right"},
|
||
{"label": "总部", "destinationId": "square_headquarters", "icon": "home", "pos": Vector2(0.505, 0.188), "side": "right"},
|
||
{"label": "广场", "destinationId": "square_center", "icon": "whale", "pos": Vector2(0.505, 0.515), "side": "right"},
|
||
{"label": "小屋", "destinationId": "square_cottage", "icon": "home", "pos": Vector2(0.830, 0.400), "side": "right"},
|
||
{"label": "工坊", "destinationId": "square_workshop", "icon": "tool", "pos": Vector2(0.752, 0.760), "side": "left"},
|
||
{"label": "公告", "destinationId": "square_notice", "icon": "notice", "pos": Vector2(0.288, 0.838), "side": "right"},
|
||
{"label": "入口", "destinationId": "square_work_zone_gate", "icon": "gate", "pos": Vector2(0.500, 0.900), "side": "right"},
|
||
],
|
||
},
|
||
"WorkZone": {
|
||
"title": "打工区地图",
|
||
"sourceNodes": [
|
||
"StageBackdrop",
|
||
"RoadPavementLayer",
|
||
"RoadAsphaltLayer",
|
||
"RoadCrosswalkLayer",
|
||
"RoadMarkingLayer",
|
||
"YSortWorld",
|
||
],
|
||
"markers": [
|
||
{"label": "商城", "destinationId": "work_mall", "icon": "home", "pos": Vector2(0.500, 0.180), "side": "right"},
|
||
{"label": "咖啡店", "destinationId": "work_cafe_gate", "icon": "home", "pos": Vector2(0.092, 0.620), "side": "right"},
|
||
{"label": "任务", "destinationId": "work_jobs", "icon": "notice", "pos": Vector2(0.304, 0.600), "side": "right"},
|
||
{"label": "课程", "destinationId": "work_courses", "icon": "notice", "pos": Vector2(0.694, 0.500), "side": "left"},
|
||
{"label": "AI站", "destinationId": "work_ai", "icon": "tool", "pos": Vector2(0.676, 0.785), "side": "left"},
|
||
{"label": "鲸币", "destinationId": "work_exchange", "icon": "whale", "pos": Vector2(0.920, 0.785), "side": "left"},
|
||
{"label": "入口", "destinationId": "work_entrance", "icon": "gate", "pos": Vector2(0.500, 0.900), "side": "right"},
|
||
],
|
||
},
|
||
"CafeInterior": {
|
||
"title": "鲸鱼咖啡馆",
|
||
"worldSize": Vector2(1536, 1024),
|
||
"sourceNodes": [
|
||
"StageBackdrop",
|
||
"CafeServiceHallBase",
|
||
],
|
||
"markers": [
|
||
{"label": "服务台", "destinationId": "cafe_counter", "icon": "whale", "pos": Vector2(0.500, 0.465), "side": "right"},
|
||
{"label": "陪伴区", "destinationId": "cafe_companion", "icon": "notice", "pos": Vector2(0.240, 0.280), "side": "right"},
|
||
{"label": "出口", "destinationId": "cafe_entrance", "icon": "gate", "pos": Vector2(0.500, 0.855), "side": "right"},
|
||
],
|
||
},
|
||
"PersonalSpace": {
|
||
"title": "我的房间",
|
||
"worldSize": Vector2(1024, 768),
|
||
"sourceNodes": [
|
||
"RoomBase",
|
||
],
|
||
"markers": [],
|
||
},
|
||
}
|
||
|
||
var _panel: PanelContainer
|
||
var _mapFrame: PanelContainer
|
||
var _mapLayer: Control
|
||
var _mapViewport: SubViewport
|
||
var _mapWorld: Node2D
|
||
var _mapCamera: Camera2D
|
||
var _titleLabel: Label
|
||
var _markerNodes: Array[Control] = []
|
||
var _isOpen: bool = false
|
||
var _travelDestinations: Dictionary = {}
|
||
var _travelLocked: bool = false
|
||
var _destinationMenu: PopupMenu
|
||
var _destinationMenuIds: Dictionary = {}
|
||
|
||
func _ready() -> void:
|
||
add_to_group("whaletown_escape_dismissible")
|
||
mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||
_build_ui()
|
||
set_panel_open(false)
|
||
_subscribe_to_events()
|
||
|
||
func _exit_tree() -> void:
|
||
var eventSystem := _get_event_system()
|
||
if eventSystem != null:
|
||
eventSystem.call("disconnect_event", EventNames.HUD_MAP_TOGGLE, _on_map_toggle, self)
|
||
|
||
func _notification(what: int) -> void:
|
||
if what == NOTIFICATION_RESIZED and is_instance_valid(_panel):
|
||
_anchor_panel()
|
||
|
||
func _input(event: InputEvent) -> void:
|
||
if get_viewport().is_input_handled():
|
||
return
|
||
if not _isOpen:
|
||
return
|
||
if event is InputEventKey:
|
||
var keyEvent := event as InputEventKey
|
||
if keyEvent.pressed and not keyEvent.echo and keyEvent.keycode == KEY_ESCAPE:
|
||
set_panel_open(false)
|
||
get_viewport().set_input_as_handled()
|
||
|
||
func set_panel_open(open: bool) -> void:
|
||
_isOpen = open
|
||
visible = _isOpen
|
||
mouse_filter = Control.MOUSE_FILTER_STOP if _isOpen else Control.MOUSE_FILTER_IGNORE
|
||
if is_instance_valid(_panel):
|
||
_panel.visible = _isOpen
|
||
if _isOpen:
|
||
_rebuild_minimap_world()
|
||
_load_travel_destinations()
|
||
|
||
func is_panel_open() -> bool:
|
||
return _isOpen
|
||
|
||
func is_escape_dismissible() -> bool:
|
||
return _isOpen
|
||
|
||
func get_escape_priority() -> int:
|
||
return 600
|
||
|
||
func request_escape_close() -> void:
|
||
set_panel_open(false)
|
||
|
||
func toggle_panel() -> void:
|
||
set_panel_open(not _isOpen)
|
||
|
||
func _build_ui() -> void:
|
||
_panel = PanelContainer.new()
|
||
_panel.name = "MapCard"
|
||
_panel.custom_minimum_size = PANEL_SIZE
|
||
_panel.mouse_filter = Control.MOUSE_FILTER_STOP
|
||
_panel.add_theme_stylebox_override("panel", _create_panel_style())
|
||
add_child(_panel)
|
||
_anchor_panel()
|
||
|
||
var margin := MarginContainer.new()
|
||
margin.add_theme_constant_override("margin_left", 24)
|
||
margin.add_theme_constant_override("margin_top", 20)
|
||
margin.add_theme_constant_override("margin_right", 24)
|
||
margin.add_theme_constant_override("margin_bottom", 22)
|
||
_panel.add_child(margin)
|
||
|
||
var content := VBoxContainer.new()
|
||
content.add_theme_constant_override("separation", 12)
|
||
margin.add_child(content)
|
||
|
||
content.add_child(_build_header())
|
||
content.add_child(_build_map_view())
|
||
_destinationMenu = PopupMenu.new()
|
||
_destinationMenu.id_pressed.connect(_on_destination_menu_selected)
|
||
add_child(_destinationMenu)
|
||
|
||
func _build_header() -> Control:
|
||
var header := HBoxContainer.new()
|
||
header.custom_minimum_size = Vector2(0, 42)
|
||
header.alignment = BoxContainer.ALIGNMENT_CENTER
|
||
header.add_theme_constant_override("separation", 12)
|
||
|
||
var icon := MapGlyph.new()
|
||
icon.custom_minimum_size = Vector2(32, 32)
|
||
icon.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||
header.add_child(icon)
|
||
|
||
var title := Label.new()
|
||
title.name = "TitleLabel"
|
||
_titleLabel = title
|
||
title.text = "小镇地图"
|
||
title.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
title.add_theme_color_override("font_color", TEXT_COLOR)
|
||
title.add_theme_font_size_override("font_size", 24)
|
||
header.add_child(title)
|
||
|
||
var destinationButton := Button.new()
|
||
destinationButton.text = "目的地"
|
||
destinationButton.tooltip_text = "查看全部快速传送目的地"
|
||
destinationButton.custom_minimum_size = Vector2(76, 38)
|
||
destinationButton.focus_mode = Control.FOCUS_NONE
|
||
destinationButton.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
|
||
destinationButton.add_theme_font_size_override("font_size", 14)
|
||
destinationButton.add_theme_color_override("font_color", ACCENT_COLOR)
|
||
destinationButton.add_theme_stylebox_override("normal", _create_round_style(Color(0.902, 0.962, 0.995, 1.0), 14))
|
||
destinationButton.add_theme_stylebox_override("hover", _create_round_style(Color(0.818, 0.925, 0.980, 1.0), 14))
|
||
destinationButton.add_theme_stylebox_override("focus", StyleBoxEmpty.new())
|
||
destinationButton.pressed.connect(func() -> void: _show_destination_menu(destinationButton))
|
||
header.add_child(destinationButton)
|
||
|
||
var closeButton := Button.new()
|
||
closeButton.text = "×"
|
||
closeButton.tooltip_text = "关闭地图"
|
||
closeButton.custom_minimum_size = Vector2(42, 42)
|
||
closeButton.focus_mode = Control.FOCUS_NONE
|
||
closeButton.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
|
||
closeButton.add_theme_font_size_override("font_size", 28)
|
||
closeButton.add_theme_color_override("font_color", MUTED_COLOR)
|
||
closeButton.add_theme_color_override("font_hover_color", ACCENT_COLOR)
|
||
closeButton.add_theme_color_override("font_pressed_color", ACCENT_COLOR.darkened(0.18))
|
||
closeButton.add_theme_color_override("font_disabled_color", Color(0.520, 0.600, 0.680, 0.55))
|
||
closeButton.add_theme_stylebox_override("normal", _create_round_style(SOFT_BLUE, 21))
|
||
closeButton.add_theme_stylebox_override("hover", _create_round_style(Color(0.858, 0.925, 0.980, 1.0), 21))
|
||
closeButton.add_theme_stylebox_override("pressed", _create_round_style(Color(0.778, 0.884, 0.965, 1.0), 21))
|
||
closeButton.add_theme_stylebox_override("focus", StyleBoxEmpty.new())
|
||
closeButton.pressed.connect(func() -> void: set_panel_open(false))
|
||
header.add_child(closeButton)
|
||
|
||
return header
|
||
|
||
func _build_map_view() -> Control:
|
||
_mapFrame = PanelContainer.new()
|
||
_mapFrame.custom_minimum_size = MAP_VIEW_SIZE
|
||
_mapFrame.mouse_filter = Control.MOUSE_FILTER_STOP
|
||
_mapFrame.add_theme_stylebox_override("panel", _create_map_frame_style())
|
||
|
||
_mapLayer = Control.new()
|
||
_mapLayer.custom_minimum_size = MAP_VIEW_SIZE
|
||
_mapLayer.clip_contents = true
|
||
_mapLayer.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||
_mapFrame.add_child(_mapLayer)
|
||
|
||
var viewportContainer := SubViewportContainer.new()
|
||
viewportContainer.name = "SquareMapViewportContainer"
|
||
viewportContainer.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||
viewportContainer.stretch = true
|
||
viewportContainer.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||
_mapLayer.add_child(viewportContainer)
|
||
|
||
_mapViewport = SubViewport.new()
|
||
_mapViewport.name = "SquareMapViewport"
|
||
_mapViewport.size = Vector2i(int(MAP_VIEW_SIZE.x), int(MAP_VIEW_SIZE.y))
|
||
_mapViewport.render_target_update_mode = SubViewport.UPDATE_WHEN_VISIBLE
|
||
_mapViewport.world_2d = World2D.new()
|
||
viewportContainer.add_child(_mapViewport)
|
||
|
||
_mapWorld = Node2D.new()
|
||
_mapWorld.name = "RenderedMapWorld"
|
||
_mapViewport.add_child(_mapWorld)
|
||
|
||
_mapCamera = Camera2D.new()
|
||
_mapCamera.name = "MinimapCamera"
|
||
_mapCamera.position = Vector2.ZERO
|
||
_mapCamera.zoom = MAP_CAMERA_ZOOM
|
||
_mapCamera.enabled = true
|
||
_mapViewport.add_child(_mapCamera)
|
||
|
||
var wash := ColorRect.new()
|
||
wash.name = "MapSoftWash"
|
||
wash.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||
wash.color = Color(1.0, 0.985, 0.930, 0.10)
|
||
wash.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||
_mapLayer.add_child(wash)
|
||
|
||
_render_markers()
|
||
|
||
return _mapFrame
|
||
|
||
func _render_markers() -> void:
|
||
for markerNode in _markerNodes:
|
||
if is_instance_valid(markerNode):
|
||
markerNode.queue_free()
|
||
_markerNodes.clear()
|
||
|
||
for marker in _current_markers():
|
||
var markerNode := _create_marker(marker)
|
||
_markerNodes.append(markerNode)
|
||
_mapLayer.add_child(markerNode)
|
||
|
||
func _create_marker(marker: Dictionary) -> Control:
|
||
var button := Button.new()
|
||
button.name = "Marker%s" % str(marker.get("label", ""))
|
||
button.text = ""
|
||
button.focus_mode = Control.FOCUS_NONE
|
||
button.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
|
||
button.custom_minimum_size = Vector2(82, 34)
|
||
button.add_theme_stylebox_override("normal", _create_marker_style(Color(1.0, 1.0, 1.0, 0.93), Color(0.650, 0.792, 0.910, 0.88)))
|
||
button.add_theme_stylebox_override("hover", _create_marker_style(Color(0.925, 0.966, 0.996, 0.98), ACCENT_COLOR))
|
||
button.add_theme_stylebox_override("pressed", _create_marker_style(Color(0.858, 0.925, 0.980, 0.98), ACCENT_COLOR.darkened(0.05)))
|
||
button.add_theme_stylebox_override("focus", StyleBoxEmpty.new())
|
||
var destinationId := str(marker.get("destinationId", "")).strip_edges()
|
||
var destination: Dictionary = _travelDestinations.get(destinationId, {})
|
||
var unlocked := destinationId.is_empty() or _travelDestinations.is_empty() or bool(destination.get("unlocked", false))
|
||
button.disabled = not unlocked or _travelLocked
|
||
button.tooltip_text = "点击快速传送" if unlocked else "首次到访后解锁"
|
||
if not unlocked:
|
||
button.modulate = Color(0.66, 0.69, 0.72, 0.72)
|
||
button.pressed.connect(func() -> void: return)
|
||
else:
|
||
button.pressed.connect(func() -> void: _request_travel(destinationId))
|
||
|
||
var row := HBoxContainer.new()
|
||
row.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||
row.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||
row.offset_left = 8
|
||
row.offset_right = -9
|
||
row.alignment = BoxContainer.ALIGNMENT_CENTER
|
||
row.add_theme_constant_override("separation", 5)
|
||
button.add_child(row)
|
||
|
||
var icon := MarkerGlyph.new()
|
||
icon.set("iconName", str(marker.get("icon", "pin")))
|
||
icon.custom_minimum_size = Vector2(20, 20)
|
||
icon.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||
row.add_child(icon)
|
||
|
||
var label := Label.new()
|
||
label.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||
label.text = ("🔒 " if not unlocked else "") + str(marker.get("label", "地点"))
|
||
label.add_theme_color_override("font_color", TEXT_COLOR)
|
||
label.add_theme_font_size_override("font_size", 14)
|
||
row.add_child(label)
|
||
|
||
var position := marker.get("pos", Vector2.ZERO) as Vector2
|
||
var buttonSize := button.custom_minimum_size
|
||
var xOffset := 8.0 if str(marker.get("side", "right")) == "right" else -buttonSize.x - 8.0
|
||
button.position = Vector2(
|
||
position.x * MAP_VIEW_SIZE.x + xOffset,
|
||
position.y * MAP_VIEW_SIZE.y - buttonSize.y * 0.5
|
||
)
|
||
return button
|
||
|
||
func _load_travel_destinations() -> void:
|
||
var api := get_node_or_null("/root/ApiClient")
|
||
if api == null:
|
||
return
|
||
api.call("get_json", "/world/travel-destinations", func(success: bool, response: Dictionary, _error: Dictionary) -> void:
|
||
if not success:
|
||
return
|
||
var data_variant: Variant = response.get("data", [])
|
||
if not (data_variant is Array):
|
||
return
|
||
_travelDestinations.clear()
|
||
for entry in data_variant as Array:
|
||
if entry is Dictionary:
|
||
var destination: Dictionary = entry
|
||
_travelDestinations[str(destination.get("id", ""))] = destination
|
||
_render_markers()
|
||
, true)
|
||
|
||
func _show_destination_menu(origin: Control) -> void:
|
||
if not is_instance_valid(_destinationMenu):
|
||
return
|
||
_destinationMenu.clear()
|
||
_destinationMenuIds.clear()
|
||
if _travelDestinations.is_empty():
|
||
_destinationMenu.add_item("正在读取目的地…", 0)
|
||
_destinationMenu.set_item_disabled(0, true)
|
||
else:
|
||
var destinations: Array[Dictionary] = []
|
||
for destination_variant in _travelDestinations.values():
|
||
if destination_variant is Dictionary:
|
||
destinations.append(destination_variant as Dictionary)
|
||
destinations.sort_custom(func(a: Dictionary, b: Dictionary) -> bool:
|
||
var map_order := {"whale_port": 0, "work_zone": 1, "whale_cafe": 2, "personal_space": 3}
|
||
var order_a := int(map_order.get(str(a.get("mapId", "")), 9))
|
||
var order_b := int(map_order.get(str(b.get("mapId", "")), 9))
|
||
if order_a != order_b:
|
||
return order_a < order_b
|
||
return str(a.get("label", "")).naturalnocasecmp_to(str(b.get("label", ""))) < 0
|
||
)
|
||
var current_map := ""
|
||
var menu_id := 1
|
||
for destination in destinations:
|
||
var map_id := str(destination.get("mapId", ""))
|
||
if map_id != current_map:
|
||
if not current_map.is_empty():
|
||
_destinationMenu.add_separator()
|
||
_destinationMenu.add_item("— %s —" % _map_label(map_id), menu_id)
|
||
_destinationMenu.set_item_disabled(_destinationMenu.item_count - 1, true)
|
||
menu_id += 1
|
||
current_map = map_id
|
||
var unlocked := bool(destination.get("unlocked", false))
|
||
_destinationMenu.add_item(("" if unlocked else "🔒 ") + str(destination.get("label", "地点")), menu_id)
|
||
_destinationMenu.set_item_disabled(_destinationMenu.item_count - 1, not unlocked or _travelLocked)
|
||
_destinationMenuIds[menu_id] = str(destination.get("id", ""))
|
||
menu_id += 1
|
||
_destinationMenu.reset_size()
|
||
_destinationMenu.position = Vector2i(origin.get_screen_position() + Vector2(0.0, origin.size.y))
|
||
_destinationMenu.popup()
|
||
|
||
func _on_destination_menu_selected(menu_id: int) -> void:
|
||
var destination_id := str(_destinationMenuIds.get(menu_id, ""))
|
||
if not destination_id.is_empty():
|
||
_request_travel(destination_id)
|
||
|
||
func _request_travel(destinationId: String) -> void:
|
||
if destinationId.is_empty() or _travelLocked:
|
||
return
|
||
var socialManager := get_node_or_null("/root/SocialManager")
|
||
if socialManager == null or not socialManager.has_method("request_travel"):
|
||
return
|
||
_travelLocked = true
|
||
_render_markers()
|
||
socialManager.call("request_travel", destinationId, Callable(self, "_on_travel_authorized"))
|
||
|
||
func _on_travel_authorized(success: bool, destination: Dictionary) -> void:
|
||
if not success:
|
||
await get_tree().create_timer(1.0).timeout
|
||
_travelLocked = false
|
||
_render_markers()
|
||
return
|
||
var mapId := str(destination.get("mapId", ""))
|
||
var position := _scene_position_for_destination(mapId, Vector2(float(destination.get("x", 0.0)), float(destination.get("y", 0.0))))
|
||
var sceneName: String = str({"whale_port": "square", "work_zone": "work_zone", "whale_cafe": "cafe_interior", "personal_space": "personal_space"}.get(mapId, ""))
|
||
if sceneName.is_empty():
|
||
await get_tree().create_timer(1.0).timeout
|
||
_travelLocked = false
|
||
_render_markers()
|
||
return
|
||
set_panel_open(false)
|
||
if sceneName == "personal_space":
|
||
SceneManager.clear_room_visit_context()
|
||
SceneManager.set_next_destination_id(str(destination.get("id", "")))
|
||
SceneManager.set_next_scene_position(position)
|
||
SceneManager.change_scene(sceneName)
|
||
|
||
func _scene_position_for_destination(map_id: String, map_position: Vector2) -> Vector2:
|
||
# 服务端旅行目录使用小地图坐标;游戏场景则以地图中心为原点。
|
||
var origin := {
|
||
"whale_port": Vector2(1280, 960),
|
||
"work_zone": Vector2(1280, 960),
|
||
"whale_cafe": Vector2(768, 512),
|
||
"personal_space": Vector2(768, 512),
|
||
}.get(map_id, Vector2.ZERO) as Vector2
|
||
return map_position - origin
|
||
|
||
func _map_label(map_id: String) -> String:
|
||
return {
|
||
"whale_port": "中心广场",
|
||
"work_zone": "打工区",
|
||
"whale_cafe": "鲸鱼咖啡馆",
|
||
"personal_space": "我的房间",
|
||
}.get(map_id, map_id)
|
||
|
||
func _anchor_panel() -> void:
|
||
_panel.set_anchors_preset(Control.PRESET_TOP_RIGHT)
|
||
_panel.offset_left = -PANEL_SIZE.x - PANEL_MARGIN_RIGHT
|
||
_panel.offset_top = PANEL_MARGIN_TOP
|
||
_panel.offset_right = -PANEL_MARGIN_RIGHT
|
||
_panel.offset_bottom = PANEL_MARGIN_TOP + PANEL_SIZE.y
|
||
_panel.grow_horizontal = Control.GROW_DIRECTION_BEGIN
|
||
|
||
func _subscribe_to_events() -> void:
|
||
var eventSystem := _get_event_system()
|
||
if eventSystem == null:
|
||
push_warning("MapPanel: EventSystem autoload is not available.")
|
||
return
|
||
eventSystem.call("connect_event", EventNames.HUD_MAP_TOGGLE, _on_map_toggle, self)
|
||
|
||
func _on_map_toggle(_data: Variant = null) -> void:
|
||
toggle_panel()
|
||
|
||
func _rebuild_minimap_world() -> void:
|
||
if not is_instance_valid(_mapWorld):
|
||
return
|
||
_clear_children(_mapWorld)
|
||
|
||
var sourceScene := _find_source_scene()
|
||
if sourceScene == null:
|
||
push_warning("MapPanel: source scene for minimap is unavailable.")
|
||
return
|
||
_apply_scene_config(sourceScene)
|
||
|
||
for nodeName in _current_source_node_names(sourceScene):
|
||
var sourceNode := sourceScene.get_node_or_null(nodeName)
|
||
if sourceNode == null:
|
||
continue
|
||
var clone := sourceNode.duplicate(0)
|
||
if clone == null:
|
||
continue
|
||
_remove_excluded_minimap_nodes(clone)
|
||
_mapWorld.add_child(clone)
|
||
|
||
func _find_source_scene() -> Node:
|
||
var node: Node = self
|
||
while node != null:
|
||
if MAP_CONFIGS.has(str(node.name)):
|
||
return node
|
||
node = node.get_parent()
|
||
return get_tree().current_scene
|
||
|
||
func _apply_scene_config(sourceScene: Node) -> void:
|
||
var config := _map_config_for_scene(sourceScene)
|
||
if is_instance_valid(_titleLabel):
|
||
_titleLabel.text = str(config.get("title", "区域地图"))
|
||
_configure_map_camera(config)
|
||
_render_markers()
|
||
|
||
func _configure_map_camera(config: Dictionary) -> void:
|
||
if not is_instance_valid(_mapCamera):
|
||
return
|
||
var worldSize := config.get("worldSize", MAP_WORLD_SIZE) as Vector2
|
||
if worldSize.x <= 0.0 or worldSize.y <= 0.0:
|
||
worldSize = MAP_WORLD_SIZE
|
||
var zoomScale := minf(MAP_VIEW_SIZE.x / worldSize.x, MAP_VIEW_SIZE.y / worldSize.y)
|
||
_mapCamera.zoom = Vector2.ONE * zoomScale
|
||
|
||
func _current_source_node_names(sourceScene: Node) -> Array:
|
||
var config := _map_config_for_scene(sourceScene)
|
||
var nodeNames: Array = config.get("sourceNodes", [])
|
||
return nodeNames
|
||
|
||
func _current_markers() -> Array:
|
||
var config := _map_config_for_scene(_find_source_scene())
|
||
var markers: Array = config.get("markers", [])
|
||
return markers
|
||
|
||
func _map_config_for_scene(sourceScene: Node) -> Dictionary:
|
||
if sourceScene != null:
|
||
var sceneName := str(sourceScene.name)
|
||
if MAP_CONFIGS.has(sceneName):
|
||
return MAP_CONFIGS[sceneName]
|
||
return MAP_CONFIGS["Square"]
|
||
|
||
func _remove_excluded_minimap_nodes(root: Node) -> void:
|
||
for nodeName in MINIMAP_EXCLUDED_NODE_NAMES:
|
||
var node := root.find_child(nodeName, true, false)
|
||
if node != null and node.get_parent() != null:
|
||
node.get_parent().remove_child(node)
|
||
node.queue_free()
|
||
|
||
func _clear_children(container: Node) -> void:
|
||
for child in container.get_children():
|
||
container.remove_child(child)
|
||
child.queue_free()
|
||
|
||
func _create_panel_style() -> StyleBoxFlat:
|
||
var style := StyleBoxFlat.new()
|
||
style.bg_color = Color(1.0, 1.0, 1.0, 0.948)
|
||
style.corner_radius_top_left = 28
|
||
style.corner_radius_top_right = 28
|
||
style.corner_radius_bottom_left = 28
|
||
style.corner_radius_bottom_right = 28
|
||
style.shadow_color = Color(0.12549, 0.282353, 0.407843, 0.18)
|
||
style.shadow_size = 22
|
||
style.shadow_offset = Vector2(0, 9)
|
||
return style
|
||
|
||
func _create_map_frame_style() -> StyleBoxFlat:
|
||
var style := StyleBoxFlat.new()
|
||
style.bg_color = Color(0.918, 0.961, 0.992, 0.96)
|
||
style.border_color = Color(0.431, 0.733, 0.929, 0.86)
|
||
style.border_width_left = 2
|
||
style.border_width_top = 2
|
||
style.border_width_right = 2
|
||
style.border_width_bottom = 2
|
||
style.corner_radius_top_left = 16
|
||
style.corner_radius_top_right = 16
|
||
style.corner_radius_bottom_left = 16
|
||
style.corner_radius_bottom_right = 16
|
||
return style
|
||
|
||
func _create_marker_style(color: Color, borderColor: Color) -> StyleBoxFlat:
|
||
var style := StyleBoxFlat.new()
|
||
style.bg_color = color
|
||
style.border_color = borderColor
|
||
style.border_width_left = 2
|
||
style.border_width_top = 2
|
||
style.border_width_right = 2
|
||
style.border_width_bottom = 2
|
||
style.corner_radius_top_left = 18
|
||
style.corner_radius_top_right = 18
|
||
style.corner_radius_bottom_left = 18
|
||
style.corner_radius_bottom_right = 18
|
||
style.shadow_color = Color(0.078, 0.223, 0.360, 0.14)
|
||
style.shadow_size = 7
|
||
style.shadow_offset = Vector2(0, 3)
|
||
return style
|
||
|
||
func _create_round_style(color: Color, radius: int) -> StyleBoxFlat:
|
||
var style := StyleBoxFlat.new()
|
||
style.bg_color = color
|
||
style.corner_radius_top_left = radius
|
||
style.corner_radius_top_right = radius
|
||
style.corner_radius_bottom_left = radius
|
||
style.corner_radius_bottom_right = radius
|
||
return style
|
||
|
||
func _get_event_system() -> Node:
|
||
return get_node_or_null("/root/EventSystem")
|
||
|
||
class MapGlyph:
|
||
extends Control
|
||
|
||
func _draw() -> void:
|
||
var color := Color(0.258824, 0.627451, 0.913725)
|
||
var width := 2.2
|
||
draw_polyline(PackedVector2Array([
|
||
Vector2(6, 9), Vector2(12, 7), Vector2(19, 9), Vector2(26, 7),
|
||
Vector2(26, 25), Vector2(19, 27), Vector2(12, 25), Vector2(6, 27),
|
||
Vector2(6, 9)
|
||
]), color, width, true)
|
||
draw_line(Vector2(12, 8), Vector2(12, 24), color, 1.6, true)
|
||
draw_line(Vector2(19, 10), Vector2(19, 26), color, 1.6, true)
|
||
|
||
class MarkerGlyph:
|
||
extends Control
|
||
|
||
var iconName: String = "pin"
|
||
|
||
func _draw() -> void:
|
||
var color := Color(0.258824, 0.627451, 0.913725)
|
||
var center := size * 0.5
|
||
match iconName:
|
||
"anchor":
|
||
_draw_anchor(center, color)
|
||
"home":
|
||
_draw_home(center, color)
|
||
"whale":
|
||
_draw_whale(center, color)
|
||
"tool":
|
||
_draw_tool(center, color)
|
||
"notice":
|
||
_draw_notice(center, color)
|
||
"gate":
|
||
_draw_gate(center, color)
|
||
_:
|
||
draw_circle(center, 5.0, color)
|
||
|
||
func _draw_anchor(center: Vector2, color: Color) -> void:
|
||
draw_line(center + Vector2(0, -7), center + Vector2(0, 7), color, 2.0, true)
|
||
draw_circle(center + Vector2(0, -7), 2.8, color)
|
||
draw_arc(center + Vector2(0, 2), 7.0, 0.20, PI - 0.20, 24, color, 2.0, true)
|
||
draw_line(center + Vector2(-7, 3), center + Vector2(-10, 0), color, 2.0, true)
|
||
draw_line(center + Vector2(7, 3), center + Vector2(10, 0), color, 2.0, true)
|
||
|
||
func _draw_home(center: Vector2, color: Color) -> void:
|
||
draw_polyline(PackedVector2Array([
|
||
center + Vector2(-8, -1), center + Vector2(0, -8), center + Vector2(8, -1)
|
||
]), color, 2.0, true)
|
||
draw_rect(Rect2(center + Vector2(-6, -1), Vector2(12, 10)), color, false, 2.0)
|
||
draw_line(center + Vector2(-1, 9), center + Vector2(-1, 3), color, 2.0, true)
|
||
|
||
func _draw_whale(center: Vector2, color: Color) -> void:
|
||
draw_arc(center + Vector2(-1, 2), 8.0, PI, TAU, 28, color, 2.0, true)
|
||
draw_line(center + Vector2(6, 0), center + Vector2(11, -4), color, 2.0, true)
|
||
draw_line(center + Vector2(6, 0), center + Vector2(11, 4), color, 2.0, true)
|
||
draw_circle(center + Vector2(-4, 0), 1.2, color)
|
||
|
||
func _draw_tool(center: Vector2, color: Color) -> void:
|
||
draw_line(center + Vector2(-7, 7), center + Vector2(7, -7), color, 2.4, true)
|
||
draw_arc(center + Vector2(7, -7), 5.0, PI * 0.1, PI * 1.3, 18, color, 2.0, true)
|
||
draw_circle(center + Vector2(-7, 7), 2.4, color)
|
||
|
||
func _draw_notice(center: Vector2, color: Color) -> void:
|
||
draw_rect(Rect2(center + Vector2(-8, -7), Vector2(16, 13)), color, false, 2.0)
|
||
draw_line(center + Vector2(-5, -2), center + Vector2(5, -2), color, 1.7, true)
|
||
draw_line(center + Vector2(-5, 2), center + Vector2(2, 2), color, 1.7, true)
|
||
|
||
func _draw_gate(center: Vector2, color: Color) -> void:
|
||
draw_rect(Rect2(center + Vector2(-9, -3), Vector2(18, 11)), color, false, 2.0)
|
||
draw_line(center + Vector2(0, -3), center + Vector2(0, 8), color, 1.7, true)
|
||
draw_line(center + Vector2(-11, 8), center + Vector2(11, 8), color, 2.0, true)
|