Files
whale-town/DEVELOPER_GUIDE.md
2025-12-05 19:00:14 +08:00

42 KiB
Raw Blame History

AI Town Game 开发者技术文档

📋 目录

  1. 项目概述
  2. 技术架构
  3. 开发环境配置
  4. 核心系统详解
  5. 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          # 任务列表

技术架构

整体架构图

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:

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:

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:

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. 克隆项目:
git clone <repository-url>
cd ai_community
  1. 配置 Godot:
# 下载并安装 Godot 4.5.1
# 导入项目: 选择 project.godot 文件
  1. 配置服务器:
cd server
yarn install
yarn build
  1. 启动开发环境:
# 终端 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:

{
    "godot_tools.editor_path": "/path/to/godot",
    "typescript.preferences.importModuleSpecifier": "relative",
    "editor.formatOnSave": true,
    "editor.codeActionsOnSave": {
        "source.organizeImports": true
    }
}

核心系统详解

网络系统

WebSocket 连接管理

客户端连接流程:

# 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)

服务器连接处理:

// 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);
    });
});

消息协议实现

消息序列化:

# 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

消息路由:

// 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}`);
        }
    }
}

状态管理系统

游戏状态机

# 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)

数据持久化

客户端数据保存:

# 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")

服务器数据持久化:

// 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);
    }
}

角色系统

角色控制器

# 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)

角色状态同步

客户端同步:

# 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()

服务器状态广播:

// 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();
}

对话系统

对话管理

# 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)

对话气泡系统

# 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

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

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

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

身份验证:

// 请求
{
    "type": "auth_request",
    "data": {
        "username": string
    },
    "timestamp": number
}

// 响应
{
    "type": "auth_response",
    "data": {
        "success": boolean,
        "clientId": string,
        "message"?: string
    },
    "timestamp": number
}

角色创建:

// 请求
{
    "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
}

角色移动:

// 客户端 -> 服务器
{
    "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 (管理接口)

系统状态:

GET /api/status
Authorization: Bearer <admin_token>

Response:
{
    "status": "healthy",
    "uptime": 3600,
    "connections": 5,
    "characters": 12,
    "memory": {
        "used": 45.2,
        "total": 512
    }
}

备份管理:

POST /api/backup
Authorization: Bearer <admin_token>

Response:
{
    "success": true,
    "backupId": "backup_1234567890",
    "timestamp": 1234567890
}

数据库设计

数据模型

Character 模型

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 模型

interface WorldState {
    sceneId: string;              // 场景 ID
    characters: Character[];       // 所有角色
    timestamp: number;            // 状态时间戳
}

Message 模型

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/

数据验证:

// 角色数据验证
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'
    );
}

网络协议

连接生命周期

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

错误处理

网络错误:

{
    "type": "error",
    "data": {
        "code": "E001",
        "message": "Connection timeout",
        "details": "Server did not respond within 10 seconds"
    },
    "timestamp": number
}

业务逻辑错误:

{
    "type": "character_create",
    "data": {
        "success": false,
        "message": "Character name already exists",
        "code": "G001"
    },
    "timestamp": number
}

心跳机制

客户端心跳:

# 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)

服务器心跳检查:

// 每 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);

测试框架

单元测试

测试结构:

# 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)

属性测试

属性测试框架:

# 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)
    }

集成测试

场景测试:

# 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))

部署指南

开发环境部署

本地开发:

# 1. 启动服务器
cd server
yarn dev

# 2. 启动 Godot 客户端
# 在 Godot 编辑器中按 F5

# 3. 运行测试
# 在 Godot 中打开 tests/RunAllTests.tscn按 F6

生产环境部署

服务器部署

使用 PM2:

# 安装 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
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"]
# 构建镜像
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 导出:

# 在 Godot 编辑器中
1. 项目 -> 导出
2. 添加 "HTML5" 导出预设
3. 配置导出选项:
   - 线程支持: 启用
   - 导出路径: build/web/
4. 导出项目

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;
    }
}

监控和维护

系统监控:

# 查看服务状态
pm2 status

# 查看系统资源
pm2 monit

# 重启服务
pm2 restart ai-town-server

# 查看错误日志
pm2 logs ai-town-server --err

数据备份:

# 手动备份
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. 添加新的消息类型

客户端:

# 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)

服务器:

// 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 组件

# 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. 添加新的游戏系统

# 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

性能优化

客户端优化

对象池:

# 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)

空间分区:

# 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)
    )

服务器优化

消息批处理:

// 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; // 清空数组
            }
        }
    }
}

内存管理:

// 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

解决方案:

# 重启服务器
pm2 restart ai-town-server

# 检查配置
cat server/src/server.ts | grep PORT

# 测试连接
curl -I http://localhost:8080

性能问题

症状: 游戏卡顿,帧率低 诊断工具:

# 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. 检查是否有内存泄漏

数据同步问题

症状: 角色位置不同步 调试代码:

# 在 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

日志分析脚本:

#!/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"

调试工具

网络调试:

# 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

性能分析器:

# 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。