# AI Town Game 开发者技术文档 ## 📋 目录 1. [项目概述](#项目概述) 2. [技术架构](#技术架构) 3. [开发环境配置](#开发环境配置) 4. [核心系统详解](#核心系统详解) 5. [API 参考](#api-参考) 6. [数据库设计](#数据库设计) 7. [网络协议](#网络协议) 8. [测试框架](#测试框架) 9. [部署指南](#部署指南) 10. [扩展开发](#扩展开发) 11. [性能优化](#性能优化) 12. [故障排除](#故障排除) ## 项目概述 ### 技术栈 **客户端**: - **游戏引擎**: Godot 4.5.1 - **编程语言**: GDScript - **导出平台**: HTML5 (Web), Windows, macOS, Linux - **UI 框架**: Godot 内置 UI 系统 **服务器**: - **运行时**: Node.js 24.7.0+ - **编程语言**: TypeScript - **网络协议**: WebSocket (ws 库) - **数据存储**: JSON 文件系统 - **包管理**: Yarn 1.22.22+ **开发工具**: - **版本控制**: Git - **代码规范**: ESLint (TypeScript), GDScript 内置检查 - **测试框架**: 自定义 GDScript 测试框架 - **构建工具**: TypeScript Compiler, Godot Export ### 项目结构 ``` ai_community/ ├── project.godot # Godot 项目配置 ├── scenes/ # 游戏场景文件 │ ├── Main.tscn # 主场景 │ ├── DatawhaleOffice.tscn # Datawhale 办公室场景 │ ├── PlayerCharacter.tscn # 玩家角色场景 │ ├── RemoteCharacter.tscn # 远程角色场景 │ └── TestGameplay.tscn # 测试场景 ├── scripts/ # GDScript 脚本 │ ├── Main.gd # 主脚本 │ ├── NetworkManager.gd # 网络管理器 │ ├── GameStateManager.gd # 游戏状态管理器 │ ├── CharacterController.gd # 角色控制器 │ ├── DialogueSystem.gd # 对话系统 │ ├── InputHandler.gd # 输入处理器 │ ├── WorldManager.gd # 世界管理器 │ └── Utils.gd # 工具函数 ├── assets/ # 游戏资源 │ ├── sprites/ # 精灵图像 │ ├── tilesets/ # 瓦片集 │ └── ui/ # UI 资源 ├── tests/ # 测试文件 │ ├── RunAllTests.tscn # 测试运行器 │ ├── test_*.gd # 单元测试 │ └── test_property_*.gd # 属性测试 ├── server/ # WebSocket 服务器 │ ├── src/ # TypeScript 源码 │ │ ├── server.ts # 主服务器文件 │ │ ├── api/ # API 模块 │ │ ├── backup/ # 备份管理 │ │ ├── logging/ # 日志管理 │ │ ├── maintenance/ # 维护管理 │ │ └── monitoring/ # 监控模块 │ ├── data/ # 数据存储 │ │ ├── characters.json # 角色数据 │ │ ├── logs/ # 日志文件 │ │ └── backups/ # 备份文件 │ ├── dist/ # 编译输出 │ ├── admin/ # Web 管理界面 │ ├── package.json # 依赖配置 │ └── tsconfig.json # TypeScript 配置 └── .kiro/specs/ # 项目规范文档 └── godot-ai-town-game/ ├── requirements.md # 需求文档 ├── design.md # 设计文档 └── tasks.md # 任务列表 ``` ## 技术架构 ### 整体架构图 ```mermaid graph TB subgraph "客户端 (Godot)" A[Main Scene] --> B[NetworkManager] A --> C[GameStateManager] A --> D[UILayer] A --> E[GameWorld] B --> F[WebSocket Client] C --> G[State Machine] D --> H[UI Components] E --> I[Characters] E --> J[TileMap] end subgraph "网络层" F <--> K[WebSocket Connection] end subgraph "服务器 (Node.js)" K <--> L[WebSocket Server] L --> M[Connection Manager] L --> N[Message Router] L --> O[World State] L --> P[Data Persistence] M --> Q[Authentication] N --> R[Message Handlers] O --> S[Character Manager] P --> T[JSON Storage] end subgraph "管理系统" L --> U[Health Monitor] L --> V[Backup Manager] L --> W[Log Manager] U --> X[Admin API] V --> Y[Auto Backup] W --> Z[Log Analysis] end ``` ### 客户端架构 #### 场景树结构 ``` Main (Node) ├── NetworkManager (Node) ├── GameStateManager (Node) ├── InputHandler (Node) ├── UILayer (CanvasLayer) │ ├── LoginScreen (Control) │ ├── CharacterCreation (Control) │ ├── HUD (Control) │ ├── DialogueBox (Control) │ ├── ErrorNotification (Control) │ └── LoadingIndicator (Control) └── GameWorld (Node2D) ├── DatawhaleOffice (TileMap) ├── Characters (Node2D) │ ├── PlayerCharacter (CharacterBody2D) │ └── RemoteCharacter (CharacterBody2D) [多个] └── Camera2D ``` #### 核心组件职责 **NetworkManager**: - 管理 WebSocket 连接 - 处理消息序列化/反序列化 - 实现断线重连机制 - 维护心跳检测 **GameStateManager**: - 管理游戏状态机 - 处理数据持久化 - 协调状态转换 - 发射状态变化信号 **InputHandler**: - 处理键盘/触摸输入 - 设备类型检测 - 虚拟控件管理 - 输入事件分发 **WorldManager**: - 管理游戏世界中的所有角色 - 处理角色生成/销毁 - 维护角色状态同步 - 提供空间查询功能 ### 服务器架构 #### 模块设计 **ConnectionManager**: ```typescript class ConnectionManager { private clients: Map private heartbeats: Map addClient(clientId: string, ws: WebSocket): void removeClient(clientId: string): void broadcastMessage(message: any, excludeClient?: string): void sendToClient(clientId: string, message: any): void checkHeartbeats(): void } ``` **MessageRouter**: ```typescript class MessageRouter { private handlers: Map registerHandler(type: string, handler: MessageHandler): void routeMessage(clientId: string, message: any): void createResponse(type: string, data: any): any } ``` **WorldState**: ```typescript class WorldState { private characters: Map private scenes: Map addCharacter(character: Character): void updateCharacter(characterId: string, updates: Partial): void removeCharacter(characterId: string): void getWorldSnapshot(): WorldSnapshot } ``` ## 开发环境配置 ### 环境要求 **开发工具**: - Godot 4.5.1+ (游戏引擎) - Node.js 24.7.0+ (服务器运行时) - Yarn 1.22.22+ (包管理器) - Git (版本控制) - VS Code (推荐编辑器) **系统要求**: - Windows 10+ / macOS 10.14+ / Ubuntu 18.04+ - 8GB RAM (推荐) - 2GB 可用磁盘空间 ### 快速配置 1. **克隆项目**: ```bash git clone cd ai_community ``` 2. **配置 Godot**: ```bash # 下载并安装 Godot 4.5.1 # 导入项目: 选择 project.godot 文件 ``` 3. **配置服务器**: ```bash cd server yarn install yarn build ``` 4. **启动开发环境**: ```bash # 终端 1: 启动服务器 cd server yarn dev # 终端 2: 启动 Godot 编辑器 # 在 Godot 中按 F5 运行项目 ``` ### VS Code 配置 推荐的 VS Code 扩展: - **godot-tools**: GDScript 语法支持 - **TypeScript Importer**: TypeScript 开发支持 - **GitLens**: Git 增强功能 - **Prettier**: 代码格式化 `.vscode/settings.json`: ```json { "godot_tools.editor_path": "/path/to/godot", "typescript.preferences.importModuleSpecifier": "relative", "editor.formatOnSave": true, "editor.codeActionsOnSave": { "source.organizeImports": true } } ``` ## 核心系统详解 ### 网络系统 #### WebSocket 连接管理 **客户端连接流程**: ```gdscript # NetworkManager.gd func connect_to_server(url: String) -> void: _websocket = WebSocketPeer.new() var error = _websocket.connect_to_url(url) if error != OK: emit_signal("connection_error", "Failed to connect: " + str(error)) return _connection_state = ConnectionState.CONNECTING _connection_timer = 0.0 emit_signal("connection_attempt_started") func _process(delta: float) -> void: if _websocket: _websocket.poll() var state = _websocket.get_ready_state() match state: WebSocketPeer.STATE_OPEN: _handle_connected() WebSocketPeer.STATE_CLOSED: _handle_disconnected() _handle_incoming_messages() _update_heartbeat(delta) ``` **服务器连接处理**: ```typescript // server.ts wss.on('connection', (ws: WebSocket, req: IncomingMessage) => { const clientId = generateClientId(); const clientInfo = { id: clientId, ws: ws, lastHeartbeat: Date.now(), authenticated: false, characterId: null }; clients.set(clientId, clientInfo); console.log(`✅ Client connected: ${clientId}`); ws.on('message', (data: Buffer) => { handleMessage(clientId, data); }); ws.on('close', () => { handleDisconnection(clientId); }); }); ``` #### 消息协议实现 **消息序列化**: ```gdscript # MessageProtocol.gd static func create_message(type: String, data: Dictionary = {}) -> Dictionary: return { "type": type, "data": data, "timestamp": Time.get_unix_time_from_system() } static func serialize_message(message: Dictionary) -> String: return JSON.stringify(message) static func deserialize_message(json_string: String) -> Dictionary: var json = JSON.new() var parse_result = json.parse(json_string) if parse_result != OK: ErrorHandler.log_network_error("Failed to parse message", {"json": json_string}) return {} return json.data ``` **消息路由**: ```typescript // MessageRouter.ts class MessageRouter { private handlers = new Map(); constructor() { this.registerHandler('auth_request', new AuthHandler()); this.registerHandler('character_create', new CharacterCreateHandler()); this.registerHandler('character_move', new CharacterMoveHandler()); this.registerHandler('dialogue_send', new DialogueHandler()); this.registerHandler('ping', new PingHandler()); } routeMessage(clientId: string, message: any): void { const handler = this.handlers.get(message.type); if (handler) { handler.handle(clientId, message.data); } else { console.warn(`Unknown message type: ${message.type}`); } } } ``` ### 状态管理系统 #### 游戏状态机 ```gdscript # GameStateManager.gd enum GameState { LOGIN, CHARACTER_CREATION, IN_GAME, DISCONNECTED } var current_state: GameState = GameState.LOGIN var player_data: Dictionary = {} func change_state(new_state: GameState) -> void: var old_state = current_state current_state = new_state print("State changed: ", GameState.keys()[old_state], " -> ", GameState.keys()[new_state]) match new_state: GameState.LOGIN: _show_login_screen() GameState.CHARACTER_CREATION: _show_character_creation() GameState.IN_GAME: _enter_game_world() GameState.DISCONNECTED: _show_disconnected_screen() emit_signal("state_changed", old_state, new_state) ``` #### 数据持久化 **客户端数据保存**: ```gdscript # GameStateManager.gd func save_player_data() -> void: var save_data = { "player_id": player_data.get("id", ""), "character_name": player_data.get("character_name", ""), "last_position": player_data.get("position", {"x": 1000, "y": 750}), "settings": player_data.get("settings", {}), "timestamp": Time.get_unix_time_from_system() } var file = FileAccess.open("user://player_data.json", FileAccess.WRITE) if file: file.store_string(JSON.stringify(save_data)) file.close() print("Player data saved successfully") else: ErrorHandler.log_game_error("Failed to save player data") ``` **服务器数据持久化**: ```typescript // DataPersistence.ts class DataPersistence { private dataPath = './data/characters.json'; private backupInterval = 5 * 60 * 1000; // 5 minutes async saveCharacters(characters: Character[]): Promise { try { const data = JSON.stringify(characters, null, 2); await fs.writeFile(this.dataPath, data, 'utf8'); console.log('💾 Characters data saved'); } catch (error) { console.error('Failed to save characters:', error); } } async loadCharacters(): Promise { try { const data = await fs.readFile(this.dataPath, 'utf8'); return JSON.parse(data); } catch (error) { console.log('📂 No existing character data found, starting fresh'); return []; } } startAutoSave(): void { setInterval(() => { this.saveCharacters(Array.from(worldState.characters.values())); }, this.backupInterval); } } ``` ### 角色系统 #### 角色控制器 ```gdscript # CharacterController.gd extends CharacterBody2D class_name CharacterController @export var character_id: String = "" @export var character_name: String = "" @export var is_online: bool = false @export var move_speed: float = 200.0 var target_position: Vector2 var is_moving: bool = false signal position_updated(new_position: Vector2) signal animation_changed(animation_name: String) func _ready(): target_position = global_position _setup_animation() _setup_collision() func move_to(direction: Vector2) -> void: if direction.length() > 0: velocity = direction.normalized() * move_speed is_moving = true _play_animation("walk") else: velocity = Vector2.ZERO is_moving = false _play_animation("idle") move_and_slide() if global_position != target_position: target_position = global_position emit_signal("position_updated", global_position) func set_position_smooth(new_position: Vector2, duration: float = 0.2) -> void: var tween = create_tween() tween.tween_property(self, "global_position", new_position, duration) tween.tween_callback(func(): target_position = new_position) ``` #### 角色状态同步 **客户端同步**: ```gdscript # WorldManager.gd func update_character_state(character_id: String, state_data: Dictionary) -> void: if not characters.has(character_id): ErrorHandler.log_game_error("Character not found for update", {"id": character_id}) return var character = characters[character_id] # 更新位置 if state_data.has("position"): var pos = state_data["position"] character.set_position_smooth(Vector2(pos["x"], pos["y"])) # 更新在线状态 if state_data.has("isOnline"): character.set_online_status(state_data["isOnline"]) # 更新名称 if state_data.has("name"): character.character_name = state_data["name"] character.update_name_label() ``` **服务器状态广播**: ```typescript // CharacterManager.ts updateCharacterPosition(characterId: string, position: Position): void { const character = this.characters.get(characterId); if (!character) return; character.position = position; character.lastSeen = Date.now(); // 广播位置更新给所有客户端 const message = { type: 'character_move', data: { characterId: characterId, position: position }, timestamp: Date.now() }; connectionManager.broadcastMessage(message); // 触发自动保存 this.scheduleAutoSave(); } ``` ### 对话系统 #### 对话管理 ```gdscript # DialogueSystem.gd extends Node class_name DialogueSystem var active_dialogues: Dictionary = {} var dialogue_history: Array = [] signal dialogue_started(character_id: String) signal dialogue_ended() signal message_received(sender: String, message: String) func start_dialogue(target_character_id: String) -> void: if active_dialogues.has(target_character_id): print("Dialogue already active with character: ", target_character_id) return var dialogue_data = { "target_id": target_character_id, "start_time": Time.get_unix_time_from_system(), "messages": [] } active_dialogues[target_character_id] = dialogue_data emit_signal("dialogue_started", target_character_id) # 显示对话界面 var dialogue_box = get_node("../UILayer/DialogueBox") dialogue_box.show_dialogue(target_character_id) func send_message(target_character_id: String, message: String) -> void: if not active_dialogues.has(target_character_id): ErrorHandler.log_game_error("No active dialogue with character", {"id": target_character_id}) return # 验证消息内容 if message.strip_edges().is_empty(): return if message.length() > 500: message = message.substr(0, 500) # 添加到对话历史 var message_data = { "sender": "player", "content": message, "timestamp": Time.get_unix_time_from_system() } active_dialogues[target_character_id]["messages"].append(message_data) dialogue_history.append(message_data) # 发送到服务器 var network_message = MessageProtocol.create_message("dialogue_send", { "receiverId": target_character_id, "message": message }) NetworkManager.send_message(network_message) emit_signal("message_received", "player", message) ``` #### 对话气泡系统 ```gdscript # ChatBubble.gd extends Control class_name ChatBubble @onready var label: Label = $Background/Label @onready var background: NinePatchRect = $Background @onready var timer: Timer = $Timer var character_node: Node2D var offset: Vector2 = Vector2(0, -60) func show_bubble(character: Node2D, message: String, duration: float = 3.0) -> void: character_node = character label.text = message # 调整气泡大小 var text_size = label.get_theme_font("font").get_string_size( message, HORIZONTAL_ALIGNMENT_LEFT, -1, label.get_theme_font_size("font_size") ) var bubble_size = text_size + Vector2(20, 16) background.size = bubble_size size = bubble_size # 设置位置 _update_position() # 显示气泡 modulate.a = 0.0 visible = true var tween = create_tween() tween.tween_property(self, "modulate:a", 1.0, 0.2) # 设置自动隐藏 timer.wait_time = duration timer.start() func _update_position() -> void: if character_node: global_position = character_node.global_position + offset ``` ## API 参考 ### 客户端 API #### NetworkManager ```gdscript class_name NetworkManager extends Node # 信号 signal connected_to_server() signal disconnected_from_server() signal connection_error(error: String) signal message_received(message: Dictionary) # 方法 func connect_to_server(url: String) -> void func disconnect_from_server() -> void func send_message(message: Dictionary) -> void func is_connected() -> bool func get_connection_state() -> ConnectionState ``` #### GameStateManager ```gdscript class_name GameStateManager extends Node # 枚举 enum GameState { LOGIN, CHARACTER_CREATION, IN_GAME, DISCONNECTED } # 信号 signal state_changed(old_state: GameState, new_state: GameState) signal data_saved() signal data_loaded(data: Dictionary) # 属性 var current_state: GameState var player_data: Dictionary # 方法 func change_state(new_state: GameState) -> void func save_player_data() -> void func load_player_data() -> Dictionary func get_current_state() -> GameState ``` #### CharacterController ```gdscript class_name CharacterController extends CharacterBody2D # 信号 signal position_updated(new_position: Vector2) signal animation_changed(animation_name: String) signal online_status_changed(is_online: bool) # 属性 @export var character_id: String @export var character_name: String @export var is_online: bool @export var move_speed: float # 方法 func move_to(direction: Vector2) -> void func set_position_smooth(target_pos: Vector2, duration: float = 0.2) -> void func set_online_status(online: bool) -> void func play_animation(anim_name: String) -> void ``` ### 服务器 API #### WebSocket 消息 API **身份验证**: ```typescript // 请求 { "type": "auth_request", "data": { "username": string }, "timestamp": number } // 响应 { "type": "auth_response", "data": { "success": boolean, "clientId": string, "message"?: string }, "timestamp": number } ``` **角色创建**: ```typescript // 请求 { "type": "character_create", "data": { "name": string }, "timestamp": number } // 响应 { "type": "character_create", "data": { "success": boolean, "character"?: { "id": string, "name": string, "position": { "x": number, "y": number }, "isOnline": boolean }, "message"?: string }, "timestamp": number } ``` **角色移动**: ```typescript // 客户端 -> 服务器 { "type": "character_move", "data": { "position": { "x": number, "y": number } }, "timestamp": number } // 服务器 -> 所有客户端 { "type": "character_move", "data": { "characterId": string, "position": { "x": number, "y": number } }, "timestamp": number } ``` #### REST API (管理接口) **系统状态**: ```http GET /api/status Authorization: Bearer Response: { "status": "healthy", "uptime": 3600, "connections": 5, "characters": 12, "memory": { "used": 45.2, "total": 512 } } ``` **备份管理**: ```http POST /api/backup Authorization: Bearer Response: { "success": true, "backupId": "backup_1234567890", "timestamp": 1234567890 } ``` ## 数据库设计 ### 数据模型 #### Character 模型 ```typescript interface Character { id: string; // UUID name: string; // 角色名称 (2-20 字符) ownerId: string; // 所属玩家 ID position: { // 角色位置 x: number; y: number; }; isOnline: boolean; // 在线状态 appearance?: { // 外观设置 (可选) sprite: string; color: string; }; createdAt: number; // 创建时间戳 lastSeen: number; // 最后在线时间戳 } ``` #### WorldState 模型 ```typescript interface WorldState { sceneId: string; // 场景 ID characters: Character[]; // 所有角色 timestamp: number; // 状态时间戳 } ``` #### Message 模型 ```typescript interface DialogueMessage { senderId: string; // 发送者角色 ID receiverId?: string; // 接收者角色 ID (可选,为空表示广播) message: string; // 消息内容 timestamp: number; // 发送时间戳 } ``` ### 数据存储 **文件结构**: ``` server/data/ ├── characters.json # 角色数据 ├── maintenance_tasks.json # 维护任务 ├── logs/ # 日志文件 │ └── server_YYYY-MM-DD.log └── backups/ # 备份文件 └── backup_/ ├── backup_info.json ├── characters.json.gz └── logs/ ``` **数据验证**: ```typescript // 角色数据验证 function validateCharacter(character: any): boolean { return ( typeof character.id === 'string' && typeof character.name === 'string' && character.name.length >= 2 && character.name.length <= 20 && typeof character.ownerId === 'string' && typeof character.position === 'object' && typeof character.position.x === 'number' && typeof character.position.y === 'number' && typeof character.isOnline === 'boolean' ); } ``` ## 网络协议 ### 连接生命周期 ```mermaid sequenceDiagram participant C as Client participant S as Server C->>S: WebSocket Connection S->>C: Connection Established C->>S: auth_request S->>C: auth_response (success) C->>S: character_create S->>C: character_create (success) S->>C: world_state (initial) loop Game Loop C->>S: character_move S->>C: character_move (broadcast) C->>S: ping S->>C: pong end C->>S: Connection Close S->>S: Update character offline ``` ### 错误处理 **网络错误**: ```typescript { "type": "error", "data": { "code": "E001", "message": "Connection timeout", "details": "Server did not respond within 10 seconds" }, "timestamp": number } ``` **业务逻辑错误**: ```typescript { "type": "character_create", "data": { "success": false, "message": "Character name already exists", "code": "G001" }, "timestamp": number } ``` ### 心跳机制 **客户端心跳**: ```gdscript # NetworkManager.gd func _update_heartbeat(delta: float) -> void: _heartbeat_timer += delta if _heartbeat_timer >= HEARTBEAT_INTERVAL: _send_ping() _heartbeat_timer = 0.0 func _send_ping() -> void: var ping_message = MessageProtocol.create_message("ping") send_message(ping_message) ``` **服务器心跳检查**: ```typescript // 每 30 秒检查一次心跳 setInterval(() => { const now = Date.now(); for (const [clientId, client] of clients) { if (now - client.lastHeartbeat > HEARTBEAT_TIMEOUT) { console.log(`⏰ Client ${clientId} heartbeat timeout`); handleDisconnection(clientId); } } }, 30000); ``` ## 测试框架 ### 单元测试 **测试结构**: ```gdscript # test_example.gd extends Node var test_results: Array = [] func _ready(): run_all_tests() print_results() func run_all_tests(): test_basic_functionality() test_edge_cases() test_error_handling() func test_basic_functionality(): var result = TestResult.new("Basic Functionality") # 测试逻辑 var expected = "expected_value" var actual = function_under_test() if actual == expected: result.pass("Function returns expected value") else: result.fail("Expected %s, got %s" % [expected, actual]) test_results.append(result) ``` ### 属性测试 **属性测试框架**: ```gdscript # PropertyTest.gd class_name PropertyTest const DEFAULT_ITERATIONS = 100 static func run_property_test( property_name: String, test_function: Callable, iterations: int = DEFAULT_ITERATIONS ) -> PropertyTestResult: var result = PropertyTestResult.new(property_name) for i in range(iterations): var test_data = generate_test_data() var success = test_function.call(test_data) if success: result.add_success() else: result.add_failure(i, test_data) return result static func generate_test_data() -> Dictionary: # 生成随机测试数据 return { "character_id": "char_" + str(randi()), "position": Vector2(randf_range(0, 2000), randf_range(0, 1500)), "name": "Test" + str(randi() % 1000) } ``` ### 集成测试 **场景测试**: ```gdscript # test_scene_integration.gd extends Node func test_character_spawning(): # 加载测试场景 var scene = preload("res://scenes/DatawhaleOffice.tscn").instantiate() add_child(scene) # 创建角色 var character_data = { "id": "test_char", "name": "Test Character", "position": {"x": 1000, "y": 750} } var world_manager = scene.get_node("WorldManager") world_manager.spawn_character(character_data) # 验证角色是否正确生成 assert(world_manager.characters.has("test_char")) var character = world_manager.characters["test_char"] assert(character.character_name == "Test Character") assert(character.global_position == Vector2(1000, 750)) ``` ## 部署指南 ### 开发环境部署 **本地开发**: ```bash # 1. 启动服务器 cd server yarn dev # 2. 启动 Godot 客户端 # 在 Godot 编辑器中按 F5 # 3. 运行测试 # 在 Godot 中打开 tests/RunAllTests.tscn,按 F6 ``` ### 生产环境部署 #### 服务器部署 **使用 PM2**: ```bash # 安装 PM2 npm install -g pm2 # 构建项目 cd server yarn build # 启动服务 pm2 start dist/server.js --name ai-town-server # 查看日志 pm2 logs ai-town-server # 设置开机自启 pm2 startup pm2 save ``` **使用 Docker**: ```dockerfile # Dockerfile FROM node:18-alpine WORKDIR /app # 复制依赖文件 COPY server/package*.json ./ COPY server/yarn.lock ./ # 安装依赖 RUN yarn install --frozen-lockfile # 复制源码 COPY server/ ./ # 构建项目 RUN yarn build # 暴露端口 EXPOSE 8080 8081 # 启动服务 CMD ["yarn", "start"] ``` ```bash # 构建镜像 docker build -t ai-town-server . # 运行容器 docker run -d \ --name ai-town-server \ -p 8080:8080 \ -p 8081:8081 \ -v $(pwd)/data:/app/data \ ai-town-server ``` #### 客户端部署 **Web 导出**: ```bash # 在 Godot 编辑器中 1. 项目 -> 导出 2. 添加 "HTML5" 导出预设 3. 配置导出选项: - 线程支持: 启用 - 导出路径: build/web/ 4. 导出项目 ``` **Nginx 配置**: ```nginx server { listen 80; server_name your-domain.com; # 静态文件 location / { root /var/www/ai-town/web; index index.html; try_files $uri $uri/ /index.html; } # WebSocket 代理 location /ws { proxy_pass http://localhost:8080; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } # 管理 API 代理 location /api { proxy_pass http://localhost:8081; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } } ``` ### 监控和维护 **系统监控**: ```bash # 查看服务状态 pm2 status # 查看系统资源 pm2 monit # 重启服务 pm2 restart ai-town-server # 查看错误日志 pm2 logs ai-town-server --err ``` **数据备份**: ```bash # 手动备份 curl -X POST http://localhost:8081/api/backup \ -H "Authorization: Bearer admin123" # 自动备份 (crontab) 0 2 * * * curl -X POST http://localhost:8081/api/backup -H "Authorization: Bearer admin123" ``` ## 扩展开发 ### 添加新功能 #### 1. 添加新的消息类型 **客户端**: ```gdscript # MessageProtocol.gd enum MessageType { # ... 现有类型 NEW_FEATURE_REQUEST, NEW_FEATURE_RESPONSE } static func create_new_feature_message(data: Dictionary) -> Dictionary: return create_message("new_feature_request", data) ``` **服务器**: ```typescript // MessageRouter.ts constructor() { // ... 现有处理器 this.registerHandler('new_feature_request', new NewFeatureHandler()); } // NewFeatureHandler.ts class NewFeatureHandler implements MessageHandler { handle(clientId: string, data: any): void { // 处理新功能请求 const response = { type: 'new_feature_response', data: { success: true }, timestamp: Date.now() }; connectionManager.sendToClient(clientId, response); } } ``` #### 2. 添加新的 UI 组件 ```gdscript # NewUIComponent.gd extends Control class_name NewUIComponent signal component_action(action: String, data: Dictionary) @onready var button: Button = $Button @onready var label: Label = $Label func _ready(): button.pressed.connect(_on_button_pressed) _setup_component() func _setup_component(): # 组件初始化逻辑 pass func _on_button_pressed(): emit_signal("component_action", "button_clicked", {}) func update_display(data: Dictionary): # 更新显示内容 label.text = data.get("text", "") ``` #### 3. 添加新的游戏系统 ```gdscript # NewGameSystem.gd extends Node class_name NewGameSystem signal system_event(event_type: String, data: Dictionary) var system_data: Dictionary = {} var is_initialized: bool = false func _ready(): initialize_system() func initialize_system(): # 系统初始化 system_data = load_system_data() is_initialized = true emit_signal("system_event", "initialized", {}) func process_system_update(delta: float): if not is_initialized: return # 系统更新逻辑 pass func handle_network_message(message: Dictionary): # 处理网络消息 match message.type: "system_update": _handle_system_update(message.data) func _handle_system_update(data: Dictionary): # 处理系统更新 pass ``` ### 性能优化 #### 客户端优化 **对象池**: ```gdscript # ObjectPool.gd class_name ObjectPool var pool: Array = [] var scene_template: PackedScene var max_size: int func _init(template: PackedScene, size: int = 50): scene_template = template max_size = size _populate_pool() func get_object() -> Node: if pool.is_empty(): return scene_template.instantiate() return pool.pop_back() func return_object(obj: Node): if pool.size() < max_size: obj.reset() # 假设对象有 reset 方法 pool.append(obj) else: obj.queue_free() func _populate_pool(): for i in range(max_size / 2): var obj = scene_template.instantiate() pool.append(obj) ``` **空间分区**: ```gdscript # SpatialGrid.gd class_name SpatialGrid var grid: Dictionary = {} var cell_size: float = 100.0 func add_object(obj: Node2D, id: String): var cell = _get_cell(obj.global_position) if not grid.has(cell): grid[cell] = {} grid[cell][id] = obj func get_nearby_objects(position: Vector2, radius: float) -> Array: var nearby = [] var cells = _get_cells_in_radius(position, radius) for cell in cells: if grid.has(cell): nearby.append_array(grid[cell].values()) return nearby func _get_cell(position: Vector2) -> Vector2i: return Vector2i( int(position.x / cell_size), int(position.y / cell_size) ) ``` #### 服务器优化 **消息批处理**: ```typescript // MessageBatcher.ts class MessageBatcher { private batches = new Map(); private batchInterval = 50; // 50ms constructor() { setInterval(() => this.flushBatches(), this.batchInterval); } addMessage(clientId: string, message: any): void { if (!this.batches.has(clientId)) { this.batches.set(clientId, []); } this.batches.get(clientId)!.push(message); } private flushBatches(): void { for (const [clientId, messages] of this.batches) { if (messages.length > 0) { const batchMessage = { type: 'batch', data: { messages }, timestamp: Date.now() }; connectionManager.sendToClient(clientId, batchMessage); messages.length = 0; // 清空数组 } } } } ``` **内存管理**: ```typescript // MemoryManager.ts class MemoryManager { private cleanupInterval = 5 * 60 * 1000; // 5 minutes constructor() { setInterval(() => this.cleanup(), this.cleanupInterval); } cleanup(): void { // 清理过期的连接 this.cleanupExpiredConnections(); // 清理旧的消息历史 this.cleanupMessageHistory(); // 强制垃圾回收 if (global.gc) { global.gc(); } } private cleanupExpiredConnections(): void { const now = Date.now(); const timeout = 10 * 60 * 1000; // 10 minutes for (const [clientId, client] of clients) { if (now - client.lastHeartbeat > timeout) { clients.delete(clientId); } } } } ``` ## 故障排除 ### 常见问题诊断 #### 网络连接问题 **症状**: 客户端无法连接服务器 **诊断步骤**: 1. 检查服务器是否运行: `pm2 status` 2. 检查端口是否开放: `netstat -an | grep 8080` 3. 检查防火墙设置 4. 查看服务器日志: `pm2 logs ai-town-server` **解决方案**: ```bash # 重启服务器 pm2 restart ai-town-server # 检查配置 cat server/src/server.ts | grep PORT # 测试连接 curl -I http://localhost:8080 ``` #### 性能问题 **症状**: 游戏卡顿,帧率低 **诊断工具**: ```gdscript # PerformanceMonitor.gd func _process(delta): var fps = Engine.get_frames_per_second() var memory = OS.get_static_memory_usage() if fps < 30: print("Low FPS detected: ", fps) if memory > 100 * 1024 * 1024: # 100MB print("High memory usage: ", memory / 1024 / 1024, "MB") ``` **优化建议**: 1. 减少同时显示的角色数量 2. 使用对象池减少内存分配 3. 优化渲染设置 4. 检查是否有内存泄漏 #### 数据同步问题 **症状**: 角色位置不同步 **调试代码**: ```gdscript # 在 CharacterController.gd 中添加调试信息 func set_position_smooth(new_position: Vector2, duration: float = 0.2): print("Position update: ", character_id, " from ", global_position, " to ", new_position) # ... 原有代码 ``` **检查清单**: 1. 网络连接是否稳定 2. 服务器是否正确广播位置更新 3. 客户端是否正确处理位置消息 4. 是否存在消息丢失 ### 日志分析 **服务器日志格式**: ``` [2024-12-05 10:30:15] INFO: Server started on port 8080 [2024-12-05 10:30:20] INFO: ✅ Client connected: client_abc123 [2024-12-05 10:30:25] INFO: 🔐 Authentication successful: client_abc123 [2024-12-05 10:30:30] INFO: 👤 Character created: char_def456 (Hero) [2024-12-05 10:30:35] ERROR: ❌ Invalid message format from client_abc123 ``` **日志分析脚本**: ```bash #!/bin/bash # analyze_logs.sh LOG_FILE="server/data/logs/server_$(date +%Y-%m-%d).log" echo "=== Connection Statistics ===" grep "Client connected" $LOG_FILE | wc -l echo "Total connections today" echo "=== Error Summary ===" grep "ERROR" $LOG_FILE | cut -d' ' -f4- | sort | uniq -c | sort -nr echo "=== Character Creation ===" grep "Character created" $LOG_FILE | wc -l echo "Characters created today" ``` ### 调试工具 **网络调试**: ```gdscript # NetworkDebugger.gd extends Node var message_log: Array = [] var max_log_size: int = 1000 func log_message(direction: String, message: Dictionary): var log_entry = { "timestamp": Time.get_unix_time_from_system(), "direction": direction, # "sent" or "received" "type": message.get("type", "unknown"), "data": message.get("data", {}), "size": JSON.stringify(message).length() } message_log.append(log_entry) if message_log.size() > max_log_size: message_log.pop_front() print("[NET %s] %s: %s bytes" % [direction.to_upper(), log_entry.type, log_entry.size]) func get_message_statistics() -> Dictionary: var stats = { "total_messages": message_log.size(), "sent_messages": 0, "received_messages": 0, "message_types": {} } for entry in message_log: if entry.direction == "sent": stats.sent_messages += 1 else: stats.received_messages += 1 var type = entry.type if not stats.message_types.has(type): stats.message_types[type] = 0 stats.message_types[type] += 1 return stats ``` **性能分析器**: ```gdscript # Profiler.gd extends Node var frame_times: Array = [] var function_times: Dictionary = {} func start_profiling(function_name: String): function_times[function_name] = Time.get_ticks_usec() func end_profiling(function_name: String): if function_times.has(function_name): var elapsed = Time.get_ticks_usec() - function_times[function_name] print("Function %s took %d microseconds" % [function_name, elapsed]) function_times.erase(function_name) func _process(delta): frame_times.append(delta) if frame_times.size() > 60: # 保持最近 60 帧 frame_times.pop_front() # 每秒输出一次平均帧时间 if Engine.get_process_frames() % 60 == 0: var avg_frame_time = 0.0 for time in frame_times: avg_frame_time += time avg_frame_time /= frame_times.size() var fps = 1.0 / avg_frame_time print("Average FPS: %.1f" % fps) ``` --- ## 总结 本技术文档涵盖了 AI Town Game 项目的所有技术细节,包括架构设计、API 参考、部署指南和扩展开发。开发者可以根据这份文档: 1. **快速上手**: 通过环境配置和快速开始指南 2. **深入理解**: 通过核心系统详解和架构图 3. **扩展功能**: 通过扩展开发指南添加新功能 4. **优化性能**: 通过性能优化建议提升游戏体验 5. **解决问题**: 通过故障排除指南快速定位和解决问题 项目采用模块化设计,具有良好的可扩展性和可维护性。所有核心系统都经过充分测试,并提供了完整的 API 文档和使用示例。 如有任何技术问题或改进建议,欢迎通过项目 GitHub 页面提交 Issue 或 Pull Request。