Initial commit
This commit is contained in:
597
docs/architecture.md
Normal file
597
docs/architecture.md
Normal file
@@ -0,0 +1,597 @@
|
||||
# ZP2 全局架构文档
|
||||
|
||||
## 1. 项目概述
|
||||
|
||||
ZP2 是一款多人在线俯视角 3D 游戏,玩家在 32x32 网格地图上移动、实时同步位置。本项目是 ZP1 的精简版,仅保留房间管理、地图加载、玩家移动和基础 3D 显示功能。
|
||||
|
||||
### 技术栈
|
||||
|
||||
| 层级 | 技术 | 版本 |
|
||||
|------|------|------|
|
||||
| 后端 | Java | 17 |
|
||||
| 构建 | Maven | - |
|
||||
| 通信 | Java-WebSocket | 1.5.4 |
|
||||
| JSON | Gson + Jackson | 2.10.1 / 2.15.2 |
|
||||
| 前端 | JavaScript (ES Module) | - |
|
||||
| 渲染 | Three.js | ^0.170.0 |
|
||||
| 构建 | Vite | ^6.0.0 |
|
||||
|
||||
---
|
||||
|
||||
## 2. 系统架构总览
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Frontend (Browser) │
|
||||
│ │
|
||||
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
|
||||
│ │ main.js │───→│ engine.js│───→│ scene.js │ │ client.js │ │
|
||||
│ │ (App) │ │ (Loop/ │ │ (Three.js│ │(WebSocket)│ │
|
||||
│ │ │ │ Predict) │ │ Render) │ │ │ │
|
||||
│ └──────────┘ └──────────┘ └──────────┘ └─────┬────┘ │
|
||||
│ │ │
|
||||
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
|
||||
│ │ lobby.js │ │ input.js │ │ grid.js │ │ │
|
||||
│ └──────────┘ └──────────┘ └──────────┘ │ │
|
||||
└─────────────────────────────────────────────────────────┼────────┘
|
||||
│ WebSocket
|
||||
│ JSON
|
||||
┌─────────────────────────────────────────────────────────┼────────┐
|
||||
│ Backend (Java) │ │
|
||||
│ ┌──────────────────────────────────────────────────────┤ │
|
||||
│ │ GameWebSocketServer (8080) │←───────┘
|
||||
│ │ onMessage 消息路由 → 房间CRUD / 玩家输入 / 状态广播 │
|
||||
│ └──────┬───────────┬──────────────┬───────────────────┘
|
||||
│ │ │ │
|
||||
│ ┌──────┴───┐ ┌────┴─────┐ ┌────┴──────────┐
|
||||
│ │RoomMgr │ │GameService│ │ GameLoop │
|
||||
│ │(房间生命周期)│ │(游戏会话) │ │(30TPS固定帧) │
|
||||
│ └──────────┘ └────┬─────┘ └───────────────┘
|
||||
│ │
|
||||
│ ┌───────────────────┴────────────────────────────┐
|
||||
│ │ ECSWorld │
|
||||
│ │ 实体(Entities) + 组件(组件) + 系统(Systems) │
|
||||
│ │ ┌──────────────────────────────────────────┐ │
|
||||
│ │ │ PlayerInputSystem: 移动 + 碰撞 + 朝向 │ │
|
||||
│ │ │ StateSyncSystem: 构建状态快照 → 广播 │ │
|
||||
│ │ └──────────────────────────────────────────┘ │
|
||||
│ └────────────────────────────────────────────────┘
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 后端架构
|
||||
|
||||
### 3.1 启动流程
|
||||
|
||||
```
|
||||
GameServerMain.main()
|
||||
├── 解析端口参数 (默认 8080)
|
||||
├── 创建 GameWebSocketServer
|
||||
├── server.start() 启动 WebSocket 监听
|
||||
└── 注册 shutdown hook(优雅关闭)
|
||||
```
|
||||
|
||||
### 3.2 网络层 — GameWebSocketServer
|
||||
|
||||
核心职责:管理 WebSocket 连接、路由消息、广播状态。
|
||||
|
||||
**连接管理:**
|
||||
- `connectionToPlayer`: WebSocket → playerId 映射
|
||||
- `playerToConnection`: playerId → WebSocket 映射
|
||||
|
||||
**消息路由(onMessage):**
|
||||
|
||||
| 消息类型 | 处理方法 | 说明 |
|
||||
|----------|----------|------|
|
||||
| `create_room` | `handleCreateRoom` | 创建房间,生成 playerId 和 roomId |
|
||||
| `join_room` | `handleJoinRoom` | 加入房间,广播新状态 |
|
||||
| `leave_room` | `handleLeaveRoomByConn` | 离开房间,清理游戏会话 |
|
||||
| `room_list` | `handleRoomList` | 返回可用房间列表 |
|
||||
| `ready` | `handleReady` | 切换玩家准备状态 |
|
||||
| `start_game` | `handleStartGame` | 房主开始游戏(需全员准备) |
|
||||
| `player_input` | `handlePlayerInput` | 转发玩家输入到 GameService |
|
||||
|
||||
**定时任务:**
|
||||
- 每 2 秒广播房间列表给未加入房间的玩家
|
||||
|
||||
### 3.3 房间管理 — RoomManager
|
||||
|
||||
使用 `ConcurrentHashMap` 保证线程安全:
|
||||
|
||||
- `rooms`: roomId → Room 映射
|
||||
- `playerToRoom`: playerId → roomId 映射
|
||||
- `getAvailableRooms()`: 返回未开始游戏的房间
|
||||
|
||||
**Room 实体:**
|
||||
- `id`: 房间唯一标识(UUID 前8位)
|
||||
- `hostId`: 房主 playerId
|
||||
- `players`: LinkedHashMap 保持加入顺序(最多4人)
|
||||
- `gameStarted`: 游戏是否已开始
|
||||
|
||||
### 3.4 游戏服务 — GameService
|
||||
|
||||
**startGame(room) 流程:**
|
||||
```
|
||||
1. 加载地图文件 (maps/d540209a.json)
|
||||
2. 创建 ECSWorld
|
||||
3. 注册 PlayerInputSystem
|
||||
4. 为每个玩家创建 PlayerEntity(分配到出生点)
|
||||
5. 创建并启动 GameLoop
|
||||
6. 返回每个玩家的初始化数据(mapData + players)
|
||||
```
|
||||
|
||||
**processPlayerInput:**
|
||||
- 查找玩家对应的 ECS 实体
|
||||
- 更新 PlayerInput 组件(dx, dy, aimX, aimY, seq)
|
||||
- 使用 `synchronized(world.lock)` 保证线程安全
|
||||
|
||||
### 3.5 游戏循环 — GameLoop
|
||||
|
||||
固定时间步长循环(30 TPS):
|
||||
|
||||
```
|
||||
ScheduledExecutorService
|
||||
└── scheduleAtFixedRate(tick, 0, 33ms)
|
||||
├── synchronized(world.lock) { world.update(dt) }
|
||||
└── broadcaster.broadcast(roomId, world)
|
||||
```
|
||||
|
||||
- 每 33ms(1000/30)执行一次 tick
|
||||
- tick 内更新所有 ECS 系统
|
||||
- 更新完成后广播游戏状态
|
||||
|
||||
### 3.6 ECS 架构
|
||||
|
||||
#### ECSWorld
|
||||
|
||||
核心容器,管理所有实体和组件:
|
||||
|
||||
| 组件映射 | 类型 | 说明 |
|
||||
|----------|------|------|
|
||||
| `entities` | `LinkedHashSet<Integer>` | 所有活跃实体 |
|
||||
| `players` | `LinkedHashSet<Integer>` | 玩家实体集合 |
|
||||
| `positions` | `Map<Integer, Position>` | 位置组件 |
|
||||
| `healths` | `Map<Integer, Health>` | 生命值组件 |
|
||||
| `collisions` | `Map<Integer, Collision>` | 碰撞组件 |
|
||||
| `renderInfos` | `Map<Integer, RenderInfo>` | 渲染组件 |
|
||||
| `playerInputs` | `Map<Integer, PlayerInput>` | 输入组件 |
|
||||
| `systems` | `List<System>` | 系统列表 |
|
||||
| `playerIdToEntity` | `Map<String, Integer>` | 玩家ID到实体ID映射 |
|
||||
|
||||
**createPlayerEntity(playerId, name, x, y):**
|
||||
```
|
||||
1. createEntity() 获取新实体ID
|
||||
2. 添加 Position(x, y)
|
||||
3. 添加 Health(100)
|
||||
4. 添加 Collision(PLAYER_SIZE=0.8)
|
||||
5. 添加 RenderInfo(PLAYER)
|
||||
6. 添加 PlayerInput()
|
||||
7. 加入 players 集合
|
||||
8. 建立 playerId → entityId 映射
|
||||
```
|
||||
|
||||
#### System 接口
|
||||
|
||||
```java
|
||||
public interface System {
|
||||
void update(float dt, ECSWorld world);
|
||||
}
|
||||
```
|
||||
|
||||
#### PlayerInputSystem
|
||||
|
||||
每帧处理所有玩家输入:
|
||||
|
||||
```
|
||||
对于每个玩家实体:
|
||||
1. 获取 PlayerInput / Position / Health
|
||||
2. 跳过死亡或缺少组件的实体
|
||||
3. 计算移动:newX = x + dx * PLAYER_SPEED * TICK_INTERVAL
|
||||
4. 逐轴碰撞检测:isWalkable(newX, y, PLAYER_SIZE)
|
||||
5. 限制在地图边界 [0.5, GRID_SIZE-0.5]
|
||||
6. 计算朝向:atan2(aimX - x, aimY - y)
|
||||
```
|
||||
|
||||
#### StateSyncSystem
|
||||
|
||||
构建游戏状态快照:
|
||||
|
||||
```json
|
||||
{
|
||||
"players": [
|
||||
{
|
||||
"id": "player-uuid",
|
||||
"x": 12.5,
|
||||
"y": 8.3,
|
||||
"angle": 1.57,
|
||||
"health": 100,
|
||||
"lastProcessedSeq": 42
|
||||
}
|
||||
],
|
||||
"gameTime": 12.5
|
||||
}
|
||||
```
|
||||
|
||||
### 3.7 地图系统
|
||||
|
||||
**GameMap** 从 JSON 文件加载:
|
||||
|
||||
```json
|
||||
{
|
||||
"width": 32,
|
||||
"height": 32,
|
||||
"walls": [{"x": 5, "y": 10, "type": "static"}],
|
||||
"playerSpawns": [{"x": 2, "y": 2}],
|
||||
"zombieSpawns": [{"x": 15, "y": 15}]
|
||||
}
|
||||
```
|
||||
|
||||
- `cells[y][x]`: 0=空地, 1=墙, 2=玩家出生点, 3=僵尸出生点
|
||||
- `isWalkable(wx, wy, size)`: 检查实体四个角是否均可通行
|
||||
- `isWall(gx, gy)`: 判断网格是否为墙或越界
|
||||
|
||||
**MapStorage**: 基于文件的地图持久化(save/load/list/delete)
|
||||
|
||||
---
|
||||
|
||||
## 4. 前端架构
|
||||
|
||||
### 4.1 应用入口 — main.js
|
||||
|
||||
```
|
||||
App 构造函数
|
||||
├── 创建 lobbyEl 和 gameCanvasEl
|
||||
├── 实例化 LobbyUI
|
||||
└── _setupLobby() 注册回调
|
||||
```
|
||||
|
||||
**视图切换:**
|
||||
- 默认显示 lobby,隐藏 canvas
|
||||
- `game_started` 消息后:隐藏 lobby,显示 canvas
|
||||
|
||||
**连接管理(_ensureConnection):**
|
||||
```
|
||||
if (!engine):
|
||||
创建 GameEngine → connect(WS_URL) → 注册网络处理器
|
||||
else if (!connected):
|
||||
重连
|
||||
```
|
||||
|
||||
### 4.2 游戏引擎 — engine.js
|
||||
|
||||
#### 核心数据结构
|
||||
|
||||
```javascript
|
||||
this.players = Map<playerId, {
|
||||
id, name, x, y, angle, health, color, isLocal
|
||||
}>
|
||||
this.pendingInputs = [] // 待确认输入队列
|
||||
```
|
||||
|
||||
#### 游戏循环(固定时间步长)
|
||||
|
||||
```
|
||||
requestAnimationFrame(_loop)
|
||||
delta = now - lastTick
|
||||
accumulator += delta
|
||||
while (accumulator >= TICK_INTERVAL):
|
||||
_tick()
|
||||
accumulator -= TICK_INTERVAL
|
||||
updateCamera(localPlayer)
|
||||
scene.render()
|
||||
```
|
||||
|
||||
#### 客户端预测(_applyLocalPrediction)
|
||||
|
||||
```
|
||||
1. 读取输入状态 (dx, dy)
|
||||
2. 计算新位置: newX = x + dx * SPEED * dt
|
||||
3. 逐轴碰撞检测
|
||||
4. 限制在地图范围内 [0.5, 31.5]
|
||||
5. 计算朝向: atan2(aimX - x, aimY - y)
|
||||
6. 更新场景中的玩家模型
|
||||
```
|
||||
|
||||
#### 服务器校正(_reconcileLocalPlayer)
|
||||
|
||||
```
|
||||
1. 用服务器权威状态覆盖本地位置
|
||||
2. 移除 seq <= lastProcessedSeq 的输入(已确认)
|
||||
3. 对剩余未确认输入重新执行预测
|
||||
4. 更新场景
|
||||
```
|
||||
|
||||
这是经典的 **客户端预测 + 服务器校正** 模式:
|
||||
- 客户端立即响应输入(零延迟手感)
|
||||
- 服务器每帧广播权威状态
|
||||
- 客户端用 seq 号匹配已处理输入,重新模拟未确认输入
|
||||
|
||||
### 4.3 3D 渲染 — scene.js
|
||||
|
||||
**场景设置:**
|
||||
- 透视相机 (FOV=45°, 偏移量 (0, 25, 18))
|
||||
- 环境光 + 方向光(带阴影)+ 点光源
|
||||
- 窗口大小自适应
|
||||
|
||||
**地图渲染(buildMap):**
|
||||
- 地板: 32x32 Plane (深灰色)
|
||||
- 墙壁: BoxGeometry(1, 1.5, 1) (蓝灰色)
|
||||
- 出生点: BoxGeometry(1, 0.1, 1) (绿色半透明)
|
||||
|
||||
**玩家模型(createPlayerModel):**
|
||||
```
|
||||
Group
|
||||
├── 身体: Cylinder(半径 0.4, 高 0.8)
|
||||
├── 头部: Sphere(半径 0.2, 肤色)
|
||||
└── 武器: Box(0.08, 0.08, 0.5, 深色)
|
||||
```
|
||||
|
||||
**鼠标射线投射(getMouseGroundPos):**
|
||||
```
|
||||
鼠标屏幕坐标 → NDC → Raycaster → 与 Y=0 平面相交 → 地面世界坐标
|
||||
```
|
||||
|
||||
### 4.4 网络客户端 — client.js
|
||||
|
||||
WebSocket 封装,提供:
|
||||
- 事件注册:`on(type, handler)`
|
||||
- 消息发送:`send(type, data)`
|
||||
- 便捷方法:`createRoom`, `joinRoom`, `sendInput` 等
|
||||
|
||||
消息格式:
|
||||
```json
|
||||
{
|
||||
"type": "player_input",
|
||||
"data": {"dx": 1, "dy": 0, "aimX": 10, "aimY": 5, "seq": 42}
|
||||
}
|
||||
```
|
||||
|
||||
### 4.5 输入管理 — input.js
|
||||
|
||||
**键盘输入:**
|
||||
- WASD / 方向键 → 移动向量
|
||||
- 对角线归一化(×0.7071)
|
||||
|
||||
**鼠标输入:**
|
||||
- 位置追踪 (clientX, clientY)
|
||||
- 左右键状态
|
||||
|
||||
**buildInputState:**
|
||||
```javascript
|
||||
{
|
||||
seq: sequenceNumber++, // 递增序列号
|
||||
dx: movement.dx, // 水平输入 [-1, 1]
|
||||
dy: movement.dy, // 垂直输入 [-1, 1]
|
||||
aimX: mouseGroundPos.x, // 瞄准点 X
|
||||
aimY: mouseGroundPos.y // 瞄准点 Y
|
||||
}
|
||||
```
|
||||
|
||||
### 4.6 大厅 UI — lobby.js
|
||||
|
||||
两个视图状态:
|
||||
|
||||
**房间列表视图:**
|
||||
- 玩家名称输入框
|
||||
- 创建房间 / 刷新按钮
|
||||
- 房间列表(点击 Join 加入)
|
||||
|
||||
**房间视图:**
|
||||
- 玩家列表(显示准备状态)
|
||||
- Ready 按钮
|
||||
- Start Game 按钮(仅房主可见)
|
||||
- Leave 按钮
|
||||
|
||||
### 4.7 网格碰撞 — grid.js
|
||||
|
||||
**Grid 类:**
|
||||
- `parseMap(mapData)`: 解析二维数组
|
||||
- `isWall(gx, gy)`: 越界返回 true,检查 cells == 1
|
||||
- `worldToGrid(wx, wy)`: 世界坐标 → 网格坐标
|
||||
- `isWalkable(wx, wy, size)`: 检查四个角是否可通行
|
||||
|
||||
**generateDefaultMap:**
|
||||
- 边界墙(最外层)
|
||||
- 预设墙体段(14段)
|
||||
- 4个角落出生点(周围自动清理墙体)
|
||||
|
||||
---
|
||||
|
||||
## 5. 网络协议
|
||||
|
||||
### 消息格式
|
||||
|
||||
所有消息使用统一的 `{type, data}` 信封格式:
|
||||
|
||||
```json
|
||||
{"type": "xxx", "data": {...}}
|
||||
```
|
||||
|
||||
### 消息类型汇总
|
||||
|
||||
| 方向 | type | 说明 |
|
||||
|------|------|------|
|
||||
| C→S | `create_room` | 创建房间 `{playerName}` |
|
||||
| C→S | `join_room` | 加入房间 `{roomId, playerName}` |
|
||||
| C→S | `leave_room` | 离开房间 |
|
||||
| C→S | `room_list` | 请求房间列表 |
|
||||
| C→S | `ready` | 切换准备状态 |
|
||||
| C→S | `start_game` | 开始游戏(仅房主) |
|
||||
| C→S | `player_input` | 玩家输入 `{dx, dy, aimX, aimY, seq}` |
|
||||
| S→C | `room_list` | 房间列表 `{rooms[]}` |
|
||||
| S→C | `room_state` | 房间状态 `{roomId, hostId, isHost, playerId, players[]}` |
|
||||
| S→C | `game_started` | 游戏开始 `{playerId, mapData, players[]}` |
|
||||
| S→C | `game_state` | 游戏状态 `{players[{id, x, y, angle, health, lastProcessedSeq}], gameTime}` |
|
||||
| S→C | `error` | 错误 `{message}` |
|
||||
|
||||
### 通信时序
|
||||
|
||||
```
|
||||
Client A Server Client B
|
||||
| | |
|
||||
|── create_room ──────→| |
|
||||
|←── room_state ──────| |
|
||||
| |←── join_room ────────|
|
||||
|←── room_state ──────|─── room_state ──────→|
|
||||
| | |
|
||||
|── ready ───────────→| |
|
||||
|←── room_state ──────|─── room_state ──────→|
|
||||
| | |
|
||||
|── start_game ──────→| |
|
||||
|←── game_started ────|─── game_started ────→|
|
||||
| | |
|
||||
|═══ player_input ═══→| |
|
||||
| |═══ game_state ══════→|
|
||||
|←══ game_state ══════| |
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 数据流
|
||||
|
||||
### 6.1 玩家移动完整数据流
|
||||
|
||||
```
|
||||
InputManager (键盘/鼠标)
|
||||
│
|
||||
▼
|
||||
buildInputState() → {seq, dx, dy, aimX, aimY}
|
||||
│
|
||||
├──→ _applyLocalPrediction() [客户端预测]
|
||||
│ ├── 新位置计算
|
||||
│ ├── 碰撞检测
|
||||
│ └── 更新 Three.js 模型
|
||||
│
|
||||
└──→ network.sendInput() ──→ WebSocket → Server
|
||||
│
|
||||
▼
|
||||
GameService.processPlayerInput()
|
||||
│
|
||||
▼
|
||||
ECSWorld.update(dt)
|
||||
│
|
||||
▼
|
||||
PlayerInputSystem.update()
|
||||
│
|
||||
├── 更新 Position 组件
|
||||
└── 更新朝向角度
|
||||
│
|
||||
▼
|
||||
StateSyncSystem.buildGameState()
|
||||
│
|
||||
▼
|
||||
broadcast → WebSocket → Client
|
||||
│
|
||||
▼
|
||||
_processServerState()
|
||||
│
|
||||
├── 远程玩家: 直接更新
|
||||
└── 本地玩家: _reconcileLocalPlayer()
|
||||
├── 应用服务器状态
|
||||
├── 移除已确认输入
|
||||
└── 重放未确认输入
|
||||
```
|
||||
|
||||
### 6.2 生命周期
|
||||
|
||||
```
|
||||
[创建房间]
|
||||
Client → create_room → Server → RoomManager.addRoom() → Room 实例
|
||||
|
||||
[加入房间]
|
||||
Client → join_room → Server → RoomManager.joinRoom() → 广播 room_state
|
||||
|
||||
[开始游戏]
|
||||
Client → start_game → Server → GameService.startGame()
|
||||
├── 加载地图
|
||||
├── 创建 ECSWorld + PlayerInputSystem
|
||||
├── 创建 PlayerEntity(分配出生点)
|
||||
├── 启动 GameLoop (30 TPS)
|
||||
└── 广播 game_started(含 mapData + players)
|
||||
|
||||
[游戏进行中]
|
||||
GameLoop.tick() → ECSWorld.update() → PlayerInputSystem → 广播 game_state
|
||||
|
||||
[玩家断开]
|
||||
onClose → RoomManager.leaveRoom() → 清理游戏会话(如房间为空)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 关键设计模式
|
||||
|
||||
### 7.1 ECS (Entity-Component-System)
|
||||
|
||||
- **Entity**: 整数 ID,仅作为标识符
|
||||
- **Component**: 纯数据(Position, Health, PlayerInput 等)
|
||||
- **System**: 处理逻辑(PlayerInputSystem, StateSyncSystem)
|
||||
|
||||
优势:解耦数据与逻辑,便于扩展新实体类型。
|
||||
|
||||
### 7.2 客户端预测 + 服务器校正
|
||||
|
||||
- 客户端立即响应输入,不等待服务器
|
||||
- 服务器每帧广播权威状态
|
||||
- 客户端用序列号匹配已处理输入,重新模拟未确认输入
|
||||
- 结果:零延迟手感 + 服务器权威防作弊
|
||||
|
||||
### 7.3 固定时间步长
|
||||
|
||||
- 客户端和服务器均使用 30 TPS
|
||||
- 渲染帧率与逻辑帧率解耦(渲染用 requestAnimationFrame)
|
||||
- 保证确定性模拟
|
||||
|
||||
### 7.4 线程安全
|
||||
|
||||
- ECSWorld 使用 `synchronized(lock)` 保护
|
||||
- RoomManager 使用 `ConcurrentHashMap`
|
||||
- GameLoop 使用单线程 ScheduledExecutorService
|
||||
|
||||
---
|
||||
|
||||
## 8. 文件索引
|
||||
|
||||
### 后端
|
||||
|
||||
| 文件 | 职责 |
|
||||
|------|------|
|
||||
| `GameServerMain.java` | 服务器入口,启动 WebSocket |
|
||||
| `model/Constants.java` | 游戏常量 + 消息类型 |
|
||||
| `model/Room.java` | 房间实体 |
|
||||
| `model/PlayerInfo.java` | 玩家信息 |
|
||||
| `model/GameMap.java` | 地图加载 + 碰撞检测 |
|
||||
| `model/MapData.java` | 地图序列化 DTO |
|
||||
| `model/Wall.java` | 墙体抽象基类 |
|
||||
| `model/StaticWall.java` | 不可破坏墙体 |
|
||||
| `ecs/ECSWorld.java` | ECS 世界容器 |
|
||||
| `ecs/System.java` | 系统接口 |
|
||||
| `ecs/components/Position.java` | 位置组件 |
|
||||
| `ecs/components/PlayerInput.java` | 输入组件 |
|
||||
| `ecs/components/Health.java` | 生命值组件 |
|
||||
| `ecs/components/Collision.java` | 碰撞组件 |
|
||||
| `ecs/components/RenderInfo.java` | 渲染信息组件 |
|
||||
| `server/GameWebSocketServer.java` | WebSocket 服务器 |
|
||||
| `server/RoomManager.java` | 房间生命周期管理 |
|
||||
| `server/GameService.java` | 游戏会话管理 |
|
||||
| `server/GameLoop.java` | 30TPS 固定帧循环 |
|
||||
| `server/MessageUtils.java` | JSON 安全提取工具 |
|
||||
| `server/MapStorage.java` | 文件地图持久化 |
|
||||
| `systems/PlayerInputSystem.java` | 移动 + 碰撞处理 |
|
||||
| `systems/StateSyncSystem.java` | 状态快照构建 |
|
||||
|
||||
### 前端
|
||||
|
||||
| 文件 | 职责 |
|
||||
|------|------|
|
||||
| `index.html` | HTML 外壳 |
|
||||
| `src/main.js` | 应用入口,大厅/游戏切换 |
|
||||
| `src/style.css` | 全局样式 |
|
||||
| `src/game/engine.js` | 游戏引擎,预测 + 校正 |
|
||||
| `src/game/scene.js` | Three.js 3D 渲染 |
|
||||
| `src/network/client.js` | WebSocket 客户端 |
|
||||
| `src/ui/lobby.js` | 大厅 UI |
|
||||
| `src/utils/constants.js` | 游戏常量 |
|
||||
| `src/utils/grid.js` | 网格碰撞 + 默认地图 |
|
||||
| `src/utils/input.js` | 键盘/鼠标输入 |
|
||||
Reference in New Issue
Block a user