diff --git a/_Core/managers/NetworkManager.gd b/_Core/managers/NetworkManager.gd index 6e0b04d..4bacc7b 100644 --- a/_Core/managers/NetworkManager.gd +++ b/_Core/managers/NetworkManager.gd @@ -38,15 +38,17 @@ signal request_completed(request_id: String, success: bool, data: Dictionary) # message: String - 错误消息 signal request_failed(request_id: String, error_type: String, message: String) +# 公告列表接收信号 +signal notices_received(data: Array) + # ============ 常量定义 ============ # API基础URL - 所有请求的根地址 # [Remote] 正式环境地址 (实际正式项目用此地址) -# [Remote] 正式环境地址 (实际正式项目用此地址) -const API_BASE_URL = "https://whaletownend.xinghangee.icu" +# const API_BASE_URL = "https://whaletownend.xinghangee.icu" # [Local] 本地调试地址 (本地调试用此地址) -# const API_BASE_URL = "http://localhost:3000" +const API_BASE_URL = "http://localhost:3000" # 默认请求超时时间(秒) const DEFAULT_TIMEOUT = 30.0 @@ -119,6 +121,7 @@ var request_counter: int = 0 # 请求计数器,用于 # 初始化网络管理器 # 在节点准备就绪时调用 func _ready(): + process_mode = Node.PROCESS_MODE_ALWAYS print("NetworkManager 已初始化") # ============ 公共API接口 ============ @@ -441,6 +444,18 @@ func github_login(github_id: String, username: String, nickname: String, email: return post_request("/auth/github", data, callback) +# TODO: 获取公告列表 +func request_notices(): + # 发送 GET 请求到 /notices 接口 + get_request("/notices", _on_notices_response) + +func _on_notices_response(success: bool, data: Dictionary, error_info: Dictionary): + if success and data.has("data"): + notices_received.emit(data["data"]) + else: + # 失败或无数据时发送空数组 + notices_received.emit([]) + # ============ 核心请求处理 ============ # 发送请求的核心方法 diff --git a/assets/materials/NoticeBoard.png b/assets/materials/NoticeBoard.png new file mode 100644 index 0000000..52c97d0 Binary files /dev/null and b/assets/materials/NoticeBoard.png differ diff --git a/assets/materials/NoticeBoard.png.import b/assets/materials/NoticeBoard.png.import new file mode 100644 index 0000000..a8759e7 --- /dev/null +++ b/assets/materials/NoticeBoard.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b4aildrnhbpl4" +path="res://.godot/imported/NoticeBoard.png-038eefee12f116fb9502ed755594cede.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/materials/NoticeBoard.png" +dest_files=["res://.godot/imported/NoticeBoard.png-038eefee12f116fb9502ed755594cede.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/scenes/Maps/square.tscn b/scenes/Maps/square.tscn index 3ed8bc5..ae7475a 100644 --- a/scenes/Maps/square.tscn +++ b/scenes/Maps/square.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=38 format=4 uid="uid://5cc0c6cpnhe8"] +[gd_scene load_steps=39 format=4 uid="uid://5cc0c6cpnhe8"] [ext_resource type="Script" uid="uid://c5ml4722ptwp2" path="res://scenes/Maps/BaseLevel.gd" id="1_m4als"] [ext_resource type="Texture2D" uid="uid://baa5wkuyqouh6" path="res://assets/sprites/environment/standard_brick_128_128.jpg" id="1_rb5kq"] @@ -17,6 +17,7 @@ [ext_resource type="PackedScene" uid="uid://vq5qgk3k6t7e" path="res://scenes/Maps/fountain.tscn" id="16_2rqka"] [ext_resource type="PackedScene" uid="uid://c7k8yay002w4" path="res://scenes/prefabs/items/welcome_board.tscn" id="16_edt5w"] [ext_resource type="PackedScene" uid="uid://bvfyllcy5fi8o" path="res://scenes/Maps/datawhale_home.tscn" id="16_m4als"] +[ext_resource type="PackedScene" uid="uid://rdmrm7j4iokr" path="res://scenes/prefabs/items/notice_board.tscn" id="16_rixdf"] [ext_resource type="Script" uid="uid://b068cnbw3a8wt" path="res://scenes/Maps/DoorTeleport.gd" id="18_0xqio"] [sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_7nixu"] @@ -1091,7 +1092,7 @@ position = Vector2(0, 320) position = Vector2(8, -128) [node name="NPC" parent="." instance=ExtResource("15_0xqio")] -position = Vector2(-88, -80) +position = Vector2(-81, -64) [node name="DefaultSpawn" type="Marker2D" parent="."] position = Vector2(647, 500) @@ -1099,6 +1100,9 @@ position = Vector2(647, 500) [node name="FromRoom" type="Marker2D" parent="."] position = Vector2(648, 24) +[node name="NoticeBoard" parent="." instance=ExtResource("16_rixdf")] +position = Vector2(-184, -76) + [node name="WelcomeBoard" parent="." instance=ExtResource("16_edt5w")] position = Vector2(128, -80) diff --git a/scenes/prefabs/items/NoticeBoard.gd b/scenes/prefabs/items/NoticeBoard.gd new file mode 100644 index 0000000..55c7ba4 --- /dev/null +++ b/scenes/prefabs/items/NoticeBoard.gd @@ -0,0 +1,13 @@ +extends StaticBody2D + +func interact(): + print("Interacted with Notice Board") + + # Check if dialog already exists + if get_tree().root.has_node("NoticeDialog"): + return + + var dialog = preload("res://scenes/ui/notice_dialog.tscn").instantiate() + dialog.name = "NoticeDialog" + get_tree().root.add_child(dialog) + return null # No bubble text needed diff --git a/scenes/prefabs/items/NoticeBoard.gd.uid b/scenes/prefabs/items/NoticeBoard.gd.uid new file mode 100644 index 0000000..4ff1775 --- /dev/null +++ b/scenes/prefabs/items/NoticeBoard.gd.uid @@ -0,0 +1 @@ +uid://pnlgf420wktn diff --git a/scenes/prefabs/items/notice_board.tscn b/scenes/prefabs/items/notice_board.tscn new file mode 100644 index 0000000..b6fa509 --- /dev/null +++ b/scenes/prefabs/items/notice_board.tscn @@ -0,0 +1,19 @@ +[gd_scene load_steps=4 format=3 uid="uid://rdmrm7j4iokr"] + +[ext_resource type="Script" uid="uid://pnlgf420wktn" path="res://scenes/prefabs/items/NoticeBoard.gd" id="1_script"] +[ext_resource type="Texture2D" uid="uid://b4aildrnhbpl4" path="res://assets/materials/NoticeBoard.png" id="2_sprite"] + +[sub_resource type="RectangleShape2D" id="RectangleShape2D_nb"] +size = Vector2(160, 53.33333) + +[node name="NoticeBoard" type="StaticBody2D"] +scale = Vector2(0.6, 0.6) +script = ExtResource("1_script") + +[node name="Sprite2D" type="Sprite2D" parent="."] +position = Vector2(0, -16) +scale = Vector2(0.5, 0.5) +texture = ExtResource("2_sprite") + +[node name="CollisionShape2D" type="CollisionShape2D" parent="."] +shape = SubResource("RectangleShape2D_nb") diff --git a/scenes/ui/AuthScene.tscn b/scenes/ui/AuthScene.tscn index 3cf8c76..5ba50c8 100644 --- a/scenes/ui/AuthScene.tscn +++ b/scenes/ui/AuthScene.tscn @@ -2,7 +2,7 @@ [ext_resource type="Texture2D" uid="uid://bx17oy8lvaca4" path="res://assets/ui/auth/bg_auth_scene.png" id="1_background"] [ext_resource type="Texture2D" uid="uid://de4q4s1gxivtf" path="res://assets/ui/auth/login_frame_smart_transparent.png" id="2_frame"] -[ext_resource type="Script" path="res://scenes/ui/AuthScene.gd" id="3_script"] +[ext_resource type="Script" uid="uid://b514h2wuido0h" path="res://scenes/ui/AuthScene.gd" id="3_script"] [sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_1"] diff --git a/scenes/ui/NoticeDialog.gd b/scenes/ui/NoticeDialog.gd new file mode 100644 index 0000000..8fb95a2 --- /dev/null +++ b/scenes/ui/NoticeDialog.gd @@ -0,0 +1,156 @@ +extends CanvasLayer + +@onready var content_label = $CenterContainer/PanelContainer/VBoxContainer/ContentContainer/TextPanel/ContentLabel +@onready var prev_btn = $CenterContainer/PanelContainer/VBoxContainer/Footer/PrevButton +@onready var next_btn = $CenterContainer/PanelContainer/VBoxContainer/Footer/NextButton +@onready var dots_container = $CenterContainer/PanelContainer/VBoxContainer/Footer/DotsContainer +@onready var content_container = $CenterContainer/PanelContainer/VBoxContainer/ContentContainer + +# Mock Data +var pages = [ + { + "text": "欢迎来到 [color=#3399ff]Datawhale Town[/color]!\n\n这里是开源学习者的家园。在这里,我们一同探索知识,分享成长。\n\n[center]🐋[/center]", + # 使用社区图片作为封面 + "image_path": "res://assets/sprites/environment/community_512_512.png", + "image_color": Color(0.9, 0.9, 0.9) # 保留作为后备选项 + }, + { + "text": "最新活动:\n\n- 镇长刚刚搬进来了,就在喷泉左边。\n- 欢迎板已经设立,查看最新动态。\n- 玩家名字现在显示在头顶了!", + # 使用喷泉图片对应"喷泉左边"的描述 + "image_path": "res://assets/sprites/environment/fountain_256_192.png", + "image_color": Color(0.8, 0.9, 0.8) + }, + { + "text": "操作提示:\n\n- 按 [color=#ffaa00]F[/color] 键可以与物体互动。\n- 在下方输入框输入文字并在气泡中显示。\n- 点击右下角按钮发送聊天。", + # 使用公告板图片对应"操作提示" + "image_path": "res://assets/sprites/environment/board.png", + "image_color": Color(0.9, 0.8, 0.8) + } +] + +var current_page = 0 +var tween: Tween +var mock_pages = [] + +func _ready(): + # Pause the game + get_tree().paused = true + + $CenterContainer/PanelContainer/VBoxContainer/Header/RightContainer/CloseButton.pressed.connect(_on_close_pressed) + prev_btn.pressed.connect(_on_prev_pressed) + next_btn.pressed.connect(_on_next_pressed) + + # Network Integration - Use direct callback for better error handling + # Short timeout (2.0s) so mock data appears quickly if server is down + NetworkManager.get_request("/notices", _on_notices_response, 2.0) + + # Initial Setup (with generic "Loading" state) + mock_pages = pages.duplicate(true) + pages = [{"text": "[center]Loading notices...[/center]", "image_color": Color(0.9, 0.9, 0.9)}] + _setup_dots() + _update_ui(false) + +func _on_notices_response(success: bool, data: Dictionary, _error_info: Dictionary): + var new_pages = [] + if success and data.has("data") and data["data"] is Array: + new_pages = data["data"] + + if new_pages.is_empty(): + pages = mock_pages + else: + pages = new_pages + # Handle color strings from JSON if necessary + for p in pages: + if p.has("image_color") and p["image_color"] is String: + p["image_color"] = Color(p["image_color"]) + + current_page = 0 + _setup_dots() + _update_ui(true) + + +func _setup_dots(): + for child in dots_container.get_children(): + child.queue_free() + + for i in range(pages.size()): + var dot = ColorRect.new() + dot.custom_minimum_size = Vector2(10, 10) # Base size + dots_container.add_child(dot) + +func _update_ui(animate: bool = true): + if pages.is_empty(): + return + + # Update Buttons + prev_btn.disabled = (current_page == 0) + next_btn.disabled = (current_page == pages.size() - 1) + + # Update Dots Logic + var dots = dots_container.get_children() + for i in range(dots.size()): + if i == current_page: + dots[i].color = Color(0.2, 0.2, 0.2, 1) # Dark Active + dots[i].custom_minimum_size = Vector2(12, 12) # Active Slightly Larger + else: + dots[i].color = Color(0.8, 0.8, 0.8, 1) # Light Inactive + dots[i].custom_minimum_size = Vector2(10, 10) + + # Update Content + if animate: + _animate_content_change() + else: + _set_content_immediate() + +@onready var image_rect = $CenterContainer/PanelContainer/VBoxContainer/ContentContainer/ImagePanel/ImageRect +@onready var image_label = $CenterContainer/PanelContainer/VBoxContainer/ContentContainer/ImagePanel/ImageLabel + +func _set_content_immediate(): + var page = pages[current_page] + content_label.text = page.get("text", "") + + if page.has("image_path") and page["image_path"] != "": + var path = page["image_path"] + if ResourceLoader.exists(path): + image_rect.texture = load(path) + image_label.visible = false + else: + image_rect.texture = null + image_label.visible = true + image_label.text = "Image Not Found" + else: + image_rect.texture = null + image_label.visible = true + image_label.text = "No Image" + + + +func _animate_content_change(): + if tween and tween.is_valid(): + tween.kill() + + tween = create_tween() + + # Fade Out + tween.tween_property(content_container, "modulate:a", 0.0, 0.15) + + # Callback to change text + tween.tween_callback(self._set_content_immediate) + + # Fade In + tween.tween_property(content_container, "modulate:a", 1.0, 0.15) + +func _on_prev_pressed(): + if current_page > 0: + current_page -= 1 + _update_ui() + +func _on_next_pressed(): + if current_page < pages.size() - 1: + current_page += 1 + _update_ui() + +func _on_close_pressed(): + # Unpause the game + get_tree().paused = false + queue_free() diff --git a/scenes/ui/NoticeDialog.gd.uid b/scenes/ui/NoticeDialog.gd.uid new file mode 100644 index 0000000..a4c36f8 --- /dev/null +++ b/scenes/ui/NoticeDialog.gd.uid @@ -0,0 +1 @@ +uid://cxi5rchnmk07p diff --git a/scenes/ui/notice_dialog.tscn b/scenes/ui/notice_dialog.tscn new file mode 100644 index 0000000..b7da23e --- /dev/null +++ b/scenes/ui/notice_dialog.tscn @@ -0,0 +1,132 @@ +[gd_scene load_steps=3 format=3 uid="uid://rdmro1jxs6ga"] + +[ext_resource type="Script" uid="uid://cxi5rchnmk07p" path="res://scenes/ui/NoticeDialog.gd" id="1_script"] + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_rounded"] +bg_color = Color(0.95, 0.95, 0.95, 1) +corner_radius_top_left = 16 +corner_radius_top_right = 16 +corner_radius_bottom_right = 16 +corner_radius_bottom_left = 16 +shadow_color = Color(0, 0, 0, 0.2) +shadow_size = 8 + +[node name="NoticeDialog" type="CanvasLayer"] +process_mode = 3 +script = ExtResource("1_script") + +[node name="Dimmer" type="ColorRect" parent="."] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +color = Color(0, 0, 0, 0.5) + +[node name="CenterContainer" type="CenterContainer" parent="."] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="PanelContainer" type="PanelContainer" parent="CenterContainer"] +custom_minimum_size = Vector2(480, 420) +layout_mode = 2 +theme_override_styles/panel = SubResource("StyleBoxFlat_rounded") + +[node name="VBoxContainer" type="VBoxContainer" parent="CenterContainer/PanelContainer"] +layout_mode = 2 +theme_override_constants/separation = 12 + +[node name="HeaderSpacer" type="Control" parent="CenterContainer/PanelContainer/VBoxContainer"] +custom_minimum_size = Vector2(0, 4) +layout_mode = 2 + +[node name="Header" type="HBoxContainer" parent="CenterContainer/PanelContainer/VBoxContainer"] +layout_mode = 2 + +[node name="LeftSpacer" type="Control" parent="CenterContainer/PanelContainer/VBoxContainer/Header"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="Title" type="Label" parent="CenterContainer/PanelContainer/VBoxContainer/Header"] +layout_mode = 2 +theme_override_colors/font_color = Color(0.2, 0.2, 0.2, 1) +theme_override_font_sizes/font_size = 22 +text = "公告板" +horizontal_alignment = 1 + +[node name="RightContainer" type="HBoxContainer" parent="CenterContainer/PanelContainer/VBoxContainer/Header"] +layout_mode = 2 +size_flags_horizontal = 3 +alignment = 2 + +[node name="CloseButton" type="Button" parent="CenterContainer/PanelContainer/VBoxContainer/Header/RightContainer"] +custom_minimum_size = Vector2(32, 32) +layout_mode = 2 +text = "X" +flat = true + +[node name="RightMargin" type="Control" parent="CenterContainer/PanelContainer/VBoxContainer/Header/RightContainer"] +custom_minimum_size = Vector2(8, 0) +layout_mode = 2 + +[node name="ContentContainer" type="VBoxContainer" parent="CenterContainer/PanelContainer/VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 3 +theme_override_constants/separation = 10 + +[node name="ImagePanel" type="PanelContainer" parent="CenterContainer/PanelContainer/VBoxContainer/ContentContainer"] +custom_minimum_size = Vector2(0, 200) +layout_mode = 2 + +[node name="ImageRect" type="TextureRect" parent="CenterContainer/PanelContainer/VBoxContainer/ContentContainer/ImagePanel"] +layout_mode = 2 +expand_mode = 1 +stretch_mode = 5 + +[node name="ImageLabel" type="Label" parent="CenterContainer/PanelContainer/VBoxContainer/ContentContainer/ImagePanel"] +layout_mode = 2 +theme_override_colors/font_color = Color(0.6, 0.6, 0.6, 1) +text = "Image Placeholder" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="TextPanel" type="MarginContainer" parent="CenterContainer/PanelContainer/VBoxContainer/ContentContainer"] +layout_mode = 2 +size_flags_vertical = 3 +theme_override_constants/margin_left = 16 +theme_override_constants/margin_right = 16 + +[node name="ContentLabel" type="RichTextLabel" parent="CenterContainer/PanelContainer/VBoxContainer/ContentContainer/TextPanel"] +layout_mode = 2 +theme_override_colors/default_color = Color(0.3, 0.3, 0.3, 1) +theme_override_font_sizes/normal_font_size = 16 +bbcode_enabled = true +text = "Announcement Content..." + +[node name="Footer" type="HBoxContainer" parent="CenterContainer/PanelContainer/VBoxContainer"] +custom_minimum_size = Vector2(0, 48) +layout_mode = 2 +theme_override_constants/separation = 20 +alignment = 1 + +[node name="PrevButton" type="Button" parent="CenterContainer/PanelContainer/VBoxContainer/Footer"] +custom_minimum_size = Vector2(40, 40) +layout_mode = 2 +text = "<" + +[node name="DotsContainer" type="HBoxContainer" parent="CenterContainer/PanelContainer/VBoxContainer/Footer"] +layout_mode = 2 +theme_override_constants/separation = 8 +alignment = 1 + +[node name="NextButton" type="Button" parent="CenterContainer/PanelContainer/VBoxContainer/Footer"] +custom_minimum_size = Vector2(40, 40) +layout_mode = 2 +text = ">" + +[node name="BottomSpacer" type="Control" parent="CenterContainer/PanelContainer/VBoxContainer"] +custom_minimum_size = Vector2(0, 4) +layout_mode = 2