326 lines
8.5 KiB
Markdown
326 lines
8.5 KiB
Markdown
|
|
# Game 模块
|
||
|
|
|
||
|
|
## 概述
|
||
|
|
|
||
|
|
Game 模块是游戏的核心业务模块,负责管理游戏世界、玩家控制、网络同步等功能。该模块基于玩家输入发送给服务器,服务器广播给所有玩家的在线游戏开发思路,确保帧间同步。
|
||
|
|
|
||
|
|
## 目录结构
|
||
|
|
|
||
|
|
```
|
||
|
|
App/Game/
|
||
|
|
├── World.ts # 世界管理器,管理所有玩家
|
||
|
|
├── PlayerController.ts # 本地玩家控制器,处理输入和移动
|
||
|
|
├── RemotePlayer.ts # 远程玩家类,处理其他玩家的显示和同步
|
||
|
|
├── UIGame.ts # 游戏主界面UI
|
||
|
|
└── README.md # 本文档
|
||
|
|
```
|
||
|
|
|
||
|
|
## 核心类说明
|
||
|
|
|
||
|
|
### 1. World (世界管理器)
|
||
|
|
|
||
|
|
**职责:**
|
||
|
|
- 管理游戏世界中的所有玩家(本地玩家 + 远程玩家)
|
||
|
|
- 加载玩家模型预制体
|
||
|
|
- 监听网络消息(MsgPlayerJoin、MsgPlayerMove)
|
||
|
|
- 创建和销毁玩家对象
|
||
|
|
|
||
|
|
**主要方法:**
|
||
|
|
- `init(worldRoot, localPlayer)`: 初始化世界,传入世界根节点和本地玩家信息
|
||
|
|
- `destroy()`: 销毁世界,清理所有资源
|
||
|
|
- `getLocalPlayerController()`: 获取本地玩家控制器
|
||
|
|
|
||
|
|
**使用示例:**
|
||
|
|
```typescript
|
||
|
|
import { World } from './App/Game/World';
|
||
|
|
import { PlayerInfo } from './Shared/protocols/PtlLogin';
|
||
|
|
|
||
|
|
const worldRoot = this.node; // 世界根节点
|
||
|
|
const playerInfo: PlayerInfo = { ... }; // 登录返回的玩家信息
|
||
|
|
|
||
|
|
await World.getInstance().init(worldRoot, playerInfo);
|
||
|
|
```
|
||
|
|
|
||
|
|
### 2. PlayerController (本地玩家控制器)
|
||
|
|
|
||
|
|
**职责:**
|
||
|
|
- 监听键盘输入(WASD)
|
||
|
|
- 控制本地玩家移动
|
||
|
|
- 控制角色动画(待机、移动)
|
||
|
|
- 向服务器发送移动请求(PtlMove)
|
||
|
|
|
||
|
|
**主要特性:**
|
||
|
|
- **WASD 移动控制**: W(前)、S(后)、A(左)、D(右)
|
||
|
|
- **自动朝向**: 角色会自动面向移动方向
|
||
|
|
- **动画切换**: 移动时播放 move 动画,静止时播放 idle 动画
|
||
|
|
- **阈值同步**: 移动距离超过 0.5 单位时才向服务器发送移动请求,减少网络负担
|
||
|
|
|
||
|
|
**主要方法:**
|
||
|
|
- `init(playerInfo)`: 初始化玩家控制器
|
||
|
|
- `getPlayerInfo()`: 获取玩家信息
|
||
|
|
|
||
|
|
**内部实现:**
|
||
|
|
```typescript
|
||
|
|
// 键盘输入 → 计算移动方向 → 更新本地位置 → 检查阈值 → 发送网络请求
|
||
|
|
```
|
||
|
|
|
||
|
|
### 3. RemotePlayer (远程玩家)
|
||
|
|
|
||
|
|
**职责:**
|
||
|
|
- 显示其他玩家的角色模型
|
||
|
|
- 接收服务器广播的移动消息
|
||
|
|
- 平滑移动到目标位置(使用 Tween 补间)
|
||
|
|
- 控制远程玩家的动画
|
||
|
|
|
||
|
|
**主要特性:**
|
||
|
|
- **平滑移动**: 使用 Tween 让远程玩家平滑移动到目标位置
|
||
|
|
- **自动朝向**: 根据移动方向自动旋转角色
|
||
|
|
- **动画同步**: 移动时播放 move 动画,到达目标后播放 idle 动画
|
||
|
|
|
||
|
|
**主要方法:**
|
||
|
|
- `init(playerNode, playerId, playerName, position)`: 初始化远程玩家
|
||
|
|
- `updatePosition(position)`: 更新远程玩家位置(收到 MsgPlayerMove 时调用)
|
||
|
|
- `destroy()`: 销毁远程玩家
|
||
|
|
|
||
|
|
### 4. UIGame (游戏主界面)
|
||
|
|
|
||
|
|
**职责:**
|
||
|
|
- 游戏主界面 UI 组件
|
||
|
|
- 提供世界根节点(用于创建玩家)
|
||
|
|
- 继承自 Framework/UI/UIBase
|
||
|
|
|
||
|
|
**主要方法:**
|
||
|
|
- `onGetUrl()`: 返回 UI 预制体路径 `res://UI/UIGame`
|
||
|
|
- `getWorldRoot()`: 获取世界根节点
|
||
|
|
|
||
|
|
## 协议使用
|
||
|
|
|
||
|
|
### 1. 登录返回数据 (ResLogin)
|
||
|
|
|
||
|
|
登录成功后,服务器返回玩家信息,包含:
|
||
|
|
- `player`: 玩家详细信息(id、name、position 等)
|
||
|
|
- `isNewPlayer`: 是否新玩家
|
||
|
|
|
||
|
|
该数据在进入 Game 状态时传递给 World 和 PlayerController。
|
||
|
|
|
||
|
|
### 2. 移动请求 (PtlMove)
|
||
|
|
|
||
|
|
**请求 (ReqMove):**
|
||
|
|
```typescript
|
||
|
|
{
|
||
|
|
x: number, // 目标 X 坐标
|
||
|
|
y: number // 目标 Y 坐标(对应 3D 中的 Z 轴)
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**响应 (ResMove):**
|
||
|
|
```typescript
|
||
|
|
{
|
||
|
|
success: boolean,
|
||
|
|
message?: string,
|
||
|
|
position?: Position // 服务器可能修正的位置
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### 3. 玩家加入广播 (MsgPlayerJoin)
|
||
|
|
|
||
|
|
当有新玩家登录或加入游戏时,服务器广播给所有在线玩家:
|
||
|
|
```typescript
|
||
|
|
{
|
||
|
|
playerId: string,
|
||
|
|
playerName: string,
|
||
|
|
position: Position,
|
||
|
|
isNewPlayer: boolean,
|
||
|
|
timestamp: number
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### 4. 玩家移动广播 (MsgPlayerMove)
|
||
|
|
|
||
|
|
当有玩家移动时,服务器广播给所有在线玩家:
|
||
|
|
```typescript
|
||
|
|
{
|
||
|
|
playerId: string,
|
||
|
|
playerName: string,
|
||
|
|
position: Position,
|
||
|
|
timestamp: number
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
## 工作流程
|
||
|
|
|
||
|
|
### 初始化流程
|
||
|
|
|
||
|
|
```
|
||
|
|
登录成功 (ResLogin)
|
||
|
|
↓
|
||
|
|
进入 AppStatusGame 状态
|
||
|
|
↓
|
||
|
|
加载 UIGame 界面
|
||
|
|
↓
|
||
|
|
初始化 World
|
||
|
|
↓
|
||
|
|
加载玩家模型预制体 (res://Actor/M1/M1)
|
||
|
|
↓
|
||
|
|
注册网络监听 (MsgPlayerJoin, MsgPlayerMove)
|
||
|
|
↓
|
||
|
|
创建本地玩家 (PlayerController)
|
||
|
|
↓
|
||
|
|
游戏开始
|
||
|
|
```
|
||
|
|
|
||
|
|
### 本地玩家移动流程
|
||
|
|
|
||
|
|
```
|
||
|
|
按下 WASD 键
|
||
|
|
↓
|
||
|
|
PlayerController 计算移动方向
|
||
|
|
↓
|
||
|
|
更新本地节点位置
|
||
|
|
↓
|
||
|
|
播放 move 动画
|
||
|
|
↓
|
||
|
|
检查移动距离是否超过阈值 (0.5)
|
||
|
|
↓
|
||
|
|
发送 PtlMove 请求到服务器
|
||
|
|
↓
|
||
|
|
服务器返回确认
|
||
|
|
↓
|
||
|
|
(可选)使用服务器修正的位置
|
||
|
|
```
|
||
|
|
|
||
|
|
### 远程玩家同步流程
|
||
|
|
|
||
|
|
```
|
||
|
|
服务器广播 MsgPlayerMove
|
||
|
|
↓
|
||
|
|
World 接收消息
|
||
|
|
↓
|
||
|
|
查找对应的 RemotePlayer
|
||
|
|
↓
|
||
|
|
RemotePlayer.updatePosition()
|
||
|
|
↓
|
||
|
|
计算移动方向和距离
|
||
|
|
↓
|
||
|
|
启动 Tween 补间动画
|
||
|
|
↓
|
||
|
|
播放 move 动画
|
||
|
|
↓
|
||
|
|
平滑移动到目标位置
|
||
|
|
↓
|
||
|
|
到达后播放 idle 动画
|
||
|
|
```
|
||
|
|
|
||
|
|
## 角色模型和动画
|
||
|
|
|
||
|
|
### 模型路径
|
||
|
|
- 预制体路径: `res://Actor/M1/M1`
|
||
|
|
- 使用 ResMgr 加载
|
||
|
|
|
||
|
|
### RoleController 脚本
|
||
|
|
|
||
|
|
模型下挂载了 `RoleController` 脚本,可以控制角色动画:
|
||
|
|
- `PlayAnimation(action, loop)`: 播放指定动画
|
||
|
|
- `action`: "idle"(待机) | "move"(移动) | "attack"(攻击) | "death"(死亡)
|
||
|
|
- `loop`: 是否循环播放
|
||
|
|
|
||
|
|
**使用示例:**
|
||
|
|
```typescript
|
||
|
|
const roleController = playerNode.getComponentInChildren(RoleController);
|
||
|
|
roleController.PlayAnimation('move', true); // 播放移动动画并循环
|
||
|
|
roleController.PlayAnimation('idle', true); // 播放待机动画并循环
|
||
|
|
```
|
||
|
|
|
||
|
|
## 输入控制
|
||
|
|
|
||
|
|
### 键盘映射
|
||
|
|
- **W**: 向前移动(世界坐标 -Z 方向)
|
||
|
|
- **S**: 向后移动(世界坐标 +Z 方向)
|
||
|
|
- **A**: 向左移动(世界坐标 -X 方向)
|
||
|
|
- **D**: 向右移动(世界坐标 +X 方向)
|
||
|
|
|
||
|
|
### 移动特性
|
||
|
|
- **多方向合成**: 可以同时按多个方向键,移动方向会自动合成并归一化
|
||
|
|
- **移动速度**: 默认 5 单位/秒
|
||
|
|
- **自动朝向**: 角色会自动旋转面向移动方向
|
||
|
|
|
||
|
|
## 网络同步策略
|
||
|
|
|
||
|
|
### 本地玩家
|
||
|
|
- **客户端预测**: 立即更新本地位置,无需等待服务器确认
|
||
|
|
- **阈值同步**: 移动距离超过 0.5 单位时才发送网络请求,减少网络流量
|
||
|
|
- **服务器修正**: 如果服务器返回修正后的位置,使用服务器位置
|
||
|
|
|
||
|
|
### 远程玩家
|
||
|
|
- **延迟补偿**: 使用 Tween 补间动画平滑移动到目标位置
|
||
|
|
- **基于速度的时间**: 根据距离和移动速度计算补间时间,确保移动看起来自然
|
||
|
|
|
||
|
|
## 常见问题
|
||
|
|
|
||
|
|
### 1. 如何修改移动速度?
|
||
|
|
|
||
|
|
在 `PlayerController.ts` 中修改:
|
||
|
|
```typescript
|
||
|
|
private moveSpeed: number = 5; // 修改为你想要的速度
|
||
|
|
```
|
||
|
|
|
||
|
|
### 2. 如何修改网络同步阈值?
|
||
|
|
|
||
|
|
在 `PlayerController.ts` 中修改:
|
||
|
|
```typescript
|
||
|
|
private moveSendThreshold: number = 0.5; // 修改为你想要的阈值
|
||
|
|
```
|
||
|
|
|
||
|
|
### 3. 如何添加更多动画?
|
||
|
|
|
||
|
|
在 RoleController 中添加对应的动画剪辑,然后在 PlayerController 或 RemotePlayer 中调用:
|
||
|
|
```typescript
|
||
|
|
this.roleController.PlayAnimation('attack', false); // 播放攻击动画,不循环
|
||
|
|
```
|
||
|
|
|
||
|
|
### 4. 如何处理玩家离线?
|
||
|
|
|
||
|
|
目前未实现玩家离线处理,可以添加 `MsgPlayerLeave` 消息监听:
|
||
|
|
```typescript
|
||
|
|
netManager.listenMsg('PlayerLeave', (msg) => {
|
||
|
|
const remotePlayer = this.remotePlayers.get(msg.playerId);
|
||
|
|
if (remotePlayer) {
|
||
|
|
remotePlayer.destroy();
|
||
|
|
this.remotePlayers.delete(msg.playerId);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
```
|
||
|
|
|
||
|
|
## 待实现功能
|
||
|
|
|
||
|
|
- [ ] 玩家名称显示
|
||
|
|
- [ ] 玩家血条显示
|
||
|
|
- [ ] 玩家离线处理
|
||
|
|
- [ ] 战斗系统
|
||
|
|
- [ ] 技能系统
|
||
|
|
- [ ] 物品拾取
|
||
|
|
- [ ] 聊天系统 UI
|
||
|
|
- [ ] 小地图
|
||
|
|
|
||
|
|
## 注意事项
|
||
|
|
|
||
|
|
1. **坐标系转换**: Cocos 3D 使用 (x, y, z),服务器协议使用 (x, y),需要注意:
|
||
|
|
- 服务器的 y 对应 Cocos 的 z
|
||
|
|
- Cocos 的 y 是高度,固定为 0
|
||
|
|
|
||
|
|
2. **网络消息监听**: 所有网络消息监听在 World 中注册,退出时需要取消监听
|
||
|
|
|
||
|
|
3. **资源释放**: 退出游戏时需要调用 `World.clear()` 清理所有资源
|
||
|
|
|
||
|
|
4. **单例模式**: World 使用单例模式,确保全局只有一个实例
|
||
|
|
|
||
|
|
5. **UI 预制体**: 需要在 Cocos Creator 中创建 `res://UI/UIGame` 预制体,并添加 `worldRoot` 节点
|
||
|
|
|
||
|
|
## 相关文档
|
||
|
|
|
||
|
|
- [Framework/FSM 模块文档](../../Framework/FSM/README.md)
|
||
|
|
- [Framework/Net 模块文档](../../Framework/Net/README.md)
|
||
|
|
- [Framework/ResMgr 模块文档](../../Framework/ResMgr/README.md)
|
||
|
|
- [Framework/UI 模块文档](../../Framework/UI/README.md)
|
||
|
|
- [App/AppStatus 模块文档](../AppStatus/README.md)
|