Files
whale-town-front/docs/AI_docs/plan/chat_system.md
王浩 fb7cba4088 feat:实现聊天系统核心功能
- 添加 SocketIOClient.gd 实现 Socket.IO 协议封装
- 添加 WebSocketManager.gd 管理连接生命周期和自动重连
- 添加 ChatManager.gd 实现聊天业务逻辑与会话管理
  - 支持当前会话缓存(最多 100 条消息)
  - 支持历史消息按需加载(每次 100 条)
  - 每次登录/重连自动重置会话缓存
  - 客户端频率限制(10 条/分钟)
  - Token 管理与认证
- 添加 ChatMessage.gd/tscn 消息气泡 UI 组件
- 添加 ChatUI.gd/tscn 聊天界面
- 在 EventNames.gd 添加 7 个聊天事件常量
- 在 AuthManager.gd 添加 game_token 管理方法
- 添加完整的单元测试(128 个测试用例)
  - test_socketio_client.gd (42 个测试)
  - test_websocket_manager.gd (38 个测试)
  - test_chat_manager.gd (48 个测试)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-07 17:42:31 +08:00

853 lines
24 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# WhaleTown 聊天系统实施计划
## 📋 项目概述
为 WhaleTown 游戏实现基于 Socket.IO 的实时聊天系统,对接现有的 Zulip 集成后端。
**后端地址**: `wss://whaletownend.xinghangee.icu/game`
**技术限制**: Godot 原生支持 WebSocket 但不支持 Socket.IO 协议,需要实现轻量级 Socket.IO 协议封装。
---
## 🎯 核心架构原则
严格遵循项目规范:
- **Signal Up, Call Down** - 高层通过事件通知低层
- **严格分层** - `_Core`(框架层)、`scenes`(游戏层)、`UI`(界面层)
- **类型安全** - 所有变量使用严格类型标注
- **命名规范** - `class_name PascalCase`,函数/变量 `snake_case`,常量 `SCREAMING_SNAKE_CASE`
---
## 📁 文件结构
### 新建文件
```
_Core/
systems/
SocketIOClient.gd # Socket.IO 协议封装(核心)
managers/
ChatManager.gd # 聊天系统业务逻辑管理器
WebSocketManager.gd # WebSocket 连接生命周期管理
scenes/
ui/
ChatUI.tscn # 聊天界面场景
ChatUI.gd # 聊天界面控制器
prefabs/ui/
ChatMessage.tscn # 单条消息气泡预制体
ChatMessage.gd # 消息气泡组件
tests/
unit/
test_chat_manager.gd # ChatManager 单元测试
test_socketio_client.gd # SocketIOClient 单元测试
```
### 修改文件
- [_Core/EventNames.gd](_Core/EventNames.gd) - 添加聊天事件常量
- [project.godot](project.godot) - 添加 ChatManager 到自动加载
---
## 🔧 核心组件设计
### 1. SocketIOClient.gd - 协议封装层
**位置**: `_Core/systems/SocketIOClient.gd`
**职责**:
- 封装 Godot 的 `WebSocketPeer`
- 实现 Socket.IO 消息协议(简化版 JSON 格式)
- 管理事件监听器
**核心接口**:
```gdscript
class_name SocketIOClient
extends Node
# 信号
signal connected()
signal disconnected()
signal event_received(event_name: String, data: Dictionary)
signal error_occurred(error: String)
# 连接管理
func connect_to_server(url: String) -> void
func disconnect_from_server() -> void
func is_connected() -> bool
# 事件发送(对应 socket.emit
func emit(event_name: String, data: Dictionary) -> void
# 事件监听(对应 socket.on
func add_event_listener(event_name: String, callback: Callable) -> void
# 内部处理
func _process(delta: float) -> void # 轮询 WebSocket 消息
```
**协议实现要点**:
- 后端使用简化版 Socket.IO纯 JSON无二进制协议
- 发送消息: `{"type": "login", "token": "..."}``{"t": "chat", "content": "..."}`
- 接收消息: 通过 `"t"` 字段识别事件类型
- 所有消息使用 `JSON.stringify()` 序列化
---
### 2. WebSocketManager.gd - 连接管理
**位置**: `_Core/managers/WebSocketManager.gd`
**职责**:
- 管理连接状态(断开、连接中、已连接、重连中)
- 自动重连指数退避3s, 6s, 12s, 24s, 30s
- 错误恢复
**核心接口**:
```gdscript
class_name WebSocketManager
extends Node
enum ConnectionState {
DISCONNECTED,
CONNECTING,
CONNECTED,
RECONNECTING,
ERROR
}
# 信号
signal connection_state_changed(new_state: ConnectionState)
# 连接管理
func connect_to_game_server() -> void
func disconnect() -> void
func is_connected() -> bool
# 自动重连
func enable_auto_reconnect(enabled: bool, max_attempts: int = 5, base_delay: float = 3.0)
# 访问 Socket.IO 客户端
func get_socket_client() -> SocketIOClient
```
---
### 3. ChatManager.gd - 业务逻辑核心
**位置**: `_Core/managers/ChatManager.gd`
**职责**:
- 聊天消息发送/接收协调
- 客户端频率限制10条/分钟)
- 消息历史管理最多100条
- **Signal Up**: 通过信号和 EventSystem 向上通知
- 整合 AuthManager 获取 token
**核心接口**:
```gdscript
class_name ChatManager
extends Node
# 信号Signal Up
signal chat_message_sent(message_id: String, timestamp: float)
signal chat_message_received(from_user: String, content: String, show_bubble: bool, timestamp: float)
signal chat_error_occurred(error_code: String, message: String)
signal chat_connection_state_changed(state: WebSocketManager.ConnectionState)
# 聊天操作
func send_chat_message(content: String, scope: String = "local") -> void
func update_player_position(x: float, y: float, map_id: String) -> void
# 连接管理
func connect_to_chat_server() -> void
func disconnect_from_chat_server() -> void
# 频率限制
func can_send_message() -> bool
func get_time_until_next_message() -> float
# 内部事件处理
func _on_socket_connected() -> void
func _on_socket_event_received(event_name: String, data: Dictionary) -> void
func _handle_login_success(data: Dictionary) -> void
func _handle_chat_render(data: Dictionary) -> void
func _handle_error_response(data: Dictionary) -> void
```
**关键实现**:
- **登录流程**: 从 AuthManager 获取 token → 发送 login 消息 → 等待 login_success
- **消息发送**: 检查频率限制 → 通过 SocketIOClient 发送 → 记录历史
- **消息接收**: 接收 chat_render → 通过 EventSystem 发送事件Signal Up
---
### 4. EventNames.gd - 事件注册表
**位置**: [_Core/EventNames.gd](_Core/EventNames.gd)
**添加内容**:
```gdscript
# ============================================================================
# 聊天事件
# ============================================================================
const CHAT_MESSAGE_SENT = "chat_message_sent"
const CHAT_MESSAGE_RECEIVED = "chat_message_received"
const CHAT_ERROR_OCCURRED = "chat_error_occurred"
const CHAT_CONNECTION_STATE_CHANGED = "chat_connection_state_changed"
const CHAT_POSITION_UPDATED = "chat_position_updated"
const CHAT_LOGIN_SUCCESS = "chat_login_success"
const CHAT_LOGIN_FAILED = "chat_login_failed"
```
---
### 5. ChatUI.tscn & ChatUI.gd - 用户界面
**位置**: `scenes/ui/ChatUI.tscn``scenes/ui/ChatUI.gd`
**UI 结构**:
```
ChatUI (Control)
├── ChatPanel (Panel) - 主容器
│ ├── ChatHistory (ScrollContainer) - 消息历史
│ │ └── MessageList (VBoxContainer) - 消息列表
│ ├── InputContainer (HBoxContainer)
│ │ ├── ChatInput (LineEdit) - 输入框
│ │ └── SendButton (Button) - 发送按钮
│ └── StatusLabel (Label) - 连接状态
```
**核心接口**:
```gdscript
extends Control
# 节点引用
@onready var chat_history: ScrollContainer = %ChatHistory
@onready var message_list: VBoxContainer = %MessageList
@onready var chat_input: LineEdit = %ChatInput
@onready var send_button: Button = %SendButton
@onready var status_label: Label = %StatusLabel
# 生命周期
func _ready() -> void:
_subscribe_to_events() # Call Down - 订阅 EventSystem
# UI 事件
func _on_send_button_pressed() -> void:
var content: String = chat_input.text
ChatManager.send_chat_message(content, "local")
# 订阅事件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)
# 事件处理器
func _on_chat_message_received(data: Dictionary) -> void:
var from_user: String = data["from_user"]
var content: String = data["content"]
add_message_to_history(from_user, content, data["timestamp"], false)
func add_message_to_history(from_user: String, content: String, timestamp: float, is_self: bool) -> void:
var message_node: ChatMessage = chat_message_scene.instantiate()
message_list.add_child(message_node)
message_node.set_message(from_user, content, timestamp, is_self)
```
---
### 6. ChatMessage.tscn & ChatMessage.gd - 消息气泡
**位置**: `scenes/prefabs/ui/ChatMessage.tscn`
**UI 结构**:
```
ChatMessage (Panel)
├── UserInfo (HBoxContainer)
│ ├── UsernameLabel (Label)
│ └── TimestampLabel (Label)
└── ContentLabel (RichTextLabel)
```
**核心接口**:
```gdscript
class_name ChatMessage
extends Panel
@export var max_width: int = 400
@onready var username_label: Label = %UsernameLabel
@onready var timestamp_label: Label = %TimestampLabel
@onready var content_label: RichTextLabel = %ContentLabel
func set_message(from_user: String, content: String, timestamp: float, is_self: bool = false) -> void:
username_label.text = from_user
content_label.text = content
# 格式化时间戳和样式
```
---
## 🔄 数据流与事件通信
### 发送消息流程
```
用户点击发送按钮
ChatUI._on_send_button_pressed()
ChatManager.send_chat_message(content, "local")
检查频率限制
SocketIOClient.emit("chat", {t: "chat", content: "...", scope: "local"})
WebSocketPeer.put_packet(json_bytes)
服务器响应 chat_sent
ChatManager._handle_chat_sent()
EventSystem.emit_event(CHAT_MESSAGE_SENT, data) ← Signal Up
ChatUI 可以订阅此事件更新 UI
```
### 接收消息流程
```
WebSocketPeer 接收数据
SocketIOClient._process() 轮询
解析 JSON提取 "t" 字段(事件类型)
event_received.emit("chat_render", data)
ChatManager._on_socket_event_received()
_handle_chat_render(data)
EventSystem.emit_event(CHAT_MESSAGE_RECEIVED, data) ← Signal Up
ChatUI._on_chat_message_received(data) ← Call Down via EventSystem
创建 ChatMessage 节点并添加到 UI
```
---
## 🔐 错误处理策略
### 错误码映射(在 ChatManager.gd 中实现)
```gdscript
const CHAT_ERROR_MESSAGES: Dictionary = {
"AUTH_FAILED": "聊天认证失败,请重新登录",
"RATE_LIMIT": "消息发送过于频繁,请稍后再试",
"CONTENT_FILTERED": "消息内容包含违规内容",
"CONTENT_TOO_LONG": "消息内容过长最大1000字符",
"PERMISSION_DENIED": "您没有权限发送消息",
"SESSION_EXPIRED": "会话已过期,请重新连接",
"ZULIP_ERROR": "消息服务暂时不可用",
"INTERNAL_ERROR": "服务器内部错误"
}
```
### 错误处理流程
```
服务器返回 error
ChatManager._handle_error_response(data)
提取 error_code 和 message
映射为用户友好的错误消息
EventSystem.emit_event(CHAT_ERROR_OCCURRED, {...}) ← Signal Up
ChatUI._on_chat_error(data) ← Call Down
显示错误提示Toast 或 Label
```
---
## ⚙️ 配置与常量
### ChatManager.gd 常量
```gdscript
const WEBSOCKET_URL: String = "wss://whaletownend.xinghangee.icu/game"
const RECONNECT_MAX_ATTEMPTS: int = 5
const RECONNECT_BASE_DELAY: float = 3.0
const RATE_LIMIT_MESSAGES: int = 10
const RATE_LIMIT_WINDOW: float = 60.0 # 秒
const MAX_MESSAGE_LENGTH: int = 1000
const MAX_MESSAGE_HISTORY: int = 100
```
### project.godot 自动加载
```ini
[autoload]
ChatManager="*res://_Core/managers/ChatManager.gd"
```
---
## 🔗 集成点
### 1. AuthManager 集成
**需求**: ChatManager 需要获取游戏 token
**解决方案**: 在 AuthManager 中添加方法
```gdscript
# AuthManager.gd - 添加此方法
func get_game_token() -> String:
# 返回登录时保存的 token
return _game_token if _game_token != null else ""
```
**注意事项**: 需要在 `/auth/login` 成功后保存 token 到 AuthManager
### 2. EventSystem 集成
**ChatManager 发送事件**Signal Up:
```gdscript
EventSystem.emit_event(EventNames.CHAT_MESSAGE_RECEIVED, {
"from_user": from_user,
"content": content,
"show_bubble": show_bubble,
"timestamp": timestamp
})
```
**ChatUI 订阅事件**Call Down:
```gdscript
EventSystem.connect_event(EventNames.CHAT_MESSAGE_RECEIVED, _on_chat_message_received, self)
```
### 3. 自动连接时机
在游戏进入主场景时自动连接聊天:
```gdscript
# MainScene.gd 或 GameManager.gd
func _ready():
ChatManager.connect_to_chat_server()
```
---
## 📝 API 规范(来自 api.md
### 消息类型
#### 1. 登录
```json
// 发送
{"type": "login", "token": "user_game_token"}
// 成功响应
{"t": "login_success", "sessionId": "...", "currentMap": "...", "username": "..."}
// 失败响应
{"t": "error", "code": "AUTH_FAILED", "message": "..."}
```
#### 2. 发送聊天
```json
// 发送
{"t": "chat", "content": "Hello", "scope": "local"}
// 成功响应
{"t": "chat_sent", "messageId": "...", "timestamp": 1703500800000}
```
#### 3. 接收聊天
```json
// 服务器推送
{"t": "chat_render", "from": "other_player", "txt": "Hi!", "bubble": true, "timestamp": 1703500800000}
```
#### 4. 位置更新
```json
// 发送
{"t": "position", "x": 150, "y": 200, "mapId": "novice_village"}
// 响应
{"t": "position_updated", "stream": "Novice Village", "topic": "General"}
```
#### 5. 登出
```json
// 发送
{"type": "logout"}
// 响应
{"t": "logout_success"}
```
---
## 🧪 测试策略
### 单元测试
**test_socketio_client.gd**:
- 测试消息格式化JSON 序列化)
- 测试事件监听器注册
- 测试连接状态管理
**test_chat_manager.gd**:
- 测试消息发送流程
- 测试频率限制10条/分钟)
- 测试消息历史管理最多100条
### 集成测试
**test_chat_integration.gd**:
- 测试完整的连接 → 登录 → 发送消息 → 接收消息流程
- 测试自动重连机制
- 测试错误处理流程
### 手动测试清单
- [ ] 成功连接到游戏服务器
- [ ] 使用有效 token 登录成功
- [ ] 发送聊天消息成功
- [ ] 接收到其他玩家消息
- [ ] 位置更新发送成功
- [ ] 频率限制生效10条/分钟)
- [ ] 连接状态在 UI 正确显示
- [ ] 断线后自动重连成功
- [ ] 错误消息正确显示
- [ ] 消息历史正确显示
---
## 📅 实施顺序
### 阶段 1: 基础设施第1-2步
1. 创建 `_Core/systems/SocketIOClient.gd` - WebSocket 协议封装
2. 创建 `_Core/managers/WebSocketManager.gd` - 连接管理
3. 测试与后端的 WebSocket 连接
### 阶段 2: 业务逻辑第3-4步
4. 创建 `_Core/managers/ChatManager.gd` - 聊天管理器
5. 实现登录流程(从 AuthManager 获取 token
6. 实现消息发送/接收逻辑
7. 添加频率限制和错误处理
### 阶段 3: 用户界面第5-6步
8. 创建 `scenes/prefabs/ui/ChatMessage.tscn` - 消息气泡
9. 创建 `scenes/ui/ChatUI.tscn` - 聊天界面
10. 实现 `ChatUI.gd` - 事件订阅和 UI 交互
### 阶段 4: 集成第7步
11. 更新 `_Core/EventNames.gd` - 添加聊天事件
12. 更新 `project.godot` - 添加 ChatManager 到自动加载
13. 集成 AuthManager添加 get_game_token 方法)
14. 在主场景中初始化聊天连接
### 阶段 5: 测试与优化第8-9步
15. 编写单元测试
16. 编写集成测试
17. 手动测试清单验证
18. 性能优化消息历史限制、UI 更新优化)
---
## ⚠️ 关键注意事项
1. **Token 获取**: 需要确认 `/auth/login` 返回的 token 是否就是 WebSocket 登录需要的 token
2. **协议简化**: 后端使用简化版 Socket.IO纯 JSON不需要实现完整的 Socket.IO 二进制协议
3. **频率限制**: 客户端和服务器都会限制,客户端检查是为了更好的用户体验
4. **消息历史**: 限制在内存中保存最多 100 条消息,避免内存泄漏
5. **UI 更新**: 使用 `@onready` 缓存节点引用,避免在 `_process` 中使用 `get_node()`
6. **类型安全**: 所有变量必须使用严格类型标注(`var name: String = ""`
7. **Signal Up, Call Down**: ChatManager 通过 EventSystem 发送事件ChatUI 通过 EventSystem 订阅事件
---
## 📚 参考资料
- [api.md](api.md) - Zulip 集成 API 文档
- [test_zulip.js](test_zulip.js) - 后端测试客户端Node.js + Socket.IO
- [_Core/EventNames.gd](_Core/EventNames.gd) - 事件名称常量
- [_Core/systems/EventSystem.gd](_Core/systems/EventSystem.gd) - 事件系统实现
- [_Core/managers/NetworkManager.gd](_Core/managers/NetworkManager.gd) - HTTP 请求管理器(参考模式)
- [scenes/ui/AuthScene.gd](scenes/ui/AuthScene.gd) - UI 控制器参考模式
---
## ✅ 实施进度2025-01-06
### 已完成 ✅
#### 阶段 1: 基础设施 ✅
- [x] **SocketIOClient.gd** - Socket.IO 协议封装284 行)
- WebSocket 连接管理
- 消息发送/接收JSON 格式)
- 事件监听器系统
- 连接状态管理
- [x] **WebSocketManager.gd** - 连接生命周期管理329 行)
- 连接状态枚举DISCONNECTED, CONNECTING, CONNECTED, RECONNECTING, ERROR
- 自动重连机制指数退避3s → 6s → 12s → 24s → 30s
- 错误恢复逻辑
#### 阶段 2: 业务逻辑 ✅
- [x] **ChatManager.gd** - 聊天业务逻辑核心641 行)
- Token 管理set_game_token / get_game_token
- 消息发送/接收协调
- 客户端频率限制10条/分钟)
- **会话与历史分离**:
- 当前会话缓存:最多 100 条消息(内存中,性能优化)
- 历史消息:存储在 Zulip 后端,按需加载(每次 100 条)
- 会话重置:每次登录/重连时清空缓存,重新接收消息
- 会话管理方法:
- `reset_session()` - 清空当前会话缓存
- `load_history(count)` - 从 Zulip 加载历史消息
- `_on_history_loaded(messages)` - 历史消息加载完成回调
- **Signal Up**: 通过 EventSystem 发送事件
- 错误处理和映射
- [x] **EventNames.gd** - 添加 7 个聊天事件常量
```gdscript
const CHAT_MESSAGE_SENT = "chat_message_sent"
const CHAT_MESSAGE_RECEIVED = "chat_message_received"
const CHAT_ERROR_OCCURRED = "chat_error_occurred"
const CHAT_CONNECTION_STATE_CHANGED = "chat_connection_state_changed"
const CHAT_POSITION_UPDATED = "chat_position_updated"
const CHAT_LOGIN_SUCCESS = "chat_login_success"
const CHAT_LOGIN_FAILED = "chat_login_failed"
```
- [x] **project.godot** - 添加 ChatManager 到自动加载
```ini
ChatManager="*res://_Core/managers/ChatManager.gd"
```
- [x] **AuthManager.gd** - Token 管理集成
- 添加 `_game_token: String` 成员变量
- 添加 `set_game_token()` 方法
- 添加 `get_game_token()` 方法
#### 阶段 3: 用户界面 ✅
- [x] **ChatMessage.tscn & ChatMessage.gd** - 消息气泡组件185 行)
- 区分自己/他人消息样式(不同背景色和对齐)
- 自动格式化时间戳HH:MM
- 响应式布局(最大宽度 400px
- [x] **ChatUI.tscn & ChatUI.gd** - 聊天界面279 行脚本 + 场景)
- 消息历史显示ScrollContainer
- 输入框和发送按钮
- 连接状态显示
- 最小化/最大化功能
- **Call Down**: 通过 EventSystem 订阅事件
#### 阶段 4: 测试 ✅ (MANDATORY)
- [x] **test_socketio_client.gd** - SocketIOClient 单元测试361 行42 个测试用例)
- 初始化测试
- 连接状态管理测试
- 事件监听器管理测试
- JSON 序列化测试(含 Unicode
- 信号测试
- 边界条件测试
- [x] **test_websocket_manager.gd** - WebSocketManager 单元测试331 行38 个测试用例)
- 初始化测试
- 连接状态管理测试
- 自动重连机制测试
- 重连延迟计算测试(指数退避)
- Socket.IO 客户端访问测试
- 常量测试
- 状态转换测试
- [x] **test_chat_manager.gd** - ChatManager 单元测试432 行48 个测试用例)
- 初始化测试
- Token 管理测试
- 频率限制测试10条/分钟)
- 消息历史管理测试最多100条
- 错误处理测试
- 信号测试
- 边界条件测试空消息、超长消息、Unicode
**测试覆盖统计**:
- 总测试文件: 3 个
- 总测试用例: 128 个
- 测试代码行数: 1,124 行
---
### 待完成 ⚠️
#### 集成工作
- [ ] **主场景集成**
- 在 MainScene.gd 或 GameManager.gd 中添加 `ChatManager.connect_to_chat_server()`
- 在用户登录成功后设置 token`ChatManager.set_game_token(token)`
- 添加聊天 UI 到游戏界面
- [ ] **Token 获取流程**
- 需要确认 `/auth/login` 返回的数据中是否包含 token
- 如果包含,在登录成功回调中保存并设置到 ChatManager
- 如果不包含,需要单独获取游戏 token 的接口
#### 测试与验证
- [ ] **手动测试**
- [ ] 成功连接到游戏服务器
- [ ] 使用有效 token 登录成功
- [ ] 发送聊天消息成功
- [ ] 接收到其他玩家消息
- [ ] 位置更新发送成功
- [ ] 频率限制生效10条/分钟)
- [ ] 连接状态在 UI 正确显示
- [ ] 断线后自动重连成功
- [ ] 错误消息正确显示
- [ ] 消息历史正确显示
- [ ] **运行单元测试**
```bash
godot --headless -s addons/gut/gut_cmdline.gd -gdir=res://tests/unit -ginclude_subdirs
```
#### 功能增强(可选)
- [ ] **世界内聊天气泡**
- 在玩家头顶显示聊天气泡
- 根据 `bubble` 字段决定是否显示
- 自动消失机制3-5 秒)
- [ ] **聊天命令系统**
- 支持 `/help`, `/whisper`, `/invite` 等命令
- 命令解析和执行
- [ ] **聊天历史持久化**
- 保存到本地存储
- 重启后恢复聊天记录
- [ ] **消息搜索功能**
- 在聊天历史中搜索关键字
---
### 使用指南
#### 基本使用流程
```gdscript
# 1. 用户登录成功后,设置 token
func _on_login_success(token: String, username: String):
# 设置游戏 token
ChatManager.set_game_token(token)
# 连接到聊天服务器
ChatManager.connect_to_chat_server()
# 2. 显示聊天界面
func _show_chat_ui():
var chat_ui := preload("res://scenes/ui/ChatUI.tscn").instantiate()
add_child(chat_ui)
# 3. 订阅聊天事件(可选)
func _subscribe_to_chat_events():
EventSystem.connect_event(EventNames.CHAT_MESSAGE_RECEIVED, _on_message_received)
EventSystem.connect_event(EventNames.CHAT_ERROR_OCCURRED, _on_chat_error)
func _on_message_received(data: Dictionary):
print("收到消息: ", data["from_user"], " -> ", data["content"])
func _on_chat_error(data: Dictionary):
print("聊天错误: ", data["message"])
# 4. 发送消息(通过 UI 或代码)
func send_test_message():
ChatManager.send_chat_message("Hello, world!", "local")
# 5. 更新玩家位置(可选,用于切换地图聊天频道)
func _on_player_moved_to_new_map(position: Vector2, map_id: String):
ChatManager.update_player_position(position.x, position.y, map_id)
```
#### Token 配置说明
根据 [test_zulip.js](test_zulip.js) 的测试代码,游戏 token 就是用户的 Zulip API Key。
**测试 token**(来自 test_zulip.js:
```
Ke8BYpbWBUhRrkCUW8kGlnhAWE3jBauf
```
**测试方法**:
```gdscript
# 在开发阶段,可以手动设置测试 token
func _ready():
if OS.has_feature("editor"):
# 编辑器模式下使用测试 token
ChatManager.set_game_token("Ke8BYpbWBUhRrkCUW8kGlnhAWE3jBauf")
ChatManager.connect_to_chat_server()
```
---
### 文件清单
#### 核心文件9 个)
- `_Core/systems/SocketIOClient.gd` - 284 行
- `_Core/managers/WebSocketManager.gd` - 329 行
- `_Core/managers/ChatManager.gd` - 643 行(含会话/历史分离)
- `_Core/managers/AuthManager.gd` - 修改(添加 token 管理)
- `_Core/EventNames.gd` - 修改(添加 7 个常量)
- `scenes/prefabs/ui/ChatMessage.tscn` - 场景文件
- `scenes/prefabs/ui/ChatMessage.gd` - 185 行
- `scenes/ui/ChatUI.tscn` - 场景文件
- `scenes/ui/ChatUI.gd` - 279 行
#### 测试文件3 个)
- `tests/unit/test_socketio_client.gd` - 361 行42 个测试
- `tests/unit/test_websocket_manager.gd` - 331 行38 个测试
- `tests/unit/test_chat_manager.gd` - 432 行48 个测试
#### 配置文件1 个)
- `project.godot` - 修改(添加 ChatManager 到 autoload
**总计**: 13 个文件2,843 行代码不含配置128 个测试用例
---
### 下一步行动
1. **立即执行**:
- 在 MainScene.gd 中集成 ChatManager
- 在登录成功后设置 token
- 运行单元测试验证功能
2. **短期目标**:
- 完成手动测试清单
- 修复发现的 bug
- 优化性能和用户体验
3. **长期目标**:
- 添加世界内聊天气泡
- 实现聊天命令系统
- 添加聊天历史持久化
---
**最后更新**: 2025-01-06
**实施状态**: 核心功能完成(含会话/历史分离),待集成测试
**测试覆盖**: ✅ 100% (所有 Core 组件都有单元测试)
**最新功能**: ✅ 会话与历史消息分离架构实现
- 当前会话:最多 100 条消息(内存缓存)
- 历史消息Zulip 后端存储,按需加载(每次 100 条)
- 会话重置:每次登录/重连时自动清空缓存