revert d671e4d311
revert Merge pull request '聊天系统' (#13) from qbb0530/whale-town-front:main into main Reviewed-on: #13
This commit is contained in:
@@ -14,7 +14,6 @@ extends Control
|
||||
# 场景节点引用
|
||||
@onready var auth_scene: Control = $AuthScene
|
||||
@onready var main_game_ui: Control = $MainGameUI
|
||||
@onready var chat_ui: Control = %ChatUI
|
||||
@onready var user_label: Label = $MainGameUI/TopBar/HBoxContainer/UserLabel
|
||||
@onready var logout_button: Button = $MainGameUI/TopBar/HBoxContainer/LogoutButton
|
||||
|
||||
@@ -88,25 +87,11 @@ func _on_login_success(username: String):
|
||||
# 登录成功后的处理
|
||||
current_user = username
|
||||
print("用户 ", username, " 登录成功!")
|
||||
|
||||
# 连接到聊天服务器(在进入游戏界面之前)
|
||||
# 注意:token 已在 AuthScene._on_controller_login_success 中设置
|
||||
print("🔌 开始连接聊天服务器...")
|
||||
ChatManager.connect_to_chat_server()
|
||||
|
||||
show_main_game()
|
||||
|
||||
# 登录成功后隐藏聊天框(需要按Enter才显示)
|
||||
chat_ui.hide_chat()
|
||||
|
||||
func _on_logout_pressed():
|
||||
# 登出处理
|
||||
current_user = ""
|
||||
|
||||
# 断开聊天服务器连接
|
||||
print("🔌 断开聊天服务器...")
|
||||
ChatManager.disconnect_from_chat_server()
|
||||
|
||||
show_auth_scene()
|
||||
|
||||
# 游戏功能按钮处理
|
||||
@@ -139,4 +124,4 @@ func _input(event):
|
||||
get_tree().quit()
|
||||
GameState.MAIN_GAME:
|
||||
# 在游戏中按ESC可能显示菜单或返回登录
|
||||
show_auth_scene()
|
||||
show_auth_scene()
|
||||
@@ -1,16 +1,13 @@
|
||||
[gd_scene load_steps=4 format=3 uid="uid://cjabtnqbdd2ey"]
|
||||
[gd_scene load_steps=3 format=3 uid="uid://cjabtnqbdd2ey"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://ghehm4srs0ho" path="res://scenes/MainScene.gd" id="1_script"]
|
||||
[ext_resource type="Script" path="res://scenes/MainScene.gd" id="1_script"]
|
||||
[ext_resource type="PackedScene" uid="uid://by7m8snb4xllf" path="res://scenes/ui/AuthScene.tscn" id="2_main"]
|
||||
[ext_resource type="PackedScene" uid="uid://bv7k2nan4xj8q" path="res://scenes/ui/ChatUI.tscn" id="3_chat_ui"]
|
||||
|
||||
[node name="Main" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
script = ExtResource("1_script")
|
||||
|
||||
[node name="AuthScene" parent="." instance=ExtResource("2_main")]
|
||||
@@ -22,15 +19,12 @@ layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="TopBar" type="Panel" parent="MainGameUI"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 10
|
||||
anchor_right = 1.0
|
||||
offset_bottom = 60.0
|
||||
grow_horizontal = 2
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="MainGameUI/TopBar"]
|
||||
layout_mode = 1
|
||||
@@ -41,8 +35,6 @@ offset_left = 20.0
|
||||
offset_top = 10.0
|
||||
offset_right = -20.0
|
||||
offset_bottom = -10.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="UserLabel" type="Label" parent="MainGameUI/TopBar/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
@@ -60,23 +52,19 @@ anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_top = 60.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="CenterContainer" type="CenterContainer" parent="MainGameUI/MainContent"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="MainGameUI/MainContent/CenterContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="StatusPanel" type="Panel" parent="MainGameUI/MainContent/CenterContainer/VBoxContainer"]
|
||||
custom_minimum_size = Vector2(400, 150)
|
||||
layout_mode = 2
|
||||
custom_minimum_size = Vector2(400, 150)
|
||||
|
||||
[node name="StatusContainer" type="MarginContainer" parent="MainGameUI/MainContent/CenterContainer/VBoxContainer/StatusPanel"]
|
||||
layout_mode = 1
|
||||
@@ -87,8 +75,6 @@ offset_left = 20.0
|
||||
offset_top = 20.0
|
||||
offset_right = -20.0
|
||||
offset_bottom = -20.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="StatusGrid" type="GridContainer" parent="MainGameUI/MainContent/CenterContainer/VBoxContainer/StatusPanel/StatusContainer"]
|
||||
layout_mode = 2
|
||||
@@ -115,24 +101,21 @@ layout_mode = 2
|
||||
columns = 2
|
||||
|
||||
[node name="ExploreButton" type="Button" parent="MainGameUI/MainContent/CenterContainer/VBoxContainer/GameMenuGrid"]
|
||||
custom_minimum_size = Vector2(150, 50)
|
||||
layout_mode = 2
|
||||
custom_minimum_size = Vector2(150, 50)
|
||||
text = "🗺️ 探索小镇"
|
||||
|
||||
[node name="InventoryButton" type="Button" parent="MainGameUI/MainContent/CenterContainer/VBoxContainer/GameMenuGrid"]
|
||||
custom_minimum_size = Vector2(150, 50)
|
||||
layout_mode = 2
|
||||
custom_minimum_size = Vector2(150, 50)
|
||||
text = "🎒 背包"
|
||||
|
||||
[node name="ShopButton" type="Button" parent="MainGameUI/MainContent/CenterContainer/VBoxContainer/GameMenuGrid"]
|
||||
custom_minimum_size = Vector2(150, 50)
|
||||
layout_mode = 2
|
||||
custom_minimum_size = Vector2(150, 50)
|
||||
text = "🏪 商店"
|
||||
|
||||
[node name="FriendsButton" type="Button" parent="MainGameUI/MainContent/CenterContainer/VBoxContainer/GameMenuGrid"]
|
||||
custom_minimum_size = Vector2(150, 50)
|
||||
layout_mode = 2
|
||||
text = "👥 好友"
|
||||
|
||||
[node name="ChatUI" parent="MainGameUI" instance=ExtResource("3_chat_ui")]
|
||||
layout_mode = 1
|
||||
custom_minimum_size = Vector2(150, 50)
|
||||
text = "👥 好友"
|
||||
@@ -1 +0,0 @@
|
||||
uid://b7bgtip4yxeg8
|
||||
@@ -1,167 +0,0 @@
|
||||
extends Panel
|
||||
|
||||
# ============================================================================
|
||||
# ChatMessage.gd - 聊天消息气泡组件
|
||||
# ============================================================================
|
||||
# 显示单条聊天消息的 UI 组件
|
||||
#
|
||||
# 核心职责:
|
||||
# - 显示消息发送者、内容、时间戳
|
||||
# - 区分自己和他人的消息样式
|
||||
# - 自动格式化时间戳
|
||||
#
|
||||
# 使用方式:
|
||||
# var message := chat_message_scene.instantiate()
|
||||
# message.set_message("PlayerName", "Hello!", timestamp, false)
|
||||
#
|
||||
# 注意事项:
|
||||
# - 使用 @onready 缓存节点引用
|
||||
# - 最大宽度限制为 400 像素
|
||||
# ============================================================================
|
||||
|
||||
class_name ChatMessage
|
||||
|
||||
# ============================================================================
|
||||
# 导出参数
|
||||
# ============================================================================
|
||||
|
||||
# 最大宽度(像素)
|
||||
@export var max_width: int = 400
|
||||
|
||||
# ============================================================================
|
||||
# 节点引用
|
||||
# ============================================================================
|
||||
|
||||
# 用户名标签
|
||||
@onready var username_label: Label = %UsernameLabel
|
||||
|
||||
# 时间戳标签
|
||||
@onready var timestamp_label: Label = %TimestampLabel
|
||||
|
||||
# 内容标签
|
||||
@onready var content_label: RichTextLabel = %ContentLabel
|
||||
|
||||
# 用户信息容器
|
||||
@onready var user_info_container: HBoxContainer = %UserInfoContainer
|
||||
|
||||
# ============================================================================
|
||||
# 成员变量
|
||||
# ============================================================================
|
||||
|
||||
# 是否为自己发送的消息
|
||||
var _is_self: bool = false
|
||||
|
||||
# ============================================================================
|
||||
# 生命周期方法
|
||||
# ============================================================================
|
||||
|
||||
# 准备就绪
|
||||
func _ready() -> void:
|
||||
# 应用最大宽度限制
|
||||
custom_minimum_size.x = min(max_width, get_parent().size.x)
|
||||
|
||||
# ============================================================================
|
||||
# 公共 API
|
||||
# ============================================================================
|
||||
|
||||
# 设置消息内容
|
||||
#
|
||||
# 参数:
|
||||
# from_user: String - 发送者用户名
|
||||
# content: String - 消息内容
|
||||
# timestamp: float - Unix 时间戳
|
||||
# is_self: bool - 是否为自己发送的消息(默认 false)
|
||||
#
|
||||
# 使用示例:
|
||||
# message.set_message("Alice", "Hello!", 1703500800.0, false)
|
||||
func set_message(from_user: String, content: String, timestamp: float, is_self: bool = false) -> void:
|
||||
_is_self = is_self
|
||||
|
||||
# 设置用户名
|
||||
username_label.text = from_user
|
||||
|
||||
# 设置内容
|
||||
content_label.text = content
|
||||
|
||||
# 设置时间戳
|
||||
timestamp_label.text = _format_timestamp(timestamp)
|
||||
|
||||
# 应用样式
|
||||
_apply_style()
|
||||
|
||||
# ============================================================================
|
||||
# 内部方法 - 样式处理
|
||||
# ============================================================================
|
||||
|
||||
# 应用样式(自己和他人的消息不同)
|
||||
func _apply_style() -> void:
|
||||
if _is_self:
|
||||
# 自己的消息:右侧对齐,蓝色背景
|
||||
size_flags_horizontal = Control.SIZE_SHRINK_END
|
||||
user_info_container.alignment = BoxContainer.ALIGNMENT_END
|
||||
|
||||
# 设置面板样式
|
||||
add_theme_stylebox_override("panel", _get_self_style())
|
||||
|
||||
# 设置文字颜色 - ID使用金色 #FFD700
|
||||
username_label.add_theme_color_override("font_color", Color(1.0, 0.8431373, 0.0))
|
||||
timestamp_label.add_theme_color_override("font_color", Color(0.7, 0.7, 0.7))
|
||||
else:
|
||||
# 他人的消息:左侧对齐,灰色背景
|
||||
size_flags_horizontal = Control.SIZE_SHRINK_BEGIN
|
||||
user_info_container.alignment = BoxContainer.ALIGNMENT_BEGIN
|
||||
|
||||
# 设置面板样式
|
||||
add_theme_stylebox_override("panel", _get_other_style())
|
||||
|
||||
# 设置文字颜色 - ID使用蓝色 #69c0ff
|
||||
username_label.add_theme_color_override("font_color", Color(0.4117647, 0.7529412, 1.0))
|
||||
timestamp_label.add_theme_color_override("font_color", Color(0.5, 0.5, 0.5))
|
||||
|
||||
# 获取自己消息的样式
|
||||
func _get_self_style() -> StyleBoxFlat:
|
||||
var style := StyleBoxFlat.new()
|
||||
style.bg_color = Color(0.2, 0.6, 1.0, 0.3)
|
||||
style.corner_radius_top_left = 10
|
||||
style.corner_radius_top_right = 10
|
||||
style.corner_radius_bottom_left = 10
|
||||
style.corner_radius_bottom_right = 2
|
||||
style.content_margin_left = 10
|
||||
style.content_margin_right = 10
|
||||
style.content_margin_top = 8
|
||||
style.content_margin_bottom = 8
|
||||
return style
|
||||
|
||||
# 获取他人消息的样式
|
||||
func _get_other_style() -> StyleBoxFlat:
|
||||
var style := StyleBoxFlat.new()
|
||||
style.bg_color = Color(0.9, 0.9, 0.9, 0.5)
|
||||
style.corner_radius_top_left = 10
|
||||
style.corner_radius_top_right = 10
|
||||
style.corner_radius_bottom_left = 2
|
||||
style.corner_radius_bottom_right = 10
|
||||
style.content_margin_left = 10
|
||||
style.content_margin_right = 10
|
||||
style.content_margin_top = 8
|
||||
style.content_margin_bottom = 8
|
||||
return style
|
||||
|
||||
# ============================================================================
|
||||
# 内部方法 - 工具函数
|
||||
# ============================================================================
|
||||
|
||||
# 格式化时间戳
|
||||
#
|
||||
# 参数:
|
||||
# timestamp: float - Unix 时间戳
|
||||
#
|
||||
# 返回值:
|
||||
# String - 格式化的时间字符串
|
||||
func _format_timestamp(timestamp: float) -> String:
|
||||
if timestamp == 0:
|
||||
return ""
|
||||
|
||||
var datetime := Time.get_datetime_dict_from_unix_time(timestamp)
|
||||
|
||||
# 格式化为 HH:MM
|
||||
return "%02d:%02d" % [datetime.hour, datetime.minute]
|
||||
@@ -1 +0,0 @@
|
||||
uid://djqrgj3h0lif7
|
||||
@@ -1,45 +0,0 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://dqx8k3n8yqjvu"]
|
||||
|
||||
[ext_resource type="Script" path="res://scenes/prefabs/ui/ChatMessage.gd" id="1"]
|
||||
|
||||
[node name="ChatMessage" type="Panel"]
|
||||
offset_right = 400.0
|
||||
offset_bottom = 100.0
|
||||
size_flags_horizontal = 3
|
||||
script = ExtResource("1")
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
theme_override_constants/separation = 4
|
||||
|
||||
[node name="UserInfoContainer" type="HBoxContainer" parent="VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 8
|
||||
|
||||
[node name="UsernameLabel" type="Label" parent="VBoxContainer/UserInfoContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
theme_override_colors/font_color = Color(0.2, 0.4, 0.8, 1)
|
||||
theme_override_font_sizes/font_size = 14
|
||||
text = "Username"
|
||||
|
||||
[node name="TimestampLabel" type="Label" parent="VBoxContainer/UserInfoContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
theme_override_colors/font_color = Color(0.5, 0.5, 0.5, 1)
|
||||
theme_override_font_sizes/font_size = 12
|
||||
text = "12:34"
|
||||
|
||||
[node name="ContentLabel" type="RichTextLabel" parent="VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
bbcode_enabled = true
|
||||
text = "Message content here"
|
||||
fit_content = true
|
||||
@@ -328,7 +328,7 @@ func _on_login_enter(_text: String):
|
||||
# ============ 控制器信号处理 ============
|
||||
|
||||
# 登录成功处理
|
||||
func _on_controller_login_success(username: String) -> void:
|
||||
func _on_controller_login_success(username: String):
|
||||
# 清空表单
|
||||
login_username.text = ""
|
||||
login_password.text = ""
|
||||
@@ -336,13 +336,7 @@ func _on_controller_login_success(username: String) -> void:
|
||||
_hide_field_error(login_username_error)
|
||||
_hide_field_error(login_password_error)
|
||||
_hide_field_error(login_verification_error)
|
||||
|
||||
# 设置 token 给 ChatManager(用于 WebSocket 聊天认证)
|
||||
var token: String = auth_manager.get_access_token()
|
||||
if not token.is_empty():
|
||||
ChatManager.set_game_token(token)
|
||||
print("✅ 已设置 ChatManager token: ", token.substr(0, 20) + "...")
|
||||
|
||||
|
||||
# 发送登录成功信号给上层
|
||||
login_success.emit(username)
|
||||
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
[gd_scene load_steps=13 format=3 uid="uid://by7m8snb4xllf"]
|
||||
[gd_scene load_steps=10 format=3 uid="uid://by7m8snb4xllf"]
|
||||
|
||||
[ext_resource type="Texture2D" uid="uid://bx17oy8lvaca4" path="res://assets/ui/auth/bg_auth_scene.png" id="1_background"]
|
||||
[ext_resource type="Script" uid="uid://b514h2wuido0h" path="res://scenes/ui/AuthScene.gd" id="3_script"]
|
||||
[ext_resource type="Texture2D" uid="uid://dyma4hpodhdxi" path="res://assets/ui/auth/登录背景.png" id="3_wh4n4"]
|
||||
[ext_resource type="Texture2D" uid="uid://cnrffaqbtw8f5" path="res://assets/ui/auth/输入框.png" id="4_lnw07"]
|
||||
[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"]
|
||||
|
||||
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_26vyf"]
|
||||
|
||||
[sub_resource type="StyleBoxTexture" id="StyleBoxTexture_cjyup"]
|
||||
texture = ExtResource("4_lnw07")
|
||||
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_1"]
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_hover"]
|
||||
bg_color = Color(0.3, 0.6, 0.9, 1)
|
||||
@@ -63,8 +59,6 @@ Button/styles/hover = SubResource("StyleBoxFlat_hover")
|
||||
Button/styles/normal = SubResource("StyleBoxFlat_normal")
|
||||
Button/styles/pressed = SubResource("StyleBoxFlat_pressed")
|
||||
|
||||
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_1"]
|
||||
|
||||
[node name="AuthScene" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
@@ -87,14 +81,22 @@ texture = ExtResource("1_background")
|
||||
expand_mode = 1
|
||||
stretch_mode = 6
|
||||
|
||||
[node name="ColorRect" type="ColorRect" parent="."]
|
||||
modulate = Color(0.84313726, 0.92941177, 0.98039216, 0.47058824)
|
||||
[node name="WhaleFrame" type="TextureRect" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
anchors_preset = 8
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.5
|
||||
offset_left = -300.0
|
||||
offset_top = -300.0
|
||||
offset_right = 300.0
|
||||
offset_bottom = 300.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
texture = ExtResource("2_frame")
|
||||
expand_mode = 1
|
||||
stretch_mode = 5
|
||||
|
||||
[node name="CenterContainer" type="CenterContainer" parent="."]
|
||||
layout_mode = 1
|
||||
@@ -110,45 +112,35 @@ offset_bottom = 236.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="WhaleFrame" type="TextureRect" parent="CenterContainer"]
|
||||
custom_minimum_size = Vector2(500, 0)
|
||||
layout_mode = 2
|
||||
texture = ExtResource("3_wh4n4")
|
||||
expand_mode = 4
|
||||
stretch_mode = 5
|
||||
|
||||
[node name="LoginPanel" type="Panel" parent="CenterContainer"]
|
||||
custom_minimum_size = Vector2(300, 0)
|
||||
custom_minimum_size = Vector2(350, 400)
|
||||
layout_mode = 2
|
||||
theme_override_styles/panel = SubResource("StyleBoxEmpty_26vyf")
|
||||
theme_override_styles/panel = SubResource("StyleBoxEmpty_1")
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="CenterContainer/LoginPanel"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 8
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.5
|
||||
offset_left = -140.0
|
||||
offset_top = -185.5
|
||||
offset_right = 140.0
|
||||
offset_bottom = 185.5
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_left = 30.0
|
||||
offset_top = 30.0
|
||||
offset_right = -30.0
|
||||
offset_bottom = -30.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="TitleLabel" type="Label" parent="CenterContainer/LoginPanel/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
theme_override_colors/font_color = Color(0, 0, 0, 1)
|
||||
theme_override_colors/font_shadow_color = Color(0.011764706, 0.12156863, 0.101960786, 0)
|
||||
theme_override_font_sizes/font_size = 32
|
||||
theme_override_font_sizes/font_size = 24
|
||||
text = "Whaletown"
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
|
||||
[node name="SubtitleLabel" type="Label" parent="CenterContainer/LoginPanel/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
theme_override_colors/font_color = Color(0.53333336, 0.53333336, 0.53333336, 1)
|
||||
theme_override_font_sizes/font_size = 16
|
||||
theme_override_colors/font_color = Color(0, 0, 0, 1)
|
||||
theme_override_font_sizes/font_size = 14
|
||||
text = "开始你的小镇之旅!"
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
@@ -188,13 +180,10 @@ theme_override_font_sizes/font_size = 12
|
||||
text = "用户名不能为空"
|
||||
horizontal_alignment = 2
|
||||
|
||||
[node name="UsernameInput" type="LineEdit" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm"]
|
||||
custom_minimum_size = Vector2(0, 48)
|
||||
[node name="UsernameInput" type="LineEdit" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm/UsernameContainer"]
|
||||
layout_mode = 2
|
||||
theme_override_colors/font_color = Color(0, 0, 0, 1)
|
||||
theme_override_colors/font_placeholder_color = Color(0.5, 0.5, 0.5, 1)
|
||||
theme_override_colors/selection_color = Color(0.5, 0.5, 0.5, 1)
|
||||
theme_override_styles/normal = SubResource("StyleBoxTexture_cjyup")
|
||||
placeholder_text = "用户名/手机/邮箱"
|
||||
|
||||
[node name="PasswordContainer" type="VBoxContainer" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm"]
|
||||
@@ -563,8 +552,6 @@ theme = SubResource("Theme_button")
|
||||
text = "返回登录"
|
||||
|
||||
[node name="ToastContainer" type="Control" parent="."]
|
||||
modulate = Color(0.84313726, 0.92941177, 0.98039216, 1)
|
||||
self_modulate = Color(0.84313726, 0.92941177, 0.9843137, 1)
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
|
||||
@@ -1,333 +0,0 @@
|
||||
extends Control
|
||||
|
||||
# ============================================================================
|
||||
# ChatUI.gd - 聊天界面控制器(Enter 显示/隐藏版本)
|
||||
# ============================================================================
|
||||
# 聊天系统的用户界面控制器
|
||||
#
|
||||
# 核心职责:
|
||||
# - 显示聊天消息历史
|
||||
# - 处理用户输入
|
||||
# - 显示连接状态
|
||||
# - Enter 显示/隐藏 + 点击外部取消输入状态 + 5秒自动隐藏
|
||||
# - 只有按 Enter 才会取消倒计时
|
||||
# - Call Down: 通过 EventSystem 订阅聊天事件
|
||||
#
|
||||
# 使用方式:
|
||||
# var chat_ui := preload("res://scenes/ui/ChatUI.tscn").instantiate()
|
||||
# add_child(chat_ui)
|
||||
#
|
||||
# 注意事项:
|
||||
# - 遵循 "Signal Up, Call Down" 架构
|
||||
# - 使用 @onready 缓存节点引用
|
||||
# - 所有 UI 操作通过 ChatManager
|
||||
# ============================================================================
|
||||
|
||||
# ============================================================================
|
||||
# 节点引用
|
||||
# ============================================================================
|
||||
|
||||
# 聊天面板
|
||||
@onready var chat_panel: Control = %ChatPanel
|
||||
|
||||
# 聊天历史容器
|
||||
@onready var chat_history: ScrollContainer = %ChatHistory
|
||||
|
||||
# 消息列表
|
||||
@onready var message_list: VBoxContainer = %MessageList
|
||||
|
||||
# 聊天输入框
|
||||
@onready var chat_input: LineEdit = %ChatInput
|
||||
|
||||
# ============================================================================
|
||||
# 预加载资源
|
||||
# ============================================================================
|
||||
|
||||
# 聊天消息场景
|
||||
@onready var chat_message_scene: PackedScene = preload("res://scenes/prefabs/ui/ChatMessage.tscn")
|
||||
|
||||
# ============================================================================
|
||||
# 成员变量
|
||||
# ============================================================================
|
||||
|
||||
# 是否显示聊天框
|
||||
var _is_chat_visible: bool = false
|
||||
|
||||
# 隐藏计时器
|
||||
var _hide_timer: Timer = null
|
||||
|
||||
# 是否在输入中(输入时不隐藏)
|
||||
var _is_typing: bool = false
|
||||
|
||||
# 当前用户名
|
||||
var _current_username: String = ""
|
||||
|
||||
# ============================================================================
|
||||
# 生命周期方法
|
||||
# ============================================================================
|
||||
|
||||
# 准备就绪
|
||||
func _ready() -> void:
|
||||
# 初始隐藏聊天框
|
||||
hide_chat()
|
||||
|
||||
# 创建隐藏计时器
|
||||
_create_hide_timer()
|
||||
|
||||
# 订阅事件(Call Down via EventSystem)
|
||||
_subscribe_to_events()
|
||||
|
||||
# 连接 UI 信号
|
||||
_connect_ui_signals()
|
||||
|
||||
# 清理
|
||||
func _exit_tree() -> void:
|
||||
# 取消事件订阅
|
||||
if EventSystem:
|
||||
EventSystem.disconnect_event(EventNames.CHAT_MESSAGE_RECEIVED, _on_chat_message_received, self)
|
||||
EventSystem.disconnect_event(EventNames.CHAT_ERROR_OCCURRED, _on_chat_error, self)
|
||||
EventSystem.disconnect_event(EventNames.CHAT_CONNECTION_STATE_CHANGED, _on_connection_state_changed, self)
|
||||
EventSystem.disconnect_event(EventNames.CHAT_LOGIN_SUCCESS, _on_login_success, self)
|
||||
|
||||
# 清理计时器
|
||||
if _hide_timer:
|
||||
_hide_timer.queue_free()
|
||||
|
||||
# ============================================================================
|
||||
# 输入处理
|
||||
# ============================================================================
|
||||
|
||||
# 处理全局输入
|
||||
func _input(event: InputEvent) -> void:
|
||||
# 检查是否按下 Enter 键
|
||||
if event is InputEventKey and event.keycode == KEY_ENTER:
|
||||
_handle_enter_pressed()
|
||||
|
||||
# 处理 Enter 键按下
|
||||
func _handle_enter_pressed() -> void:
|
||||
# 如果聊天框未显示,显示它
|
||||
if not _is_chat_visible:
|
||||
show_chat()
|
||||
# 使用 call_deferred 避免在同一个事件周期内触发 LineEdit 的 text_submitted 信号
|
||||
call_deferred("_grab_input_focus")
|
||||
return
|
||||
|
||||
# 如果聊天框已显示且输入框有焦点,检查输入框内容
|
||||
if chat_input.has_focus():
|
||||
# 如果输入框有内容,发送消息
|
||||
if not chat_input.text.is_empty():
|
||||
_on_send_button_pressed()
|
||||
return
|
||||
|
||||
# 如果聊天框已显示但输入框无焦点,重新聚焦(取消倒计时)
|
||||
chat_input.grab_focus()
|
||||
|
||||
# 延迟获取输入框焦点(避免事件冲突)
|
||||
func _grab_input_focus() -> void:
|
||||
if chat_input:
|
||||
chat_input.grab_focus()
|
||||
|
||||
# 处理 GUI 输入(鼠标点击)
|
||||
func _gui_input(event: InputEvent) -> void:
|
||||
# 检查鼠标点击
|
||||
if event is InputEventMouseButton and event.pressed:
|
||||
if event.button_index == MOUSE_BUTTON_LEFT:
|
||||
_handle_click_outside()
|
||||
|
||||
# 处理点击聊天框外部区域
|
||||
func _handle_click_outside() -> void:
|
||||
# 检查点击是否在聊天面板外部
|
||||
if not chat_panel.get_global_rect().has_point(get_global_mouse_position()):
|
||||
# 延迟释放输入框焦点,避免事件冲突
|
||||
if chat_input.has_focus():
|
||||
call_deferred("_release_input_focus")
|
||||
|
||||
# 延迟释放输入框焦点(由 call_deferred 调用)
|
||||
func _release_input_focus() -> void:
|
||||
if chat_input and chat_input.has_focus():
|
||||
chat_input.release_focus()
|
||||
|
||||
# ============================================================================
|
||||
# 显示/隐藏逻辑
|
||||
# ============================================================================
|
||||
|
||||
# 显示聊天框
|
||||
func show_chat() -> void:
|
||||
if _is_chat_visible:
|
||||
return
|
||||
|
||||
_is_chat_visible = true
|
||||
chat_panel.show()
|
||||
|
||||
# 停止隐藏计时器
|
||||
_stop_hide_timer()
|
||||
|
||||
# 隐藏聊天框
|
||||
func hide_chat() -> void:
|
||||
if not _is_chat_visible:
|
||||
return
|
||||
|
||||
_is_chat_visible = false
|
||||
chat_panel.hide()
|
||||
|
||||
# 停止隐藏计时器
|
||||
_stop_hide_timer()
|
||||
|
||||
# 创建隐藏计时器
|
||||
func _create_hide_timer() -> void:
|
||||
_hide_timer = Timer.new()
|
||||
_hide_timer.wait_time = 5.0 # 5 秒
|
||||
_hide_timer.one_shot = true
|
||||
_hide_timer.timeout.connect(_on_hide_timeout)
|
||||
add_child(_hide_timer)
|
||||
|
||||
# 开始隐藏倒计时
|
||||
func _start_hide_timer() -> void:
|
||||
if _is_typing:
|
||||
return # 输入时不隐藏
|
||||
|
||||
_stop_hide_timer() # 先停止之前的计时器
|
||||
_hide_timer.start()
|
||||
|
||||
# 停止隐藏倒计时
|
||||
func _stop_hide_timer() -> void:
|
||||
if _hide_timer:
|
||||
_hide_timer.stop()
|
||||
|
||||
# 隐藏计时器超时
|
||||
func _on_hide_timeout() -> void:
|
||||
hide_chat()
|
||||
|
||||
# ============================================================================
|
||||
# UI 事件处理
|
||||
# ============================================================================
|
||||
|
||||
# 连接 UI 信号
|
||||
func _connect_ui_signals() -> void:
|
||||
# 输入框回车
|
||||
chat_input.text_submitted.connect(_on_chat_input_submitted)
|
||||
|
||||
# 输入框焦点变化
|
||||
chat_input.focus_entered.connect(_on_input_focus_entered)
|
||||
chat_input.focus_exited.connect(_on_input_focus_exited)
|
||||
|
||||
# 输入框获得焦点
|
||||
func _on_input_focus_entered() -> void:
|
||||
_is_typing = true
|
||||
_stop_hide_timer() # 停止隐藏计时器
|
||||
|
||||
# 输入框失去焦点
|
||||
func _on_input_focus_exited() -> void:
|
||||
_is_typing = false
|
||||
# 开始 5 秒倒计时
|
||||
if not _is_chat_visible:
|
||||
return
|
||||
_start_hide_timer()
|
||||
|
||||
# 发送按钮点击处理
|
||||
func _on_send_button_pressed() -> void:
|
||||
var content: String = chat_input.text.strip_edges()
|
||||
|
||||
if content.is_empty():
|
||||
return
|
||||
|
||||
# 发送消息
|
||||
ChatManager.send_chat_message(content, "local")
|
||||
|
||||
# 清空输入框
|
||||
chat_input.clear()
|
||||
|
||||
# 重新聚焦输入框
|
||||
chat_input.grab_focus()
|
||||
|
||||
# 聊天输入提交(回车键)处理
|
||||
func _on_chat_input_submitted(text: String) -> void:
|
||||
_on_send_button_pressed()
|
||||
|
||||
# ============================================================================
|
||||
# 事件订阅(Call Down)
|
||||
# ============================================================================
|
||||
|
||||
# 订阅事件
|
||||
func _subscribe_to_events() -> void:
|
||||
# 订阅聊天消息接收事件
|
||||
EventSystem.connect_event(EventNames.CHAT_MESSAGE_RECEIVED, _on_chat_message_received, self)
|
||||
|
||||
# 订阅聊天错误事件
|
||||
EventSystem.connect_event(EventNames.CHAT_ERROR_OCCURRED, _on_chat_error, self)
|
||||
|
||||
# 订阅连接状态变化事件
|
||||
EventSystem.connect_event(EventNames.CHAT_CONNECTION_STATE_CHANGED, _on_connection_state_changed, self)
|
||||
|
||||
# 订阅登录成功事件
|
||||
EventSystem.connect_event(EventNames.CHAT_LOGIN_SUCCESS, _on_login_success, self)
|
||||
|
||||
# ============================================================================
|
||||
# 事件处理器
|
||||
# ============================================================================
|
||||
|
||||
# 处理接收到的聊天消息
|
||||
func _on_chat_message_received(data: Dictionary) -> void:
|
||||
var from_user: String = data.get("from_user", "")
|
||||
var content: String = data.get("content", "")
|
||||
var timestamp: float = data.get("timestamp", 0.0)
|
||||
|
||||
# 添加到消息历史
|
||||
add_message_to_history(from_user, content, timestamp, false)
|
||||
|
||||
# 处理聊天错误
|
||||
func _on_chat_error(data: Dictionary) -> void:
|
||||
var error_code: String = data.get("error_code", "")
|
||||
var message: String = data.get("message", "")
|
||||
|
||||
print("❌ ChatUI 错误: [", error_code, "] ", message)
|
||||
|
||||
# 处理连接状态变化
|
||||
func _on_connection_state_changed(data: Dictionary) -> void:
|
||||
# 连接状态变化处理(当前不更新UI)
|
||||
pass
|
||||
|
||||
# 处理登录成功
|
||||
func _on_login_success(data: Dictionary) -> void:
|
||||
_current_username = data.get("username", "")
|
||||
|
||||
# ============================================================================
|
||||
# 公共 API - 消息管理
|
||||
# ============================================================================
|
||||
|
||||
# 添加消息到历史
|
||||
#
|
||||
# 参数:
|
||||
# from_user: String - 发送者用户名
|
||||
# content: String - 消息内容
|
||||
# timestamp: float - 时间戳
|
||||
# is_self: bool - 是否为自己发送的消息(默认 false)
|
||||
func add_message_to_history(from_user: String, content: String, timestamp: float, is_self: bool) -> void:
|
||||
# 如果聊天框隐藏,自动显示
|
||||
if not _is_chat_visible:
|
||||
show_chat()
|
||||
|
||||
# 创建消息节点
|
||||
var message_node: ChatMessage = chat_message_scene.instantiate()
|
||||
|
||||
# 设置消息内容
|
||||
message_node.set_message(from_user, content, timestamp, is_self)
|
||||
|
||||
# 添加到列表
|
||||
message_list.add_child(message_node)
|
||||
|
||||
# 自动滚动到底部
|
||||
call_deferred("_scroll_to_bottom")
|
||||
|
||||
# ============================================================================
|
||||
# 内部方法 - UI 更新
|
||||
# ============================================================================
|
||||
|
||||
# 滚动到底部
|
||||
func _scroll_to_bottom() -> void:
|
||||
# 等待一帧,确保 UI 更新完成
|
||||
await get_tree().process_frame
|
||||
|
||||
# 滚动到底部
|
||||
if is_instance_valid(chat_history):
|
||||
chat_history.scroll_vertical = chat_history.get_v_scroll_bar().max_value
|
||||
@@ -1 +0,0 @@
|
||||
uid://pibdlvhb12q8
|
||||
@@ -1,119 +0,0 @@
|
||||
[gd_scene load_steps=8 format=3 uid="uid://bv7k2nan4xj8q"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://pibdlvhb12q8" path="res://scenes/ui/ChatUI.gd" id="1"]
|
||||
[ext_resource type="Texture2D" uid="uid://cchjgp6qh7u61" path="res://assets/ui/chat/缩略框背景.png" id="2_7dhmv"]
|
||||
[ext_resource type="Texture2D" uid="uid://clmgyxpeh5742" path="res://assets/ui/chat/输入框背景.png" id="3_fbft8"]
|
||||
[ext_resource type="Texture2D" uid="uid://q0ijn5y0tbw3" path="res://assets/ui/chat/装饰2.png" id="4_xo31h"]
|
||||
[ext_resource type="Texture2D" uid="uid://ct0cl4h2i6ydn" path="res://assets/ui/chat/装饰.png" id="5_xlxdo"]
|
||||
|
||||
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_xo31h"]
|
||||
|
||||
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_1ahvy"]
|
||||
|
||||
[node name="ChatUI" type="Control"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(10, 20)
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
script = ExtResource("1")
|
||||
|
||||
[node name="ChatPanel" type="Control" parent="."]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 1
|
||||
anchor_left = 0.007
|
||||
anchor_top = 0.98700005
|
||||
anchor_right = 0.007
|
||||
anchor_bottom = 0.98700005
|
||||
offset_left = 0.36800003
|
||||
offset_top = -240.01605
|
||||
offset_right = 340.368
|
||||
offset_bottom = -0.016052246
|
||||
grow_vertical = 0
|
||||
|
||||
[node name="TextureRect" type="TextureRect" parent="ChatPanel"]
|
||||
layout_mode = 1
|
||||
anchors_preset = -1
|
||||
anchor_top = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_top = -240.0
|
||||
offset_right = 340.0
|
||||
texture = ExtResource("2_7dhmv")
|
||||
expand_mode = 1
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="ChatPanel"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_left = 15.0
|
||||
offset_top = 15.0
|
||||
offset_right = -15.0
|
||||
offset_bottom = -55.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
theme_override_constants/separation = 8
|
||||
|
||||
[node name="ChatHistory" type="ScrollContainer" parent="ChatPanel/VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="MessageList" type="VBoxContainer" parent="ChatPanel/VBoxContainer/ChatHistory"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
theme_override_constants/separation = 8
|
||||
|
||||
[node name="TextureRect2" type="TextureRect" parent="ChatPanel"]
|
||||
layout_mode = 0
|
||||
offset_left = 10.0
|
||||
offset_top = 10.0
|
||||
offset_right = 20.0
|
||||
offset_bottom = 20.0
|
||||
texture = ExtResource("4_xo31h")
|
||||
expand_mode = 1
|
||||
|
||||
[node name="TextureRect3" type="TextureRect" parent="ChatPanel"]
|
||||
layout_mode = 0
|
||||
offset_left = 320.0
|
||||
offset_top = 180.0
|
||||
offset_right = 330.0
|
||||
offset_bottom = 190.0
|
||||
texture = ExtResource("5_xlxdo")
|
||||
expand_mode = 1
|
||||
|
||||
[node name="TextureRect4" type="TextureRect" parent="ChatPanel"]
|
||||
layout_mode = 1
|
||||
anchors_preset = -1
|
||||
anchor_top = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_left = 15.0
|
||||
offset_top = -40.0
|
||||
offset_right = 325.0
|
||||
offset_bottom = -10.0
|
||||
texture = ExtResource("3_fbft8")
|
||||
expand_mode = 1
|
||||
|
||||
[node name="InputContainer" type="HBoxContainer" parent="ChatPanel"]
|
||||
self_modulate = Color(1, 1, 1, 0.03529412)
|
||||
layout_mode = 0
|
||||
offset_left = 25.0
|
||||
offset_top = 202.5
|
||||
offset_right = 315.0
|
||||
offset_bottom = 227.5
|
||||
theme_override_constants/separation = 4
|
||||
|
||||
[node name="ChatInput" type="LineEdit" parent="ChatPanel/InputContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
localize_numeral_system = false
|
||||
theme_override_font_sizes/font_size = 12
|
||||
theme_override_styles/normal = SubResource("StyleBoxEmpty_xo31h")
|
||||
theme_override_styles/read_only = SubResource("StyleBoxEmpty_xo31h")
|
||||
theme_override_styles/focus = SubResource("StyleBoxEmpty_1ahvy")
|
||||
Reference in New Issue
Block a user