refactor
This commit is contained in:
		| @@ -8,7 +8,6 @@ export abstract class EntityManager extends Component { | ||||
|   fsm: StateMachine | ||||
|   private _state: EntityStateEnum | ||||
|  | ||||
|  | ||||
|   get state() { | ||||
|     return this._state | ||||
|   } | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| import { _decorator, Animation, Component } from 'cc' | ||||
| import { EntityTypeEnum, FsmParamTypeEnum } from '../Enum' | ||||
| import { EntityTypeEnum } from '../Common' | ||||
| import { FsmParamTypeEnum } from '../Enum' | ||||
| const { ccclass } = _decorator | ||||
| import State from './State' | ||||
| import SubStateMachine from './SubStateMachine' | ||||
|   | ||||
| @@ -1,8 +1,9 @@ | ||||
| import { _decorator, instantiate, ProgressBar, Label, Vec3, Tween, tween } from 'cc'; | ||||
| import { _decorator, instantiate, ProgressBar, Label, Vec3, Tween, tween, director } from 'cc'; | ||||
| import { EntityManager } from '../../Base/EntityManager'; | ||||
| import { ApiMsgEnum, EntityTypeEnum, IActor, InputTypeEnum, IVec2, toFixed } from '../../Common'; | ||||
| import { EntityStateEnum } from '../../Enum'; | ||||
| import { EntityStateEnum, EventEnum, SceneEnum } from '../../Enum'; | ||||
| import DataManager from '../../Global/DataManager'; | ||||
| import EventManager from '../../Global/EventManager'; | ||||
| import NetworkManager from '../../Global/NetworkManager'; | ||||
| import { rad2Angle } from '../../Utils'; | ||||
| import { WeaponManager } from '../Weapon/WeaponManager'; | ||||
| @@ -30,6 +31,8 @@ export class ActorManager extends EntityManager implements IActor { | ||||
|     private tw: Tween<any> | ||||
|     private targetPos: Vec3 | ||||
|  | ||||
|     private isDead = false | ||||
|  | ||||
|     get isSelf() { | ||||
|         return DataManager.Instance.myPlayerId === this.id | ||||
|     } | ||||
| @@ -60,11 +63,17 @@ export class ActorManager extends EntityManager implements IActor { | ||||
|         this.node.active = false | ||||
|     } | ||||
|  | ||||
|     tick(dt: number) { | ||||
|     async tick(dt: number) { | ||||
|         if (!this.isSelf) { | ||||
|             return | ||||
|         } | ||||
|  | ||||
|         if (this.hp <= 0 && !this.isDead) { | ||||
|             EventManager.Instance.emit(EventEnum.GameEnd) | ||||
|             this.isDead = true | ||||
|             return | ||||
|         } | ||||
|  | ||||
|         if (DataManager.Instance.jm.input.length()) { | ||||
|             const { x, y } = DataManager.Instance.jm.input | ||||
|             NetworkManager.Instance.sendMsg(ApiMsgEnum.MsgClientSync, { | ||||
| @@ -87,6 +96,7 @@ export class ActorManager extends EntityManager implements IActor { | ||||
|     } | ||||
|  | ||||
|     renderHP(data: IActor) { | ||||
|         this.hp = data.hp | ||||
|         this.hpBar.progress = data.hp / this.hpBar.totalLength | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -21,6 +21,7 @@ export enum EventEnum { | ||||
|   ExplosionBorn = 'ExplosionBorn', | ||||
|   RoomJoin = 'RoomJoin', | ||||
|   GameStart = 'GameStart', | ||||
|   GameEnd = 'GameEnd', | ||||
| } | ||||
|  | ||||
| export enum PrefabPathEnum { | ||||
|   | ||||
| @@ -16,25 +16,36 @@ const WEAPON_DAMAGE = 5 | ||||
| const PLAYER_RADIUS = 50 | ||||
| const BULLET_RADIUS = 10 | ||||
|  | ||||
| const mapW = 960 | ||||
| const mapH = 640 | ||||
|  | ||||
| export default class DataManager extends Singleton { | ||||
|   static get Instance() { | ||||
|     return super.GetInstance<DataManager>() | ||||
|   } | ||||
|  | ||||
|   //登陆数据 | ||||
|   myPlayerId = 1 | ||||
|  | ||||
|   //大厅数据 | ||||
|   roomInfo: IRoom | ||||
|  | ||||
|   //游戏数据 | ||||
|   stage: Node | ||||
|   jm: JoyStickManager | ||||
|  | ||||
|   prefabMap: Map<string, Prefab> = new Map() | ||||
|   textureMap: Map<string, SpriteFrame[]> = new Map() | ||||
|  | ||||
|   actorMap: Map<number, ActorManager> = new Map() | ||||
|   bulletMap: Map<number, BulletManager> = new Map() | ||||
|  | ||||
|   myPlayerId = 1 | ||||
|   roomInfo: IRoom | ||||
|   mapSize = { | ||||
|     x: 960, | ||||
|     y: 640, | ||||
|   reset() { | ||||
|     this.stage = null | ||||
|     this.jm = null | ||||
|     this.actorMap.clear() | ||||
|     this.bulletMap.clear() | ||||
|     this.prefabMap.clear() | ||||
|     this.textureMap.clear() | ||||
|   } | ||||
|  | ||||
|   state: IState = { | ||||
| @@ -85,8 +96,8 @@ export default class DataManager extends Singleton { | ||||
|         player.position.x += toFixed(x * PLAYER_SPEED * dt) | ||||
|         player.position.y += toFixed(y * PLAYER_SPEED * dt) | ||||
|  | ||||
|         player.position.x = clamp(player.position.x, -this.mapSize.x / 2, this.mapSize.x / 2) | ||||
|         player.position.y = clamp(player.position.y, -this.mapSize.y / 2, this.mapSize.y / 2) | ||||
|         player.position.x = clamp(player.position.x, -mapW / 2, mapW / 2) | ||||
|         player.position.y = clamp(player.position.y, -mapH / 2, mapH / 2) | ||||
|  | ||||
|         player.direction = { x, y } | ||||
|         break | ||||
| @@ -124,7 +135,7 @@ export default class DataManager extends Singleton { | ||||
|               break | ||||
|             } | ||||
|           } | ||||
|           if (Math.abs(bullet.position.x) > this.mapSize.x / 2 || Math.abs(bullet.position.y) > this.mapSize.y / 2) { | ||||
|           if (Math.abs(bullet.position.x) > mapW / 2 || Math.abs(bullet.position.y) > mapH / 2) { | ||||
|             EventManager.Instance.emit(EventEnum.ExplosionBorn, bullet.id, { | ||||
|               x: bullet.position.x, | ||||
|               y: bullet.position.y, | ||||
|   | ||||
| @@ -2,7 +2,7 @@ import Singleton from '../Base/Singleton' | ||||
| import { EventEnum } from '../Enum'; | ||||
|  | ||||
| interface IItem { | ||||
|   func: Function; | ||||
|   cb: Function; | ||||
|   ctx: unknown; | ||||
| } | ||||
|  | ||||
| @@ -13,25 +13,25 @@ export default class EventManager extends Singleton { | ||||
|  | ||||
|   private map: Map<EventEnum, Array<IItem>> = new Map(); | ||||
|  | ||||
|   on(event: EventEnum, func: Function, ctx?: unknown) { | ||||
|   on(event: EventEnum, cb: Function, ctx: unknown) { | ||||
|     if (this.map.has(event)) { | ||||
|       this.map.get(event).push({ func, ctx }); | ||||
|       this.map.get(event).push({ cb, ctx }); | ||||
|     } else { | ||||
|       this.map.set(event, [{ func, ctx }]); | ||||
|       this.map.set(event, [{ cb, ctx }]); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   off(event: EventEnum, func: Function, ctx?: unknown) { | ||||
|   off(event: EventEnum, cb: Function, ctx: unknown) { | ||||
|     if (this.map.has(event)) { | ||||
|       const index = this.map.get(event).findIndex(i => func === i.func && i.ctx === ctx); | ||||
|       const index = this.map.get(event).findIndex(i => cb === i.cb && i.ctx === ctx); | ||||
|       index > -1 && this.map.get(event).splice(index, 1); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   emit(event: EventEnum, ...params: unknown[]) { | ||||
|     if (this.map.has(event)) { | ||||
|       this.map.get(event).forEach(({ func, ctx }) => { | ||||
|         ctx ? func.apply(ctx, params) : func(...params); | ||||
|       this.map.get(event).forEach(({ cb, ctx }) => { | ||||
|         cb.apply(ctx, params) | ||||
|       }); | ||||
|     } | ||||
|   } | ||||
|   | ||||
| @@ -1,9 +1,14 @@ | ||||
| import Singleton from '../Base/Singleton' | ||||
| import { ApiMsgEnum, IModel, strdecode, strencode } from '../Common'; | ||||
| import { ApiMsgEnum, IModel } from '../Common'; | ||||
| import { binaryEncode, binaryDecode } from '../Common/Binary'; | ||||
|  | ||||
| const TIMEOUT = 5000 | ||||
|  | ||||
| interface IItem { | ||||
|   cb: Function; | ||||
|   ctx: unknown; | ||||
| } | ||||
|  | ||||
| export interface ICallApiRet<T> { | ||||
|   success: boolean; | ||||
|   error?: Error; | ||||
| @@ -18,7 +23,7 @@ export default class NetworkManager extends Singleton { | ||||
|  | ||||
|   ws: WebSocket | ||||
|   port = 8888 | ||||
|   maps: Map<ApiMsgEnum, Function[]> = new Map() | ||||
|   maps: Map<ApiMsgEnum, Array<IItem>> = new Map() | ||||
|   isConnected = false | ||||
|  | ||||
|   connect() { | ||||
| @@ -28,16 +33,17 @@ export default class NetworkManager extends Singleton { | ||||
|         return | ||||
|       } | ||||
|       this.ws = new WebSocket(`ws://localhost:${this.port}`) | ||||
|  | ||||
|       //onmessage接受的数据类型,只有在后端返回字节数组的时候才有效果 | ||||
|       this.ws.binaryType = 'arraybuffer'; | ||||
|  | ||||
|       this.ws.onopen = () => { | ||||
|         console.log("ws onopen") | ||||
|         this.isConnected = true | ||||
|         resolve(true) | ||||
|       } | ||||
|  | ||||
|       this.ws.onerror = () => { | ||||
|       this.ws.onerror = (e) => { | ||||
|         this.isConnected = false | ||||
|         console.log(e) | ||||
|         reject("ws onerror") | ||||
|       } | ||||
|  | ||||
| @@ -53,12 +59,11 @@ export default class NetworkManager extends Singleton { | ||||
|           try { | ||||
|             if (this.maps.has(name) && this.maps.get(name).length) { | ||||
|               console.log(json); | ||||
|               this.maps.get(name).forEach(cb => cb(data)) | ||||
|               this.maps.get(name).forEach(({ cb, ctx }) => cb.call(ctx, data)) | ||||
|             } | ||||
|           } catch (error) { | ||||
|             console.log("this.maps.get(name).forEach(cb => cb(restData))", error) | ||||
|             console.log("onmessage:", error) | ||||
|           } | ||||
|  | ||||
|         } catch (error) { | ||||
|           console.log('解析失败,不是合法的JSON格式', error) | ||||
|         } | ||||
| @@ -72,20 +77,19 @@ export default class NetworkManager extends Singleton { | ||||
|         // 超时处理 | ||||
|         const timer = setTimeout(() => { | ||||
|           resolve({ success: false, error: new Error('timeout') }) | ||||
|           this.unlistenMsg(name, cb) | ||||
|           this.unlistenMsg(name as any, cb, null) | ||||
|         }, TIMEOUT) | ||||
|  | ||||
|         // 回调处理 | ||||
|         const cb = (res) => { | ||||
|           resolve(res) | ||||
|           clearTimeout(timer) | ||||
|           this.unlistenMsg(name, cb) | ||||
|           this.unlistenMsg(name as any, cb, null) | ||||
|         } | ||||
|         this.listenMsg(name as any, cb) | ||||
|         this.listenMsg(name as any, cb, null) | ||||
|  | ||||
|         this.sendMsg(name as any, data) | ||||
|       } catch (error) { | ||||
|         console.log(error) | ||||
|         resolve({ success: false, error: error as Error }) | ||||
|       } | ||||
|     }) | ||||
| @@ -98,18 +102,19 @@ export default class NetworkManager extends Singleton { | ||||
|     this.ws.send(view.buffer) | ||||
|   } | ||||
|  | ||||
|   listenMsg<T extends keyof IModel['msg']>(name: T, cb: (args: IModel['msg'][T]) => void) { | ||||
|   listenMsg<T extends keyof IModel['msg']>(name: T, cb: (args: IModel['msg'][T]) => void, ctx: unknown) { | ||||
|     if (this.maps.has(name)) { | ||||
|       this.maps.get(name).push(cb) | ||||
|       this.maps.get(name).push({ ctx, cb }) | ||||
|     } else { | ||||
|       this.maps.set(name, [cb]) | ||||
|       this.maps.set(name, [{ ctx, cb }]) | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   unlistenMsg(name: ApiMsgEnum, cb: Function) { | ||||
|   unlistenMsg<T extends keyof IModel['msg']>(name: T, cb: (args: IModel['msg'][T]) => void, ctx: unknown) { | ||||
|     if (this.maps.has(name)) { | ||||
|       const index = this.maps.get(name).indexOf(cb) | ||||
|       index > -1 && this.maps.get(name).splice(index, 1) | ||||
|       const items = this.maps.get(name) | ||||
|       const index = items.findIndex(i => cb === i.cb && i.ctx === ctx); | ||||
|       index > -1 && items.splice(index, 1) | ||||
|     } | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -15,6 +15,11 @@ export default class ObjectPoolManager extends Singleton { | ||||
|     return objectName + 'Pool' | ||||
|   } | ||||
|  | ||||
|   reset() { | ||||
|     this.objectPool = null | ||||
|     this.map.clear() | ||||
|   } | ||||
|  | ||||
|   get(objectName: EntityTypeEnum) { | ||||
|     if (this.objectPool === null) { | ||||
|       this.objectPool = new Node("ObjectPool") | ||||
|   | ||||
| @@ -1,37 +1,52 @@ | ||||
| import { _decorator, Component, Node, Prefab, instantiate, SpriteFrame } from 'cc'; | ||||
| import { _decorator, Component, Node, Prefab, instantiate, SpriteFrame, director } from 'cc'; | ||||
| import { ActorManager } from '../Entity/Actor/ActorManager'; | ||||
| import DataManager from '../Global/DataManager'; | ||||
| import { JoyStickManager } from '../UI/JoyStickManager'; | ||||
| import { ResourceManager } from '../Global/ResourceManager'; | ||||
| import { PrefabPathEnum, TexturePathEnum } from '../Enum'; | ||||
| import { EventEnum, PrefabPathEnum, SceneEnum, TexturePathEnum } from '../Enum'; | ||||
| import NetworkManager from '../Global/NetworkManager'; | ||||
| import ObjectPoolManager from '../Global/ObjectPoolManager'; | ||||
| import { BulletManager } from '../Entity/Bullet/BulletManager'; | ||||
| import { ApiMsgEnum, EntityTypeEnum, IMsgServerSync, InputTypeEnum } from '../Common'; | ||||
| import EventManager from '../Global/EventManager'; | ||||
|  | ||||
| const { ccclass } = _decorator; | ||||
|  | ||||
| @ccclass('BattleManager') | ||||
| export class BattleManager extends Component { | ||||
|     stage: Node | ||||
|     ui: Node | ||||
|     isInited = false | ||||
|     onLoad() { | ||||
|         this.stage = DataManager.Instance.stage = this.node.getChildByName("Stage") | ||||
|         this.ui = this.node.getChildByName("UI") | ||||
|     } | ||||
|     private stage: Node | ||||
|     private ui: Node | ||||
|     private shouldUpdate = false | ||||
|  | ||||
|     async start() { | ||||
|         //清空 | ||||
|         this.clearGame() | ||||
|         await this.loadRes() | ||||
|         this.initScene() | ||||
|         await this.connectServer() | ||||
|  | ||||
|         //资源加载和网络连接同步执行 | ||||
|         await Promise.all([this.loadRes(), this.connectServer()]) | ||||
|  | ||||
|         this.initGame() | ||||
|  | ||||
|         // 在场景初始化完毕之前,卡主别的玩家,准备好以后再告知服务器,等所有玩家都准备好以后才开始,这里就不做了 | ||||
|         NetworkManager.Instance.listenMsg(ApiMsgEnum.MsgServerSync, this.handleSync); | ||||
|         this.isInited = true | ||||
|         NetworkManager.Instance.listenMsg(ApiMsgEnum.MsgServerSync, this.handleSync, this); | ||||
|         NetworkManager.Instance.listenMsg(ApiMsgEnum.MsgGameEnd, this.leaveGame, this); | ||||
|         EventManager.Instance.on(EventEnum.GameEnd, this.handleGameEnd, this) | ||||
|     } | ||||
|  | ||||
|     clearGame() { | ||||
|         //监听 | ||||
|         NetworkManager.Instance.unlistenMsg(ApiMsgEnum.MsgServerSync, this.handleSync, this); | ||||
|         NetworkManager.Instance.unlistenMsg(ApiMsgEnum.MsgGameEnd, this.leaveGame, this); | ||||
|         EventManager.Instance.off(EventEnum.GameEnd, this.handleGameEnd, this) | ||||
|  | ||||
|         //数据 | ||||
|         this.shouldUpdate = false | ||||
|         ObjectPoolManager.Instance.reset() | ||||
|         DataManager.Instance.reset() | ||||
|  | ||||
|         //节点 | ||||
|         this.stage = DataManager.Instance.stage = this.node.getChildByName("Stage") | ||||
|         this.ui = this.node.getChildByName("UI") | ||||
|         this.stage.destroyAllChildren() | ||||
|         this.ui.destroyAllChildren() | ||||
|     } | ||||
| @@ -53,12 +68,6 @@ export class BattleManager extends Component { | ||||
|         await Promise.all(list) | ||||
|     } | ||||
|  | ||||
|     async initScene() { | ||||
|         this.initJoyStick() | ||||
|         this.initShoot() | ||||
|         this.initMap() | ||||
|     } | ||||
|  | ||||
|     async connectServer() { | ||||
|         if (!await NetworkManager.Instance.connect().catch(() => false)) { | ||||
|             await new Promise((resolve) => setTimeout(resolve, 1000)) | ||||
| @@ -66,6 +75,26 @@ export class BattleManager extends Component { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     leaveGame() { | ||||
|         this.clearGame() | ||||
|         director.loadScene(SceneEnum.Hall); | ||||
|     } | ||||
|  | ||||
|     async handleGameEnd() { | ||||
|         const { success, res, error } = await NetworkManager.Instance.callApi(ApiMsgEnum.ApiGameEnd, { rid: DataManager.Instance.roomInfo.id }) | ||||
|         if (!success) { | ||||
|             console.log(error) | ||||
|             return; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     async initGame() { | ||||
|         this.initJoyStick() | ||||
|         this.initShoot() | ||||
|         this.initMap() | ||||
|         this.shouldUpdate = true | ||||
|     } | ||||
|  | ||||
|     initJoyStick() { | ||||
|         const prefab = DataManager.Instance.prefabMap.get(EntityTypeEnum.JoyStick) | ||||
|         const joySitck = instantiate(prefab) | ||||
| @@ -86,14 +115,8 @@ export class BattleManager extends Component { | ||||
|         map.setParent(this.stage) | ||||
|     } | ||||
|  | ||||
|     handleSync(inputs: IMsgServerSync) { | ||||
|         for (const input of inputs) { | ||||
|             DataManager.Instance.applyInput(input) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     update(dt: number) { | ||||
|         if (!this.isInited) { | ||||
|         if (!this.shouldUpdate) { | ||||
|             return | ||||
|         } | ||||
|         this.render() | ||||
| @@ -158,5 +181,12 @@ export class BattleManager extends Component { | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     handleSync(inputs: IMsgServerSync) { | ||||
|         for (const input of inputs) { | ||||
|             DataManager.Instance.applyInput(input) | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -25,14 +25,14 @@ export class HallManager extends Component { | ||||
|     onLoad() { | ||||
|         director.preloadScene(SceneEnum.Room); | ||||
|         EventManager.Instance.on(EventEnum.RoomJoin, this.joinRoom, this) | ||||
|         NetworkManager.Instance.listenMsg(ApiMsgEnum.MsgPlayerList, this.renderPlayers); | ||||
|         NetworkManager.Instance.listenMsg(ApiMsgEnum.MsgRoomList, this.renderRooms); | ||||
|         NetworkManager.Instance.listenMsg(ApiMsgEnum.MsgPlayerList, this.renderPlayers, this); | ||||
|         NetworkManager.Instance.listenMsg(ApiMsgEnum.MsgRoomList, this.renderRooms, this); | ||||
|     } | ||||
|  | ||||
|     onDestroy() { | ||||
|         EventManager.Instance.off(EventEnum.RoomJoin, this.joinRoom, this) | ||||
|         NetworkManager.Instance.unlistenMsg(ApiMsgEnum.MsgPlayerList, this.renderPlayers); | ||||
|         NetworkManager.Instance.unlistenMsg(ApiMsgEnum.MsgRoomList, this.renderRooms); | ||||
|         NetworkManager.Instance.unlistenMsg(ApiMsgEnum.MsgPlayerList, this.renderPlayers, this); | ||||
|         NetworkManager.Instance.unlistenMsg(ApiMsgEnum.MsgRoomList, this.renderRooms, this); | ||||
|     } | ||||
|  | ||||
|     start() { | ||||
|   | ||||
| @@ -1,8 +1,7 @@ | ||||
| import { _decorator, Component, Node, Prefab, director, instantiate } from 'cc'; | ||||
| import { ApiMsgEnum, IMsgGameStart, IMsgRoom } from '../Common'; | ||||
| import { EventEnum, SceneEnum } from '../Enum'; | ||||
| import { SceneEnum } from '../Enum'; | ||||
| import DataManager from '../Global/DataManager'; | ||||
| import EventManager from '../Global/EventManager'; | ||||
| import NetworkManager from '../Global/NetworkManager'; | ||||
| import { PlayerManager } from '../UI/PlayerManager'; | ||||
| const { ccclass, property } = _decorator; | ||||
| @@ -17,13 +16,13 @@ export class RoomManager extends Component { | ||||
|  | ||||
|     onLoad() { | ||||
|         director.preloadScene(SceneEnum.Battle); | ||||
|         NetworkManager.Instance.listenMsg(ApiMsgEnum.MsgRoom, this.renderPlayers); | ||||
|         NetworkManager.Instance.listenMsg(ApiMsgEnum.MsgGameStart, this.startGame); | ||||
|         NetworkManager.Instance.listenMsg(ApiMsgEnum.MsgRoom, this.renderPlayers, this); | ||||
|         NetworkManager.Instance.listenMsg(ApiMsgEnum.MsgGameStart, this.handleGameStart, this); | ||||
|     } | ||||
|  | ||||
|     onDestroy() { | ||||
|         NetworkManager.Instance.unlistenMsg(ApiMsgEnum.MsgRoom, this.renderPlayers); | ||||
|         NetworkManager.Instance.unlistenMsg(ApiMsgEnum.MsgGameStart, this.startGame); | ||||
|         NetworkManager.Instance.unlistenMsg(ApiMsgEnum.MsgRoom, this.renderPlayers, this); | ||||
|         NetworkManager.Instance.unlistenMsg(ApiMsgEnum.MsgGameStart, this.handleGameStart, this); | ||||
|     } | ||||
|  | ||||
|     async start() { | ||||
| @@ -32,7 +31,7 @@ export class RoomManager extends Component { | ||||
|         }) | ||||
|     } | ||||
|  | ||||
|     renderPlayers = ({ room: { players: list } }: IMsgRoom) => { | ||||
|     renderPlayers({ room: { players: list } }: IMsgRoom) { | ||||
|         for (const item of this.playerContainer.children) { | ||||
|             item.active = false | ||||
|         } | ||||
| @@ -67,13 +66,10 @@ export class RoomManager extends Component { | ||||
|             console.log(error) | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // director.loadScene(SceneEnum.Battle); | ||||
|     } | ||||
|  | ||||
|     startGame = ({ state }: IMsgGameStart) => { | ||||
|     handleGameStart({ state }: IMsgGameStart) { | ||||
|         DataManager.Instance.state = state | ||||
|  | ||||
|         director.loadScene(SceneEnum.Battle); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -9,7 +9,7 @@ export class RoomManager extends Component { | ||||
|   init({ id, players }: { id: number, players: Array<{ id: number, nickname: string }> }) { | ||||
|     this.id = id | ||||
|     const label = this.getComponent(Label) | ||||
|     label.string = `房间id:${id},当前人数:${players.length}` | ||||
|     label.string = `房间id:${id},当前人数:${players.length}` | ||||
|     this.node.active = true | ||||
|   } | ||||
|  | ||||
|   | ||||
| @@ -1,4 +1,10 @@ | ||||
| import { IPlayer, IRoom } from "./Model" | ||||
| export interface IPlayer { | ||||
|   id: number, nickname: string, rid: number | ||||
| } | ||||
|  | ||||
| export interface IRoom { | ||||
|   id: number, players: Array<IPlayer> | ||||
| } | ||||
|  | ||||
| export interface IApiPlayerListReq { | ||||
| } | ||||
| @@ -47,3 +53,9 @@ export interface IApiGameStartReq { | ||||
| } | ||||
|  | ||||
| export interface IApiGameStartRes { } | ||||
|  | ||||
| export interface IApiGameEndReq { | ||||
|   rid: number | ||||
| } | ||||
|  | ||||
| export interface IApiGameEndRes { } | ||||
| @@ -22,10 +22,12 @@ export enum ApiMsgEnum { | ||||
|   ApiRoomJoin, | ||||
|   ApiRoomLeave, | ||||
|   ApiGameStart, | ||||
|   ApiGameEnd, | ||||
|   MsgPlayerList, | ||||
|   MsgRoomList, | ||||
|   MsgRoom, | ||||
|   MsgGameStart, | ||||
|   MsgGameEnd, | ||||
|   MsgClientSync, | ||||
|   MsgServerSync, | ||||
| } | ||||
|   | ||||
| @@ -1,9 +1,50 @@ | ||||
| import { IApiGameEndReq, IApiGameStartReq, IApiGameStartRes, IApiPlayerJoinReq, IApiPlayerJoinRes, IApiPlayerListReq, IApiPlayerListRes, IApiRoomCreateReq, IApiRoomCreateRes, IApiRoomJoinReq, IApiRoomJoinRes, IApiRoomLeaveReq, IApiRoomLeaveRes, IApiRoomListReq, IApiRoomListRes, IApiGameEndRes } from './Api' | ||||
| import { ApiMsgEnum } from './Enum' | ||||
| import { IMsgClientSync, IMsgGameEnd, IMsgGameStart, IMsgPlayerList, IMsgRoom, IMsgRoomList, IMsgServerSync } from './Msg' | ||||
|  | ||||
| export interface IPlayer { | ||||
|   id: number, nickname: string, rid: number | ||||
| } | ||||
|  | ||||
| export interface IRoom { | ||||
|   id: number, players: Array<IPlayer> | ||||
| export interface IModel { | ||||
|   api: { | ||||
|     [ApiMsgEnum.ApiPlayerJoin]: { | ||||
|       req: IApiPlayerJoinReq, | ||||
|       res: IApiPlayerJoinRes, | ||||
|     } | ||||
|     [ApiMsgEnum.ApiPlayerList]: { | ||||
|       req: IApiPlayerListReq, | ||||
|       res: IApiPlayerListRes, | ||||
|     } | ||||
|     [ApiMsgEnum.ApiRoomList]: { | ||||
|       req: IApiRoomListReq, | ||||
|       res: IApiRoomListRes, | ||||
|     } | ||||
|     [ApiMsgEnum.ApiRoomCreate]: { | ||||
|       req: IApiRoomCreateReq, | ||||
|       res: IApiRoomCreateRes, | ||||
|     } | ||||
|     [ApiMsgEnum.ApiRoomJoin]: { | ||||
|       req: IApiRoomJoinReq, | ||||
|       res: IApiRoomJoinRes, | ||||
|     } | ||||
|     [ApiMsgEnum.ApiRoomLeave]: { | ||||
|       req: IApiRoomLeaveReq, | ||||
|       res: IApiRoomLeaveRes, | ||||
|     } | ||||
|     [ApiMsgEnum.ApiGameStart]: { | ||||
|       req: IApiGameStartReq, | ||||
|       res: IApiGameStartRes, | ||||
|     }, | ||||
|     [ApiMsgEnum.ApiGameEnd]: { | ||||
|       req: IApiGameEndReq, | ||||
|       res: IApiGameEndRes, | ||||
|     } | ||||
|   }, | ||||
|   msg: { | ||||
|     [ApiMsgEnum.MsgPlayerList]: IMsgPlayerList | ||||
|     [ApiMsgEnum.MsgRoomList]: IMsgRoomList, | ||||
|     [ApiMsgEnum.MsgRoom]: IMsgRoom, | ||||
|     [ApiMsgEnum.MsgGameStart]: IMsgGameStart, | ||||
|     [ApiMsgEnum.MsgGameEnd]: IMsgGameEnd, | ||||
|     [ApiMsgEnum.MsgClientSync]: IMsgClientSync, | ||||
|     [ApiMsgEnum.MsgServerSync]: IMsgServerSync, | ||||
|   } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| import { IPlayer, IRoom } from "./Model" | ||||
| import { IPlayer, IRoom } from "./Api" | ||||
| import { IClientInput, IState } from "./State" | ||||
|  | ||||
| export interface IMsgPlayerList { | ||||
| @@ -22,6 +22,10 @@ export interface IMsgGameStart { | ||||
|   state: IState | ||||
| } | ||||
|  | ||||
| export interface IMsgGameEnd { | ||||
| } | ||||
|  | ||||
|  | ||||
| export type IMsgClientSync = IClientInput | ||||
|  | ||||
| export type IMsgServerSync = Array<IClientInput> | ||||
| @@ -7,36 +7,43 @@ export default class Room { | ||||
|   id: number | ||||
|   players: Set<Player> = new Set() | ||||
|   lastSyncTime?: number | ||||
|   timers: NodeJS.Timer[] = [] | ||||
|  | ||||
|   private timers: NodeJS.Timer[] = [] | ||||
|   private inputs: Array<IClientInput> = [] | ||||
|  | ||||
|   constructor(rid: number) { | ||||
|     this.id = rid | ||||
|   } | ||||
|  | ||||
|  | ||||
|   join(uid: number) { | ||||
|     const player = PlayerManager.Instance.getPlayerById(uid) | ||||
|     if (player) { | ||||
|       player.rid = this.id | ||||
|       this.players.add(player) | ||||
|     } | ||||
|  | ||||
|   } | ||||
|  | ||||
|   leave(uid: number) { | ||||
|     const player = PlayerManager.Instance.getPlayerById(uid) | ||||
|     if (player) { | ||||
|       player.rid = -1 | ||||
|       player.connection.unlistenMsg(ApiMsgEnum.MsgClientSync, this.getInput, this) | ||||
|       this.players.delete(player) | ||||
|       if (!this.players.size) { | ||||
|         this.timers.forEach(t => clearInterval(t)) | ||||
|         RoomManager.Instance.closeRoom(this.id) | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   close() { | ||||
|     this.timers.forEach(t => clearInterval(t)) | ||||
|     for (const player of this.players) { | ||||
|       player.connection.sendMsg(ApiMsgEnum.MsgGameEnd, {}) | ||||
|       player.connection.unlistenMsg(ApiMsgEnum.MsgClientSync, this.getInput, this) | ||||
|     } | ||||
|     this.players.clear() | ||||
|   } | ||||
|  | ||||
|   sync() { | ||||
|     for (const player of this.players) { | ||||
|       player.connection.sendMsg(ApiMsgEnum.MsgRoom, { | ||||
| @@ -71,10 +78,10 @@ export default class Room { | ||||
|       player.connection.sendMsg(ApiMsgEnum.MsgGameStart, { | ||||
|         state | ||||
|       }) | ||||
|       player.connection.listenMsg(ApiMsgEnum.MsgClientSync, this.getInput, this) | ||||
|     } | ||||
|     this.listenPlayer() | ||||
|     let t1 = setInterval(() => { | ||||
|       this.syncInput() | ||||
|       this.sendInput() | ||||
|     }, 100) | ||||
|     let t2 = setInterval(() => { | ||||
|       this.timePast() | ||||
| @@ -82,19 +89,14 @@ export default class Room { | ||||
|     this.timers = [t1, t2] | ||||
|   } | ||||
|  | ||||
|   listenPlayer() { | ||||
|     for (const player of this.players) { | ||||
|       player.connection.listenMsg(ApiMsgEnum.MsgClientSync, (input) => { | ||||
|         this.inputs.push(input) | ||||
|       }) | ||||
|     } | ||||
|   getInput(input: IClientInput) { | ||||
|     this.inputs.push(input) | ||||
|   } | ||||
|  | ||||
|   syncInput() { | ||||
|   sendInput() { | ||||
|     const inputs = this.inputs | ||||
|     this.inputs = [] | ||||
|  | ||||
|  | ||||
|     for (const player of this.players) { | ||||
|       player.connection.sendMsg(ApiMsgEnum.MsgServerSync, inputs) | ||||
|     } | ||||
|   | ||||
| @@ -37,6 +37,7 @@ export default class RoomManager extends Singleton { | ||||
|   closeRoom(rid: number) { | ||||
|     const room = this.getRoomById(rid) | ||||
|     if (room) { | ||||
|       room.close() | ||||
|       this.rooms.delete(room) | ||||
|       this.idMapRoom.delete(rid) | ||||
|     } | ||||
|   | ||||
| @@ -1,6 +1,3 @@ | ||||
| import { IApiGameStartReq, IApiGameStartRes, IApiPlayerJoinReq, IApiPlayerJoinRes, IApiPlayerListReq, IApiPlayerListRes, IApiRoomCreateReq, IApiRoomCreateRes, IApiRoomJoinReq, IApiRoomJoinRes, IApiRoomLeaveReq, IApiRoomLeaveRes, IApiRoomListReq, IApiRoomListRes } from './Api' | ||||
| import { ApiMsgEnum } from './Enum' | ||||
| import { IMsgClientSync, IMsgGameStart, IMsgPlayerList, IMsgRoom, IMsgRoomList, IMsgServerSync } from './Msg' | ||||
| export * from './Api' | ||||
| export * from './Msg' | ||||
| export * from './Enum' | ||||
| @@ -9,44 +6,3 @@ export * from './State' | ||||
| export * from './Utils' | ||||
| export * from './Binary' | ||||
|  | ||||
| export interface IModel { | ||||
|   api: { | ||||
|     [ApiMsgEnum.ApiPlayerJoin]: { | ||||
|       req: IApiPlayerJoinReq, | ||||
|       res: IApiPlayerJoinRes, | ||||
|     } | ||||
|     [ApiMsgEnum.ApiPlayerList]: { | ||||
|       req: IApiPlayerListReq, | ||||
|       res: IApiPlayerListRes, | ||||
|     } | ||||
|     [ApiMsgEnum.ApiRoomList]: { | ||||
|       req: IApiRoomListReq, | ||||
|       res: IApiRoomListRes, | ||||
|     } | ||||
|     [ApiMsgEnum.ApiRoomCreate]: { | ||||
|       req: IApiRoomCreateReq, | ||||
|       res: IApiRoomCreateRes, | ||||
|     } | ||||
|     [ApiMsgEnum.ApiRoomJoin]: { | ||||
|       req: IApiRoomJoinReq, | ||||
|       res: IApiRoomJoinRes, | ||||
|     } | ||||
|     [ApiMsgEnum.ApiRoomLeave]: { | ||||
|       req: IApiRoomLeaveReq, | ||||
|       res: IApiRoomLeaveRes, | ||||
|     } | ||||
|     [ApiMsgEnum.ApiGameStart]: { | ||||
|       req: IApiGameStartReq, | ||||
|       res: IApiGameStartRes, | ||||
|     } | ||||
|   }, | ||||
|   msg: { | ||||
|     [ApiMsgEnum.MsgPlayerList]: IMsgPlayerList | ||||
|     [ApiMsgEnum.MsgRoomList]: IMsgRoomList, | ||||
|     [ApiMsgEnum.MsgRoom]: IMsgRoom, | ||||
|     [ApiMsgEnum.MsgGameStart]: IMsgGameStart, | ||||
|     [ApiMsgEnum.MsgClientSync]: IMsgClientSync, | ||||
|     [ApiMsgEnum.MsgServerSync]: IMsgServerSync, | ||||
|   } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -9,10 +9,15 @@ export enum ConnectionEventEnum { | ||||
|   Close = 'Close', | ||||
| } | ||||
|  | ||||
| interface IItem { | ||||
|   cb: Function; | ||||
|   ctx: unknown; | ||||
| } | ||||
|  | ||||
| export class Connection extends EventEmitter { | ||||
|   server: MyServer | ||||
|   ws: WebSocket | ||||
|   msgMap: Map<ApiMsgEnum, Function[]> = new Map() | ||||
|   msgMap: Map<ApiMsgEnum, Array<IItem>> = new Map() | ||||
|   playerId?: number; | ||||
|  | ||||
|   constructor(server: MyServer, ws: WebSocket) { | ||||
| @@ -34,7 +39,7 @@ export class Connection extends EventEmitter { | ||||
|         if (this.server.apiMap.has(name)) { | ||||
|           try { | ||||
|             const cb = this.server.apiMap.get(name) | ||||
|             const res = cb?.(this, data) | ||||
|             const res = cb.call(null, data) | ||||
|             this.sendMsg(name, { | ||||
|               success: true, | ||||
|               res, | ||||
| @@ -45,31 +50,34 @@ export class Connection extends EventEmitter { | ||||
|               error: (error as Error)?.message, | ||||
|             }) | ||||
|           } | ||||
|           return | ||||
|         } | ||||
|  | ||||
|         if (this.msgMap.has(name)) { | ||||
|           this.msgMap.get(name)?.forEach(cb => cb(data)) | ||||
|         } else { | ||||
|           try { | ||||
|             if (this.msgMap.has(name)) { | ||||
|               this.msgMap.get(name)?.forEach(({ cb, ctx }) => cb.call(ctx, data)) | ||||
|             } | ||||
|           } catch (error) { | ||||
|             console.log(error) | ||||
|           } | ||||
|         } | ||||
|       } catch (error) { | ||||
|         // console.log(`解析失败,${str}不是合法的JSON格式:`, error) | ||||
|         console.log(error) | ||||
|         console.log(`解析失败,不是合法的JSON格式:`, error) | ||||
|       } | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|   listenMsg<T extends keyof IModel['msg']>(name: T, cb: (args: IModel['msg'][T]) => void) { | ||||
|   listenMsg<T extends keyof IModel['msg']>(name: T, cb: (args: IModel['msg'][T]) => void, ctx: unknown) { | ||||
|     if (this.msgMap.has(name)) { | ||||
|       this.msgMap.get(name)?.push(cb) | ||||
|       this.msgMap.get(name)?.push({ cb, ctx }) | ||||
|     } else { | ||||
|       this.msgMap.set(name, [cb]) | ||||
|       this.msgMap.set(name, [{ cb, ctx }]) | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   unlistenMsg<T extends keyof IModel['msg']>(name: T, cb: (args: IModel['msg'][T]) => void) { | ||||
|   unlistenMsg<T extends keyof IModel['msg']>(name: T, cb: (args: IModel['msg'][T]) => void, ctx: unknown) { | ||||
|     if (this.msgMap.has(name)) { | ||||
|       const index = this.msgMap.get(name)?.indexOf(cb) || -1 | ||||
|       index > -1 && this.msgMap.get(name)?.splice(index, 1) | ||||
|       const items = this.msgMap.get(name) | ||||
|       const index = items.findIndex(i => cb === i.cb && i.ctx === ctx); | ||||
|       index > -1 && items.splice(index, 1) | ||||
|     } | ||||
|   } | ||||
|  | ||||
| @@ -80,7 +88,7 @@ export class Connection extends EventEmitter { | ||||
|     }) | ||||
|     const view = binaryEncode(name, data) | ||||
|     const buffer = Buffer.from(view.buffer) | ||||
|     console.log(`${getTime()}发送|字节数${buffer.length}|${this.playerId || -1}|${msg}`) | ||||
|     console.log(`${getTime()}发送|字节数${buffer.length}|${this.playerId || -1}|内存${(process.memoryUsage().heapUsed / 1024 / 1024).toFixed(2)}MB|${msg}`) | ||||
|     this.ws.send(buffer) | ||||
|   } | ||||
| } | ||||
| @@ -2,7 +2,7 @@ import { Connection, MyServer, MyServerEventEnum } from './Core'; | ||||
| import PlayerManager from './Biz/PlayerManager'; | ||||
| import RoomManager from './Biz/RoomManager'; | ||||
| import { getTime, symlinkCommon } from './Utils'; | ||||
| import { ApiMsgEnum, IApiGameStartReq, IApiGameStartRes, IApiPlayerJoinReq, IApiPlayerJoinRes, IApiPlayerListReq, IApiPlayerListRes, IApiRoomCreateReq, IApiRoomCreateRes, IApiRoomJoinReq, IApiRoomJoinRes, IApiRoomLeaveReq, IApiRoomLeaveRes, IApiRoomListReq, IApiRoomListRes, IModel } from './Common'; | ||||
| import { ApiMsgEnum, IApiGameEndReq, IApiGameEndRes, IApiGameStartReq, IApiGameStartRes, IApiPlayerJoinReq, IApiPlayerJoinRes, IApiPlayerListReq, IApiPlayerListRes, IApiRoomCreateReq, IApiRoomCreateRes, IApiRoomJoinReq, IApiRoomJoinRes, IApiRoomLeaveReq, IApiRoomLeaveRes, IApiRoomListReq, IApiRoomListRes, IModel } from './Common'; | ||||
|  | ||||
| const server = new MyServer({ port: 8888 }) | ||||
|  | ||||
| @@ -31,7 +31,6 @@ server.setApi(ApiMsgEnum.ApiPlayerJoin, (connection: Connection, { nickname }: I | ||||
|   } | ||||
| }) | ||||
|  | ||||
|  | ||||
| server.setApi(ApiMsgEnum.ApiRoomList, (connection: Connection, data: IApiRoomListReq): IApiRoomListRes => { | ||||
|   return { list: RoomManager.Instance.getRoomsView() } | ||||
| }) | ||||
| @@ -114,6 +113,28 @@ server.setApi(ApiMsgEnum.ApiGameStart, (connection: Connection, data: IApiGameSt | ||||
|   } | ||||
| }) | ||||
|  | ||||
| server.setApi(ApiMsgEnum.ApiGameEnd, (connection: Connection, data: IApiGameEndReq): IApiGameEndRes => { | ||||
|   if (connection.playerId) { | ||||
|     const player = PlayerManager.Instance.getPlayerById(connection.playerId) | ||||
|     if (player) { | ||||
|       const rid = player.rid | ||||
|       if (rid) { | ||||
|         RoomManager.Instance.closeRoom(rid) | ||||
|         PlayerManager.Instance.syncPlayers() | ||||
|         RoomManager.Instance.syncRooms() | ||||
|         return {} | ||||
|       } else { | ||||
|         throw new Error("ApiGameEnd 玩家不在房间") | ||||
|       } | ||||
|     } else { | ||||
|       throw new Error("ApiGameEnd 玩家不存在") | ||||
|     } | ||||
|   } else { | ||||
|     throw new Error("ApiGameEnd 玩家未登录") | ||||
|   } | ||||
| }) | ||||
|  | ||||
|  | ||||
| // start!! | ||||
| server.start().then(() => { | ||||
|   symlinkCommon() | ||||
|   | ||||
| @@ -72,7 +72,7 @@ | ||||
|     // "preserveSymlinks": true,                         /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ | ||||
|     "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ | ||||
|     /* Type Checking */ | ||||
|     "strict": true, /* Enable all strict type-checking options. */ | ||||
|     // "strict": true, /* Enable all strict type-checking options. */ | ||||
|     // "noImplicitAny": true,                            /* Enable error reporting for expressions and declarations with an implied 'any' type. */ | ||||
|     // "strictNullChecks": true,                         /* When type checking, take into account 'null' and 'undefined'. */ | ||||
|     // "strictFunctionTypes": true,                      /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ | ||||
|   | ||||
| @@ -5,5 +5,10 @@ | ||||
|   "version": "1.0.0", | ||||
|   "workspaces": [ | ||||
|     "apps/*" | ||||
|   ] | ||||
|   ], | ||||
|   "scripts": { | ||||
|     "lint": "eslint .", | ||||
|     "lint:fix": "eslint . --fix", | ||||
|     "dev": "yarn workspace @game/server run dev" | ||||
|   } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user