diff --git a/apps/client/assets/Scripts/Base/EntityManager.ts b/apps/client/assets/Scripts/Base/EntityManager.ts index 371641c..f5301a0 100644 --- a/apps/client/assets/Scripts/Base/EntityManager.ts +++ b/apps/client/assets/Scripts/Base/EntityManager.ts @@ -8,7 +8,6 @@ export abstract class EntityManager extends Component { fsm: StateMachine private _state: EntityStateEnum - get state() { return this._state } diff --git a/apps/client/assets/Scripts/Base/StateMachine.ts b/apps/client/assets/Scripts/Base/StateMachine.ts index 82f875e..b6bd400 100644 --- a/apps/client/assets/Scripts/Base/StateMachine.ts +++ b/apps/client/assets/Scripts/Base/StateMachine.ts @@ -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' diff --git a/apps/client/assets/Scripts/Entity/Actor/ActorManager.ts b/apps/client/assets/Scripts/Entity/Actor/ActorManager.ts index 6611215..f0dc438 100644 --- a/apps/client/assets/Scripts/Entity/Actor/ActorManager.ts +++ b/apps/client/assets/Scripts/Entity/Actor/ActorManager.ts @@ -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 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 } diff --git a/apps/client/assets/Scripts/Enum/index.ts b/apps/client/assets/Scripts/Enum/index.ts index a6decc0..c700b67 100644 --- a/apps/client/assets/Scripts/Enum/index.ts +++ b/apps/client/assets/Scripts/Enum/index.ts @@ -21,6 +21,7 @@ export enum EventEnum { ExplosionBorn = 'ExplosionBorn', RoomJoin = 'RoomJoin', GameStart = 'GameStart', + GameEnd = 'GameEnd', } export enum PrefabPathEnum { diff --git a/apps/client/assets/Scripts/Global/DataManager.ts b/apps/client/assets/Scripts/Global/DataManager.ts index 963466b..2fbf804 100644 --- a/apps/client/assets/Scripts/Global/DataManager.ts +++ b/apps/client/assets/Scripts/Global/DataManager.ts @@ -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() } + //登陆数据 + myPlayerId = 1 + + //大厅数据 + roomInfo: IRoom + + //游戏数据 stage: Node jm: JoyStickManager prefabMap: Map = new Map() textureMap: Map = new Map() - actorMap: Map = new Map() bulletMap: Map = 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, diff --git a/apps/client/assets/Scripts/Global/EventManager.ts b/apps/client/assets/Scripts/Global/EventManager.ts index aaf9311..42e99a6 100644 --- a/apps/client/assets/Scripts/Global/EventManager.ts +++ b/apps/client/assets/Scripts/Global/EventManager.ts @@ -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> = 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) }); } } diff --git a/apps/client/assets/Scripts/Global/NetworkManager.ts b/apps/client/assets/Scripts/Global/NetworkManager.ts index 905c754..e4cce26 100644 --- a/apps/client/assets/Scripts/Global/NetworkManager.ts +++ b/apps/client/assets/Scripts/Global/NetworkManager.ts @@ -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 { success: boolean; error?: Error; @@ -18,7 +23,7 @@ export default class NetworkManager extends Singleton { ws: WebSocket port = 8888 - maps: Map = new Map() + maps: Map> = 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(name: T, cb: (args: IModel['msg'][T]) => void) { + listenMsg(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(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) } } } diff --git a/apps/client/assets/Scripts/Global/ObjectPoolManager.ts b/apps/client/assets/Scripts/Global/ObjectPoolManager.ts index 41c332a..1a496fc 100644 --- a/apps/client/assets/Scripts/Global/ObjectPoolManager.ts +++ b/apps/client/assets/Scripts/Global/ObjectPoolManager.ts @@ -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") diff --git a/apps/client/assets/Scripts/Scene/BattleManager.ts b/apps/client/assets/Scripts/Scene/BattleManager.ts index 824d151..147837d 100644 --- a/apps/client/assets/Scripts/Scene/BattleManager.ts +++ b/apps/client/assets/Scripts/Scene/BattleManager.ts @@ -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) + } + } + } diff --git a/apps/client/assets/Scripts/Scene/HallManager.ts b/apps/client/assets/Scripts/Scene/HallManager.ts index 2c30918..b0766cb 100644 --- a/apps/client/assets/Scripts/Scene/HallManager.ts +++ b/apps/client/assets/Scripts/Scene/HallManager.ts @@ -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() { diff --git a/apps/client/assets/Scripts/Scene/RoomManager.ts b/apps/client/assets/Scripts/Scene/RoomManager.ts index e8cead8..a25cac1 100644 --- a/apps/client/assets/Scripts/Scene/RoomManager.ts +++ b/apps/client/assets/Scripts/Scene/RoomManager.ts @@ -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); } } diff --git a/apps/client/assets/Scripts/UI/RoomManager.ts b/apps/client/assets/Scripts/UI/RoomManager.ts index b2ae3bd..f0986ee 100644 --- a/apps/client/assets/Scripts/UI/RoomManager.ts +++ b/apps/client/assets/Scripts/UI/RoomManager.ts @@ -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 } diff --git a/apps/server/src/Common/Api.ts b/apps/server/src/Common/Api.ts index 908a54b..72d12cb 100644 --- a/apps/server/src/Common/Api.ts +++ b/apps/server/src/Common/Api.ts @@ -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 +} export interface IApiPlayerListReq { } @@ -46,4 +52,10 @@ export interface IApiGameStartReq { rid: number } -export interface IApiGameStartRes { } \ No newline at end of file +export interface IApiGameStartRes { } + +export interface IApiGameEndReq { + rid: number +} + +export interface IApiGameEndRes { } \ No newline at end of file diff --git a/apps/server/src/Common/Enum.ts b/apps/server/src/Common/Enum.ts index 0ec8245..f7cf599 100644 --- a/apps/server/src/Common/Enum.ts +++ b/apps/server/src/Common/Enum.ts @@ -22,10 +22,12 @@ export enum ApiMsgEnum { ApiRoomJoin, ApiRoomLeave, ApiGameStart, + ApiGameEnd, MsgPlayerList, MsgRoomList, MsgRoom, MsgGameStart, + MsgGameEnd, MsgClientSync, MsgServerSync, } diff --git a/apps/server/src/Common/Model.ts b/apps/server/src/Common/Model.ts index 7dbc0d8..53655fc 100644 --- a/apps/server/src/Common/Model.ts +++ b/apps/server/src/Common/Model.ts @@ -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 +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, + } } diff --git a/apps/server/src/Common/Msg.ts b/apps/server/src/Common/Msg.ts index c22c949..fff1eb2 100644 --- a/apps/server/src/Common/Msg.ts +++ b/apps/server/src/Common/Msg.ts @@ -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 \ No newline at end of file diff --git a/apps/server/src/biz/Room.ts b/apps/server/src/biz/Room.ts index 32b6a45..7e14021 100644 --- a/apps/server/src/biz/Room.ts +++ b/apps/server/src/biz/Room.ts @@ -7,36 +7,43 @@ export default class Room { id: number players: Set = new Set() lastSyncTime?: number - timers: NodeJS.Timer[] = [] + private timers: NodeJS.Timer[] = [] private inputs: Array = [] 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) } diff --git a/apps/server/src/biz/RoomManager.ts b/apps/server/src/biz/RoomManager.ts index 03104c9..313cc39 100644 --- a/apps/server/src/biz/RoomManager.ts +++ b/apps/server/src/biz/RoomManager.ts @@ -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) } diff --git a/apps/server/src/common/index.ts b/apps/server/src/common/index.ts index 94f03e6..9f27c3d 100644 --- a/apps/server/src/common/index.ts +++ b/apps/server/src/common/index.ts @@ -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, - } -} - diff --git a/apps/server/src/core/Connection.ts b/apps/server/src/core/Connection.ts index 313e8f0..288b3b4 100644 --- a/apps/server/src/core/Connection.ts +++ b/apps/server/src/core/Connection.ts @@ -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 = new Map() + msgMap: Map> = 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(name: T, cb: (args: IModel['msg'][T]) => void) { + listenMsg(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(name: T, cb: (args: IModel['msg'][T]) => void) { + unlistenMsg(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) } } \ No newline at end of file diff --git a/apps/server/src/index.ts b/apps/server/src/index.ts index c45650c..98728cc 100644 --- a/apps/server/src/index.ts +++ b/apps/server/src/index.ts @@ -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() diff --git a/apps/server/tsconfig.json b/apps/server/tsconfig.json index ce84d99..16e7643 100644 --- a/apps/server/tsconfig.json +++ b/apps/server/tsconfig.json @@ -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. */ diff --git a/package.json b/package.json index 664d151..b4c8538 100644 --- a/package.json +++ b/package.json @@ -5,5 +5,10 @@ "version": "1.0.0", "workspaces": [ "apps/*" - ] + ], + "scripts": { + "lint": "eslint .", + "lint:fix": "eslint . --fix", + "dev": "yarn workspace @game/server run dev" + } } \ No newline at end of file diff --git a/test.js b/test.js deleted file mode 100644 index 5333ce5..0000000 --- a/test.js +++ /dev/null @@ -1,6 +0,0 @@ -const ab = new ArrayBuffer(10) -const view = new DataView(ab) -view.setFloat32(0, 0.0012) - - -console.log(view.getFloat32(0)); \ No newline at end of file