Files
rougelike-demo/client/assets/scripts/App/Game/README.md
2025-12-14 23:35:08 +08:00

8.5 KiB

Game 模块

概述

Game 模块是游戏的核心业务模块,负责管理游戏世界、玩家控制、网络同步等功能。该模块基于玩家输入发送给服务器,服务器广播给所有玩家的在线游戏开发思路,确保帧间同步。

目录结构

App/Game/
├── World.ts              # 世界管理器,管理所有玩家
├── PlayerController.ts   # 本地玩家控制器,处理输入和移动
├── RemotePlayer.ts       # 远程玩家类,处理其他玩家的显示和同步
├── UIGame.ts            # 游戏主界面UI
└── README.md            # 本文档

核心类说明

1. World (世界管理器)

职责:

  • 管理游戏世界中的所有玩家(本地玩家 + 远程玩家)
  • 加载玩家模型预制体
  • 监听网络消息(MsgPlayerJoin、MsgPlayerMove)
  • 创建和销毁玩家对象

主要方法:

  • init(worldRoot, localPlayer): 初始化世界,传入世界根节点和本地玩家信息
  • destroy(): 销毁世界,清理所有资源
  • getLocalPlayerController(): 获取本地玩家控制器

使用示例:

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(): 获取玩家信息

内部实现:

// 键盘输入 → 计算移动方向 → 更新本地位置 → 检查阈值 → 发送网络请求

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

{
    x: number,  // 目标 X 坐标
    y: number   // 目标 Y 坐标(对应 3D 中的 Z 轴)
}

响应 (ResMove):

{
    success: boolean,
    message?: string,
    position?: Position  // 服务器可能修正的位置
}

3. 玩家加入广播 (MsgPlayerJoin)

当有新玩家登录或加入游戏时,服务器广播给所有在线玩家:

{
    playerId: string,
    playerName: string,
    position: Position,
    isNewPlayer: boolean,
    timestamp: number
}

4. 玩家移动广播 (MsgPlayerMove)

当有玩家移动时,服务器广播给所有在线玩家:

{
    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: 是否循环播放

使用示例:

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 中修改:

private moveSpeed: number = 5;  // 修改为你想要的速度

2. 如何修改网络同步阈值?

PlayerController.ts 中修改:

private moveSendThreshold: number = 0.5;  // 修改为你想要的阈值

3. 如何添加更多动画?

在 RoleController 中添加对应的动画剪辑,然后在 PlayerController 或 RemotePlayer 中调用:

this.roleController.PlayAnimation('attack', false);  // 播放攻击动画,不循环

4. 如何处理玩家离线?

目前未实现玩家离线处理,可以添加 MsgPlayerLeave 消息监听:

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 节点

相关文档