游戏逻辑模块

This commit is contained in:
janing
2025-12-14 23:35:08 +08:00
parent aefe242b76
commit 3612ee74ea
11 changed files with 1017 additions and 0 deletions

View File

@@ -0,0 +1,9 @@
{
"ver": "1.2.0",
"importer": "directory",
"imported": true,
"uuid": "7884d98f-e4a9-4e92-aed3-214cddfcd2b4",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -0,0 +1,207 @@
import { _decorator, Component, Node, EventKeyboard, KeyCode, Input, input, Vec3 } from 'cc';
import { NetManager } from '../../Framework/Net/NetManager';
import { ReqMove, ResMove } from '../../Shared/protocols/PtlMove';
import { PlayerInfo } from '../../Shared/protocols/PtlLogin';
import { RoleController } from '../../CC/RoleController';
const { ccclass } = _decorator;
/**
* PlayerController 本地玩家控制器
* 负责处理本地玩家的输入、移动和动画
*/
@ccclass('PlayerController')
export class PlayerController extends Component {
/** 玩家信息 */
private playerInfo: PlayerInfo = null;
/** 角色控制器(控制动画) */
private roleController: RoleController = null;
/** 移动速度 */
private moveSpeed: number = 5;
/** 当前移动方向 */
private moveDirection: Vec3 = new Vec3(0, 0, 0);
/** 按键状态 */
private keyStates: Map<KeyCode, boolean> = new Map();
/** 是否正在移动 */
private isMoving: boolean = false;
/** 上次发送移动请求的位置 */
private lastSentPosition: Vec3 = new Vec3();
/** 移动阈值(超过这个距离才发送移动请求) */
private moveSendThreshold: number = 0.5;
/**
* 初始化玩家控制器
*/
public init(playerInfo: PlayerInfo): void {
this.playerInfo = playerInfo;
this.lastSentPosition.set(playerInfo.position.x, 0, playerInfo.position.y);
// 获取 RoleController 组件
this.roleController = this.node.getComponentInChildren(RoleController);
if (!this.roleController) {
console.warn('[PlayerController] 未找到 RoleController 组件');
} else {
// 初始播放待机动画
this.roleController.PlayAnimation('idle', true);
}
console.log('[PlayerController] 初始化完成:', playerInfo.name);
}
protected onEnable(): void {
// 注册键盘事件
input.on(Input.EventType.KEY_DOWN, this.onKeyDown, this);
input.on(Input.EventType.KEY_UP, this.onKeyUp, this);
}
protected onDisable(): void {
// 取消注册键盘事件
input.off(Input.EventType.KEY_DOWN, this.onKeyDown, this);
input.off(Input.EventType.KEY_UP, this.onKeyUp, this);
}
/**
* 键盘按下事件
*/
private onKeyDown(event: EventKeyboard): void {
this.keyStates.set(event.keyCode, true);
}
/**
* 键盘抬起事件
*/
private onKeyUp(event: EventKeyboard): void {
this.keyStates.set(event.keyCode, false);
}
protected update(dt: number): void {
// 计算移动方向
this.updateMoveDirection();
// 如果有移动方向,移动角色
if (this.moveDirection.lengthSqr() > 0) {
this.move(dt);
} else {
// 没有移动,播放待机动画
if (this.isMoving) {
this.isMoving = false;
if (this.roleController) {
this.roleController.PlayAnimation('idle', true);
}
}
}
}
/**
* 更新移动方向
*/
private updateMoveDirection(): void {
this.moveDirection.set(0, 0, 0);
// W - 向前
if (this.keyStates.get(KeyCode.KEY_W)) {
this.moveDirection.z -= 1;
}
// S - 向后
if (this.keyStates.get(KeyCode.KEY_S)) {
this.moveDirection.z += 1;
}
// A - 向左
if (this.keyStates.get(KeyCode.KEY_A)) {
this.moveDirection.x -= 1;
}
// D - 向右
if (this.keyStates.get(KeyCode.KEY_D)) {
this.moveDirection.x += 1;
}
// 归一化移动方向
if (this.moveDirection.lengthSqr() > 0) {
this.moveDirection.normalize();
}
}
/**
* 移动角色
*/
private move(dt: number): void {
// 计算移动增量
const moveOffset = this.moveDirection.clone().multiplyScalar(this.moveSpeed * dt);
// 更新节点位置
const currentPos = this.node.position.clone();
currentPos.add(moveOffset);
this.node.setPosition(currentPos);
// 更新朝向(让角色面向移动方向)
if (this.moveDirection.lengthSqr() > 0) {
const targetAngle = Math.atan2(this.moveDirection.x, -this.moveDirection.z) * (180 / Math.PI);
this.node.setRotationFromEuler(0, targetAngle, 0);
}
// 播放移动动画
if (!this.isMoving) {
this.isMoving = true;
if (this.roleController) {
this.roleController.PlayAnimation('move', true);
}
}
// 检查是否需要发送移动请求
this.checkSendMoveRequest(currentPos);
}
/**
* 检查是否需要发送移动请求
*/
private checkSendMoveRequest(currentPos: Vec3): void {
const distance = Vec3.distance(currentPos, this.lastSentPosition);
// 如果移动距离超过阈值,发送移动请求
if (distance >= this.moveSendThreshold) {
this.sendMoveRequest(currentPos.x, currentPos.z);
this.lastSentPosition.set(currentPos);
}
}
/**
* 发送移动请求到服务器
*/
private async sendMoveRequest(x: number, z: number): Promise<void> {
try {
const netManager = NetManager.getInstance();
const result = await netManager.callApi<ReqMove, ResMove>('Move', {
x: x,
y: z
});
if (!result) {
console.error('[PlayerController] 移动请求失败');
return;
}
// 服务器可能会修正位置,使用服务器返回的位置
if (result.position) {
const serverPos = result.position;
this.node.setPosition(serverPos.x, 0, serverPos.y);
this.lastSentPosition.set(serverPos.x, 0, serverPos.y);
}
} catch (error) {
console.error('[PlayerController] 发送移动请求异常:', error);
}
}
/**
* 获取玩家信息
*/
public getPlayerInfo(): PlayerInfo {
return this.playerInfo;
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "9e1f7c66-cb9e-4e5f-8642-605d3568c4ac",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -0,0 +1,325 @@
# 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)

View File

@@ -0,0 +1,11 @@
{
"ver": "1.0.1",
"importer": "text",
"imported": true,
"uuid": "fe661d46-0792-4a28-9fcc-11ef37219910",
"files": [
".json"
],
"subMetas": {},
"userData": {}
}

View File

@@ -0,0 +1,156 @@
import { Node, Vec3, Tween, tween } from 'cc';
import { Position } from '../../Shared/protocols/base';
import { RoleController } from '../../CC/RoleController';
/**
* RemotePlayer 远程玩家
* 负责管理远程玩家的显示和位置同步
*/
export class RemotePlayer {
/** 玩家节点 */
private playerNode: Node = null;
/** 玩家ID */
private playerId: string = '';
/** 玩家名称 */
private playerName: string = '';
/** 当前位置 */
private currentPosition: Vec3 = new Vec3();
/** 目标位置 */
private targetPosition: Vec3 = new Vec3();
/** 角色控制器(控制动画) */
private roleController: RoleController = null;
/** 移动补间 */
private moveTween: Tween<Node> = null;
/** 是否正在移动 */
private isMoving: boolean = false;
/**
* 初始化远程玩家
*/
public init(playerNode: Node, playerId: string, playerName: string, position: Position): void {
this.playerNode = playerNode;
this.playerId = playerId;
this.playerName = playerName;
this.currentPosition.set(position.x, 0, position.y);
this.targetPosition.set(position.x, 0, position.y);
// 获取 RoleController 组件
this.roleController = this.playerNode.getComponentInChildren(RoleController);
if (!this.roleController) {
console.warn('[RemotePlayer] 未找到 RoleController 组件');
} else {
// 初始播放待机动画
this.roleController.PlayAnimation('idle', true);
}
console.log('[RemotePlayer] 初始化完成:', playerName);
}
/**
* 更新位置
* 收到服务器广播的移动消息时调用
*/
public updatePosition(position: Position): void {
this.targetPosition.set(position.x, 0, position.y);
// 计算移动方向和距离
const direction = this.targetPosition.clone().subtract(this.currentPosition);
const distance = direction.length();
// 如果距离太小,不做处理
if (distance < 0.1) {
return;
}
// 停止之前的移动补间
if (this.moveTween) {
this.moveTween.stop();
this.moveTween = null;
}
// 计算朝向
if (distance > 0.01) {
const targetAngle = Math.atan2(direction.x, -direction.z) * (180 / Math.PI);
this.playerNode.setRotationFromEuler(0, targetAngle, 0);
}
// 播放移动动画
if (!this.isMoving) {
this.isMoving = true;
if (this.roleController) {
this.roleController.PlayAnimation('move', true);
}
}
// 计算移动时间(根据距离,假设速度为5单位/秒)
const moveSpeed = 5;
const moveDuration = distance / moveSpeed;
// 创建移动补间
this.moveTween = tween(this.playerNode)
.to(moveDuration, { position: this.targetPosition }, {
onUpdate: (target, ratio) => {
// 更新当前位置
this.currentPosition.set(this.playerNode.position);
},
onComplete: () => {
// 移动完成,播放待机动画
this.isMoving = false;
if (this.roleController) {
this.roleController.PlayAnimation('idle', true);
}
this.moveTween = null;
}
})
.start();
}
/**
* 获取玩家ID
*/
public getPlayerId(): string {
return this.playerId;
}
/**
* 获取玩家名称
*/
public getPlayerName(): string {
return this.playerName;
}
/**
* 获取玩家节点
*/
public getPlayerNode(): Node {
return this.playerNode;
}
/**
* 销毁远程玩家
*/
public destroy(): void {
// 停止移动补间
if (this.moveTween) {
this.moveTween.stop();
this.moveTween = null;
}
// 销毁节点
if (this.playerNode) {
this.playerNode.destroy();
this.playerNode = null;
}
this.roleController = null;
console.log('[RemotePlayer] 已销毁:', this.playerName);
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "48c1adc3-a2a6-4f7f-97b2-4df92ee822ee",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -0,0 +1,45 @@
import { _decorator, Component, Node } from 'cc';
import { UIBase } from '../../Framework/UI/UIBase';
import { World } from './World';
const { ccclass, property } = _decorator;
/**
* UIGame 游戏主界面
* 显示游戏UI和世界场景
*/
@ccclass('UIGame')
export class UIGame extends UIBase {
@property(Node)
worldRoot: Node = null;
protected onLoad(): void {
console.log('[UIGame] onLoad');
}
protected onEnable(): void {
console.log('[UIGame] onEnable');
}
protected onDisable(): void {
console.log('[UIGame] onDisable');
}
protected onDestroy(): void {
console.log('[UIGame] onDestroy');
}
/**
* 获取 UI 预制体路径
*/
public onGetUrl(): string {
return 'res://UI/UIGame';
}
/**
* 获取世界根节点
*/
public getWorldRoot(): Node {
return this.worldRoot;
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "1fa0f67a-24a5-4acc-a866-d5c288f16fc7",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -0,0 +1,228 @@
import { Node, Vec3, instantiate, Prefab } from 'cc';
import { NetManager } from '../../Framework/Net/NetManager';
import { ResMgr } from '../../Framework/ResMgr/ResMgr';
import { MsgPlayerJoin } from '../../Shared/protocols/MsgPlayerJoin';
import { MsgPlayerMove } from '../../Shared/protocols/MsgPlayerMove';
import { PlayerInfo } from '../../Shared/protocols/PtlLogin';
import { PlayerController } from './PlayerController';
import { RemotePlayer } from './RemotePlayer';
/**
* World 世界管理器
* 负责管理游戏世界中的所有玩家
* 包括本地玩家和远程玩家的创建、更新和销毁
*/
export class World {
private static instance: World = null;
/** 世界根节点 */
private worldRoot: Node = null;
/** 本地玩家信息 */
private localPlayer: PlayerInfo = null;
/** 本地玩家控制器 */
private localPlayerController: PlayerController = null;
/** 本地玩家节点 */
private localPlayerNode: Node = null;
/** 远程玩家列表 Map<playerId, RemotePlayer> */
private remotePlayers: Map<string, RemotePlayer> = new Map();
/** 玩家模型预制体 */
private playerPrefab: Prefab = null;
private constructor() {}
public static getInstance(): World {
if (!World.instance) {
World.instance = new World();
}
return World.instance;
}
/**
* 初始化世界
* @param worldRoot 世界根节点
* @param localPlayer 本地玩家信息
*/
public async init(worldRoot: Node, localPlayer: PlayerInfo): Promise<void> {
this.worldRoot = worldRoot;
this.localPlayer = localPlayer;
// 加载玩家模型预制体
await this.loadPlayerPrefab();
// 注册网络消息监听
this.registerNetworkListeners();
// 创建本地玩家
await this.createLocalPlayer();
console.log('[World] 世界初始化完成');
}
/**
* 加载玩家模型预制体
*/
private async loadPlayerPrefab(): Promise<void> {
try {
this.playerPrefab = await ResMgr.getInstance().load('resources', 'res://Actor/M1/M1', Prefab);
console.log('[World] 玩家模型预制体加载成功');
} catch (error) {
console.error('[World] 加载玩家模型预制体失败:', error);
}
}
/**
* 注册网络消息监听
*/
private registerNetworkListeners(): void {
const netManager = NetManager.getInstance();
// 监听玩家加入消息
netManager.listenMsg('PlayerJoin', (msg: MsgPlayerJoin) => {
this.onPlayerJoin(msg);
});
// 监听玩家移动消息
netManager.listenMsg('PlayerMove', (msg: MsgPlayerMove) => {
this.onPlayerMove(msg);
});
console.log('[World] 网络消息监听注册完成');
}
/**
* 创建本地玩家
*/
private async createLocalPlayer(): Promise<void> {
if (!this.playerPrefab) {
console.error('[World] 玩家模型预制体未加载');
return;
}
// 实例化玩家节点
this.localPlayerNode = instantiate(this.playerPrefab);
this.localPlayerNode.name = `Player_${this.localPlayer.id}_Local`;
this.localPlayerNode.setPosition(this.localPlayer.position.x, 0, this.localPlayer.position.y);
this.worldRoot.addChild(this.localPlayerNode);
// 创建本地玩家控制器
this.localPlayerController = this.localPlayerNode.addComponent(PlayerController);
this.localPlayerController.init(this.localPlayer);
console.log('[World] 本地玩家创建完成:', this.localPlayer.name);
}
/**
* 处理玩家加入消息
*/
private onPlayerJoin(msg: MsgPlayerJoin): void {
console.log('[World] 玩家加入:', msg.playerName);
// 如果是本地玩家,不处理
if (msg.playerId === this.localPlayer.id) {
return;
}
// 创建远程玩家
this.createRemotePlayer(msg);
}
/**
* 创建远程玩家
*/
private async createRemotePlayer(msg: MsgPlayerJoin): Promise<void> {
if (!this.playerPrefab) {
console.error('[World] 玩家模型预制体未加载');
return;
}
// 检查是否已存在
if (this.remotePlayers.has(msg.playerId)) {
console.warn('[World] 远程玩家已存在:', msg.playerId);
return;
}
// 实例化玩家节点
const playerNode = instantiate(this.playerPrefab);
playerNode.name = `Player_${msg.playerId}_Remote`;
playerNode.setPosition(msg.position.x, 0, msg.position.y);
this.worldRoot.addChild(playerNode);
// 创建远程玩家控制器
const remotePlayer = new RemotePlayer();
remotePlayer.init(playerNode, msg.playerId, msg.playerName, msg.position);
this.remotePlayers.set(msg.playerId, remotePlayer);
console.log('[World] 远程玩家创建完成:', msg.playerName);
}
/**
* 处理玩家移动消息
*/
private onPlayerMove(msg: MsgPlayerMove): void {
// 如果是本地玩家,不处理
if (msg.playerId === this.localPlayer.id) {
return;
}
// 更新远程玩家位置
const remotePlayer = this.remotePlayers.get(msg.playerId);
if (remotePlayer) {
remotePlayer.updatePosition(msg.position);
}
}
/**
* 获取本地玩家控制器
*/
public getLocalPlayerController(): PlayerController {
return this.localPlayerController;
}
/**
* 销毁世界
*/
public destroy(): void {
// 注意: TSRPC 的 listenMsg 不提供取消监听的方法
// 在实际使用中,监听会在连接断开时自动清除
// 销毁本地玩家
if (this.localPlayerNode) {
this.localPlayerNode.destroy();
this.localPlayerNode = null;
}
this.localPlayerController = null;
// 销毁所有远程玩家
this.remotePlayers.forEach((remotePlayer) => {
remotePlayer.destroy();
});
this.remotePlayers.clear();
// 释放资源
if (this.playerPrefab) {
ResMgr.getInstance().release('resources', 'res://Actor/M1/M1');
this.playerPrefab = null;
}
this.worldRoot = null;
this.localPlayer = null;
console.log('[World] 世界已销毁');
}
/**
* 清理单例
*/
public static clear(): void {
if (World.instance) {
World.instance.destroy();
World.instance = null;
}
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "80116901-37bd-4c32-85b3-da5aefa12b10",
"files": [],
"subMetas": {},
"userData": {}
}