binary encode

This commit is contained in:
sli97 2022-12-04 22:10:30 +08:00
parent a678a5b3fc
commit c31af6b02a
23 changed files with 1089 additions and 745 deletions

View File

@ -1,10 +1,10 @@
import { _decorator, instantiate, ProgressBar, Label } from 'cc'; import { _decorator, instantiate, ProgressBar, Label } from 'cc';
import { EntityManager } from '../../Base/EntityManager'; import { EntityManager } from '../../Base/EntityManager';
import { ApiMsgEnum, EntityTypeEnum, IActor, InputTypeEnum, IVec2 } from '../../Common'; import { ApiMsgEnum, EntityTypeEnum, IActor, InputTypeEnum, IVec2, toFixed } from '../../Common';
import { EntityStateEnum } from '../../Enum'; import { EntityStateEnum } from '../../Enum';
import DataManager from '../../Global/DataManager'; import DataManager from '../../Global/DataManager';
import NetworkManager from '../../Global/NetworkManager'; import NetworkManager from '../../Global/NetworkManager';
import { rad2Angle, toFixed } from '../../Utils'; import { rad2Angle } from '../../Utils';
import { WeaponManager } from '../Weapon/WeaponManager'; import { WeaponManager } from '../Weapon/WeaponManager';
import { PlayerStateMachine } from './ActorStateMachine'; import { PlayerStateMachine } from './ActorStateMachine';
const { ccclass } = _decorator; const { ccclass } = _decorator;
@ -59,9 +59,9 @@ export class ActorManager extends EntityManager implements IActor {
return return
} }
if (DataManager.Instance.jm.input.length()) {
const { x, y } = DataManager.Instance.jm.input const { x, y } = DataManager.Instance.jm.input
NetworkManager.Instance.sendMsg(ApiMsgEnum.MsgClientSync, { NetworkManager.Instance.sendMsg(ApiMsgEnum.MsgClientSync, {
input: {
type: InputTypeEnum.ActorMove, type: InputTypeEnum.ActorMove,
id: this.id, id: this.id,
direction: { direction: {
@ -69,10 +69,11 @@ export class ActorManager extends EntityManager implements IActor {
y: toFixed(y), y: toFixed(y),
}, },
dt: toFixed(dt) dt: toFixed(dt)
}
}) })
} }
}
render(data: IActor) { render(data: IActor) {
this.renderHP(data) this.renderHP(data)
this.renderPosition(data) this.renderPosition(data)

View File

@ -1,11 +1,10 @@
import { _decorator, Node, Vec2, UITransform } from 'cc' import { _decorator, Node, Vec2, UITransform } from 'cc'
import { EntityManager } from '../../Base/EntityManager' import { EntityManager } from '../../Base/EntityManager'
import { ApiMsgEnum, EntityTypeEnum, InputTypeEnum } from '../../Common' import { ApiMsgEnum, EntityTypeEnum, InputTypeEnum, toFixed } from '../../Common'
import { EntityStateEnum, EventEnum } from '../../Enum' import { EntityStateEnum, EventEnum } from '../../Enum'
import DataManager from '../../Global/DataManager' import DataManager from '../../Global/DataManager'
import EventManager from '../../Global/EventManager' import EventManager from '../../Global/EventManager'
import NetworkManager from '../../Global/NetworkManager' import NetworkManager from '../../Global/NetworkManager'
import { toFixed } from '../../Utils'
import { WeaponStateMachine } from './WeaponStateMachine' import { WeaponStateMachine } from './WeaponStateMachine'
const { ccclass } = _decorator const { ccclass } = _decorator
@ -62,7 +61,6 @@ export class WeaponManager extends EntityManager {
const directionVec2 = new Vec2(pointWorldPos.x - anchorWorldPos.x, pointWorldPos.y - anchorWorldPos.y).normalize() const directionVec2 = new Vec2(pointWorldPos.x - anchorWorldPos.x, pointWorldPos.y - anchorWorldPos.y).normalize()
NetworkManager.Instance.sendMsg(ApiMsgEnum.MsgClientSync, { NetworkManager.Instance.sendMsg(ApiMsgEnum.MsgClientSync, {
input: {
type: InputTypeEnum.WeaponShoot, type: InputTypeEnum.WeaponShoot,
owner: this.owner, owner: this.owner,
position: { position: {
@ -73,7 +71,6 @@ export class WeaponManager extends EntityManager {
x: toFixed(directionVec2.x), x: toFixed(directionVec2.x),
y: toFixed(directionVec2.y), y: toFixed(directionVec2.y),
}, },
}
}) })
} }
} }

View File

@ -1,6 +1,6 @@
import { Node, Prefab, SpriteFrame } from 'cc' import { Node, Prefab, SpriteFrame } from 'cc'
import Singleton from '../Base/Singleton' import Singleton from '../Base/Singleton'
import { EntityTypeEnum, IBullet, IClientInput, InputTypeEnum, IRoom, IState } from '../Common' import { EntityTypeEnum, IBullet, IClientInput, InputTypeEnum, IRoom, IState, toFixed } from '../Common'
import { ActorManager } from '../Entity/Actor/ActorManager' import { ActorManager } from '../Entity/Actor/ActorManager'
import { BulletManager } from '../Entity/Bullet/BulletManager' import { BulletManager } from '../Entity/Bullet/BulletManager'
import { EventEnum } from '../Enum' import { EventEnum } from '../Enum'
@ -82,8 +82,8 @@ export default class DataManager extends Singleton {
return return
} }
player.position.x += x * PLAYER_SPEED * dt player.position.x += toFixed(x * PLAYER_SPEED * dt)
player.position.y += y * PLAYER_SPEED * dt player.position.y += toFixed(y * PLAYER_SPEED * dt)
player.direction = { x, y } player.direction = { x, y }
break break
} }
@ -112,8 +112,8 @@ export default class DataManager extends Singleton {
const player = players[j]; const player = players[j];
if (((player.position.x - bullet.position.x) ** 2 + (player.position.y - bullet.position.y) ** 2) < (PLAYER_RADIUS + BULLET_RADIUS) ** 2) { if (((player.position.x - bullet.position.x) ** 2 + (player.position.y - bullet.position.y) ** 2) < (PLAYER_RADIUS + BULLET_RADIUS) ** 2) {
EventManager.Instance.emit(EventEnum.ExplosionBorn, bullet.id, { EventManager.Instance.emit(EventEnum.ExplosionBorn, bullet.id, {
x: (player.position.x + bullet.position.x) / 2, x: toFixed((player.position.x + bullet.position.x) / 2),
y: (player.position.y + bullet.position.y) / 2, y: toFixed((player.position.y + bullet.position.y) / 2),
}) })
player.hp -= WEAPON_DAMAGE player.hp -= WEAPON_DAMAGE
@ -132,8 +132,8 @@ export default class DataManager extends Singleton {
} }
for (const bullet of this.state.bullets) { for (const bullet of this.state.bullets) {
bullet.position.x += bullet.direction.x * BULLET_SPEED * dt bullet.position.x += toFixed(bullet.direction.x * BULLET_SPEED * dt)
bullet.position.y += bullet.direction.y * BULLET_SPEED * dt bullet.position.y += toFixed(bullet.direction.y * BULLET_SPEED * dt)
} }
} }
} }

View File

@ -1,5 +1,7 @@
import Singleton from '../Base/Singleton' import Singleton from '../Base/Singleton'
import { IModel } from '../Common'; import { ApiMsgEnum, IModel, strdecode, strencode } from '../Common';
import { binaryEncode } from '../Common/Binary';
import { binaryDecode } from '../Utils';
const TIMEOUT = 5000 const TIMEOUT = 5000
@ -17,7 +19,7 @@ export default class NetworkManager extends Singleton {
ws: WebSocket ws: WebSocket
port = 8888 port = 8888
cbs: Map<string, Function[]> = new Map() maps: Map<ApiMsgEnum, Function[]> = new Map()
isConnected = false isConnected = false
connect() { connect() {
@ -27,6 +29,8 @@ export default class NetworkManager extends Singleton {
return return
} }
this.ws = new WebSocket(`ws://localhost:${this.port}`) this.ws = new WebSocket(`ws://localhost:${this.port}`)
this.ws.binaryType = 'arraybuffer';
this.ws.onopen = () => { this.ws.onopen = () => {
console.log("ws onopen") console.log("ws onopen")
this.isConnected = true this.isConnected = true
@ -45,15 +49,15 @@ export default class NetworkManager extends Singleton {
this.ws.onmessage = (e) => { this.ws.onmessage = (e) => {
try { try {
const json = JSON.parse(e.data) const json = binaryDecode(e.data)
const { name, data } = json const { name, data } = json
try { try {
if (this.cbs.has(name) && this.cbs.get(name).length) { if (this.maps.has(name) && this.maps.get(name).length) {
console.log(json); console.log(json);
this.cbs.get(name).forEach(cb => cb(data)) this.maps.get(name).forEach(cb => cb(data))
} }
} catch (error) { } catch (error) {
console.log("this.cbs.get(name).forEach(cb => cb(restData))", error) console.log("this.maps.get(name).forEach(cb => cb(restData))", error)
} }
} catch (error) { } catch (error) {
@ -80,7 +84,7 @@ export default class NetworkManager extends Singleton {
} }
this.listenMsg(name as any, cb) this.listenMsg(name as any, cb)
this.ws.send(JSON.stringify({ name, data })) this.sendMsg(name as any, data)
} catch (error) { } catch (error) {
console.log(error) console.log(error)
resolve({ success: false, error: error as Error }) resolve({ success: false, error: error as Error })
@ -89,21 +93,24 @@ export default class NetworkManager extends Singleton {
} }
sendMsg<T extends keyof IModel['msg']>(name: T, data: IModel['msg'][T]) { sendMsg<T extends keyof IModel['msg']>(name: T, data: IModel['msg'][T]) {
this.ws.send(JSON.stringify({ name, data })) const view = binaryEncode(name, data)
console.log("view", view.buffer);
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) {
if (this.cbs.has(name)) { if (this.maps.has(name)) {
this.cbs.get(name).push(cb) this.maps.get(name).push(cb)
} else { } else {
this.cbs.set(name, [cb]) this.maps.set(name, [cb])
} }
} }
unlistenMsg(name: string, cb: Function) { unlistenMsg(name: ApiMsgEnum, cb: Function) {
if (this.cbs.has(name)) { if (this.maps.has(name)) {
const index = this.cbs.get(name).indexOf(cb) const index = this.maps.get(name).indexOf(cb)
index > -1 && this.cbs.get(name).splice(index, 1) index > -1 && this.maps.get(name).splice(index, 1)
} }
} }
} }

View File

@ -8,7 +8,6 @@ import NetworkManager from '../Global/NetworkManager';
import ObjectPoolManager from '../Global/ObjectPoolManager'; import ObjectPoolManager from '../Global/ObjectPoolManager';
import { BulletManager } from '../Entity/Bullet/BulletManager'; import { BulletManager } from '../Entity/Bullet/BulletManager';
import { ApiMsgEnum, EntityTypeEnum, IMsgServerSync, InputTypeEnum } from '../Common'; import { ApiMsgEnum, EntityTypeEnum, IMsgServerSync, InputTypeEnum } from '../Common';
import { toFixed } from '../Utils';
const { ccclass } = _decorator; const { ccclass } = _decorator;
@ -86,7 +85,7 @@ export class BattleManager extends Component {
map.setParent(this.stage) map.setParent(this.stage)
} }
handleSync({ inputs }: IMsgServerSync) { handleSync(inputs: IMsgServerSync) {
for (const input of inputs) { for (const input of inputs) {
DataManager.Instance.applyInput(input) DataManager.Instance.applyInput(input)
} }
@ -102,7 +101,7 @@ export class BattleManager extends Component {
tick(dt: number) { tick(dt: number) {
this.tickPlayer(dt) this.tickPlayer(dt)
this.tickGlobal(dt) // this.tickGlobal(dt)
} }
tickPlayer(dt: number) { tickPlayer(dt: number) {
@ -115,14 +114,14 @@ export class BattleManager extends Component {
} }
} }
tickGlobal(dt: number) { // tickGlobal(dt: number) {
NetworkManager.Instance.sendMsg(ApiMsgEnum.MsgClientSync, { // NetworkManager.Instance.sendMsg(ApiMsgEnum.MsgClientSync, {
input: { // input: {
type: InputTypeEnum.TimePast, // type: InputTypeEnum.TimePast,
dt: toFixed(dt), // dt: toFixed(dt),
} // }
}) // })
} // }
render() { render() {
this.renderPlayer() this.renderPlayer()

View File

@ -1,4 +1,5 @@
import { SpriteFrame } from "cc" import { SpriteFrame } from "cc"
import { ApiMsgEnum, InputTypeEnum, strdecode } from "../Common"
const INDEX_REG = /\((\d+)\)/ const INDEX_REG = /\((\d+)\)/
@ -9,4 +10,78 @@ export const sortSpriteFrame = (spriteFrame: Array<SpriteFrame>) =>
export const rad2Angle = (rad: number) => rad / Math.PI * 180 export const rad2Angle = (rad: number) => rad / Math.PI * 180
export const toFixed = (num: number, digit: number = 4): number => Math.floor(num * 10 ** digit) / 10 ** digit export const binaryDecode = (buffer: ArrayBuffer) => {
let index = 0
const view = new DataView(buffer)
const type = view.getUint8(index++)
if (type === ApiMsgEnum.MsgClientSync) {
const inputType = view.getUint8(index++)
if (inputType === InputTypeEnum.ActorMove) {
const id = view.getUint8(index++)
const directionX = view.getFloat32(index)
index += 4
const directionY = view.getFloat32(index)
index += 4
const dt = view.getFloat32(index)
index += 4
const msg = {
name: ApiMsgEnum.MsgClientSync,
data: {
type: InputTypeEnum.ActorMove,
id,
direction: {
x: directionX,
y: directionY,
},
dt
}
}
return msg
} else if (inputType === InputTypeEnum.WeaponShoot) {
const id = view.getUint8(index++)
const positionX = view.getFloat32(index)
index += 4
const positionY = view.getFloat32(index)
index += 4
const directionX = view.getFloat32(index)
index += 4
const directionY = view.getFloat32(index)
index += 4
const msg = {
name: ApiMsgEnum.MsgClientSync,
data: {
type: InputTypeEnum.WeaponShoot,
id,
position: {
x: positionX,
y: positionY,
},
direction: {
x: directionX,
y: directionY,
},
}
}
return msg
} else {
const dt = view.getFloat32(index)
index += 4
const msg = {
name: ApiMsgEnum.MsgClientSync,
data: {
type: InputTypeEnum.TimePast,
dt,
}
}
return msg
}
} else {
return {
name: type,
data: JSON.parse(strdecode(new Uint8Array(buffer.slice(1))))
}
}
}

View File

@ -0,0 +1,67 @@
import { ApiMsgEnum, InputTypeEnum } from "./Enum";
import { strencode } from "./Utils";
export const binaryEncode = (proto: ApiMsgEnum, data: any): DataView => {
if (proto === ApiMsgEnum.MsgClientSync) {
switch (data.type) {
case InputTypeEnum.ActorMove: {
let index = 0
const ab = new ArrayBuffer(3 + 12)
const view = new DataView(ab)
view.setUint8(index++, proto)
view.setUint8(index++, data.type)
view.setUint8(index++, data.id)
view.setFloat32(index, data.direction.x)
index += 4
view.setFloat32(index, data.direction.y)
index += 4
view.setFloat32(index, data.dt)
index += 4
return view
}
case InputTypeEnum.WeaponShoot: {
let index = 0
const ab = new ArrayBuffer(3 + 16)
const view = new DataView(ab)
view.setUint8(index++, proto)
view.setUint8(index++, data.type)
view.setUint8(index++, data.id)
view.setFloat32(index, data.position.x)
index += 4
view.setFloat32(index, data.position.y)
index += 4
view.setFloat32(index, data.direction.x)
index += 4
view.setFloat32(index, data.direction.y)
index += 4
return view
}
case InputTypeEnum.TimePast: {
let index = 0
const ab = new ArrayBuffer(1 + 1 + 4)
const view = new DataView(ab)
view.setUint8(index++, proto)
view.setUint8(index++, data.type)
view.setFloat32(index, data.dt)
index += 4
return view
}
default: {
const ab = new ArrayBuffer(0)
const view = new DataView(ab)
return view
}
}
} else {
let index = 0
const str = JSON.stringify(data)
const ta = strencode(str)
const ab = new ArrayBuffer(ta.length + 1)
const view = new DataView(ab)
view.setUint8(index++, proto)
for (let i = 0; i < ta.length; i++) {
view.setUint8(index++, ta[i])
}
return view
}
}

View File

@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "9888254e-f4d1-4b2a-a814-75fb288e474f",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@ -1,23 +1,46 @@
// export enum ApiMsgEnum {
// ApiPlayerList = 'ApiPlayerList',
// ApiPlayerJoin = 'ApiPlayerJoin',
// ApiRoomList = 'ApiRoomList',
// ApiRoomCreate = 'ApiRoomCreate',
// ApiRoomJoin = 'ApiRoomJoin',
// ApiRoomLeave = 'ApiRoomLeave',
// ApiGameStart = 'ApiGameStart',
// MsgPlayerList = 'MsgPlayerList',
// MsgRoomList = 'MsgRoomList',
// MsgRoom = 'MsgRoom',
// MsgGameStart = 'MsgGameStart',
// MsgClientSync = 'MsgClientSync',
// MsgServerSync = 'MsgServerSync',
// }
export enum ApiMsgEnum { export enum ApiMsgEnum {
ApiPlayerList = 'ApiPlayerList', ApiPlayerList,
ApiPlayerJoin = 'ApiPlayerJoin', ApiPlayerJoin,
ApiRoomList = 'ApiRoomList', ApiRoomList,
ApiRoomCreate = 'ApiRoomCreate', ApiRoomCreate,
ApiRoomJoin = 'ApiRoomJoin', ApiRoomJoin,
ApiRoomLeave = 'ApiRoomLeave', ApiRoomLeave,
ApiGameStart = 'ApiGameStart', ApiGameStart,
MsgPlayerList = 'MsgPlayerList', MsgPlayerList,
MsgRoomList = 'MsgRoomList', MsgRoomList,
MsgRoom = 'MsgRoom', MsgRoom,
MsgGameStart = 'MsgGameStart', MsgGameStart,
MsgClientSync = 'MsgClientSync', MsgClientSync,
MsgServerSync = 'MsgServerSync', MsgServerSync,
} }
// export enum InputTypeEnum {
// ActorMove = 'ActorMove',
// WeaponShoot = 'WeaponShoot',
// TimePast = 'TimePast',
// }
export enum InputTypeEnum { export enum InputTypeEnum {
ActorMove = 'ActorMove', ActorMove,
WeaponShoot = 'WeaponShoot', WeaponShoot,
TimePast = 'TimePast', TimePast,
} }
export enum EntityTypeEnum { export enum EntityTypeEnum {

View File

@ -22,10 +22,6 @@ export interface IMsgGameStart {
state: IState state: IState
} }
export interface IMsgClientSync { export type IMsgClientSync = IClientInput
input: IClientInput
}
export interface IMsgServerSync { export type IMsgServerSync = Array<IClientInput>
inputs: Array<IClientInput>
}

View File

@ -0,0 +1,42 @@
export const toFixed = (num: number, digit: number = 4): number => Math.floor(num * 10 ** digit) / 10 ** digit
export const strencode = (str: string) => {
let byteArray: number[] = [];
for (let i = 0; i < str.length; i++) {
let charCode = str.charCodeAt(i);
if (charCode <= 0x7f) {
byteArray.push(charCode);
} else if (charCode <= 0x7ff) {
byteArray.push(0xc0 | (charCode >> 6), 0x80 | (charCode & 0x3f));
} else if (charCode <= 0xffff) {
byteArray.push(0xe0 | (charCode >> 12), 0x80 | ((charCode & 0xfc0) >> 6), 0x80 | (charCode & 0x3f));
} else {
byteArray.push(0xf0 | (charCode >> 18), 0x80 | ((charCode & 0x3f000) >> 12), 0x80 | ((charCode & 0xfc0) >> 6), 0x80 | (charCode & 0x3f));
}
}
return new Uint8Array(byteArray);
}
export const strdecode = (bytes: Uint8Array) => {
let array: number[] = [];
let offset = 0;
let charCode = 0;
let end = bytes.length;
while (offset < end) {
if (bytes[offset] < 128) {
charCode = bytes[offset];
offset += 1;
} else if (bytes[offset] < 224) {
charCode = ((bytes[offset] & 0x3f) << 6) + (bytes[offset + 1] & 0x3f);
offset += 2;
} else if (bytes[offset] < 240) {
charCode = ((bytes[offset] & 0x0f) << 12) + ((bytes[offset + 1] & 0x3f) << 6) + (bytes[offset + 2] & 0x3f);
offset += 3;
} else {
charCode = ((bytes[offset] & 0x07) << 18) + ((bytes[offset + 1] & 0x3f) << 12) + ((bytes[offset + 1] & 0x3f) << 6) + (bytes[offset + 2] & 0x3f);
offset += 4;
}
array.push(charCode);
}
return String.fromCharCode.apply(null, array);
}

View File

@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "b44d77f2-f4af-4b06-bc29-986d191fe180",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@ -0,0 +1,64 @@
import { EventEmitter } from 'stream';
import WebSocket, { WebSocketServer } from 'ws';
import { ApiMsgEnum } from '../Common';
import { Connection, ConnectionEventEnum } from './Connection';
export interface IMyServerOptions {
port: number
}
export enum MyServerEventEnum {
Connect = 'Connect',
DisConnect = 'DisConnect',
}
export class MyServer extends EventEmitter {
wss?: WebSocketServer
port: number
connections: Set<Connection> = new Set()
apiMap: Map<ApiMsgEnum, Function> = new Map()
constructor({ port = 8080 }: Partial<IMyServerOptions>) {
super()
this.port = port
}
start() {
return new Promise((resolve, reject) => {
this.wss = new WebSocketServer({ port: this.port });
this.wss.on('connection', this.handleConnect.bind(this));
this.wss.on("error", (e) => {
reject(e)
})
this.wss.on("close", () => {
console.log("MyServer 服务关闭");
})
this.wss.on("listening", () => {
resolve(true)
})
})
}
handleConnect(ws: WebSocket) {
//初始化
const connection = new Connection(this, ws)
//向外告知有人来了
this.connections.add(connection)
this.emit(MyServerEventEnum.Connect, connection)
//向外告知有人走了
connection.on(ConnectionEventEnum.Close, (code: number, reason: string) => {
this.connections.delete(connection)
this.emit(MyServerEventEnum.DisConnect, connection, code, reason)
})
}
setApi(apiName: ApiMsgEnum, cb: Function) {
this.apiMap.set(apiName, cb)
}
}

View File

@ -1,4 +1,4 @@
import Connection from '../Core/Connection' import { Connection } from "../Core"
export default class Player { export default class Player {
id: number id: number

View File

@ -1,9 +1,8 @@
import Singleton from '../Base/Singleton' import Singleton from '../Base/Singleton'
import { ApiMsgEnum, IApiPlayerJoinReq } from '../Common' import { ApiMsgEnum, IApiPlayerJoinReq } from '../Common'
import { Connection } from '../Core'
import Player from './Player' import Player from './Player'
import RoomManager from './RoomManager' import RoomManager from './RoomManager'
import Connection from '../Core/Connection'
export default class PlayerManager extends Singleton { export default class PlayerManager extends Singleton {
static get Instance() { static get Instance() {
return super.GetInstance<PlayerManager>() return super.GetInstance<PlayerManager>()

View File

@ -1,4 +1,4 @@
import { ApiMsgEnum, EntityTypeEnum, IClientInput, IState } from '../Common' import { ApiMsgEnum, EntityTypeEnum, IClientInput, InputTypeEnum, IState, toFixed } from '../Common'
import type Player from './Player' import type Player from './Player'
import PlayerManager from './PlayerManager' import PlayerManager from './PlayerManager'
import RoomManager from './RoomManager' import RoomManager from './RoomManager'
@ -6,12 +6,14 @@ import RoomManager from './RoomManager'
export default class Room { export default class Room {
id: number id: number
players: Set<Player> = new Set() players: Set<Player> = new Set()
lastSyncTime?: number
private inputs: Array<IClientInput> = []
constructor(rid: number) { constructor(rid: number) {
this.id = rid this.id = rid
} }
private inputs: Array<IClientInput> = []
join(uid: number) { join(uid: number) {
const player = PlayerManager.Instance.getPlayerById(uid) const player = PlayerManager.Instance.getPlayerById(uid)
@ -71,12 +73,15 @@ export default class Room {
this.listenPlayer() this.listenPlayer()
setInterval(() => { setInterval(() => {
this.syncInput() this.syncInput()
}, 300) }, 100)
setInterval(() => {
this.timePast()
}, 16)
} }
listenPlayer() { listenPlayer() {
for (const player of this.players) { for (const player of this.players) {
player.connection.listenMsg(ApiMsgEnum.MsgClientSync, ({ input }) => { player.connection.listenMsg(ApiMsgEnum.MsgClientSync, (input) => {
this.inputs.push(input) this.inputs.push(input)
}) })
} }
@ -85,10 +90,21 @@ export default class Room {
syncInput() { syncInput() {
const inputs = this.inputs const inputs = this.inputs
this.inputs = [] this.inputs = []
for (const player of this.players) { for (const player of this.players) {
player.connection.sendMsg(ApiMsgEnum.MsgServerSync, { player.connection.sendMsg(ApiMsgEnum.MsgServerSync, inputs)
inputs
})
} }
} }
timePast() {
let now = process.uptime();
const dt = now - (this.lastSyncTime ?? now)
this.inputs.push({
type: InputTypeEnum.TimePast,
dt: toFixed(dt)
})
this.lastSyncTime = now;
}
} }

View File

@ -1,12 +1,12 @@
import { IApiGameStartReq, IApiGameStartRes, IApiPlayerJoinReq, IApiPlayerJoinRes, IApiPlayerListReq, IApiPlayerListRes, IApiRoomCreateReq, IApiRoomCreateRes, IApiRoomJoinReq, IApiRoomJoinRes, IApiRoomLeaveReq, IApiRoomLeaveRes, IApiRoomListReq, IApiRoomListRes } from './Api' import { IApiGameStartReq, IApiGameStartRes, IApiPlayerJoinReq, IApiPlayerJoinRes, IApiPlayerListReq, IApiPlayerListRes, IApiRoomCreateReq, IApiRoomCreateRes, IApiRoomJoinReq, IApiRoomJoinRes, IApiRoomLeaveReq, IApiRoomLeaveRes, IApiRoomListReq, IApiRoomListRes } from './Api'
import { ApiMsgEnum } from './Enum' import { ApiMsgEnum } from './Enum'
import { IMsgClientSync, IMsgGameStart, IMsgPlayerList, IMsgRoom, IMsgRoomList, IMsgServerSync } from './Msg' import { IMsgClientSync, IMsgGameStart, IMsgPlayerList, IMsgRoom, IMsgRoomList, IMsgServerSync } from './Msg'
import { IClientInput } from './State'
export * from './Api' export * from './Api'
export * from './Msg' export * from './Msg'
export * from './Enum' export * from './Enum'
export * from './Model' export * from './Model'
export * from './State' export * from './State'
export * from './Utils'
export interface IModel { export interface IModel {
api: { api: {

View File

@ -1,17 +1,18 @@
import WebSocket from 'ws'; import WebSocket from 'ws';
import { EventEmitter } from 'stream'; import { EventEmitter } from 'stream';
import MyServer, { IData } from '.'; import { MyServer } from './MyServer';
import { getTime } from '../Utils'; import { binaryDecode, getTime } from '../Utils';
import { IModel } from '../Common'; import { ApiMsgEnum, IModel } from '../Common';
import { binaryEncode } from '../Common/Binary';
export enum ConnectionEventEnum { export enum ConnectionEventEnum {
Close = 'Close', Close = 'Close',
} }
export default class Connection extends EventEmitter { export class Connection extends EventEmitter {
server: MyServer server: MyServer
ws: WebSocket ws: WebSocket
msgMap: Map<string, Function[]> = new Map() msgMap: Map<ApiMsgEnum, Function[]> = new Map()
playerId?: number; playerId?: number;
constructor(server: MyServer, ws: WebSocket) { constructor(server: MyServer, ws: WebSocket) {
@ -24,10 +25,12 @@ export default class Connection extends EventEmitter {
}) })
this.ws.on('message', (buffer: Buffer) => { this.ws.on('message', (buffer: Buffer) => {
const str = buffer.toString() // const str = buffer.toString()
try { try {
const { name, data } = JSON.parse(str) const json = binaryDecode(buffer)
console.log(`${getTime()}接收|${this.playerId || -1}|${str}`) const { name, data } = json
// console.log(`${getTime()}接收|字节数${buffer.length}|${this.playerId || -1}|${str}`)
console.log(`${getTime()}接收|字节数${buffer.length}|${this.playerId || -1}|${JSON.stringify(json)}`)
if (this.server.apiMap.has(name)) { if (this.server.apiMap.has(name)) {
try { try {
const cb = this.server.apiMap.get(name) const cb = this.server.apiMap.get(name)
@ -49,7 +52,8 @@ export default class Connection extends EventEmitter {
this.msgMap.get(name)?.forEach(cb => cb(data)) this.msgMap.get(name)?.forEach(cb => cb(data))
} }
} catch (error) { } catch (error) {
console.log(`解析失败,${str}不是合法的JSON格式`, error) // console.log(`解析失败,${str}不是合法的JSON格式`, error)
console.log(error)
} }
}) })
} }
@ -74,7 +78,9 @@ export default class Connection extends EventEmitter {
name, name,
data data
}) })
console.log(`${getTime()}发送|${this.playerId || -1}|${msg}`) const view = binaryEncode(name, data)
this.ws.send(msg) const buffer = Buffer.from(view.buffer)
console.log(`${getTime()}发送|字节数${buffer.length}|${this.playerId || -1}|${msg}`)
this.ws.send(buffer)
} }
} }

View File

@ -1,71 +1,2 @@
import { EventEmitter } from 'stream'; export * from './MyServer'
import WebSocket, { WebSocketServer } from 'ws'; export * from './Connection'
import Connection, { ConnectionEventEnum } from './Connection';
export interface IMyServerOptions {
port: number
}
export type IData = Record<string, any>
export interface ICallApiRet {
success: boolean;
error?: Error;
res?: IData
}
export enum MyServerEventEnum {
Connect = 'Connect',
DisConnect = 'DisConnect',
}
export default class MyServer extends EventEmitter {
wss?: WebSocketServer
port: number
connections: Set<Connection> = new Set()
apiMap: Map<string, Function> = new Map()
constructor({ port = 8080 }: Partial<IMyServerOptions>) {
super()
this.port = port
}
start() {
return new Promise((resolve, reject) => {
this.wss = new WebSocketServer({ port: this.port });
this.wss.on('connection', this.handleConnect.bind(this));
this.wss.on("error", (e) => {
reject(e)
})
this.wss.on("close", () => {
console.log("MyServer 服务关闭");
})
this.wss.on("listening", () => {
resolve(true)
})
})
}
handleConnect(ws: WebSocket) {
//初始化
const connection = new Connection(this, ws)
//向外告知有人来了
this.connections.add(connection)
this.emit(MyServerEventEnum.Connect, connection)
//向外告知有人走了
connection.on(ConnectionEventEnum.Close, (code: number, reason: string) => {
this.connections.delete(connection)
this.emit(MyServerEventEnum.DisConnect, connection, code, reason)
})
}
setApi(apiName: string, cb: Function) {
this.apiMap.set(apiName, cb)
}
}

View File

@ -1,7 +1,6 @@
import MyServer, { MyServerEventEnum } from './Core'; import { Connection, MyServer, MyServerEventEnum } from './Core';
import PlayerManager from './Biz/PlayerManager'; import PlayerManager from './Biz/PlayerManager';
import RoomManager from './Biz/RoomManager'; import RoomManager from './Biz/RoomManager';
import Connection from './Core/Connection';
import { getTime, symlinkCommon } from './Utils'; 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, IApiGameStartReq, IApiGameStartRes, IApiPlayerJoinReq, IApiPlayerJoinRes, IApiPlayerListReq, IApiPlayerListRes, IApiRoomCreateReq, IApiRoomCreateRes, IApiRoomJoinReq, IApiRoomJoinRes, IApiRoomLeaveReq, IApiRoomLeaveRes, IApiRoomListReq, IApiRoomListRes, IModel } from './Common';

View File

@ -1,5 +1,6 @@
import fs from 'fs-extra' import fs from 'fs-extra'
import path from 'path' import path from 'path'
import { ApiMsgEnum, InputTypeEnum, strdecode } from '../Common'
export const getTime = () => new Date().toLocaleString().split("├")[0] export const getTime = () => new Date().toLocaleString().split("├")[0]
@ -35,3 +36,78 @@ export const copyCommon = async () => {
await fs.copy(src, dst) await fs.copy(src, dst)
console.log('同步成功!') console.log('同步成功!')
} }
export const binaryDecode = (buffer: Buffer) => {
let index = 0
const type = buffer.readUint8(index++)
if (type === ApiMsgEnum.MsgClientSync) {
const inputType = buffer.readUint8(index++)
if (inputType === InputTypeEnum.ActorMove) {
const id = buffer.readUint8(index++)
const directionX = buffer.readFloatBE(index)
index += 4
const directionY = buffer.readFloatBE(index)
index += 4
const dt = buffer.readFloatBE(index)
index += 4
const msg = {
name: ApiMsgEnum.MsgClientSync,
data: {
type: InputTypeEnum.ActorMove,
id,
direction: {
x: directionX,
y: directionY,
},
dt
}
}
return msg
} else if (inputType === InputTypeEnum.WeaponShoot) {
const id = buffer.readUint8(index++)
const positionX = buffer.readFloatBE(index)
index += 4
const positionY = buffer.readFloatBE(index)
index += 4
const directionX = buffer.readFloatBE(index)
index += 4
const directionY = buffer.readFloatBE(index)
index += 4
const msg = {
name: ApiMsgEnum.MsgClientSync,
data: {
type: InputTypeEnum.WeaponShoot,
id,
position: {
x: positionX,
y: positionY,
},
direction: {
x: directionX,
y: directionY,
},
}
}
return msg
} else {
const dt = buffer.readFloatBE(index)
index += 4
const msg = {
name: ApiMsgEnum.MsgClientSync,
data: {
type: InputTypeEnum.TimePast,
dt,
}
}
return msg
}
} else {
return {
name: type,
data: JSON.parse(strdecode(new Uint8Array(buffer.slice(1))))
}
}
}

32
test.js
View File

@ -1,2 +1,30 @@
const a = new Date() const msg = JSON.stringify({
console.log(a.toLocaleString().split("├")[0]); a: 123,
b: true,
c: "456"
})
const strencode = (str) => {
let byteArray = [];
for (let i = 0; i < str.length; i++) {
let charCode = str.charCodeAt(i);
if (charCode <= 0x7f) {
byteArray.push(charCode);
} else if (charCode <= 0x7ff) {
byteArray.push(0xc0 | (charCode >> 6), 0x80 | (charCode & 0x3f));
} else if (charCode <= 0xffff) {
byteArray.push(0xe0 | (charCode >> 12), 0x80 | ((charCode & 0xfc0) >> 6), 0x80 | (charCode & 0x3f));
} else {
byteArray.push(0xf0 | (charCode >> 18), 0x80 | ((charCode & 0x3f000) >> 12), 0x80 | ((charCode & 0xfc0) >> 6), 0x80 | (charCode & 0x3f));
}
}
return byteArray;
}
var arr = strencode(msg)
var buffer = Buffer.from(msg)
console.log(buffer)
for (let i = 0; i < arr.length; i++) {
console.log(buffer[i], arr[i]);
}