1758 lines
42 KiB
Markdown
1758 lines
42 KiB
Markdown
# 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<string, WebSocket>
|
||
private heartbeats: Map<string, number>
|
||
|
||
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<string, MessageHandler>
|
||
|
||
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<string, Character>
|
||
private scenes: Map<string, Scene>
|
||
|
||
addCharacter(character: Character): void
|
||
updateCharacter(characterId: string, updates: Partial<Character>): 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 <repository-url>
|
||
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<string, MessageHandler>();
|
||
|
||
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<void> {
|
||
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<Character[]> {
|
||
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 <admin_token>
|
||
|
||
Response:
|
||
{
|
||
"status": "healthy",
|
||
"uptime": 3600,
|
||
"connections": 5,
|
||
"characters": 12,
|
||
"memory": {
|
||
"used": 45.2,
|
||
"total": 512
|
||
}
|
||
}
|
||
```
|
||
|
||
**备份管理**:
|
||
```http
|
||
POST /api/backup
|
||
Authorization: Bearer <admin_token>
|
||
|
||
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_<timestamp>/
|
||
├── 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<string, any[]>();
|
||
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。 |