update
This commit is contained in:
		| @@ -1,11 +0,0 @@ | ||||
| { | ||||
|   "extends": "@antfu", | ||||
|   "rules": { | ||||
|     "curly": "off", | ||||
|     "no-console": "off", | ||||
|     "no-cond-assign": "off", | ||||
|     "no-useless-call": "off", | ||||
|     "@typescript-eslint/brace-style": "off", | ||||
|     "@typescript-eslint/consistent-type-imports": "off" | ||||
|   } | ||||
| } | ||||
| @@ -1887,7 +1887,7 @@ | ||||
|     }, | ||||
|     "component": "", | ||||
|     "_componentId": "a14b40zxfhFXKk12/1/OrYr", | ||||
|     "handler": "createRoom", | ||||
|     "handler": "handleCreateRoom", | ||||
|     "customEventData": "" | ||||
|   }, | ||||
|   { | ||||
|   | ||||
| @@ -1255,7 +1255,7 @@ | ||||
|       } | ||||
|     ], | ||||
|     "_interactable": true, | ||||
|     "_transition": 0, | ||||
|     "_transition": 3, | ||||
|     "_normalColor": { | ||||
|       "__type__": "cc.Color", | ||||
|       "r": 214, | ||||
| @@ -1301,7 +1301,7 @@ | ||||
|       "__expectedType__": "cc.SpriteFrame" | ||||
|     }, | ||||
|     "_duration": 0.1, | ||||
|     "_zoomScale": 1.2, | ||||
|     "_zoomScale": 0.9, | ||||
|     "_target": { | ||||
|       "__id__": 30 | ||||
|     }, | ||||
| @@ -1549,7 +1549,7 @@ | ||||
|       } | ||||
|     ], | ||||
|     "_interactable": true, | ||||
|     "_transition": 0, | ||||
|     "_transition": 3, | ||||
|     "_normalColor": { | ||||
|       "__type__": "cc.Color", | ||||
|       "r": 214, | ||||
| @@ -1595,7 +1595,7 @@ | ||||
|       "__expectedType__": "cc.SpriteFrame" | ||||
|     }, | ||||
|     "_duration": 0.1, | ||||
|     "_zoomScale": 1.2, | ||||
|     "_zoomScale": 0.9, | ||||
|     "_target": { | ||||
|       "__id__": 38 | ||||
|     }, | ||||
|   | ||||
| @@ -1,22 +1,21 @@ | ||||
| import { _decorator, Component } from 'cc'; | ||||
| import { EntityStateEnum } from '../Enum'; | ||||
| import StateMachine from './StateMachine'; | ||||
| import { _decorator, Component } from "cc"; | ||||
| import { EntityStateEnum } from "../Enum"; | ||||
| import StateMachine from "./StateMachine"; | ||||
| const { ccclass, property } = _decorator; | ||||
|  | ||||
| @ccclass('EntityManager') | ||||
| @ccclass("EntityManager") | ||||
| export abstract class EntityManager extends Component { | ||||
|   fsm: StateMachine | ||||
|   private _state: EntityStateEnum | ||||
|   fsm: StateMachine; | ||||
|   private _state: EntityStateEnum; | ||||
|  | ||||
|   get state() { | ||||
|     return this._state | ||||
|     return this._state; | ||||
|   } | ||||
|  | ||||
|   set state(newState) { | ||||
|     this._state = newState | ||||
|     this.fsm.setParams(newState, true) | ||||
|     this._state = newState; | ||||
|     this.fsm.setParams(newState, true); | ||||
|   } | ||||
|  | ||||
|   abstract init(...args: any[]): void | ||||
|   abstract init(...args: any[]): void; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,13 +1,12 @@ | ||||
| export default class Singleton { | ||||
|   private static _instance: any = null | ||||
|   private static _instance: any = null; | ||||
|  | ||||
|   static GetInstance<T>(): T { | ||||
|     if (this._instance === null) { | ||||
|       this._instance = new this() | ||||
|       this._instance = new this(); | ||||
|     } | ||||
|     return this._instance | ||||
|     return this._instance; | ||||
|   } | ||||
|  | ||||
|   protected constructor() { | ||||
|   } | ||||
| } | ||||
|   protected constructor() {} | ||||
| } | ||||
|   | ||||
| @@ -1,48 +1,45 @@ | ||||
| import { animation, AnimationClip, Sprite, SpriteFrame } from 'cc' | ||||
| import DataManager from '../Global/DataManager' | ||||
| import { ResourceManager } from '../Global/ResourceManager' | ||||
| import { sortSpriteFrame } from '../Utils' | ||||
| import StateMachine from './StateMachine' | ||||
| import { animation, AnimationClip, Sprite, SpriteFrame } from "cc"; | ||||
| import DataManager from "../Global/DataManager"; | ||||
| import { ResourceManager } from "../Global/ResourceManager"; | ||||
| import { sortSpriteFrame } from "../Utils"; | ||||
| import StateMachine from "./StateMachine"; | ||||
|  | ||||
| /*** | ||||
|  * unit:milisecond | ||||
|  */ | ||||
| export const ANIMATION_SPEED = 1 / 10 | ||||
| export const ANIMATION_SPEED = 1 / 10; | ||||
|  | ||||
| /*** | ||||
|  * 状态(每组动画的承载容器,持有SpriteAnimation组件执行播放) | ||||
|  */ | ||||
| export default class State { | ||||
|   private animationClip: AnimationClip | ||||
|   private animationClip: AnimationClip; | ||||
|   constructor( | ||||
|     private fsm: StateMachine, | ||||
|     private path: string, | ||||
|     private wrapMode: AnimationClip.WrapMode = AnimationClip.WrapMode.Normal, | ||||
|     private force: boolean = false, | ||||
|     private force: boolean = false | ||||
|   ) { | ||||
|     //生成动画轨道属性 | ||||
|     const track = new animation.ObjectTrack() | ||||
|     track.path = new animation.TrackPath().toComponent(Sprite).toProperty('spriteFrame') | ||||
|     const spriteFrames = DataManager.Instance.textureMap.get(this.path) | ||||
|     const frames: Array<[number, SpriteFrame]> = sortSpriteFrame(spriteFrames).map((item, index) => [ | ||||
|       index * ANIMATION_SPEED, | ||||
|       item, | ||||
|     ]) | ||||
|     track.channel.curve.assignSorted(frames) | ||||
|     const track = new animation.ObjectTrack(); | ||||
|     track.path = new animation.TrackPath().toComponent(Sprite).toProperty("spriteFrame"); | ||||
|     const spriteFrames = DataManager.Instance.textureMap.get(this.path); | ||||
|     const frames: Array<[number, SpriteFrame]> = sortSpriteFrame(spriteFrames).map((item, index) => [index * ANIMATION_SPEED, item]); | ||||
|     track.channel.curve.assignSorted(frames); | ||||
|  | ||||
|     //动画添加轨道 | ||||
|     this.animationClip = new AnimationClip() | ||||
|     this.animationClip.name = this.path | ||||
|     this.animationClip.duration = frames.length * ANIMATION_SPEED | ||||
|     this.animationClip.addTrack(track) | ||||
|     this.animationClip.wrapMode = this.wrapMode | ||||
|     this.animationClip = new AnimationClip(); | ||||
|     this.animationClip.name = this.path; | ||||
|     this.animationClip.duration = frames.length * ANIMATION_SPEED; | ||||
|     this.animationClip.addTrack(track); | ||||
|     this.animationClip.wrapMode = this.wrapMode; | ||||
|   } | ||||
|  | ||||
|   run() { | ||||
|     if (this.fsm.animationComponent.defaultClip?.name === this.animationClip.name && !this.force) { | ||||
|       return | ||||
|       return; | ||||
|     } | ||||
|     this.fsm.animationComponent.defaultClip = this.animationClip | ||||
|     this.fsm.animationComponent.play() | ||||
|     this.fsm.animationComponent.defaultClip = this.animationClip; | ||||
|     this.fsm.animationComponent.play(); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,30 +1,30 @@ | ||||
| import { _decorator, Animation, Component } from 'cc' | ||||
| import { EntityTypeEnum } from '../Common' | ||||
| import { FsmParamTypeEnum } from '../Enum' | ||||
| const { ccclass } = _decorator | ||||
| import State from './State' | ||||
| import SubStateMachine from './SubStateMachine' | ||||
| import { _decorator, Animation, Component } from "cc"; | ||||
| import { EntityTypeEnum } from "../Common"; | ||||
| import { FsmParamTypeEnum } from "../Enum"; | ||||
| const { ccclass } = _decorator; | ||||
| import State from "./State"; | ||||
| import SubStateMachine from "./SubStateMachine"; | ||||
|  | ||||
| type ParamsValueType = boolean | number | ||||
| type ParamsValueType = boolean | number; | ||||
|  | ||||
| export interface IParamsValue { | ||||
|   type: FsmParamTypeEnum | ||||
|   value: ParamsValueType | ||||
|   type: FsmParamTypeEnum; | ||||
|   value: ParamsValueType; | ||||
| } | ||||
|  | ||||
| export const getInitParamsTrigger = () => { | ||||
|   return { | ||||
|     type: FsmParamTypeEnum.Trigger, | ||||
|     value: false, | ||||
|   } | ||||
| } | ||||
|   }; | ||||
| }; | ||||
|  | ||||
| export const getInitParamsNumber = () => { | ||||
|   return { | ||||
|     type: FsmParamTypeEnum.Number, | ||||
|     value: 0, | ||||
|   } | ||||
| } | ||||
|   }; | ||||
| }; | ||||
|  | ||||
| /*** | ||||
|  * 流动图 | ||||
| @@ -39,47 +39,47 @@ export const getInitParamsNumber = () => { | ||||
| /*** | ||||
|  * 有限状态机基类 | ||||
|  */ | ||||
| @ccclass('StateMachine') | ||||
| @ccclass("StateMachine") | ||||
| export default abstract class StateMachine extends Component { | ||||
|   private _currentState: State | SubStateMachine = null | ||||
|   params: Map<string, IParamsValue> = new Map() | ||||
|   stateMachines: Map<string, SubStateMachine | State> = new Map() | ||||
|   animationComponent: Animation | ||||
|   type: EntityTypeEnum | ||||
|   private _currentState: State | SubStateMachine = null; | ||||
|   params: Map<string, IParamsValue> = new Map(); | ||||
|   stateMachines: Map<string, SubStateMachine | State> = new Map(); | ||||
|   animationComponent: Animation; | ||||
|   type: EntityTypeEnum; | ||||
|  | ||||
|   getParams(paramName: string) { | ||||
|     if (this.params.has(paramName)) { | ||||
|       return this.params.get(paramName).value | ||||
|       return this.params.get(paramName).value; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   setParams(paramName: string, value: ParamsValueType) { | ||||
|     if (this.params.has(paramName)) { | ||||
|       this.params.get(paramName).value = value | ||||
|       this.run() | ||||
|       this.resetTrigger() | ||||
|       this.params.get(paramName).value = value; | ||||
|       this.run(); | ||||
|       this.resetTrigger(); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   get currentState() { | ||||
|     return this._currentState | ||||
|     return this._currentState; | ||||
|   } | ||||
|  | ||||
|   set currentState(newState) { | ||||
|     if (!newState) { | ||||
|       return | ||||
|       return; | ||||
|     } | ||||
|     this._currentState = newState | ||||
|     this._currentState.run() | ||||
|     this._currentState = newState; | ||||
|     this._currentState.run(); | ||||
|   } | ||||
|  | ||||
|   /*** | ||||
|  * 清空所有trigger | ||||
|  */ | ||||
|    * 清空所有trigger | ||||
|    */ | ||||
|   resetTrigger() { | ||||
|     for (const [, value] of this.params) { | ||||
|       if (value.type === FsmParamTypeEnum.Trigger) { | ||||
|         value.value = false | ||||
|         value.value = false; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| @@ -87,6 +87,6 @@ export default abstract class StateMachine extends Component { | ||||
|   /*** | ||||
|    * 由子类重写,方法目标是根据当前状态和参数修改currentState | ||||
|    */ | ||||
|   abstract init(...args: any[]): void | ||||
|   abstract run(): void | ||||
|   abstract init(...args: any[]): void; | ||||
|   abstract run(): void; | ||||
| } | ||||
|   | ||||
| @@ -1,30 +1,30 @@ | ||||
| import State from './State' | ||||
| import StateMachine from './StateMachine' | ||||
| import State from "./State"; | ||||
| import StateMachine from "./StateMachine"; | ||||
|  | ||||
| /*** | ||||
|  * 子有限状态机基类 | ||||
|  * 用处:例如有个idle的state,但是有多个方向,为了让主状态机更整洁,可以把同类型的但具体不同的state都封装在子状态机中 | ||||
|  */ | ||||
| export default abstract class SubStateMachine { | ||||
|   private _currentState: State = null | ||||
|   stateMachines: Map<string, State> = new Map() | ||||
|   private _currentState: State = null; | ||||
|   stateMachines: Map<string, State> = new Map(); | ||||
|  | ||||
|   constructor(public fsm: StateMachine) {} | ||||
|  | ||||
|   get currentState() { | ||||
|     return this._currentState | ||||
|     return this._currentState; | ||||
|   } | ||||
|  | ||||
|   set currentState(newState) { | ||||
|     if (!newState) { | ||||
|       return | ||||
|       return; | ||||
|     } | ||||
|     this._currentState = newState | ||||
|     this._currentState.run() | ||||
|     this._currentState = newState; | ||||
|     this._currentState.run(); | ||||
|   } | ||||
|  | ||||
|   /*** | ||||
|    * 具体类实现 | ||||
|    */ | ||||
|   abstract run(): void | ||||
|   abstract run(): void; | ||||
| } | ||||
|   | ||||
| @@ -1,147 +1,141 @@ | ||||
| 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, 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'; | ||||
| import { PlayerStateMachine } from './ActorStateMachine'; | ||||
| import { _decorator, instantiate, ProgressBar, Label, Vec3, Tween, tween } from "cc"; | ||||
| import { EntityManager } from "../../Base/EntityManager"; | ||||
| import { EntityTypeEnum, IActor, InputTypeEnum, IVec2, toFixed } from "../../Common"; | ||||
| import { EntityStateEnum, EventEnum, SceneEnum } from "../../Enum"; | ||||
| import DataManager from "../../Global/DataManager"; | ||||
| import EventManager from "../../Global/EventManager"; | ||||
| import { rad2Angle } from "../../Utils"; | ||||
| import { WeaponManager } from "../Weapon/WeaponManager"; | ||||
| import { ActorStateMachine } from "./ActorStateMachine"; | ||||
| const { ccclass } = _decorator; | ||||
|  | ||||
| @ccclass('ActorManager') | ||||
| @ccclass("ActorManager") | ||||
| export class ActorManager extends EntityManager implements IActor { | ||||
|     //静态数据 | ||||
|     id: number | ||||
|     nickname: string | ||||
|     type: EntityTypeEnum | ||||
|     weaponType: EntityTypeEnum | ||||
|     bulletType: EntityTypeEnum | ||||
|   //静态数据 | ||||
|   id: number; | ||||
|   nickname: string; | ||||
|   type: EntityTypeEnum; | ||||
|   weaponType: EntityTypeEnum; | ||||
|   bulletType: EntityTypeEnum; | ||||
|  | ||||
|     //动态数据 | ||||
|     hp: number | ||||
|     position: IVec2 | ||||
|     direction: IVec2 | ||||
|   //动态数据 | ||||
|   hp: number; | ||||
|   position: IVec2; | ||||
|   direction: IVec2; | ||||
|  | ||||
|     private hpBar: ProgressBar | ||||
|     private label: Label | ||||
|     private weapon: WeaponManager | ||||
|   private hpBar: ProgressBar; | ||||
|   private label: Label; | ||||
|   private weapon: WeaponManager; | ||||
|  | ||||
|     private tw: Tween<any> | ||||
|     private targetPos: Vec3 | ||||
|   private tw: Tween<unknown>; | ||||
|   private targetPos: Vec3; | ||||
|   private isDead = false; | ||||
|  | ||||
|     private isDead = false | ||||
|   init(data: IActor) { | ||||
|     const { id, nickname, type, weaponType, bulletType } = data; | ||||
|     this.id = id; | ||||
|     this.nickname = nickname; | ||||
|     this.type = type; | ||||
|     this.weaponType = weaponType; | ||||
|     this.bulletType = bulletType; | ||||
|  | ||||
|     get isSelf() { | ||||
|         return DataManager.Instance.myPlayerId === this.id | ||||
|     this.hpBar = this.node.getComponentInChildren(ProgressBar); | ||||
|     this.label = this.node.getComponentInChildren(Label); | ||||
|     this.label.string = nickname; | ||||
|  | ||||
|     this.fsm = this.addComponent(ActorStateMachine); | ||||
|     this.fsm.init(type); | ||||
|     this.state = EntityStateEnum.Idle; | ||||
|  | ||||
|     const weaponPrefab = DataManager.Instance.prefabMap.get(this.weaponType); | ||||
|     const weapon = instantiate(weaponPrefab); | ||||
|     weapon.setParent(this.node); | ||||
|     this.weapon = weapon.addComponent(WeaponManager); | ||||
|     this.weapon.init(data); | ||||
|  | ||||
|     this.targetPos = undefined; | ||||
|     this.node.active = false; | ||||
|   } | ||||
|  | ||||
|   async tick(dt: number) { | ||||
|     if (DataManager.Instance.myPlayerId !== this.id || this.isDead) { | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     init(data: IActor) { | ||||
|         const { id, nickname, type, weaponType, bulletType } = data | ||||
|         this.id = id | ||||
|         this.nickname = nickname | ||||
|         this.type = type | ||||
|         this.weaponType = weaponType | ||||
|         this.bulletType = bulletType | ||||
|  | ||||
|         this.hpBar = this.node.getComponentInChildren(ProgressBar) | ||||
|         this.label = this.node.getComponentInChildren(Label) | ||||
|         this.label.string = nickname | ||||
|  | ||||
|         this.fsm = this.addComponent(PlayerStateMachine) | ||||
|         this.fsm.init(type) | ||||
|         this.state = EntityStateEnum.Idle | ||||
|  | ||||
|         const weaponPrefab = DataManager.Instance.prefabMap.get(this.weaponType) | ||||
|         const weapon = instantiate(weaponPrefab) | ||||
|         weapon.setParent(this.node) | ||||
|         this.weapon = weapon.addComponent(WeaponManager) | ||||
|         this.weapon.init(data) | ||||
|  | ||||
|         this.targetPos = undefined | ||||
|         this.node.active = false | ||||
|     if (this.hp <= 0) { | ||||
|       EventManager.Instance.emit(EventEnum.GameEnd); | ||||
|       this.isDead = true; | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     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, { | ||||
|                 type: InputTypeEnum.ActorMove, | ||||
|                 id: this.id, | ||||
|                 direction: { | ||||
|                     x: toFixed(x), | ||||
|                     y: toFixed(y), | ||||
|                 }, | ||||
|                 dt: toFixed(dt) | ||||
|             }) | ||||
|         } | ||||
|  | ||||
|     if (DataManager.Instance.jm.input.length()) { | ||||
|       const { x, y } = DataManager.Instance.jm.input; | ||||
|       EventManager.Instance.emit(EventEnum.ClientSync, { | ||||
|         type: InputTypeEnum.ActorMove, | ||||
|         id: this.id, | ||||
|         direction: { | ||||
|           x: toFixed(x), | ||||
|           y: toFixed(y), | ||||
|         }, | ||||
|         dt: toFixed(dt), | ||||
|       }); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|     render(data: IActor) { | ||||
|         this.renderHP(data) | ||||
|         this.renderPosition(data) | ||||
|         this.renderDirection(data) | ||||
|   render(data: IActor) { | ||||
|     this.renderHP(data); | ||||
|     this.renderPosition(data); | ||||
|     this.renderDirection(data); | ||||
|   } | ||||
|  | ||||
|   renderHP(data: IActor) { | ||||
|     this.hp = data.hp; | ||||
|     this.hpBar.progress = data.hp / this.hpBar.totalLength; | ||||
|   } | ||||
|  | ||||
|   renderPosition(data: IActor) { | ||||
|     const newPos = new Vec3(data.position.x, data.position.y); | ||||
|     if (!this.targetPos) { | ||||
|       this.node.active = true; | ||||
|       this.node.setPosition(newPos); | ||||
|       this.targetPos = new Vec3(newPos); | ||||
|     } else if (!this.targetPos.equals(newPos)) { | ||||
|       this.tw?.stop(); | ||||
|       this.node.setPosition(this.targetPos); | ||||
|       this.targetPos.set(newPos); | ||||
|       this.state = EntityStateEnum.Run; | ||||
|       this.tw = tween(this.node) | ||||
|         .to(0.1, { | ||||
|           position: this.targetPos, | ||||
|         }) | ||||
|         .call(() => { | ||||
|           this.state = EntityStateEnum.Idle; | ||||
|         }) | ||||
|         .start(); | ||||
|     } | ||||
|     // this.node.setPosition(data.position.x, data.position.y) | ||||
|   } | ||||
|  | ||||
|     renderHP(data: IActor) { | ||||
|         this.hp = data.hp | ||||
|         this.hpBar.progress = data.hp / this.hpBar.totalLength | ||||
|   renderDirection(data: IActor) { | ||||
|     const { x, y } = data.direction; | ||||
|     if (x !== 0) { | ||||
|       this.node.setScale(x > 0 ? 1 : -1, 1); | ||||
|       this.label.node.setScale(x > 0 ? 1 : -1, 1); | ||||
|       this.hpBar.node.setScale(x > 0 ? 1 : -1, 1); | ||||
|     } | ||||
|     const side = Math.sqrt(x * x + y * y); | ||||
|     const angle = rad2Angle(Math.asin(y / side)); | ||||
|     this.weapon.node.setRotationFromEuler(0, 0, angle); | ||||
|  | ||||
|     renderPosition(data: IActor) { | ||||
|         const newPos = new Vec3(data.position.x, data.position.y) | ||||
|         if (!this.targetPos) { | ||||
|             this.node.active = true | ||||
|             this.node.setPosition(newPos) | ||||
|             this.targetPos = new Vec3(newPos) | ||||
|         } else if (!this.targetPos.equals(newPos)) { | ||||
|             this.tw?.stop() | ||||
|             this.node.setPosition(this.targetPos) | ||||
|             this.targetPos.set(newPos) | ||||
|             this.state = EntityStateEnum.Run | ||||
|             this.tw = tween(this.node).to(0.1, { | ||||
|                 position: this.targetPos | ||||
|             }).call(() => { | ||||
|                 this.state = EntityStateEnum.Idle | ||||
|             }).start() | ||||
|         } | ||||
|  | ||||
|         // this.node.setPosition(data.position.x, data.position.y) | ||||
|     } | ||||
|  | ||||
|     renderDirection(data: IActor) { | ||||
|         const { x, y } = data.direction | ||||
|         if (x !== 0) { | ||||
|             this.node.setScale(x > 0 ? 1 : -1, 1) | ||||
|             this.label.node.setScale(x > 0 ? 1 : -1, 1) | ||||
|             this.hpBar.node.setScale(x > 0 ? 1 : -1, 1) | ||||
|         } | ||||
|         const side = Math.sqrt(x * x + y * y) | ||||
|         const angle = rad2Angle(Math.asin(y / side)) | ||||
|         this.weapon.node.setRotationFromEuler(0, 0, angle) | ||||
|  | ||||
|         // const { x, y } = joystick.input | ||||
|         // let angle: number, sign: number | ||||
|         // if (x !== 0) { | ||||
|         //   angle = rad2Angle(Math.atan(y / x)) | ||||
|         //   sign = joystick.input.x > 0 ? 1 : -1 | ||||
|         // } else { | ||||
|         //   angle = rad2Angle(Math.PI / 2) | ||||
|         //   sign = joystick.input.y > 0 ? 1 : -1 | ||||
|         // } | ||||
|         // this.node.setRotationFromEuler(0, 0, sign * angle) | ||||
|     } | ||||
|     // const { x, y } = joystick.input | ||||
|     // let angle: number, sign: number | ||||
|     // if (x !== 0) { | ||||
|     //   angle = rad2Angle(Math.atan(y / x)) | ||||
|     //   sign = joystick.input.x > 0 ? 1 : -1 | ||||
|     // } else { | ||||
|     //   angle = rad2Angle(Math.PI / 2) | ||||
|     //   sign = joystick.input.y > 0 ? 1 : -1 | ||||
|     // } | ||||
|     // this.node.setRotationFromEuler(0, 0, sign * angle) | ||||
|   } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,48 +1,47 @@ | ||||
| import { _decorator, Animation, AnimationClip } from 'cc' | ||||
| import State from '../../Base/State' | ||||
| import StateMachine, { getInitParamsTrigger } from '../../Base/StateMachine' | ||||
| import { EntityTypeEnum } from '../../Common' | ||||
| import { EntityStateEnum, ParamsNameEnum } from '../../Enum' | ||||
| const { ccclass } = _decorator | ||||
| import { _decorator, Animation, AnimationClip } from "cc"; | ||||
| import State from "../../Base/State"; | ||||
| import StateMachine, { getInitParamsTrigger } from "../../Base/StateMachine"; | ||||
| import { EntityTypeEnum } from "../../Common"; | ||||
| import { EntityStateEnum, ParamsNameEnum } from "../../Enum"; | ||||
| const { ccclass } = _decorator; | ||||
|  | ||||
| @ccclass('PlayerStateMachine') | ||||
| export class PlayerStateMachine extends StateMachine { | ||||
| @ccclass("ActorStateMachine") | ||||
| export class ActorStateMachine extends StateMachine { | ||||
|   init(type: EntityTypeEnum) { | ||||
|     this.type = type | ||||
|     this.animationComponent = this.node.addComponent(Animation) | ||||
|     this.initParams() | ||||
|     this.initStateMachines() | ||||
|     this.initAnimationEvent() | ||||
|     this.type = type; | ||||
|     this.animationComponent = this.node.addComponent(Animation); | ||||
|     this.initParams(); | ||||
|     this.initStateMachines(); | ||||
|     this.initAnimationEvent(); | ||||
|   } | ||||
|  | ||||
|   initParams() { | ||||
|     this.params.set(ParamsNameEnum.Idle, getInitParamsTrigger()) | ||||
|     this.params.set(ParamsNameEnum.Run, getInitParamsTrigger()) | ||||
|     this.params.set(ParamsNameEnum.Idle, getInitParamsTrigger()); | ||||
|     this.params.set(ParamsNameEnum.Run, getInitParamsTrigger()); | ||||
|   } | ||||
|  | ||||
|   initStateMachines() { | ||||
|     this.stateMachines.set(ParamsNameEnum.Idle, new State(this, `${this.type}${EntityStateEnum.Idle}`, AnimationClip.WrapMode.Loop)) | ||||
|     this.stateMachines.set(ParamsNameEnum.Run, new State(this, `${this.type}${EntityStateEnum.Run}`, AnimationClip.WrapMode.Loop)) | ||||
|     this.stateMachines.set(ParamsNameEnum.Idle, new State(this, `${this.type}${EntityStateEnum.Idle}`, AnimationClip.WrapMode.Loop)); | ||||
|     this.stateMachines.set(ParamsNameEnum.Run, new State(this, `${this.type}${EntityStateEnum.Run}`, AnimationClip.WrapMode.Loop)); | ||||
|   } | ||||
|  | ||||
|   initAnimationEvent() { | ||||
|   } | ||||
|   initAnimationEvent() {} | ||||
|  | ||||
|   run() { | ||||
|     switch (this.currentState) { | ||||
|       case this.stateMachines.get(ParamsNameEnum.Idle): | ||||
|       case this.stateMachines.get(ParamsNameEnum.Run): | ||||
|         if (this.params.get(ParamsNameEnum.Run).value) { | ||||
|           this.currentState = this.stateMachines.get(ParamsNameEnum.Run) | ||||
|           this.currentState = this.stateMachines.get(ParamsNameEnum.Run); | ||||
|         } else if (this.params.get(ParamsNameEnum.Idle).value) { | ||||
|           this.currentState = this.stateMachines.get(ParamsNameEnum.Idle) | ||||
|           this.currentState = this.stateMachines.get(ParamsNameEnum.Idle); | ||||
|         } else { | ||||
|           this.currentState = this.currentState | ||||
|           this.currentState = this.currentState; | ||||
|         } | ||||
|         break | ||||
|         break; | ||||
|       default: | ||||
|         this.currentState = this.stateMachines.get(ParamsNameEnum.Idle) | ||||
|         break | ||||
|         this.currentState = this.stateMachines.get(ParamsNameEnum.Idle); | ||||
|         break; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,79 +1,83 @@ | ||||
| import { Tween, tween, Vec3, _decorator } from 'cc' | ||||
| import { EntityManager } from '../../Base/EntityManager' | ||||
| import { EntityTypeEnum, IBullet, IVec2 } from '../../Common' | ||||
| import { EntityStateEnum, EventEnum } from '../../Enum' | ||||
| import DataManager from '../../Global/DataManager' | ||||
| import EventManager from '../../Global/EventManager' | ||||
| import ObjectPoolManager from '../../Global/ObjectPoolManager' | ||||
| import { rad2Angle } from '../../Utils' | ||||
| import { ExplosionManager } from '../Explosion/ExplosionManager' | ||||
| import { BulletStateMachine } from './BulletStateMachine' | ||||
| const { ccclass } = _decorator | ||||
| import { Tween, tween, Vec3, _decorator } from "cc"; | ||||
| import { EntityManager } from "../../Base/EntityManager"; | ||||
| import { EntityTypeEnum, IBullet, IVec2 } from "../../Common"; | ||||
| import { EntityStateEnum, EventEnum } from "../../Enum"; | ||||
| import DataManager from "../../Global/DataManager"; | ||||
| import EventManager from "../../Global/EventManager"; | ||||
| import ObjectPoolManager from "../../Global/ObjectPoolManager"; | ||||
| import { rad2Angle } from "../../Utils"; | ||||
| import { ExplosionManager } from "../Explosion/ExplosionManager"; | ||||
| import { BulletStateMachine } from "./BulletStateMachine"; | ||||
| const { ccclass } = _decorator; | ||||
|  | ||||
| @ccclass('BulletManager') | ||||
| @ccclass("BulletManager") | ||||
| export class BulletManager extends EntityManager implements IBullet { | ||||
|   //静态数据 | ||||
|   id: number | ||||
|   owner: number | ||||
|   type: EntityTypeEnum | ||||
|   id: number; | ||||
|   owner: number; | ||||
|   type: EntityTypeEnum; | ||||
|  | ||||
|   //动态数据 | ||||
|   position: IVec2 | ||||
|   direction: IVec2 | ||||
|   position: IVec2; | ||||
|   direction: IVec2; | ||||
|  | ||||
|   private angle: number | ||||
|   private tw: Tween<any> | ||||
|   private targetPos: Vec3 | ||||
|   private angle: number; | ||||
|   private tw: Tween<any>; | ||||
|   private targetPos: Vec3; | ||||
|  | ||||
|   init({ id, owner, type }: IBullet) { | ||||
|     this.id = id | ||||
|     this.owner = owner | ||||
|     this.type = type | ||||
|     this.id = id; | ||||
|     this.owner = owner; | ||||
|     this.type = type; | ||||
|  | ||||
|     this.fsm = this.addComponent(BulletStateMachine) | ||||
|     this.fsm.init(type) | ||||
|     this.state = EntityStateEnum.Idle | ||||
|     this.node.active = false | ||||
|     this.targetPos = undefined | ||||
|     this.fsm = this.addComponent(BulletStateMachine); | ||||
|     this.fsm.init(type); | ||||
|     this.state = EntityStateEnum.Idle; | ||||
|  | ||||
|     EventManager.Instance.on(EventEnum.ExplosionBorn, this.handleExplosion, this) | ||||
|     this.node.active = false; | ||||
|     this.targetPos = undefined; | ||||
|     this.angle = undefined; | ||||
|  | ||||
|     EventManager.Instance.on(EventEnum.ExplosionBorn, this.handleExplosion, this); | ||||
|   } | ||||
|  | ||||
|   handleExplosion(id: number, { x, y }: IVec2) { | ||||
|     if (this.id !== id) { | ||||
|       return | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     const explosion = ObjectPoolManager.Instance.get(EntityTypeEnum.Explosion) | ||||
|     const explosionManager = explosion.getComponent(ExplosionManager) || explosion.addComponent(ExplosionManager) | ||||
|     const explosion = ObjectPoolManager.Instance.get(EntityTypeEnum.Explosion); | ||||
|     const explosionManager = explosion.getComponent(ExplosionManager) || explosion.addComponent(ExplosionManager); | ||||
|     explosionManager.init(EntityTypeEnum.Explosion, { | ||||
|       x, y, | ||||
|     }) | ||||
|       x, | ||||
|       y, | ||||
|     }); | ||||
|  | ||||
|     EventManager.Instance.off(EventEnum.ExplosionBorn, this.handleExplosion, this) | ||||
|     ObjectPoolManager.Instance.ret(this.node) | ||||
|     DataManager.Instance.bulletMap.delete(this.id) | ||||
|     this.angle = undefined | ||||
|     EventManager.Instance.off(EventEnum.ExplosionBorn, this.handleExplosion, this); | ||||
|     ObjectPoolManager.Instance.ret(this.node); | ||||
|     DataManager.Instance.bulletMap.delete(this.id); | ||||
|   } | ||||
|  | ||||
|   render(data: IBullet) { | ||||
|     this.renderPosition(data) | ||||
|     this.renderDirection(data) | ||||
|     this.renderPosition(data); | ||||
|     this.renderDirection(data); | ||||
|   } | ||||
|  | ||||
|   renderPosition(data: IBullet) { | ||||
|     const newPos = new Vec3(data.position.x, data.position.y) | ||||
|     const newPos = new Vec3(data.position.x, data.position.y); | ||||
|     if (!this.targetPos) { | ||||
|       this.node.active = true | ||||
|       this.node.setPosition(newPos) | ||||
|       this.targetPos = new Vec3(newPos) | ||||
|       this.node.active = true; | ||||
|       this.node.setPosition(newPos); | ||||
|       this.targetPos = new Vec3(newPos); | ||||
|     } else if (!this.targetPos.equals(newPos)) { | ||||
|       this.tw?.stop() | ||||
|       this.node.setPosition(this.targetPos) | ||||
|       this.targetPos.set(newPos) | ||||
|       this.tw = tween(this.node).to(0.1, { | ||||
|         position: this.targetPos | ||||
|       }).start() | ||||
|       this.tw?.stop(); | ||||
|       this.node.setPosition(this.targetPos); | ||||
|       this.targetPos.set(newPos); | ||||
|       this.tw = tween(this.node) | ||||
|         .to(0.1, { | ||||
|           position: this.targetPos, | ||||
|         }) | ||||
|         .start(); | ||||
|     } | ||||
|  | ||||
|     // this.node.setPosition(data.position.x, data.position.y) | ||||
| @@ -81,12 +85,12 @@ export class BulletManager extends EntityManager implements IBullet { | ||||
|  | ||||
|   renderDirection(data: IBullet) { | ||||
|     if (this.angle === undefined) { | ||||
|       const { x, y } = data.direction | ||||
|       const side = Math.sqrt(x * x + y * y) | ||||
|       this.angle = x > 0 ? rad2Angle(Math.asin(y / side)) : rad2Angle(Math.asin(- y / side)) + 180 | ||||
|       const { x, y } = data.direction; | ||||
|       const side = Math.sqrt(x * x + y * y); | ||||
|       this.angle = x > 0 ? rad2Angle(Math.asin(y / side)) : rad2Angle(Math.asin(-y / side)) + 180; | ||||
|     } | ||||
|  | ||||
|     this.node.setRotationFromEuler(0, 0, this.angle) | ||||
|     this.node.setRotationFromEuler(0, 0, this.angle); | ||||
|  | ||||
|     // let angle: number, sign: number | ||||
|     // if (x !== 0) { | ||||
|   | ||||
| @@ -1,45 +1,43 @@ | ||||
| import { _decorator, Animation } from 'cc' | ||||
| import State from '../../Base/State' | ||||
| import StateMachine, { getInitParamsTrigger } from '../../Base/StateMachine' | ||||
| import { EntityTypeEnum } from '../../Common' | ||||
| import { EntityStateEnum, ParamsNameEnum } from '../../Enum' | ||||
| const { ccclass } = _decorator | ||||
| import { _decorator, Animation } from "cc"; | ||||
| import State from "../../Base/State"; | ||||
| import StateMachine, { getInitParamsTrigger } from "../../Base/StateMachine"; | ||||
| import { EntityTypeEnum } from "../../Common"; | ||||
| import { EntityStateEnum, ParamsNameEnum } from "../../Enum"; | ||||
| const { ccclass } = _decorator; | ||||
|  | ||||
| @ccclass('BulletStateMachine') | ||||
| @ccclass("BulletStateMachine") | ||||
| export class BulletStateMachine extends StateMachine { | ||||
|   init(type: EntityTypeEnum) { | ||||
|     this.type = type | ||||
|     this.animationComponent = this.node.addComponent(Animation) | ||||
|     this.type = type; | ||||
|     this.animationComponent = this.node.addComponent(Animation); | ||||
|  | ||||
|     this.initParams() | ||||
|     this.initStateMachines() | ||||
|     this.initAnimationEvent() | ||||
|     this.initParams(); | ||||
|     this.initStateMachines(); | ||||
|     this.initAnimationEvent(); | ||||
|   } | ||||
|  | ||||
|   initParams() { | ||||
|     this.params.set(ParamsNameEnum.Idle, getInitParamsTrigger()) | ||||
|     this.params.set(ParamsNameEnum.Idle, getInitParamsTrigger()); | ||||
|   } | ||||
|  | ||||
|   initStateMachines() { | ||||
|     this.stateMachines.set(ParamsNameEnum.Idle, new State(this, `${this.type}${EntityStateEnum.Idle}`)) | ||||
|     this.stateMachines.set(ParamsNameEnum.Idle, new State(this, `${this.type}${EntityStateEnum.Idle}`)); | ||||
|   } | ||||
|  | ||||
|   initAnimationEvent() { | ||||
|  | ||||
|   } | ||||
|   initAnimationEvent() {} | ||||
|  | ||||
|   run() { | ||||
|     switch (this.currentState) { | ||||
|       case this.stateMachines.get(ParamsNameEnum.Idle): | ||||
|         if (this.params.get(ParamsNameEnum.Idle).value) { | ||||
|           this.currentState = this.stateMachines.get(ParamsNameEnum.Idle) | ||||
|           this.currentState = this.stateMachines.get(ParamsNameEnum.Idle); | ||||
|         } else { | ||||
|           this.currentState = this.currentState | ||||
|           this.currentState = this.currentState; | ||||
|         } | ||||
|         break | ||||
|         break; | ||||
|       default: | ||||
|         this.currentState = this.stateMachines.get(ParamsNameEnum.Idle) | ||||
|         break | ||||
|         this.currentState = this.stateMachines.get(ParamsNameEnum.Idle); | ||||
|         break; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,17 +1,16 @@ | ||||
| import { _decorator } from 'cc' | ||||
| import { EntityManager } from '../../Base/EntityManager' | ||||
| import { EntityTypeEnum, IVec2 } from '../../Common' | ||||
| import { EntityStateEnum } from '../../Enum' | ||||
| import { ExplosionStateMachine } from './ExplosionStateMachine' | ||||
| const { ccclass, property } = _decorator | ||||
| import { _decorator } from "cc"; | ||||
| import { EntityManager } from "../../Base/EntityManager"; | ||||
| import { EntityTypeEnum, IVec2 } from "../../Common"; | ||||
| import { EntityStateEnum } from "../../Enum"; | ||||
| import { ExplosionStateMachine } from "./ExplosionStateMachine"; | ||||
| const { ccclass, property } = _decorator; | ||||
|  | ||||
| @ccclass('ExplosionManager') | ||||
| @ccclass("ExplosionManager") | ||||
| export class ExplosionManager extends EntityManager { | ||||
|  | ||||
|   init(type: EntityTypeEnum, { x, y }: IVec2) { | ||||
|     this.node.setPosition(x, y) | ||||
|     this.fsm = this.addComponent(ExplosionStateMachine) | ||||
|     this.fsm.init(type) | ||||
|     this.state = EntityStateEnum.Idle | ||||
|     this.node.setPosition(x, y); | ||||
|     this.fsm = this.addComponent(ExplosionStateMachine); | ||||
|     this.fsm.init(type); | ||||
|     this.state = EntityStateEnum.Idle; | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,52 +1,52 @@ | ||||
| import { _decorator, Animation } from 'cc' | ||||
| import State from '../../Base/State' | ||||
| import StateMachine, { getInitParamsTrigger } from '../../Base/StateMachine' | ||||
| import { EntityTypeEnum } from '../../Common' | ||||
| import { EntityStateEnum, ParamsNameEnum } from '../../Enum' | ||||
| import ObjectPoolManager from '../../Global/ObjectPoolManager' | ||||
| const { ccclass, property } = _decorator | ||||
| import { _decorator, Animation } from "cc"; | ||||
| import State from "../../Base/State"; | ||||
| import StateMachine, { getInitParamsTrigger } from "../../Base/StateMachine"; | ||||
| import { EntityTypeEnum } from "../../Common"; | ||||
| import { EntityStateEnum, ParamsNameEnum } from "../../Enum"; | ||||
| import ObjectPoolManager from "../../Global/ObjectPoolManager"; | ||||
| const { ccclass, property } = _decorator; | ||||
|  | ||||
| @ccclass('ExplosionStateMachine') | ||||
| @ccclass("ExplosionStateMachine") | ||||
| export class ExplosionStateMachine extends StateMachine { | ||||
|   init(type: EntityTypeEnum) { | ||||
|     this.type = type | ||||
|     this.animationComponent = this.node.addComponent(Animation) | ||||
|     this.type = type; | ||||
|     this.animationComponent = this.node.addComponent(Animation); | ||||
|  | ||||
|     this.initParams() | ||||
|     this.initStateMachines() | ||||
|     this.initAnimationEvent() | ||||
|     this.initParams(); | ||||
|     this.initStateMachines(); | ||||
|     this.initAnimationEvent(); | ||||
|   } | ||||
|  | ||||
|   initParams() { | ||||
|     this.params.set(ParamsNameEnum.Idle, getInitParamsTrigger()) | ||||
|     this.params.set(ParamsNameEnum.Idle, getInitParamsTrigger()); | ||||
|   } | ||||
|  | ||||
|   initStateMachines() { | ||||
|     this.stateMachines.set(ParamsNameEnum.Idle, new State(this, `${this.type}${EntityStateEnum.Idle}`)) | ||||
|     this.stateMachines.set(ParamsNameEnum.Idle, new State(this, `${this.type}${EntityStateEnum.Idle}`)); | ||||
|   } | ||||
|  | ||||
|   initAnimationEvent() { | ||||
|     this.animationComponent.on(Animation.EventType.FINISHED, () => { | ||||
|       const whiteList = [EntityStateEnum.Idle] | ||||
|       const name = this.animationComponent.defaultClip.name | ||||
|       if (whiteList.some(v => name.includes(v))) { | ||||
|         ObjectPoolManager.Instance.ret(this.node) | ||||
|       const whiteList = [EntityStateEnum.Idle]; | ||||
|       const name = this.animationComponent.defaultClip.name; | ||||
|       if (whiteList.some((v) => name.includes(v))) { | ||||
|         ObjectPoolManager.Instance.ret(this.node); | ||||
|       } | ||||
|     }) | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   run() { | ||||
|     switch (this.currentState) { | ||||
|       case this.stateMachines.get(ParamsNameEnum.Idle): | ||||
|         if (this.params.get(ParamsNameEnum.Idle).value) { | ||||
|           this.currentState = this.stateMachines.get(ParamsNameEnum.Idle) | ||||
|           this.currentState = this.stateMachines.get(ParamsNameEnum.Idle); | ||||
|         } else { | ||||
|           this.currentState = this.currentState | ||||
|           this.currentState = this.currentState; | ||||
|         } | ||||
|         break | ||||
|         break; | ||||
|       default: | ||||
|         this.currentState = this.stateMachines.get(ParamsNameEnum.Idle) | ||||
|         break | ||||
|         this.currentState = this.stateMachines.get(ParamsNameEnum.Idle); | ||||
|         break; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,66 +1,61 @@ | ||||
| import { _decorator, Node, Vec2, UITransform } from 'cc' | ||||
| import { EntityManager } from '../../Base/EntityManager' | ||||
| import { ApiMsgEnum, EntityTypeEnum, InputTypeEnum, toFixed } from '../../Common' | ||||
| import { EntityStateEnum, EventEnum } from '../../Enum' | ||||
| import DataManager from '../../Global/DataManager' | ||||
| import EventManager from '../../Global/EventManager' | ||||
| import NetworkManager from '../../Global/NetworkManager' | ||||
| import { WeaponStateMachine } from './WeaponStateMachine' | ||||
| const { ccclass } = _decorator | ||||
| import { _decorator, Node, Vec2, UITransform } from "cc"; | ||||
| import { EntityManager } from "../../Base/EntityManager"; | ||||
| import { EntityTypeEnum, InputTypeEnum, toFixed } from "../../Common"; | ||||
| import { EntityStateEnum, EventEnum } from "../../Enum"; | ||||
| import DataManager from "../../Global/DataManager"; | ||||
| import EventManager from "../../Global/EventManager"; | ||||
| import { WeaponStateMachine } from "./WeaponStateMachine"; | ||||
| const { ccclass } = _decorator; | ||||
|  | ||||
| @ccclass('WeaponManager') | ||||
| @ccclass("WeaponManager") | ||||
| export class WeaponManager extends EntityManager { | ||||
|   owner: number | ||||
|   type: EntityTypeEnum | ||||
|   owner: number; | ||||
|   type: EntityTypeEnum; | ||||
|  | ||||
|   private body: Node | ||||
|   private anchor: Node | ||||
|   private point: Node | ||||
|   private body: Node; | ||||
|   private anchor: Node; | ||||
|   private point: Node; | ||||
|  | ||||
|   get isSelf() { | ||||
|     return DataManager.Instance.myPlayerId === this.owner | ||||
|   } | ||||
|   init({ id, weaponType }: { id: number; weaponType: EntityTypeEnum }) { | ||||
|     this.owner = id; | ||||
|     this.type = weaponType; | ||||
|  | ||||
|   init({ id, weaponType }: { id: number, weaponType: EntityTypeEnum }) { | ||||
|     this.owner = id | ||||
|     this.type = weaponType | ||||
|     this.node.setSiblingIndex(0); | ||||
|     this.body = this.node.getChildByName("Body"); | ||||
|     this.anchor = this.body.getChildByName("Anchor"); | ||||
|     this.point = this.anchor.getChildByName("Point"); | ||||
|  | ||||
|     this.node.setSiblingIndex(0) | ||||
|     this.body = this.node.getChildByName("Body") | ||||
|     this.anchor = this.body.getChildByName("Anchor") | ||||
|     this.point = this.anchor.getChildByName("Point") | ||||
|     this.fsm = this.body.addComponent(WeaponStateMachine); | ||||
|     this.fsm.init(weaponType); | ||||
|     this.state = EntityStateEnum.Idle; | ||||
|  | ||||
|     this.fsm = this.body.addComponent(WeaponStateMachine) | ||||
|     this.fsm.init(weaponType) | ||||
|     this.state = EntityStateEnum.Idle | ||||
|  | ||||
|     EventManager.Instance.on(EventEnum.WeaponShoot, this.handleWeaponShoot, this) | ||||
|     EventManager.Instance.on(EventEnum.BulletBorn, this.handleBulletBorn, this) | ||||
|     EventManager.Instance.on(EventEnum.WeaponShoot, this.handleWeaponShoot, this); | ||||
|     EventManager.Instance.on(EventEnum.BulletBorn, this.handleBulletBorn, this); | ||||
|   } | ||||
|  | ||||
|   onDestroy() { | ||||
|     EventManager.Instance.off(EventEnum.WeaponShoot, this.handleWeaponShoot, this) | ||||
|     EventManager.Instance.off(EventEnum.BulletBorn, this.handleBulletBorn, this) | ||||
|     EventManager.Instance.off(EventEnum.WeaponShoot, this.handleWeaponShoot, this); | ||||
|     EventManager.Instance.off(EventEnum.BulletBorn, this.handleBulletBorn, this); | ||||
|   } | ||||
|  | ||||
|   handleBulletBorn(owner: number) { | ||||
|     if (this.owner !== owner) { | ||||
|       return | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     this.state = EntityStateEnum.Attack | ||||
|     this.state = EntityStateEnum.Attack; | ||||
|   } | ||||
|  | ||||
|   handleWeaponShoot() { | ||||
|     if (!this.isSelf) { | ||||
|       return | ||||
|     if (DataManager.Instance.myPlayerId !== this.owner) { | ||||
|       return; | ||||
|     } | ||||
|     const pointWorldPos = this.point.getWorldPosition() | ||||
|     const pointWorldPos = this.point.getWorldPosition(); | ||||
|     const pointStagePos = DataManager.Instance.stage.getComponent(UITransform).convertToNodeSpaceAR(pointWorldPos); | ||||
|     const anchorWorldPos = this.anchor.getWorldPosition() | ||||
|     const directionVec2 = new Vec2(pointWorldPos.x - anchorWorldPos.x, pointWorldPos.y - anchorWorldPos.y).normalize() | ||||
|     const anchorWorldPos = this.anchor.getWorldPosition(); | ||||
|     const directionVec2 = new Vec2(pointWorldPos.x - anchorWorldPos.x, pointWorldPos.y - anchorWorldPos.y).normalize(); | ||||
|  | ||||
|     NetworkManager.Instance.sendMsg(ApiMsgEnum.MsgClientSync, { | ||||
|     EventManager.Instance.emit(EventEnum.ClientSync, { | ||||
|       type: InputTypeEnum.WeaponShoot, | ||||
|       owner: this.owner, | ||||
|       position: { | ||||
| @@ -71,6 +66,6 @@ export class WeaponManager extends EntityManager { | ||||
|         x: toFixed(directionVec2.x), | ||||
|         y: toFixed(directionVec2.y), | ||||
|       }, | ||||
|     }) | ||||
|     }); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,59 +1,60 @@ | ||||
| export enum FsmParamTypeEnum { | ||||
|   Number = 'Number', | ||||
|   Trigger = 'Trigger', | ||||
| } | ||||
|  | ||||
| export enum EntityStateEnum { | ||||
|   Idle = 'Idle', | ||||
|   Run = 'Run', | ||||
|   Attack = 'Attack', | ||||
|   Number = "Number", | ||||
|   Trigger = "Trigger", | ||||
| } | ||||
|  | ||||
| export enum ParamsNameEnum { | ||||
|   Idle = 'Idle', | ||||
|   Run = 'Run', | ||||
|   Attack = 'Attack', | ||||
|   Idle = "Idle", | ||||
|   Run = "Run", | ||||
|   Attack = "Attack", | ||||
| } | ||||
|  | ||||
| export enum EntityStateEnum { | ||||
|   Idle = "Idle", | ||||
|   Run = "Run", | ||||
|   Attack = "Attack", | ||||
| } | ||||
|  | ||||
| export enum EventEnum { | ||||
|   WeaponShoot = 'WeaponShoot', | ||||
|   BulletBorn = 'BulletBorn', | ||||
|   ExplosionBorn = 'ExplosionBorn', | ||||
|   RoomJoin = 'RoomJoin', | ||||
|   GameStart = 'GameStart', | ||||
|   GameEnd = 'GameEnd', | ||||
|   WeaponShoot = "WeaponShoot", | ||||
|   BulletBorn = "BulletBorn", | ||||
|   ExplosionBorn = "ExplosionBorn", | ||||
|   RoomJoin = "RoomJoin", | ||||
|   GameStart = "GameStart", | ||||
|   GameEnd = "GameEnd", | ||||
|   ClientSync = "ClientSync", | ||||
| } | ||||
|  | ||||
| export enum PrefabPathEnum { | ||||
|   Map1 = 'prefab/Map1', | ||||
|   Actor1 = 'prefab/Actor', | ||||
|   Actor2 = 'prefab/Actor', | ||||
|   Weapon1 = 'prefab/Weapon1', | ||||
|   Weapon2 = 'prefab/Weapon2', | ||||
|   Bullet1 = 'prefab/Bullet', | ||||
|   Bullet2 = 'prefab/Bullet', | ||||
|   Explosion = 'prefab/Explosion', | ||||
|   JoyStick = 'prefab/JoyStick', | ||||
|   Shoot = 'prefab/Shoot', | ||||
|   Map1 = "prefab/Map1", | ||||
|   Actor1 = "prefab/Actor", | ||||
|   Actor2 = "prefab/Actor", | ||||
|   Weapon1 = "prefab/Weapon1", | ||||
|   Weapon2 = "prefab/Weapon2", | ||||
|   Bullet1 = "prefab/Bullet", | ||||
|   Bullet2 = "prefab/Bullet", | ||||
|   Explosion = "prefab/Explosion", | ||||
|   JoyStick = "prefab/JoyStick", | ||||
|   Shoot = "prefab/Shoot", | ||||
| } | ||||
|  | ||||
| export enum TexturePathEnum { | ||||
|   Actor1Idle = 'texture/actor/actor1/idle', | ||||
|   Actor1Run = 'texture/actor/actor1/run', | ||||
|   Actor2Idle = 'texture/actor/actor2/idle', | ||||
|   Actor2Run = 'texture/actor/actor2/run', | ||||
|   Weapon1Idle = 'texture/weapon/weapon1/idle', | ||||
|   Weapon1Attack = 'texture/weapon/weapon1/attack', | ||||
|   Weapon2Idle = 'texture/weapon/weapon2/idle', | ||||
|   Weapon2Attack = 'texture/weapon/weapon2/attack', | ||||
|   Bullet1Idle = 'texture/bullet/bullet1', | ||||
|   Bullet2Idle = 'texture/bullet/bullet2', | ||||
|   ExplosionIdle = 'texture/explosion', | ||||
|   Actor1Idle = "texture/actor/actor1/idle", | ||||
|   Actor1Run = "texture/actor/actor1/run", | ||||
|   Actor2Idle = "texture/actor/actor2/idle", | ||||
|   Actor2Run = "texture/actor/actor2/run", | ||||
|   Weapon1Idle = "texture/weapon/weapon1/idle", | ||||
|   Weapon1Attack = "texture/weapon/weapon1/attack", | ||||
|   Weapon2Idle = "texture/weapon/weapon2/idle", | ||||
|   Weapon2Attack = "texture/weapon/weapon2/attack", | ||||
|   Bullet1Idle = "texture/bullet/bullet1", | ||||
|   Bullet2Idle = "texture/bullet/bullet2", | ||||
|   ExplosionIdle = "texture/explosion", | ||||
| } | ||||
|  | ||||
| export enum SceneEnum { | ||||
|   Login = 'Login', | ||||
|   Hall = 'Hall', | ||||
|   Room = 'Room', | ||||
|   Battle = 'Battle', | ||||
| } | ||||
|   Login = "Login", | ||||
|   Hall = "Hall", | ||||
|   Room = "Room", | ||||
|   Battle = "Battle", | ||||
| } | ||||
|   | ||||
| @@ -1,151 +1,160 @@ | ||||
| import { Node, Prefab, SpriteFrame, clamp } from 'cc' | ||||
| import Singleton from '../Base/Singleton' | ||||
| import { EntityTypeEnum, IBullet, IClientInput, InputTypeEnum, IRoom, IState, toFixed } from '../Common' | ||||
| import { ActorManager } from '../Entity/Actor/ActorManager' | ||||
| import { BulletManager } from '../Entity/Bullet/BulletManager' | ||||
| import { EventEnum } from '../Enum' | ||||
| import { JoyStickManager } from '../UI/JoyStickManager' | ||||
| import EventManager from './EventManager' | ||||
| import { Node, Prefab, SpriteFrame, clamp } from "cc"; | ||||
| import Singleton from "../Base/Singleton"; | ||||
| import { EntityTypeEnum, IBullet, IClientInput, InputTypeEnum, IRoom, IState, toFixed } from "../Common"; | ||||
| import { ActorManager } from "../Entity/Actor/ActorManager"; | ||||
| import { BulletManager } from "../Entity/Bullet/BulletManager"; | ||||
| import { EventEnum } from "../Enum"; | ||||
| import { JoyStickManager } from "../UI/JoyStickManager"; | ||||
| import EventManager from "./EventManager"; | ||||
|  | ||||
| const PLAYER_SPEED = 100; | ||||
| const BULLET_SPEED = 600; | ||||
|  | ||||
| const PLAYER_SPEED = 100 | ||||
| const BULLET_SPEED = 600 | ||||
| const WEAPON_DAMAGE = 5; | ||||
|  | ||||
| const WEAPON_DAMAGE = 5 | ||||
| const PLAYER_RADIUS = 50; | ||||
| const BULLET_RADIUS = 10; | ||||
|  | ||||
| const PLAYER_RADIUS = 50 | ||||
| const BULLET_RADIUS = 10 | ||||
|  | ||||
| const mapW = 960 | ||||
| const mapH = 640 | ||||
| const mapW = 960; | ||||
| const mapH = 640; | ||||
|  | ||||
| export default class DataManager extends Singleton { | ||||
|   static get Instance() { | ||||
|     return super.GetInstance<DataManager>() | ||||
|     return super.GetInstance<DataManager>(); | ||||
|   } | ||||
|  | ||||
|   //登陆数据 | ||||
|   myPlayerId = 1 | ||||
|   myPlayerId = 1; | ||||
|  | ||||
|   //大厅数据 | ||||
|   roomInfo: IRoom | ||||
|   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() | ||||
|   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(); | ||||
|  | ||||
|   reset() { | ||||
|     this.stage = null | ||||
|     this.jm = null | ||||
|     this.actorMap.clear() | ||||
|     this.bulletMap.clear() | ||||
|     this.prefabMap.clear() | ||||
|     this.textureMap.clear() | ||||
|     this.frameId = 0; | ||||
|     this.stage = null; | ||||
|     this.jm = null; | ||||
|     this.actorMap.clear(); | ||||
|     this.bulletMap.clear(); | ||||
|     this.prefabMap.clear(); | ||||
|     this.textureMap.clear(); | ||||
|   } | ||||
|  | ||||
|   frameId = 0; | ||||
|   lastState: IState; | ||||
|   state: IState = { | ||||
|     players: [{ | ||||
|       id: 2, | ||||
|       nickname: "哈哈1", | ||||
|       position: { | ||||
|         x: -200, | ||||
|         y: -200 | ||||
|     actors: [ | ||||
|       { | ||||
|         id: 2, | ||||
|         nickname: "哈哈1", | ||||
|         position: { | ||||
|           x: -200, | ||||
|           y: -200, | ||||
|         }, | ||||
|         direction: { | ||||
|           x: 1, | ||||
|           y: 0, | ||||
|         }, | ||||
|         hp: 100, | ||||
|         type: EntityTypeEnum.Actor1, | ||||
|         weaponType: EntityTypeEnum.Weapon1, | ||||
|         bulletType: EntityTypeEnum.Bullet1, | ||||
|       }, | ||||
|       direction: { | ||||
|         x: 1, | ||||
|         y: 0 | ||||
|       { | ||||
|         id: 1, | ||||
|         nickname: "哈哈2", | ||||
|         position: { | ||||
|           x: 200, | ||||
|           y: 200, | ||||
|         }, | ||||
|         direction: { | ||||
|           x: 0, | ||||
|           y: -1, | ||||
|         }, | ||||
|         hp: 100, | ||||
|         type: EntityTypeEnum.Actor2, | ||||
|         weaponType: EntityTypeEnum.Weapon2, | ||||
|         bulletType: EntityTypeEnum.Bullet2, | ||||
|       }, | ||||
|       hp: 100, | ||||
|       type: EntityTypeEnum.Actor1, | ||||
|       weaponType: EntityTypeEnum.Weapon1, | ||||
|       bulletType: EntityTypeEnum.Bullet1, | ||||
|     }, { | ||||
|       id: 1, | ||||
|       nickname: "哈哈2", | ||||
|       position: { | ||||
|         x: 200, | ||||
|         y: 200 | ||||
|       }, | ||||
|       direction: { | ||||
|         x: 0, | ||||
|         y: -1 | ||||
|       }, | ||||
|       hp: 100, | ||||
|       type: EntityTypeEnum.Actor2, | ||||
|       weaponType: EntityTypeEnum.Weapon2, | ||||
|       bulletType: EntityTypeEnum.Bullet2, | ||||
|     }], | ||||
|     ], | ||||
|     bullets: [], | ||||
|     nextBulletId: 1 | ||||
|   } | ||||
|     nextBulletId: 1, | ||||
|   }; | ||||
|  | ||||
|   applyInput(input: IClientInput) { | ||||
|     switch (input.type) { | ||||
|       case InputTypeEnum.ActorMove: { | ||||
|         const { direction: { x, y }, dt, id } = input | ||||
|         const player = this.state.players.find(e => e.id === id) | ||||
|         const { | ||||
|           direction: { x, y }, | ||||
|           dt, | ||||
|           id, | ||||
|         } = input; | ||||
|         const player = this.state.actors.find((e) => e.id === id); | ||||
|         if (!player) { | ||||
|           return | ||||
|           return; | ||||
|         } | ||||
|  | ||||
|         player.position.x += toFixed(x * PLAYER_SPEED * dt) | ||||
|         player.position.y += toFixed(y * PLAYER_SPEED * dt) | ||||
|         player.position.x += toFixed(x * PLAYER_SPEED * dt); | ||||
|         player.position.y += toFixed(y * PLAYER_SPEED * dt); | ||||
|  | ||||
|         player.position.x = clamp(player.position.x, -mapW / 2, mapW / 2) | ||||
|         player.position.y = clamp(player.position.y, -mapH / 2, mapH / 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 | ||||
|         player.direction = { x, y }; | ||||
|         break; | ||||
|       } | ||||
|       case InputTypeEnum.WeaponShoot: { | ||||
|         const { owner, position, direction } = input | ||||
|         const { owner, position, direction } = input; | ||||
|         const bullet: IBullet = { | ||||
|           id: this.state.nextBulletId++, | ||||
|           owner, | ||||
|           position, | ||||
|           direction, | ||||
|           type: this.actorMap.get(owner).bulletType | ||||
|         } | ||||
|         this.state.bullets.push(bullet) | ||||
|           type: this.actorMap.get(owner).bulletType, | ||||
|         }; | ||||
|         this.state.bullets.push(bullet); | ||||
|  | ||||
|         EventManager.Instance.emit(EventEnum.BulletBorn, owner) | ||||
|         break | ||||
|         EventManager.Instance.emit(EventEnum.BulletBorn, owner); | ||||
|         break; | ||||
|       } | ||||
|       case InputTypeEnum.TimePast: { | ||||
|         const { dt } = input | ||||
|         const { bullets, players } = this.state | ||||
|         const { dt } = input; | ||||
|         const { bullets, actors } = this.state; | ||||
|  | ||||
|         for (let i = bullets.length - 1; i >= 0; i--) { | ||||
|           const bullet = bullets[i]; | ||||
|           for (let j = players.length - 1; j >= 0; 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) { | ||||
|           for (let j = actors.length - 1; j >= 0; j--) { | ||||
|             const player = actors[j]; | ||||
|             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, { | ||||
|                 x: toFixed((player.position.x + bullet.position.x) / 2), | ||||
|                 y: toFixed((player.position.y + bullet.position.y) / 2), | ||||
|               }) | ||||
|               }); | ||||
|  | ||||
|               player.hp -= WEAPON_DAMAGE | ||||
|               bullets.splice(i, 1) | ||||
|               break | ||||
|               player.hp -= WEAPON_DAMAGE; | ||||
|               bullets.splice(i, 1); | ||||
|               break; | ||||
|             } | ||||
|           } | ||||
|           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, | ||||
|             }) | ||||
|             bullets.splice(i, 1) | ||||
|             }); | ||||
|             bullets.splice(i, 1); | ||||
|           } | ||||
|         } | ||||
|  | ||||
|         for (const bullet of this.state.bullets) { | ||||
|           bullet.position.x += toFixed(bullet.direction.x * BULLET_SPEED * dt) | ||||
|           bullet.position.y += toFixed(bullet.direction.y * BULLET_SPEED * dt) | ||||
|           bullet.position.x += toFixed(bullet.direction.x * BULLET_SPEED * dt); | ||||
|           bullet.position.y += toFixed(bullet.direction.y * BULLET_SPEED * dt); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| import Singleton from '../Base/Singleton' | ||||
| import { EventEnum } from '../Enum'; | ||||
| import Singleton from "../Base/Singleton"; | ||||
| import { EventEnum } from "../Enum"; | ||||
|  | ||||
| interface IItem { | ||||
|   cb: Function; | ||||
| @@ -23,7 +23,7 @@ export default class EventManager extends Singleton { | ||||
|  | ||||
|   off(event: EventEnum, cb: Function, ctx: unknown) { | ||||
|     if (this.map.has(event)) { | ||||
|       const index = this.map.get(event).findIndex(i => cb === i.cb && 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); | ||||
|     } | ||||
|   } | ||||
| @@ -31,7 +31,7 @@ export default class EventManager extends Singleton { | ||||
|   emit(event: EventEnum, ...params: unknown[]) { | ||||
|     if (this.map.has(event)) { | ||||
|       this.map.get(event).forEach(({ cb, ctx }) => { | ||||
|         cb.apply(ctx, params) | ||||
|         cb.apply(ctx, params); | ||||
|       }); | ||||
|     } | ||||
|   } | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| import Singleton from '../Base/Singleton' | ||||
| import { ApiMsgEnum, IModel } from '../Common'; | ||||
| import { binaryEncode, binaryDecode } from '../Common/Binary'; | ||||
| import Singleton from "../Base/Singleton"; | ||||
| import { ApiMsgEnum, IModel } from "../Common"; | ||||
| import { binaryEncode, binaryDecode } from "../Common/Binary"; | ||||
|  | ||||
| const TIMEOUT = 5000 | ||||
| const TIMEOUT = 5000; | ||||
|  | ||||
| interface IItem { | ||||
|   cb: Function; | ||||
| @@ -12,109 +12,107 @@ interface IItem { | ||||
| export interface ICallApiRet<T> { | ||||
|   success: boolean; | ||||
|   error?: Error; | ||||
|   res?: T | ||||
|   res?: T; | ||||
| } | ||||
|  | ||||
| type aaa = keyof IModel | ||||
| export default class NetworkManager extends Singleton { | ||||
|   static get Instance() { | ||||
|     return super.GetInstance<NetworkManager>() | ||||
|     return super.GetInstance<NetworkManager>(); | ||||
|   } | ||||
|  | ||||
|   ws: WebSocket | ||||
|   port = 8888 | ||||
|   maps: Map<ApiMsgEnum, Array<IItem>> = new Map() | ||||
|   isConnected = false | ||||
|   ws: WebSocket; | ||||
|   port = 8888; | ||||
|   maps: Map<ApiMsgEnum, Array<IItem>> = new Map(); | ||||
|   isConnected = false; | ||||
|  | ||||
|   connect() { | ||||
|     return new Promise((resolve, reject) => { | ||||
|       if (this.isConnected) { | ||||
|         resolve(true) | ||||
|         return | ||||
|         resolve(true); | ||||
|         return; | ||||
|       } | ||||
|       this.ws = new WebSocket(`ws://localhost:${this.port}`) | ||||
|       this.ws = new WebSocket(`ws://localhost:${this.port}`); | ||||
|       //onmessage接受的数据类型,只有在后端返回字节数组的时候才有效果 | ||||
|       this.ws.binaryType = 'arraybuffer'; | ||||
|       this.ws.binaryType = "arraybuffer"; | ||||
|  | ||||
|       this.ws.onopen = () => { | ||||
|         this.isConnected = true | ||||
|         resolve(true) | ||||
|       } | ||||
|         this.isConnected = true; | ||||
|         resolve(true); | ||||
|       }; | ||||
|  | ||||
|       this.ws.onerror = (e) => { | ||||
|         this.isConnected = false | ||||
|         console.log(e) | ||||
|         reject("ws onerror") | ||||
|       } | ||||
|         this.isConnected = false; | ||||
|         console.log(e); | ||||
|         reject("ws onerror"); | ||||
|       }; | ||||
|  | ||||
|       this.ws.onclose = () => { | ||||
|         this.isConnected = false | ||||
|         reject("ws onclose") | ||||
|       } | ||||
|         this.isConnected = false; | ||||
|         reject("ws onclose"); | ||||
|       }; | ||||
|  | ||||
|       this.ws.onmessage = (e) => { | ||||
|         try { | ||||
|           const json = binaryDecode(e.data) | ||||
|           const { name, data } = json | ||||
|           const json = binaryDecode(e.data); | ||||
|           const { name, data } = json; | ||||
|           try { | ||||
|             if (this.maps.has(name) && this.maps.get(name).length) { | ||||
|               console.log(json); | ||||
|               this.maps.get(name).forEach(({ cb, ctx }) => cb.call(ctx, data)) | ||||
|               this.maps.get(name).forEach(({ cb, ctx }) => cb.call(ctx, data)); | ||||
|             } | ||||
|           } catch (error) { | ||||
|             console.log("onmessage:", error) | ||||
|             console.log("onmessage:", error); | ||||
|           } | ||||
|         } catch (error) { | ||||
|           console.log('解析失败,不是合法的JSON格式', error) | ||||
|           console.log("解析失败,不是合法的JSON格式", error); | ||||
|         } | ||||
|       } | ||||
|     }) | ||||
|       }; | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   callApi<T extends keyof IModel['api']>(name: T, data: IModel['api'][T]['req']): Promise<ICallApiRet<IModel['api'][T]['res']>> { | ||||
|   callApi<T extends keyof IModel["api"]>(name: T, data: IModel["api"][T]["req"]): Promise<ICallApiRet<IModel["api"][T]["res"]>> { | ||||
|     return new Promise((resolve) => { | ||||
|       try { | ||||
|         // 超时处理 | ||||
|         const timer = setTimeout(() => { | ||||
|           resolve({ success: false, error: new Error('timeout') }) | ||||
|           this.unlistenMsg(name as any, cb, null) | ||||
|         }, TIMEOUT) | ||||
|           resolve({ success: false, error: new Error("timeout") }); | ||||
|           this.unlistenMsg(name as any, cb, null); | ||||
|         }, TIMEOUT); | ||||
|  | ||||
|         // 回调处理 | ||||
|         const cb = (res) => { | ||||
|           resolve(res) | ||||
|           clearTimeout(timer) | ||||
|           this.unlistenMsg(name as any, cb, null) | ||||
|         } | ||||
|         this.listenMsg(name as any, cb, null) | ||||
|           resolve(res); | ||||
|           clearTimeout(timer); | ||||
|           this.unlistenMsg(name as any, cb, null); | ||||
|         }; | ||||
|         this.listenMsg(name as any, cb, null); | ||||
|  | ||||
|         this.sendMsg(name as any, data) | ||||
|         this.sendMsg(name as any, data); | ||||
|       } catch (error) { | ||||
|         resolve({ success: false, error: error as Error }) | ||||
|         resolve({ success: false, error: error as Error }); | ||||
|       } | ||||
|     }) | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   async sendMsg<T extends keyof IModel['msg']>(name: T, data: IModel['msg'][T]) { | ||||
|     const view = binaryEncode(name, data) | ||||
|     let delay = parseInt(new URLSearchParams(location.search).get('delay') || '0') || 0; | ||||
|     await new Promise((r) => setTimeout(r, delay)) | ||||
|     this.ws.send(view.buffer) | ||||
|   async sendMsg<T extends keyof IModel["msg"]>(name: T, data: IModel["msg"][T]) { | ||||
|     const view = binaryEncode(name, data); | ||||
|     let delay = parseInt(new URLSearchParams(location.search).get("delay") || "0") || 0; | ||||
|     await new Promise((r) => setTimeout(r, delay)); | ||||
|     this.ws.send(view.buffer); | ||||
|   } | ||||
|  | ||||
|   listenMsg<T extends keyof IModel['msg']>(name: T, cb: (args: IModel['msg'][T]) => void, ctx: unknown) { | ||||
|   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({ ctx, cb }) | ||||
|       this.maps.get(name).push({ ctx, cb }); | ||||
|     } else { | ||||
|       this.maps.set(name, [{ ctx, cb }]) | ||||
|       this.maps.set(name, [{ ctx, cb }]); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   unlistenMsg<T extends keyof IModel['msg']>(name: T, cb: (args: IModel['msg'][T]) => void, ctx: unknown) { | ||||
|   unlistenMsg<T extends keyof IModel["msg"]>(name: T, cb: (args: IModel["msg"][T]) => void, ctx: unknown) { | ||||
|     if (this.maps.has(name)) { | ||||
|       const items = this.maps.get(name) | ||||
|       const index = items.findIndex(i => cb === i.cb && i.ctx === ctx); | ||||
|       index > -1 && items.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); | ||||
|     } | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,55 +1,55 @@ | ||||
| import Singleton from '../Base/Singleton' | ||||
| import { instantiate, Node } from 'cc' | ||||
| import DataManager from './DataManager' | ||||
| import { EntityTypeEnum } from '../Common' | ||||
| import Singleton from "../Base/Singleton"; | ||||
| import { instantiate, Node } from "cc"; | ||||
| import DataManager from "./DataManager"; | ||||
| import { EntityTypeEnum } from "../Common"; | ||||
|  | ||||
| export default class ObjectPoolManager extends Singleton { | ||||
|   static get Instance() { | ||||
|     return super.GetInstance<ObjectPoolManager>() | ||||
|     return super.GetInstance<ObjectPoolManager>(); | ||||
|   } | ||||
|  | ||||
|   private objectPool: Node = null | ||||
|   private map: Map<EntityTypeEnum, Node[]> = new Map() | ||||
|   private objectPool: Node = null; | ||||
|   private map: Map<EntityTypeEnum, Node[]> = new Map(); | ||||
|  | ||||
|   private getContainerName(objectName: EntityTypeEnum) { | ||||
|     return objectName + 'Pool' | ||||
|     return objectName + "Pool"; | ||||
|   } | ||||
|  | ||||
|   reset() { | ||||
|     this.objectPool = null | ||||
|     this.map.clear() | ||||
|     this.objectPool = null; | ||||
|     this.map.clear(); | ||||
|   } | ||||
|  | ||||
|   get(objectName: EntityTypeEnum) { | ||||
|     if (this.objectPool === null) { | ||||
|       this.objectPool = new Node("ObjectPool") | ||||
|       this.objectPool.setParent(DataManager.Instance.stage) | ||||
|       this.objectPool = new Node("ObjectPool"); | ||||
|       this.objectPool.setParent(DataManager.Instance.stage); | ||||
|     } | ||||
|  | ||||
|     if (!this.map.has(objectName)) { | ||||
|       this.map.set(objectName, []) | ||||
|       const container = new Node(this.getContainerName(objectName)) | ||||
|       container.setParent(this.objectPool) | ||||
|       this.map.set(objectName, []); | ||||
|       const container = new Node(this.getContainerName(objectName)); | ||||
|       container.setParent(this.objectPool); | ||||
|     } | ||||
|  | ||||
|     let node: Node | ||||
|     const nodes = this.map.get(objectName) | ||||
|     let node: Node; | ||||
|     const nodes = this.map.get(objectName); | ||||
|  | ||||
|     if (!nodes.length) { | ||||
|       const prefab = DataManager.Instance.prefabMap.get(objectName) | ||||
|       node = instantiate(prefab) | ||||
|       node.name = objectName | ||||
|       node.setParent(this.objectPool.getChildByName(this.getContainerName(objectName))) | ||||
|       const prefab = DataManager.Instance.prefabMap.get(objectName); | ||||
|       node = instantiate(prefab); | ||||
|       node.name = objectName; | ||||
|       node.setParent(this.objectPool.getChildByName(this.getContainerName(objectName))); | ||||
|     } else { | ||||
|       node = nodes.pop() | ||||
|       node = nodes.pop(); | ||||
|     } | ||||
|     node.active = true | ||||
|     return node | ||||
|     node.active = true; | ||||
|     return node; | ||||
|   } | ||||
|  | ||||
|   ret(object: Node) { | ||||
|     object.active = false | ||||
|     const objectName = object.name as EntityTypeEnum | ||||
|     this.map.get(objectName).push(object) | ||||
|     object.active = false; | ||||
|     const objectName = object.name as EntityTypeEnum; | ||||
|     this.map.get(objectName).push(object); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,32 +1,32 @@ | ||||
| import { _decorator, resources, Asset } from 'cc' | ||||
| import Singleton from '../Base/Singleton' | ||||
| import { _decorator, resources, Asset } from "cc"; | ||||
| import Singleton from "../Base/Singleton"; | ||||
|  | ||||
| export class ResourceManager extends Singleton { | ||||
|   static get Instance() { | ||||
|     return super.GetInstance<ResourceManager>() | ||||
|     return super.GetInstance<ResourceManager>(); | ||||
|   } | ||||
|  | ||||
|   loadRes<T extends Asset>(path: string, type: new (...args: any[]) => T) { | ||||
|     return new Promise<T>((resolve, reject) => { | ||||
|       resources.load(path, type, (err, res) => { | ||||
|         if (err) { | ||||
|           reject(err) | ||||
|           return | ||||
|           reject(err); | ||||
|           return; | ||||
|         } | ||||
|         resolve(res) | ||||
|       }) | ||||
|     }) | ||||
|         resolve(res); | ||||
|       }); | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   loadDir<T extends Asset>(path: string, type: new (...args: any[]) => T) { | ||||
|     return new Promise<T[]>((resolve, reject) => { | ||||
|       resources.loadDir(path, type, (err, res) => { | ||||
|         if (err) { | ||||
|           reject(err) | ||||
|           return | ||||
|           reject(err); | ||||
|           return; | ||||
|         } | ||||
|         resolve(res) | ||||
|       }) | ||||
|     }) | ||||
|         resolve(res); | ||||
|       }); | ||||
|     }); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,192 +1,218 @@ | ||||
| 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 { 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'; | ||||
| 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 { 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, IClientInput, IMsgServerSync, InputTypeEnum, toFixed } from "../Common"; | ||||
| import EventManager from "../Global/EventManager"; | ||||
| import { deepClone } from "../Utils"; | ||||
|  | ||||
| const { ccclass } = _decorator; | ||||
|  | ||||
| @ccclass('BattleManager') | ||||
| @ccclass("BattleManager") | ||||
| export class BattleManager extends Component { | ||||
|     private stage: Node | ||||
|     private ui: Node | ||||
|     private shouldUpdate = false | ||||
|   private stage: Node; | ||||
|   private ui: Node; | ||||
|   private shouldUpdate = false; | ||||
|   private pendingMsg = []; | ||||
|  | ||||
|     async start() { | ||||
|         //清空 | ||||
|         this.clearGame() | ||||
|   async start() { | ||||
|     //清空 | ||||
|     this.clearGame(); | ||||
|  | ||||
|         //资源加载和网络连接同步执行 | ||||
|         await Promise.all([this.loadRes(), this.connectServer()]) | ||||
|     //资源加载和网络连接同步执行 | ||||
|     await Promise.all([this.loadRes(), this.connectServer()]); | ||||
|  | ||||
|         this.initGame() | ||||
|     this.initGame(); | ||||
|  | ||||
|         // 在场景初始化完毕之前,卡主别的玩家,准备好以后再告知服务器,等所有玩家都准备好以后才开始,这里就不做了 | ||||
|         NetworkManager.Instance.listenMsg(ApiMsgEnum.MsgServerSync, this.handleSync, this); | ||||
|         NetworkManager.Instance.listenMsg(ApiMsgEnum.MsgGameEnd, this.leaveGame, this); | ||||
|         EventManager.Instance.on(EventEnum.GameEnd, this.handleGameEnd, this) | ||||
|     // 在场景初始化完毕之前,卡主别的玩家,准备好以后再告知服务器,等所有玩家都准备好以后才开始,这里就不做了 | ||||
|     NetworkManager.Instance.listenMsg(ApiMsgEnum.MsgServerSync, this.listenServerSync, this); | ||||
|     NetworkManager.Instance.listenMsg(ApiMsgEnum.MsgGameEnd, this.listenGameEnd, this); | ||||
|     EventManager.Instance.on(EventEnum.ClientSync, this.handleClientSync, this); | ||||
|     EventManager.Instance.on(EventEnum.GameEnd, this.handleGameEnd, this); | ||||
|   } | ||||
|  | ||||
|   clearGame() { | ||||
|     //事件 | ||||
|     NetworkManager.Instance.unlistenMsg(ApiMsgEnum.MsgServerSync, this.listenServerSync, this); | ||||
|     NetworkManager.Instance.unlistenMsg(ApiMsgEnum.MsgGameEnd, this.listenGameEnd, this); | ||||
|     EventManager.Instance.off(EventEnum.ClientSync, this.handleClientSync, 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(); | ||||
|   } | ||||
|  | ||||
|   async loadRes() { | ||||
|     const list = []; | ||||
|     for (const type in PrefabPathEnum) { | ||||
|       const p = ResourceManager.Instance.loadRes(PrefabPathEnum[type], Prefab).then((prefab) => { | ||||
|         DataManager.Instance.prefabMap.set(type, prefab); | ||||
|       }); | ||||
|       list.push(p); | ||||
|     } | ||||
|  | ||||
|     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() | ||||
|     for (const type in TexturePathEnum) { | ||||
|       const p = ResourceManager.Instance.loadDir(TexturePathEnum[type], SpriteFrame).then((spriteFrames) => { | ||||
|         DataManager.Instance.textureMap.set(type, spriteFrames); | ||||
|       }); | ||||
|       list.push(p); | ||||
|     } | ||||
|     await Promise.all(list); | ||||
|   } | ||||
|  | ||||
|     async loadRes() { | ||||
|         const list = [] | ||||
|         for (const type in PrefabPathEnum) { | ||||
|             const p = ResourceManager.Instance.loadRes(PrefabPathEnum[type], Prefab).then((prefab) => { | ||||
|                 DataManager.Instance.prefabMap.set(type, prefab) | ||||
|             }) | ||||
|             list.push(p) | ||||
|         } | ||||
|         for (const type in TexturePathEnum) { | ||||
|             const p = ResourceManager.Instance.loadDir(TexturePathEnum[type], SpriteFrame).then((spriteFrames) => { | ||||
|                 DataManager.Instance.textureMap.set(type, spriteFrames) | ||||
|             }) | ||||
|             list.push(p) | ||||
|         } | ||||
|         await Promise.all(list) | ||||
|   async connectServer() { | ||||
|     if (!(await NetworkManager.Instance.connect().catch(() => false))) { | ||||
|       await new Promise((resolve) => setTimeout(resolve, 1000)); | ||||
|       await this.connectServer(); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|     async connectServer() { | ||||
|         if (!await NetworkManager.Instance.connect().catch(() => false)) { | ||||
|             await new Promise((resolve) => setTimeout(resolve, 1000)) | ||||
|             await this.connectServer() | ||||
|         } | ||||
|   async initGame() { | ||||
|     this.initJoyStick(); | ||||
|     this.initShoot(); | ||||
|     this.initMap(); | ||||
|     this.shouldUpdate = true; | ||||
|   } | ||||
|  | ||||
|   initJoyStick() { | ||||
|     const prefab = DataManager.Instance.prefabMap.get(EntityTypeEnum.JoyStick); | ||||
|     const joySitck = instantiate(prefab); | ||||
|     joySitck.setParent(this.ui); | ||||
|     const jm = (DataManager.Instance.jm = joySitck.getComponent(JoyStickManager)); | ||||
|     jm.init(); | ||||
|   } | ||||
|  | ||||
|   initShoot() { | ||||
|     const prefab = DataManager.Instance.prefabMap.get(EntityTypeEnum.Shoot); | ||||
|     const shoot = instantiate(prefab); | ||||
|     shoot.setParent(this.ui); | ||||
|   } | ||||
|  | ||||
|   initMap() { | ||||
|     const prefab = DataManager.Instance.prefabMap.get(EntityTypeEnum.Map1); | ||||
|     const map = instantiate(prefab); | ||||
|     map.setParent(this.stage); | ||||
|   } | ||||
|  | ||||
|   update(dt: number) { | ||||
|     if (!this.shouldUpdate) { | ||||
|       return; | ||||
|     } | ||||
|     this.render(); | ||||
|     this.tick(dt); | ||||
|   } | ||||
|  | ||||
|     leaveGame() { | ||||
|         this.clearGame() | ||||
|         director.loadScene(SceneEnum.Hall); | ||||
|   tick(dt: number) { | ||||
|     this.tickPlayer(dt); | ||||
|     // this.tickGlobal(dt) | ||||
|   } | ||||
|  | ||||
|   tickPlayer(dt: number) { | ||||
|     for (const p of DataManager.Instance.state.actors) { | ||||
|       const playerManager = DataManager.Instance.actorMap.get(p.id); | ||||
|       if (!playerManager) { | ||||
|         return; | ||||
|       } | ||||
|       playerManager.tick(dt); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|     async handleGameEnd() { | ||||
|         const { success, res, error } = await NetworkManager.Instance.callApi(ApiMsgEnum.ApiGameEnd, { rid: DataManager.Instance.roomInfo.id }) | ||||
|         if (!success) { | ||||
|             console.log(error) | ||||
|             return; | ||||
|         } | ||||
|   // tickGlobal(dt: number) { | ||||
|   //     DataManager.Instance.applyInput({ | ||||
|   //         type: InputTypeEnum.TimePast, | ||||
|   //         dt: toFixed(dt), | ||||
|   //     }) | ||||
|   // } | ||||
|  | ||||
|   render() { | ||||
|     this.renderPlayer(); | ||||
|     this.renderBullet(); | ||||
|   } | ||||
|  | ||||
|   renderPlayer() { | ||||
|     for (const p of DataManager.Instance.state.actors) { | ||||
|       let actorManager = DataManager.Instance.actorMap.get(p.id); | ||||
|       if (!actorManager) { | ||||
|         const playerPrefab = DataManager.Instance.prefabMap.get(p.type); | ||||
|         const player = instantiate(playerPrefab); | ||||
|         player.setParent(this.stage); | ||||
|         actorManager = player.addComponent(ActorManager); | ||||
|         DataManager.Instance.actorMap.set(p.id, actorManager); | ||||
|         actorManager.init(p); | ||||
|       } else { | ||||
|         actorManager.render(p); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|     async initGame() { | ||||
|         this.initJoyStick() | ||||
|         this.initShoot() | ||||
|         this.initMap() | ||||
|         this.shouldUpdate = true | ||||
|   renderBullet() { | ||||
|     for (const b of DataManager.Instance.state.bullets) { | ||||
|       let bulletManager = DataManager.Instance.bulletMap.get(b.id); | ||||
|       if (!bulletManager) { | ||||
|         const bullet = ObjectPoolManager.Instance.get(b.type); | ||||
|         bulletManager = bullet.getComponent(BulletManager) || bullet.addComponent(BulletManager); | ||||
|         DataManager.Instance.bulletMap.set(b.id, bulletManager); | ||||
|         bulletManager.init(b); | ||||
|       } else { | ||||
|         bulletManager.render(b); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|     initJoyStick() { | ||||
|         const prefab = DataManager.Instance.prefabMap.get(EntityTypeEnum.JoyStick) | ||||
|         const joySitck = instantiate(prefab) | ||||
|         joySitck.setParent(this.ui) | ||||
|         const jm = DataManager.Instance.jm = joySitck.getComponent(JoyStickManager) | ||||
|         jm.init() | ||||
|   handleClientSync(input: IClientInput) { | ||||
|     const msg = { | ||||
|       frameId: DataManager.Instance.frameId++, | ||||
|       input, | ||||
|     }; | ||||
|     NetworkManager.Instance.sendMsg(ApiMsgEnum.MsgClientSync, msg); | ||||
|  | ||||
|     //移动才做预测,射击不做 | ||||
|     if (input.type === InputTypeEnum.ActorMove) { | ||||
|       DataManager.Instance.applyInput(input); | ||||
|       this.pendingMsg.push(msg); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|     initShoot() { | ||||
|         const prefab = DataManager.Instance.prefabMap.get(EntityTypeEnum.Shoot) | ||||
|         const shoot = instantiate(prefab) | ||||
|         shoot.setParent(this.ui) | ||||
|   listenServerSync({ lastFrameId, inputs }: IMsgServerSync) { | ||||
|     //回滚上次服务器状态 | ||||
|     DataManager.Instance.state = DataManager.Instance.lastState; | ||||
|     //应用服务器输入 | ||||
|     for (const input of inputs) { | ||||
|       DataManager.Instance.applyInput(input); | ||||
|     } | ||||
|     //记录最新的服务器状态 | ||||
|     DataManager.Instance.lastState = deepClone(DataManager.Instance.state); | ||||
|  | ||||
|     initMap() { | ||||
|         const prefab = DataManager.Instance.prefabMap.get(EntityTypeEnum.Map1) | ||||
|         const map = instantiate(prefab) | ||||
|         map.setParent(this.stage) | ||||
|     //过滤本地输入 | ||||
|     this.pendingMsg = this.pendingMsg.filter((msg) => msg.frameId > lastFrameId); | ||||
|     //应用本地输入 | ||||
|     for (const msg of this.pendingMsg) { | ||||
|       DataManager.Instance.applyInput(msg.input); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|     update(dt: number) { | ||||
|         if (!this.shouldUpdate) { | ||||
|             return | ||||
|         } | ||||
|         this.render() | ||||
|         this.tick(dt) | ||||
|     } | ||||
|  | ||||
|     tick(dt: number) { | ||||
|         this.tickPlayer(dt) | ||||
|         // this.tickGlobal(dt) | ||||
|     } | ||||
|  | ||||
|     tickPlayer(dt: number) { | ||||
|         for (const p of DataManager.Instance.state.players) { | ||||
|             const playerManager = DataManager.Instance.actorMap.get(p.id) | ||||
|             if (!playerManager) { | ||||
|                 return | ||||
|             } | ||||
|             playerManager.tick(dt) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // tickGlobal(dt: number) { | ||||
|     //     NetworkManager.Instance.sendMsg(ApiMsgEnum.MsgClientSync, { | ||||
|     //         input: { | ||||
|     //             type: InputTypeEnum.TimePast, | ||||
|     //             dt: toFixed(dt), | ||||
|     //         } | ||||
|     //     }) | ||||
|     // } | ||||
|  | ||||
|     render() { | ||||
|         this.renderPlayer() | ||||
|         this.renderBullet() | ||||
|     } | ||||
|  | ||||
|     renderPlayer() { | ||||
|         for (const p of DataManager.Instance.state.players) { | ||||
|             let playerManager = DataManager.Instance.actorMap.get(p.id) | ||||
|             if (!playerManager) { | ||||
|                 const playerPrefab = DataManager.Instance.prefabMap.get(p.type) | ||||
|                 const player = instantiate(playerPrefab) | ||||
|                 player.setParent(this.stage) | ||||
|                 playerManager = player.addComponent(ActorManager) | ||||
|                 DataManager.Instance.actorMap.set(p.id, playerManager) | ||||
|                 playerManager.init(p) | ||||
|             } else { | ||||
|                 playerManager.render(p) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     renderBullet() { | ||||
|         for (const b of DataManager.Instance.state.bullets) { | ||||
|             let bulletManager = DataManager.Instance.bulletMap.get(b.id) | ||||
|             if (!bulletManager) { | ||||
|                 const bullet = ObjectPoolManager.Instance.get(b.type) | ||||
|                 bulletManager = bullet.getComponent(BulletManager) || bullet.addComponent(BulletManager) | ||||
|                 DataManager.Instance.bulletMap.set(b.id, bulletManager) | ||||
|                 bulletManager.init(b) | ||||
|             } else { | ||||
|                 bulletManager.render(b) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     handleSync(inputs: IMsgServerSync) { | ||||
|         for (const input of inputs) { | ||||
|             DataManager.Instance.applyInput(input) | ||||
|         } | ||||
|   async handleGameEnd() { | ||||
|     const { success, res, error } = await NetworkManager.Instance.callApi(ApiMsgEnum.ApiGameEnd, { rid: DataManager.Instance.roomInfo.id }); | ||||
|     if (!success) { | ||||
|       console.log(error); | ||||
|       return; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   listenGameEnd() { | ||||
|     this.clearGame(); | ||||
|     director.loadScene(SceneEnum.Hall); | ||||
|   } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,121 +1,122 @@ | ||||
| import { _decorator, Component, Node, Prefab, director, instantiate } from 'cc'; | ||||
| import { ApiMsgEnum, IApiPlayerListRes, IApiRoomListRes, IMsgPlayerList, IMsgRoomList } from '../Common'; | ||||
| import { EventEnum, SceneEnum } from '../Enum'; | ||||
| import DataManager from '../Global/DataManager'; | ||||
| import EventManager from '../Global/EventManager'; | ||||
| import NetworkManager from '../Global/NetworkManager'; | ||||
| import { PlayerManager } from '../UI/PlayerManager'; | ||||
| import { RoomManager } from '../UI/RoomManager'; | ||||
| import { _decorator, Component, Node, Prefab, director, instantiate } from "cc"; | ||||
| import { ApiMsgEnum, IApiPlayerListRes, IApiRoomListRes, IMsgPlayerList, IMsgRoomList } from "../Common"; | ||||
| import { EventEnum, SceneEnum } from "../Enum"; | ||||
| import DataManager from "../Global/DataManager"; | ||||
| import EventManager from "../Global/EventManager"; | ||||
| import NetworkManager from "../Global/NetworkManager"; | ||||
| import { PlayerManager } from "../UI/PlayerManager"; | ||||
| import { RoomManager } from "../UI/RoomManager"; | ||||
| const { ccclass, property } = _decorator; | ||||
|  | ||||
| @ccclass('HallManager') | ||||
| @ccclass("HallManager") | ||||
| export class HallManager extends Component { | ||||
|     @property(Node) | ||||
|     playerContainer: Node = null; | ||||
|   @property(Node) | ||||
|   playerContainer: Node = null; | ||||
|  | ||||
|     @property(Prefab) | ||||
|     playerPrefab: Prefab = null; | ||||
|   @property(Prefab) | ||||
|   playerPrefab: Prefab = null; | ||||
|  | ||||
|     @property(Node) | ||||
|     roomContainer: Node = null; | ||||
|   @property(Node) | ||||
|   roomContainer: Node = null; | ||||
|  | ||||
|     @property(Prefab) | ||||
|     roomPrefab: Prefab = null; | ||||
|   @property(Prefab) | ||||
|   roomPrefab: Prefab = null; | ||||
|  | ||||
|     onLoad() { | ||||
|         director.preloadScene(SceneEnum.Room); | ||||
|         EventManager.Instance.on(EventEnum.RoomJoin, this.joinRoom, this) | ||||
|         NetworkManager.Instance.listenMsg(ApiMsgEnum.MsgPlayerList, this.renderPlayers, this); | ||||
|         NetworkManager.Instance.listenMsg(ApiMsgEnum.MsgRoomList, this.renderRooms, this); | ||||
|   onLoad() { | ||||
|     director.preloadScene(SceneEnum.Room); | ||||
|     EventManager.Instance.on(EventEnum.RoomJoin, this.handleJoinRoom, this); | ||||
|     NetworkManager.Instance.listenMsg(ApiMsgEnum.MsgPlayerList, this.renderPlayers, this); | ||||
|     NetworkManager.Instance.listenMsg(ApiMsgEnum.MsgRoomList, this.renderRooms, this); | ||||
|   } | ||||
|  | ||||
|   onDestroy() { | ||||
|     EventManager.Instance.off(EventEnum.RoomJoin, this.handleJoinRoom, this); | ||||
|     NetworkManager.Instance.unlistenMsg(ApiMsgEnum.MsgPlayerList, this.renderPlayers, this); | ||||
|     NetworkManager.Instance.unlistenMsg(ApiMsgEnum.MsgRoomList, this.renderRooms, this); | ||||
|   } | ||||
|  | ||||
|   start() { | ||||
|     this.playerContainer.destroyAllChildren(); | ||||
|     this.roomContainer.destroyAllChildren(); | ||||
|     this.getPlayers(); | ||||
|     this.getRooms(); | ||||
|   } | ||||
|  | ||||
|   async getPlayers() { | ||||
|     const { success, res, error } = await NetworkManager.Instance.callApi(ApiMsgEnum.ApiPlayerList, {}); | ||||
|     if (!success) { | ||||
|       console.log(error); | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     onDestroy() { | ||||
|         EventManager.Instance.off(EventEnum.RoomJoin, this.joinRoom, this) | ||||
|         NetworkManager.Instance.unlistenMsg(ApiMsgEnum.MsgPlayerList, this.renderPlayers, this); | ||||
|         NetworkManager.Instance.unlistenMsg(ApiMsgEnum.MsgRoomList, this.renderRooms, this); | ||||
|     this.renderPlayers(res); | ||||
|   } | ||||
|  | ||||
|   renderPlayers({ list }: IApiPlayerListRes | IMsgPlayerList) { | ||||
|     for (const item of this.playerContainer.children) { | ||||
|       item.active = false; | ||||
|     } | ||||
|     while (this.playerContainer.children.length < list.length) { | ||||
|       const playerItem = instantiate(this.playerPrefab); | ||||
|       playerItem.active = false; | ||||
|       playerItem.setParent(this.playerContainer); | ||||
|     } | ||||
|  | ||||
|     start() { | ||||
|         this.getPlayers() | ||||
|         this.getRooms() | ||||
|     for (let i = 0; i < list.length; i++) { | ||||
|       const data = list[i]; | ||||
|       const node = this.playerContainer.children[i]; | ||||
|       const playerManager = node.getComponent(PlayerManager); | ||||
|       playerManager.init(data); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   async getRooms() { | ||||
|     const { success, res, error } = await NetworkManager.Instance.callApi(ApiMsgEnum.ApiRoomList, {}); | ||||
|     if (!success) { | ||||
|       console.log(error); | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     async getPlayers() { | ||||
|         const { success, res, error } = await NetworkManager.Instance.callApi(ApiMsgEnum.ApiPlayerList, {}); | ||||
|         if (!success) { | ||||
|             console.log(error) | ||||
|             return; | ||||
|         } | ||||
|     this.renderRooms(res); | ||||
|   } | ||||
|  | ||||
|         this.renderPlayers(res) | ||||
|   renderRooms = ({ list }: IApiRoomListRes | IMsgRoomList) => { | ||||
|     for (const item of this.roomContainer.children) { | ||||
|       item.active = false; | ||||
|     } | ||||
|     while (this.roomContainer.children.length < list.length) { | ||||
|       const roomItem = instantiate(this.roomPrefab); | ||||
|       roomItem.active = false; | ||||
|       roomItem.setParent(this.roomContainer); | ||||
|     } | ||||
|  | ||||
|     renderPlayers = ({ list }: IApiPlayerListRes | IMsgPlayerList) => { | ||||
|         for (const item of this.playerContainer.children) { | ||||
|             item.active = false | ||||
|         } | ||||
|         while (this.playerContainer.children.length < list.length) { | ||||
|             const playerItem = instantiate(this.playerPrefab); | ||||
|             playerItem.active = false | ||||
|             playerItem.setParent(this.playerContainer) | ||||
|         } | ||||
|     for (let i = 0; i < list.length; i++) { | ||||
|       const data = list[i]; | ||||
|       const node = this.roomContainer.children[i]; | ||||
|       const roomItemManager = node.getComponent(RoomManager); | ||||
|       roomItemManager.init(data); | ||||
|     } | ||||
|   }; | ||||
|  | ||||
|         for (let i = 0; i < list.length; i++) { | ||||
|             const data = list[i]; | ||||
|             const node = this.playerContainer.children[i] | ||||
|             const playerItemManager = node.getComponent(PlayerManager) | ||||
|             playerItemManager.init(data) | ||||
|         } | ||||
|   async handleCreateRoom() { | ||||
|     const { success, res, error } = await NetworkManager.Instance.callApi(ApiMsgEnum.ApiRoomCreate, {}); | ||||
|     if (!success) { | ||||
|       console.log(error); | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     async getRooms() { | ||||
|         const { success, res, error } = await NetworkManager.Instance.callApi(ApiMsgEnum.ApiRoomList, {}); | ||||
|         if (!success) { | ||||
|             console.log(error) | ||||
|             return; | ||||
|         } | ||||
|     DataManager.Instance.roomInfo = res.room; | ||||
|     director.loadScene(SceneEnum.Room); | ||||
|   } | ||||
|  | ||||
|         this.renderRooms(res) | ||||
|   async handleJoinRoom(rid: number) { | ||||
|     const { success, res, error } = await NetworkManager.Instance.callApi(ApiMsgEnum.ApiRoomJoin, { rid }); | ||||
|     if (!success) { | ||||
|       console.log(error); | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     renderRooms = ({ list }: IApiRoomListRes | IMsgRoomList) => { | ||||
|         for (const item of this.roomContainer.children) { | ||||
|             item.active = false | ||||
|         } | ||||
|         while (this.roomContainer.children.length < list.length) { | ||||
|             const roomItem = instantiate(this.roomPrefab); | ||||
|             roomItem.active = false | ||||
|             roomItem.setParent(this.roomContainer) | ||||
|         } | ||||
|  | ||||
|         for (let i = 0; i < list.length; i++) { | ||||
|             const data = list[i]; | ||||
|             const node = this.roomContainer.children[i] | ||||
|             const roomItemManager = node.getComponent(RoomManager) | ||||
|             roomItemManager.init(data) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     async createRoom() { | ||||
|         const { success, res, error } = await NetworkManager.Instance.callApi(ApiMsgEnum.ApiRoomCreate, {}); | ||||
|         if (!success) { | ||||
|             console.log(error) | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         DataManager.Instance.roomInfo = res.room | ||||
|         director.loadScene(SceneEnum.Room); | ||||
|     } | ||||
|  | ||||
|     async joinRoom(rid: number) { | ||||
|         const { success, res, error } = await NetworkManager.Instance.callApi(ApiMsgEnum.ApiRoomJoin, { rid }); | ||||
|         if (!success) { | ||||
|             console.log(error) | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         DataManager.Instance.roomInfo = res.room | ||||
|         director.loadScene(SceneEnum.Room); | ||||
|     } | ||||
|     DataManager.Instance.roomInfo = res.room; | ||||
|     director.loadScene(SceneEnum.Room); | ||||
|   } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,45 +1,44 @@ | ||||
| import { _decorator, Component, EditBox, director } from 'cc'; | ||||
| import { ApiMsgEnum } from '../Common'; | ||||
| import { SceneEnum } from '../Enum'; | ||||
| import DataManager from '../Global/DataManager'; | ||||
| import NetworkManager from '../Global/NetworkManager'; | ||||
| import { _decorator, Component, EditBox, director } from "cc"; | ||||
| import { ApiMsgEnum } from "../Common"; | ||||
| import { SceneEnum } from "../Enum"; | ||||
| import DataManager from "../Global/DataManager"; | ||||
| import NetworkManager from "../Global/NetworkManager"; | ||||
| const { ccclass, property } = _decorator; | ||||
|  | ||||
| @ccclass('LoginManager') | ||||
| @ccclass("LoginManager") | ||||
| export class LoginManager extends Component { | ||||
|     input: EditBox | ||||
|   input: EditBox; | ||||
|  | ||||
|     onLoad() { | ||||
|         this.input = this.node.getChildByName('Input').getComponent(EditBox) | ||||
|         director.preloadScene(SceneEnum.Hall); | ||||
|   onLoad() { | ||||
|     this.input = this.node.getChildByName("Input").getComponent(EditBox); | ||||
|     director.preloadScene(SceneEnum.Hall); | ||||
|   } | ||||
|  | ||||
|   async start() { | ||||
|     await NetworkManager.Instance.connect(); | ||||
|     console.log("服务连接成功!"); | ||||
|   } | ||||
|  | ||||
|   async handleClick() { | ||||
|     if (!NetworkManager.Instance.isConnected) { | ||||
|       console.log("未连接!"); | ||||
|       await NetworkManager.Instance.connect(); | ||||
|     } | ||||
|     const nickname = this.input.string; | ||||
|     if (!nickname) { | ||||
|       console.log("请输入昵称!"); | ||||
|       return; | ||||
|     } | ||||
|     let { success, res, error } = await NetworkManager.Instance.callApi(ApiMsgEnum.ApiPlayerJoin, { | ||||
|       nickname, | ||||
|     }); | ||||
|  | ||||
|     if (!success) { | ||||
|       console.log(error); | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     async start() { | ||||
|         await NetworkManager.Instance.connect(); | ||||
|         console.log("服务连接成功!"); | ||||
|     } | ||||
|  | ||||
|     async handleClick() { | ||||
|         if (!NetworkManager.Instance.isConnected) { | ||||
|             console.log("未连接!"); | ||||
|             await NetworkManager.Instance.connect(); | ||||
|         } | ||||
|         const nickname = this.input.string; | ||||
|         if (!nickname) { | ||||
|             console.log("请输入昵称!") | ||||
|             return; | ||||
|         } | ||||
|         let { success, res, error } = await NetworkManager.Instance.callApi(ApiMsgEnum.ApiPlayerJoin, { | ||||
|             nickname, | ||||
|         }); | ||||
|  | ||||
|         if (!success) { | ||||
|             console.log(error); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         DataManager.Instance.myPlayerId = res.player.id; | ||||
|         director.loadScene(SceneEnum.Hall); | ||||
|     } | ||||
|     DataManager.Instance.myPlayerId = res.player.id; | ||||
|     director.loadScene(SceneEnum.Hall); | ||||
|   } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,76 +1,77 @@ | ||||
| import { _decorator, Component, Node, Prefab, director, instantiate } from 'cc'; | ||||
| import { ApiMsgEnum, IMsgGameStart, IMsgRoom } from '../Common'; | ||||
| import { SceneEnum } from '../Enum'; | ||||
| import DataManager from '../Global/DataManager'; | ||||
| import NetworkManager from '../Global/NetworkManager'; | ||||
| import { PlayerManager } from '../UI/PlayerManager'; | ||||
| import { _decorator, Component, Node, Prefab, director, instantiate } from "cc"; | ||||
| import { ApiMsgEnum, IMsgGameStart, IMsgRoom } from "../Common"; | ||||
| import { SceneEnum } from "../Enum"; | ||||
| import DataManager from "../Global/DataManager"; | ||||
| import NetworkManager from "../Global/NetworkManager"; | ||||
| import { PlayerManager } from "../UI/PlayerManager"; | ||||
| import { deepClone } from "../Utils"; | ||||
| const { ccclass, property } = _decorator; | ||||
|  | ||||
| @ccclass('RoomManager') | ||||
| @ccclass("RoomManager") | ||||
| export class RoomManager extends Component { | ||||
|     @property(Node) | ||||
|     playerContainer: Node = null; | ||||
|   @property(Node) | ||||
|   playerContainer: Node = null; | ||||
|  | ||||
|     @property(Prefab) | ||||
|     playerPrefab: Prefab = null; | ||||
|   @property(Prefab) | ||||
|   playerPrefab: Prefab = null; | ||||
|  | ||||
|     onLoad() { | ||||
|         director.preloadScene(SceneEnum.Battle); | ||||
|         NetworkManager.Instance.listenMsg(ApiMsgEnum.MsgRoom, this.renderPlayers, this); | ||||
|         NetworkManager.Instance.listenMsg(ApiMsgEnum.MsgGameStart, this.handleGameStart, this); | ||||
|   onLoad() { | ||||
|     director.preloadScene(SceneEnum.Battle); | ||||
|     NetworkManager.Instance.listenMsg(ApiMsgEnum.MsgRoom, this.renderPlayers, this); | ||||
|     NetworkManager.Instance.listenMsg(ApiMsgEnum.MsgGameStart, this.handleGameStart, this); | ||||
|   } | ||||
|  | ||||
|   onDestroy() { | ||||
|     NetworkManager.Instance.unlistenMsg(ApiMsgEnum.MsgRoom, this.renderPlayers, this); | ||||
|     NetworkManager.Instance.unlistenMsg(ApiMsgEnum.MsgGameStart, this.handleGameStart, this); | ||||
|   } | ||||
|  | ||||
|   async start() { | ||||
|     this.renderPlayers({ | ||||
|       room: DataManager.Instance.roomInfo, | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   renderPlayers({ room: { players: list } }: IMsgRoom) { | ||||
|     for (const item of this.playerContainer.children) { | ||||
|       item.active = false; | ||||
|     } | ||||
|     while (this.playerContainer.children.length < list.length) { | ||||
|       const playerItem = instantiate(this.playerPrefab); | ||||
|       playerItem.active = false; | ||||
|       playerItem.setParent(this.playerContainer); | ||||
|     } | ||||
|  | ||||
|     onDestroy() { | ||||
|         NetworkManager.Instance.unlistenMsg(ApiMsgEnum.MsgRoom, this.renderPlayers, this); | ||||
|         NetworkManager.Instance.unlistenMsg(ApiMsgEnum.MsgGameStart, this.handleGameStart, this); | ||||
|     for (let i = 0; i < list.length; i++) { | ||||
|       const data = list[i]; | ||||
|       const node = this.playerContainer.children[i]; | ||||
|       const playerItemManager = node.getComponent(PlayerManager); | ||||
|       playerItemManager.init(data); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   async handleLeave() { | ||||
|     const { success, res, error } = await NetworkManager.Instance.callApi(ApiMsgEnum.ApiRoomLeave, {}); | ||||
|     if (!success) { | ||||
|       console.log(error); | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     async start() { | ||||
|         this.renderPlayers({ | ||||
|             room: DataManager.Instance.roomInfo | ||||
|         }) | ||||
|     DataManager.Instance.roomInfo = null; | ||||
|     director.loadScene(SceneEnum.Hall); | ||||
|   } | ||||
|  | ||||
|   async handleStart() { | ||||
|     const { success, res, error } = await NetworkManager.Instance.callApi(ApiMsgEnum.ApiGameStart, { rid: DataManager.Instance.roomInfo.id }); | ||||
|     if (!success) { | ||||
|       console.log(error); | ||||
|       return; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|     renderPlayers({ room: { players: list } }: IMsgRoom) { | ||||
|         for (const item of this.playerContainer.children) { | ||||
|             item.active = false | ||||
|         } | ||||
|         while (this.playerContainer.children.length < list.length) { | ||||
|             const playerItem = instantiate(this.playerPrefab); | ||||
|             playerItem.active = false | ||||
|             playerItem.setParent(this.playerContainer) | ||||
|         } | ||||
|  | ||||
|         for (let i = 0; i < list.length; i++) { | ||||
|             const data = list[i]; | ||||
|             const node = this.playerContainer.children[i] | ||||
|             const playerItemManager = node.getComponent(PlayerManager) | ||||
|             playerItemManager.init(data) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     async handleLeave() { | ||||
|         const { success, res, error } = await NetworkManager.Instance.callApi(ApiMsgEnum.ApiRoomLeave, {}); | ||||
|         if (!success) { | ||||
|             console.log(error) | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         DataManager.Instance.roomInfo = null | ||||
|         director.loadScene(SceneEnum.Hall); | ||||
|     } | ||||
|  | ||||
|     async handleStart() { | ||||
|         const { success, res, error } = await NetworkManager.Instance.callApi(ApiMsgEnum.ApiGameStart, { rid: DataManager.Instance.roomInfo.id }); | ||||
|         if (!success) { | ||||
|             console.log(error) | ||||
|             return; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     handleGameStart({ state }: IMsgGameStart) { | ||||
|         DataManager.Instance.state = state | ||||
|         director.loadScene(SceneEnum.Battle); | ||||
|     } | ||||
|   handleGameStart({ state }: IMsgGameStart) { | ||||
|     DataManager.Instance.state = state; | ||||
|     DataManager.Instance.lastState = deepClone(DataManager.Instance.state); | ||||
|     director.loadScene(SceneEnum.Battle); | ||||
|   } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,57 +1,55 @@ | ||||
| import { _decorator, Component, Node, input, Input, EventTouch, Vec2, Vec3, UITransform } from 'cc'; | ||||
| import { _decorator, Component, Node, input, Input, EventTouch, Vec2, Vec3, UITransform } from "cc"; | ||||
| const { ccclass, property } = _decorator; | ||||
|  | ||||
| @ccclass('JoyStickManager') | ||||
| @ccclass("JoyStickManager") | ||||
| export class JoyStickManager extends Component { | ||||
|     input: Vec2 = Vec2.ZERO | ||||
|   input: Vec2 = Vec2.ZERO; | ||||
|  | ||||
|     private body: Node | ||||
|     private stick: Node | ||||
|     private touchStartPos: Vec2 | ||||
|     private defaultPos: Vec2 | ||||
|     private radius: number = 0 | ||||
|   private body: Node; | ||||
|   private stick: Node; | ||||
|   private touchStartPos: Vec2; | ||||
|   private defaultPos: Vec2; | ||||
|   private radius: number = 0; | ||||
|  | ||||
|     init() { | ||||
|         this.body = this.node.getChildByName("Body") | ||||
|         this.stick = this.body.getChildByName("Stick") | ||||
|         const { x, y } = this.body.position | ||||
|         this.defaultPos = new Vec2(x, y) | ||||
|         this.radius = this.body.getComponent(UITransform).contentSize.x / 2 | ||||
|   init() { | ||||
|     this.body = this.node.getChildByName("Body"); | ||||
|     this.stick = this.body.getChildByName("Stick"); | ||||
|     const { x, y } = this.body.position; | ||||
|     this.defaultPos = new Vec2(x, y); | ||||
|     this.radius = this.body.getComponent(UITransform).contentSize.x / 2; | ||||
|  | ||||
|         input.on(Input.EventType.TOUCH_START, this.onTouchMove, this); | ||||
|         input.on(Input.EventType.TOUCH_MOVE, this.onTouchMove, this); | ||||
|         input.on(Input.EventType.TOUCH_END, this.onTouchEnd, this); | ||||
|     input.on(Input.EventType.TOUCH_START, this.onTouchMove, this); | ||||
|     input.on(Input.EventType.TOUCH_MOVE, this.onTouchMove, this); | ||||
|     input.on(Input.EventType.TOUCH_END, this.onTouchEnd, this); | ||||
|   } | ||||
|  | ||||
|   onDestroy() { | ||||
|     input.off(Input.EventType.TOUCH_START, this.onTouchMove, this); | ||||
|     input.off(Input.EventType.TOUCH_MOVE, this.onTouchMove, this); | ||||
|     input.off(Input.EventType.TOUCH_END, this.onTouchEnd, this); | ||||
|   } | ||||
|  | ||||
|   onTouchMove(e: EventTouch) { | ||||
|     const touchPos = e.touch.getUILocation(); | ||||
|     if (!this.touchStartPos) { | ||||
|       this.touchStartPos = touchPos.clone(); | ||||
|       this.body.setPosition(this.touchStartPos.x, this.touchStartPos.y); | ||||
|     } | ||||
|  | ||||
|     onDestroy() { | ||||
|         input.off(Input.EventType.TOUCH_START, this.onTouchMove, this); | ||||
|         input.off(Input.EventType.TOUCH_MOVE, this.onTouchMove, this); | ||||
|         input.off(Input.EventType.TOUCH_END, this.onTouchEnd, this); | ||||
|     const stickPos = new Vec2(touchPos.x - this.touchStartPos.x, touchPos.y - this.touchStartPos.y); | ||||
|     const len = stickPos.length(); | ||||
|     if (len > this.radius) { | ||||
|       stickPos.multiplyScalar(this.radius / len); | ||||
|     } | ||||
|  | ||||
|     onTouchMove(e: EventTouch) { | ||||
|         const touchPos = e.touch.getUILocation() | ||||
|         if (!this.touchStartPos) { | ||||
|             this.touchStartPos = touchPos.clone() | ||||
|             this.body.setPosition(this.touchStartPos.x, this.touchStartPos.y); | ||||
|         } | ||||
|     this.stick.setPosition(stickPos.x, stickPos.y); | ||||
|     this.input = stickPos.clone().normalize(); | ||||
|   } | ||||
|  | ||||
|         const stickPos = new Vec2(touchPos.x - this.touchStartPos.x, touchPos.y - this.touchStartPos.y) | ||||
|         const len = stickPos.length() | ||||
|         if (len > this.radius) { | ||||
|             stickPos.multiplyScalar(this.radius / len) | ||||
|         } | ||||
|  | ||||
|         this.stick.setPosition(stickPos.x, stickPos.y) | ||||
|         this.input = stickPos.clone().normalize() | ||||
|     } | ||||
|  | ||||
|     onTouchEnd() { | ||||
|         this.body.setPosition(this.defaultPos.x, this.defaultPos.y) | ||||
|         this.stick.setPosition(0, 0) | ||||
|         this.touchStartPos = undefined | ||||
|         this.input = Vec2.ZERO | ||||
|     } | ||||
|   onTouchEnd() { | ||||
|     this.body.setPosition(this.defaultPos.x, this.defaultPos.y); | ||||
|     this.stick.setPosition(0, 0); | ||||
|     this.touchStartPos = undefined; | ||||
|     this.input = Vec2.ZERO; | ||||
|   } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,22 +1,19 @@ | ||||
| import { _decorator, Component, Node, Label } from 'cc'; | ||||
| import { EventEnum } from '../Enum'; | ||||
| import DataManager from '../Global/DataManager'; | ||||
| import EventManager from '../Global/EventManager'; | ||||
| import { _decorator, Component, Node, Label } from "cc"; | ||||
| import DataManager from "../Global/DataManager"; | ||||
| const { ccclass, property } = _decorator; | ||||
|  | ||||
| @ccclass('PlayerManager') | ||||
| @ccclass("PlayerManager") | ||||
| export class PlayerManager extends Component { | ||||
|   init({ id, nickname, rid }: { id: number, nickname: string, rid: number }) { | ||||
|     const label = this.getComponent(Label) | ||||
|     let str = nickname | ||||
|   init({ id, nickname, rid }: { id: number; nickname: string; rid: number }) { | ||||
|     const label = this.getComponent(Label); | ||||
|     let str = nickname; | ||||
|     if (DataManager.Instance.myPlayerId === id) { | ||||
|       str += `(我)` | ||||
|       str += `(我)`; | ||||
|     } | ||||
|     if (rid !== -1) { | ||||
|       str += `(房间${rid})` | ||||
|       str += `(房间${rid})`; | ||||
|     } | ||||
|     label.string = str | ||||
|     this.node.active = true | ||||
|     label.string = str; | ||||
|     this.node.active = true; | ||||
|   } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,20 +1,19 @@ | ||||
| import { _decorator, Component, Node, Label } from 'cc'; | ||||
| import { EventEnum } from '../Enum'; | ||||
| import EventManager from '../Global/EventManager'; | ||||
| import { _decorator, Component, Node, Label } from "cc"; | ||||
| import { EventEnum } from "../Enum"; | ||||
| import EventManager from "../Global/EventManager"; | ||||
| const { ccclass, property } = _decorator; | ||||
|  | ||||
| @ccclass('RoomManager') | ||||
| @ccclass("RoomManager") | ||||
| export class RoomManager extends Component { | ||||
|   id: number | ||||
|   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}` | ||||
|     this.node.active = true | ||||
|   id: number; | ||||
|   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}`; | ||||
|     this.node.active = true; | ||||
|   } | ||||
|  | ||||
|   handleClick() { | ||||
|     EventManager.Instance.emit(EventEnum.RoomJoin, this.id) | ||||
|     EventManager.Instance.emit(EventEnum.RoomJoin, this.id); | ||||
|   } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,13 +1,11 @@ | ||||
| import { _decorator, Component, Node } from 'cc'; | ||||
| import { EventEnum } from '../Enum'; | ||||
| import EventManager from '../Global/EventManager'; | ||||
| import { _decorator, Component, Node } from "cc"; | ||||
| import { EventEnum } from "../Enum"; | ||||
| import EventManager from "../Global/EventManager"; | ||||
| const { ccclass, property } = _decorator; | ||||
|  | ||||
| @ccclass('ShootManager') | ||||
| @ccclass("ShootManager") | ||||
| export class ShootManager extends Component { | ||||
|     shoot() { | ||||
|         EventManager.Instance.emit(EventEnum.WeaponShoot) | ||||
|     } | ||||
|  | ||||
|   shoot() { | ||||
|     EventManager.Instance.emit(EventEnum.WeaponShoot); | ||||
|   } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,11 +1,25 @@ | ||||
| import { SpriteFrame } from "cc" | ||||
| import { ApiMsgEnum, InputTypeEnum, strdecode } from "../Common" | ||||
| import { SpriteFrame } from "cc"; | ||||
|  | ||||
| const INDEX_REG = /\((\d+)\)/ | ||||
| const INDEX_REG = /\((\d+)\)/; | ||||
|  | ||||
| const getNumberWithinString = (str: string) => parseInt(str.match(INDEX_REG)?.[1] || '0') | ||||
| const getNumberWithinString = (str: string) => parseInt(str.match(INDEX_REG)?.[1] || "0"); | ||||
|  | ||||
| export const sortSpriteFrame = (spriteFrame: Array<SpriteFrame>) => | ||||
|   spriteFrame.sort((a, b) => getNumberWithinString(a.name) - getNumberWithinString(b.name)) | ||||
|   spriteFrame.sort((a, b) => getNumberWithinString(a.name) - getNumberWithinString(b.name)); | ||||
|  | ||||
| export const rad2Angle = (rad: number) => rad / Math.PI * 180 | ||||
| export const rad2Angle = (rad: number) => (rad / Math.PI) * 180; | ||||
|  | ||||
| export const deepClone = (obj: any) => { | ||||
|   if (typeof obj !== "object" || obj === null) { | ||||
|     return obj; | ||||
|   } | ||||
|  | ||||
|   const res = Array.isArray(obj) ? [] : {}; | ||||
|   for (const key in obj) { | ||||
|     if (Object.prototype.hasOwnProperty.call(obj, key)) { | ||||
|       res[key] = deepClone(obj[key]); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   return res; | ||||
| }; | ||||
|   | ||||
| @@ -1,61 +1,60 @@ | ||||
| export interface IPlayer { | ||||
|   id: number, nickname: string, rid: number | ||||
|   id: number; | ||||
|   nickname: string; | ||||
|   rid: number; | ||||
| } | ||||
|  | ||||
| export interface IRoom { | ||||
|   id: number, players: Array<IPlayer> | ||||
|   id: number; | ||||
|   players: Array<IPlayer>; | ||||
| } | ||||
|  | ||||
| export interface IApiPlayerListReq { | ||||
| } | ||||
| export interface IApiPlayerListReq {} | ||||
|  | ||||
| export interface IApiPlayerListRes { | ||||
|   list: Array<IPlayer> | ||||
|   list: Array<IPlayer>; | ||||
| } | ||||
|  | ||||
| export interface IApiPlayerJoinReq { | ||||
|   nickname: string | ||||
|   nickname: string; | ||||
| } | ||||
|  | ||||
| export interface IApiPlayerJoinRes { | ||||
|   player: IPlayer | ||||
|   player: IPlayer; | ||||
| } | ||||
|  | ||||
| export interface IApiRoomListReq { | ||||
| } | ||||
| export interface IApiRoomListReq {} | ||||
|  | ||||
| export interface IApiRoomListRes { | ||||
|   list: Array<IRoom> | ||||
|   list: Array<IRoom>; | ||||
| } | ||||
|  | ||||
| export interface IApiRoomCreateReq { | ||||
| } | ||||
| export interface IApiRoomCreateReq {} | ||||
|  | ||||
| export interface IApiRoomCreateRes { | ||||
|   room: IRoom | ||||
|   room: IRoom; | ||||
| } | ||||
|  | ||||
| export interface IApiRoomJoinReq { | ||||
|   rid: number | ||||
|   rid: number; | ||||
| } | ||||
|  | ||||
| export interface IApiRoomJoinRes { | ||||
|   room: IRoom | ||||
|   room: IRoom; | ||||
| } | ||||
|  | ||||
| export interface IApiRoomLeaveReq { | ||||
| } | ||||
| export interface IApiRoomLeaveReq {} | ||||
|  | ||||
| export interface IApiRoomLeaveRes { } | ||||
| export interface IApiRoomLeaveRes {} | ||||
|  | ||||
| export interface IApiGameStartReq { | ||||
|   rid: number | ||||
|   rid: number; | ||||
| } | ||||
|  | ||||
| export interface IApiGameStartRes { } | ||||
| export interface IApiGameStartRes {} | ||||
|  | ||||
| export interface IApiGameEndReq { | ||||
|   rid: number | ||||
|   rid: number; | ||||
| } | ||||
|  | ||||
| export interface IApiGameEndRes { } | ||||
| export interface IApiGameEndRes {} | ||||
|   | ||||
| @@ -1,91 +1,100 @@ | ||||
| import { ApiMsgEnum, InputTypeEnum } from "./Enum"; | ||||
| import { strdecode, strencode, toFixed } from "./Utils"; | ||||
|  | ||||
| const encodeActorMove = (proto: ApiMsgEnum, data: any, view: DataView, index: number) => { | ||||
|   view.setUint8(index++, data.type) | ||||
|   view.setUint8(index++, data.id) | ||||
|   view.setFloat32(index, data.direction.x) | ||||
| const encodeActorMove = (input: any, view: DataView, index: number) => { | ||||
|   view.setUint8(index++, input.type) | ||||
|   view.setUint8(index++, input.id) | ||||
|   view.setFloat32(index, input.direction.x) | ||||
|   index += 4 | ||||
|   view.setFloat32(index, data.direction.y) | ||||
|   view.setFloat32(index, input.direction.y) | ||||
|   index += 4 | ||||
|   view.setFloat32(index, data.dt) | ||||
|   view.setFloat32(index, input.dt) | ||||
|   index += 4 | ||||
| } | ||||
|  | ||||
| const encodeWeaponShoot = (proto: ApiMsgEnum, data: any, view: DataView, index: number) => { | ||||
|   view.setUint8(index++, data.type) | ||||
|   view.setUint8(index++, data.owner) | ||||
|   view.setFloat32(index, data.position.x) | ||||
| const encodeWeaponShoot = (input: any, view: DataView, index: number) => { | ||||
|   view.setUint8(index++, input.type) | ||||
|   view.setUint8(index++, input.owner) | ||||
|   view.setFloat32(index, input.position.x) | ||||
|   index += 4 | ||||
|   view.setFloat32(index, data.position.y) | ||||
|   view.setFloat32(index, input.position.y) | ||||
|   index += 4 | ||||
|   view.setFloat32(index, data.direction.x) | ||||
|   view.setFloat32(index, input.direction.x) | ||||
|   index += 4 | ||||
|   view.setFloat32(index, data.direction.y) | ||||
|   view.setFloat32(index, input.direction.y) | ||||
|   index += 4 | ||||
| } | ||||
|  | ||||
| export const encdoeTimePast = (proto: ApiMsgEnum, data: any, view: DataView, index: number) => { | ||||
|   view.setUint8(index++, data.type) | ||||
|   view.setFloat32(index, data.dt) | ||||
| export const encodeTimePast = (input: any, view: DataView, index: number) => { | ||||
|   view.setUint8(index++, input.type) | ||||
|   view.setFloat32(index, input.dt) | ||||
|   index += 4 | ||||
| } | ||||
|  | ||||
| export const binaryEncode = (proto: ApiMsgEnum, data: any): DataView => { | ||||
|   if (proto === ApiMsgEnum.MsgClientSync) { | ||||
|     if (data.type === InputTypeEnum.ActorMove) { | ||||
| export const binaryEncode = (name: ApiMsgEnum, data: any): DataView => { | ||||
|   if (name === ApiMsgEnum.MsgClientSync) { | ||||
|     //name 1字节 + frameId 4字节 + 数据长度 n 字节 | ||||
|     const { frameId, input } = data | ||||
|     if (input.type === InputTypeEnum.ActorMove) { | ||||
|       let index = 0 | ||||
|       const ab = new ArrayBuffer(3 + 12) | ||||
|       const ab = new ArrayBuffer(1 + 4 + 14) | ||||
|       const view = new DataView(ab) | ||||
|       view.setUint8(index++, proto) | ||||
|       encodeActorMove(proto, data, view, index) | ||||
|       view.setUint8(index++, name) | ||||
|       view.setUint32(index, frameId) | ||||
|       index += 4 | ||||
|       encodeActorMove(input, view, index) | ||||
|       return view | ||||
|     } else if (data.type === InputTypeEnum.WeaponShoot) { | ||||
|     } else if (input.type === InputTypeEnum.WeaponShoot) { | ||||
|       let index = 0 | ||||
|       const ab = new ArrayBuffer(3 + 16) | ||||
|       const ab = new ArrayBuffer(1 + 4 + 18) | ||||
|       const view = new DataView(ab) | ||||
|       view.setUint8(index++, proto) | ||||
|       encodeWeaponShoot(proto, data, view, index) | ||||
|       view.setUint8(index++, name) | ||||
|       view.setUint32(index, frameId) | ||||
|       index += 4 | ||||
|       encodeWeaponShoot(input, view, index) | ||||
|       return view | ||||
|     } else { | ||||
|       let index = 0 | ||||
|       const ab = new ArrayBuffer(2 + 4) | ||||
|       const ab = new ArrayBuffer(1 + 4 + 5) | ||||
|       const view = new DataView(ab) | ||||
|       view.setUint8(index++, proto) | ||||
|       encdoeTimePast(proto, data, view, index) | ||||
|       view.setUint8(index++, name) | ||||
|       view.setUint32(index, frameId) | ||||
|       index += 4 | ||||
|       encodeTimePast(input, view, index) | ||||
|       return view | ||||
|     } | ||||
|   } else if (proto === ApiMsgEnum.MsgServerSync) { | ||||
|   } else if (name === ApiMsgEnum.MsgServerSync) { | ||||
|     const { lastFrameId, inputs } = data | ||||
|     let total = 0 | ||||
|     for (let i = 0; i < data.length; i++) { | ||||
|       const item = data[i]; | ||||
|       if (item.type === InputTypeEnum.ActorMove) { | ||||
|     for (const input of inputs) { | ||||
|       if (input.type === InputTypeEnum.ActorMove) { | ||||
|         total += 14 | ||||
|       } else if (item.type === InputTypeEnum.WeaponShoot) { | ||||
|       } else if (input.type === InputTypeEnum.WeaponShoot) { | ||||
|         total += 18 | ||||
|       } else { | ||||
|         total += 5 | ||||
|       } | ||||
|     } | ||||
|     const ab = new ArrayBuffer(1 + 1 + total) | ||||
|     //name 1字节 + lastFrameId 4字节 + 数组长度 1字节 + 数据长度 n 字节 | ||||
|     const ab = new ArrayBuffer(1 + 4 + 1 + total) | ||||
|     const view = new DataView(ab) | ||||
|     let index = 0 | ||||
|     view.setUint8(index++, proto) | ||||
|     view.setUint8(index++, data.length) | ||||
|     for (let i = 0; i < data.length; i++) { | ||||
|       const item = data[i]; | ||||
|       if (item.type === InputTypeEnum.ActorMove) { | ||||
|         encodeActorMove(proto, item, view, index) | ||||
|     view.setUint8(index++, name) | ||||
|     view.setUint32(index, lastFrameId) | ||||
|     index += 4 | ||||
|     view.setUint8(index++, inputs.length) | ||||
|     for (const input of inputs) { | ||||
|       if (input.type === InputTypeEnum.ActorMove) { | ||||
|         encodeActorMove(input, view, index) | ||||
|         index += 14 | ||||
|       } else if (item.type === InputTypeEnum.WeaponShoot) { | ||||
|         encodeWeaponShoot(proto, item, view, index) | ||||
|       } else if (input.type === InputTypeEnum.WeaponShoot) { | ||||
|         encodeWeaponShoot(input, view, index) | ||||
|         index += 18 | ||||
|       } else { | ||||
|         encdoeTimePast(proto, item, view, index) | ||||
|         encodeTimePast(input, view, index) | ||||
|         index += 5 | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     return view | ||||
|   } else { | ||||
|     let index = 0 | ||||
| @@ -93,7 +102,7 @@ export const binaryEncode = (proto: ApiMsgEnum, data: any): DataView => { | ||||
|     const ta = strencode(str) | ||||
|     const ab = new ArrayBuffer(ta.length + 1) | ||||
|     const view = new DataView(ab) | ||||
|     view.setUint8(index++, proto) | ||||
|     view.setUint8(index++, name) | ||||
|     for (let i = 0; i < ta.length; i++) { | ||||
|       view.setUint8(index++, ta[i]) | ||||
|     } | ||||
| @@ -109,20 +118,17 @@ const decodeActorMove = (view: DataView, index: number) => { | ||||
|   index += 4 | ||||
|   const dt = toFixed(view.getFloat32(index)) | ||||
|   index += 4 | ||||
|   const msg = { | ||||
|     name: ApiMsgEnum.MsgClientSync, | ||||
|     data: { | ||||
|       type: InputTypeEnum.ActorMove, | ||||
|       id, | ||||
|       direction: { | ||||
|         x: directionX, | ||||
|         y: directionY, | ||||
|       }, | ||||
|       dt | ||||
|     } | ||||
|   const input = { | ||||
|     type: InputTypeEnum.ActorMove, | ||||
|     id, | ||||
|     direction: { | ||||
|       x: directionX, | ||||
|       y: directionY, | ||||
|     }, | ||||
|     dt | ||||
|   } | ||||
|  | ||||
|   return msg | ||||
|   return input | ||||
| } | ||||
|  | ||||
| const decodeWeaponShoot = (view: DataView, index: number) => { | ||||
| @@ -135,76 +141,96 @@ const decodeWeaponShoot = (view: DataView, index: number) => { | ||||
|   index += 4 | ||||
|   const directionY = toFixed(view.getFloat32(index)) | ||||
|   index += 4 | ||||
|   const msg = { | ||||
|     name: ApiMsgEnum.MsgClientSync, | ||||
|     data: { | ||||
|       type: InputTypeEnum.WeaponShoot, | ||||
|       owner, | ||||
|       position: { | ||||
|         x: positionX, | ||||
|         y: positionY, | ||||
|       }, | ||||
|       direction: { | ||||
|         x: directionX, | ||||
|         y: directionY, | ||||
|       }, | ||||
|     } | ||||
|   const input = { | ||||
|     type: InputTypeEnum.WeaponShoot, | ||||
|     owner, | ||||
|     position: { | ||||
|       x: positionX, | ||||
|       y: positionY, | ||||
|     }, | ||||
|     direction: { | ||||
|       x: directionX, | ||||
|       y: directionY, | ||||
|     }, | ||||
|   } | ||||
|  | ||||
|   return msg | ||||
|   return input | ||||
| } | ||||
|  | ||||
| const decodeTimePast = (view: DataView, index: number) => { | ||||
|   const dt = toFixed(view.getFloat32(index)) | ||||
|   index += 4 | ||||
|   const msg = { | ||||
|     name: ApiMsgEnum.MsgClientSync, | ||||
|     data: { | ||||
|       type: InputTypeEnum.TimePast, | ||||
|       dt, | ||||
|     } | ||||
|   const input = { | ||||
|     type: InputTypeEnum.TimePast, | ||||
|     dt, | ||||
|   } | ||||
|   return msg | ||||
|   return input | ||||
| } | ||||
|  | ||||
| export const binaryDecode = (buffer: ArrayBuffer) => { | ||||
|   let index = 0 | ||||
|   const view = new DataView(buffer) | ||||
|   const proto = view.getUint8(index++) | ||||
|   const name = view.getUint8(index++) | ||||
|  | ||||
|   if (proto === ApiMsgEnum.MsgClientSync) { | ||||
|   if (name === ApiMsgEnum.MsgClientSync) { | ||||
|     const frameId = view.getUint32(index) | ||||
|     index += 4 | ||||
|     const inputType = view.getUint8(index++) | ||||
|     if (inputType === InputTypeEnum.ActorMove) { | ||||
|       return decodeActorMove(view, index) | ||||
|       const input = decodeActorMove(view, index) | ||||
|       return { | ||||
|         name, | ||||
|         data: { | ||||
|           frameId, | ||||
|           input | ||||
|         } | ||||
|       } | ||||
|     } else if (inputType === InputTypeEnum.WeaponShoot) { | ||||
|       return decodeWeaponShoot(view, index) | ||||
|       const input = decodeWeaponShoot(view, index) | ||||
|       return { | ||||
|         name, | ||||
|         data: { | ||||
|           frameId, | ||||
|           input | ||||
|         } | ||||
|       } | ||||
|     } else { | ||||
|       return decodeTimePast(view, index) | ||||
|       const input = decodeTimePast(view, index) | ||||
|       return { | ||||
|         name, | ||||
|         data: { | ||||
|           frameId, | ||||
|           input | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } else if (proto === ApiMsgEnum.MsgServerSync) { | ||||
|   } else if (name === ApiMsgEnum.MsgServerSync) { | ||||
|     const lastFrameId = view.getUint32(index) | ||||
|     index += 4 | ||||
|     const len = view.getUint8(index++) | ||||
|     const res = [] | ||||
|     const inputs = [] | ||||
|     for (let i = 0; i < len; i++) { | ||||
|       const inputType = view.getUint8(index++) | ||||
|  | ||||
|       if (inputType === InputTypeEnum.ActorMove) { | ||||
|         res.push(decodeActorMove(view, index).data) | ||||
|         inputs.push(decodeActorMove(view, index)) | ||||
|         index += 13 | ||||
|       } else if (inputType === InputTypeEnum.WeaponShoot) { | ||||
|         res.push(decodeWeaponShoot(view, index).data) | ||||
|         inputs.push(decodeWeaponShoot(view, index)) | ||||
|         index += 17 | ||||
|       } else { | ||||
|         res.push(decodeTimePast(view, index).data) | ||||
|         inputs.push(decodeTimePast(view, index)) | ||||
|         index += 4 | ||||
|       } | ||||
|     } | ||||
|     return { | ||||
|       name: ApiMsgEnum.MsgServerSync, | ||||
|       data: res | ||||
|       data: { | ||||
|         lastFrameId, | ||||
|         inputs | ||||
|       } | ||||
|     } | ||||
|   } else { | ||||
|     return { | ||||
|       name: proto, | ||||
|       name: name, | ||||
|       data: JSON.parse(strdecode(new Uint8Array(buffer.slice(1)))) | ||||
|     } | ||||
|   } | ||||
|   | ||||
| @@ -38,7 +38,6 @@ export enum ApiMsgEnum { | ||||
| //   TimePast = 'TimePast', | ||||
| // } | ||||
|  | ||||
|  | ||||
| export enum InputTypeEnum { | ||||
|   ActorMove, | ||||
|   WeaponShoot, | ||||
| @@ -46,14 +45,14 @@ export enum InputTypeEnum { | ||||
| } | ||||
|  | ||||
| export enum EntityTypeEnum { | ||||
|   Map1 = 'Map1', | ||||
|   Actor1 = 'Actor1', | ||||
|   Actor2 = 'Actor2', | ||||
|   Weapon1 = 'Weapon1', | ||||
|   Weapon2 = 'Weapon2', | ||||
|   Bullet1 = 'Bullet1', | ||||
|   Bullet2 = 'Bullet2', | ||||
|   Explosion = 'Explosion', | ||||
|   JoyStick = 'JoyStick', | ||||
|   Shoot = 'Shoot', | ||||
| } | ||||
|   Map1 = "Map1", | ||||
|   Actor1 = "Actor1", | ||||
|   Actor2 = "Actor2", | ||||
|   Weapon1 = "Weapon1", | ||||
|   Weapon2 = "Weapon2", | ||||
|   Bullet1 = "Bullet1", | ||||
|   Bullet2 = "Bullet2", | ||||
|   Explosion = "Explosion", | ||||
|   JoyStick = "JoyStick", | ||||
|   Shoot = "Shoot", | ||||
| } | ||||
|   | ||||
| @@ -1,50 +1,66 @@ | ||||
| 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' | ||||
| 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 IModel { | ||||
|   api: { | ||||
|     [ApiMsgEnum.ApiPlayerJoin]: { | ||||
|       req: IApiPlayerJoinReq, | ||||
|       res: IApiPlayerJoinRes, | ||||
|     } | ||||
|       req: IApiPlayerJoinReq; | ||||
|       res: IApiPlayerJoinRes; | ||||
|     }; | ||||
|     [ApiMsgEnum.ApiPlayerList]: { | ||||
|       req: IApiPlayerListReq, | ||||
|       res: IApiPlayerListRes, | ||||
|     } | ||||
|       req: IApiPlayerListReq; | ||||
|       res: IApiPlayerListRes; | ||||
|     }; | ||||
|     [ApiMsgEnum.ApiRoomList]: { | ||||
|       req: IApiRoomListReq, | ||||
|       res: IApiRoomListRes, | ||||
|     } | ||||
|       req: IApiRoomListReq; | ||||
|       res: IApiRoomListRes; | ||||
|     }; | ||||
|     [ApiMsgEnum.ApiRoomCreate]: { | ||||
|       req: IApiRoomCreateReq, | ||||
|       res: IApiRoomCreateRes, | ||||
|     } | ||||
|       req: IApiRoomCreateReq; | ||||
|       res: IApiRoomCreateRes; | ||||
|     }; | ||||
|     [ApiMsgEnum.ApiRoomJoin]: { | ||||
|       req: IApiRoomJoinReq, | ||||
|       res: IApiRoomJoinRes, | ||||
|     } | ||||
|       req: IApiRoomJoinReq; | ||||
|       res: IApiRoomJoinRes; | ||||
|     }; | ||||
|     [ApiMsgEnum.ApiRoomLeave]: { | ||||
|       req: IApiRoomLeaveReq, | ||||
|       res: IApiRoomLeaveRes, | ||||
|     } | ||||
|       req: IApiRoomLeaveReq; | ||||
|       res: IApiRoomLeaveRes; | ||||
|     }; | ||||
|     [ApiMsgEnum.ApiGameStart]: { | ||||
|       req: IApiGameStartReq, | ||||
|       res: IApiGameStartRes, | ||||
|     }, | ||||
|       req: IApiGameStartReq; | ||||
|       res: IApiGameStartRes; | ||||
|     }; | ||||
|     [ApiMsgEnum.ApiGameEnd]: { | ||||
|       req: IApiGameEndReq, | ||||
|       res: IApiGameEndRes, | ||||
|     } | ||||
|   }, | ||||
|       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, | ||||
|   } | ||||
|     [ApiMsgEnum.MsgPlayerList]: IMsgPlayerList; | ||||
|     [ApiMsgEnum.MsgRoomList]: IMsgRoomList; | ||||
|     [ApiMsgEnum.MsgRoom]: IMsgRoom; | ||||
|     [ApiMsgEnum.MsgGameStart]: IMsgGameStart; | ||||
|     [ApiMsgEnum.MsgGameEnd]: IMsgGameEnd; | ||||
|     [ApiMsgEnum.MsgClientSync]: IMsgClientSync; | ||||
|     [ApiMsgEnum.MsgServerSync]: IMsgServerSync; | ||||
|   }; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,31 +1,34 @@ | ||||
| import { IPlayer, IRoom } from "./Api" | ||||
| import { IClientInput, IState } from "./State" | ||||
| import { IPlayer, IRoom } from "./Api"; | ||||
| import { IClientInput, IState } from "./State"; | ||||
|  | ||||
| export interface IMsgPlayerList { | ||||
|   list: Array<IPlayer> | ||||
|   list: Array<IPlayer>; | ||||
| } | ||||
|  | ||||
| export interface IMsgRoomList { | ||||
|   list: Array<IRoom> | ||||
|   list: Array<IRoom>; | ||||
| } | ||||
|  | ||||
| export interface IMsgRoom { | ||||
|   room: IRoom | ||||
|   room: IRoom; | ||||
| } | ||||
|  | ||||
| export interface IMsgGameStart { | ||||
|   state: IState | ||||
|   state: IState; | ||||
| } | ||||
|  | ||||
|  | ||||
| export interface IMsgGameStart { | ||||
|   state: IState | ||||
|   state: IState; | ||||
| } | ||||
|  | ||||
| export interface IMsgGameEnd { | ||||
| export interface IMsgGameEnd {} | ||||
|  | ||||
| export interface IMsgClientSync { | ||||
|   frameId: number; | ||||
|   input: IClientInput; | ||||
| } | ||||
|  | ||||
|  | ||||
| export type IMsgClientSync = IClientInput | ||||
|  | ||||
| export type IMsgServerSync = Array<IClientInput> | ||||
| export interface IMsgServerSync { | ||||
|   lastFrameId: number; | ||||
|   inputs: Array<IClientInput>; | ||||
| } | ||||
|   | ||||
| @@ -1,50 +1,50 @@ | ||||
| import { EntityTypeEnum, InputTypeEnum } from "./Enum" | ||||
| import { EntityTypeEnum, InputTypeEnum } from "./Enum"; | ||||
|  | ||||
| export interface IActor { | ||||
|   id: number | ||||
|   nickname: string | ||||
|   type: EntityTypeEnum | ||||
|   weaponType: EntityTypeEnum | ||||
|   bulletType: EntityTypeEnum | ||||
|   id: number; | ||||
|   nickname: string; | ||||
|   type: EntityTypeEnum; | ||||
|   weaponType: EntityTypeEnum; | ||||
|   bulletType: EntityTypeEnum; | ||||
|  | ||||
|   //动态数据 | ||||
|   hp: number | ||||
|   position: IVec2 | ||||
|   direction: IVec2 | ||||
|   hp: number; | ||||
|   position: IVec2; | ||||
|   direction: IVec2; | ||||
| } | ||||
|  | ||||
| export interface IBullet { | ||||
|   id: number | ||||
|   owner: number | ||||
|   type: EntityTypeEnum | ||||
|   id: number; | ||||
|   owner: number; | ||||
|   type: EntityTypeEnum; | ||||
|  | ||||
|   //动态数据 | ||||
|   position: IVec2 | ||||
|   direction: IVec2 | ||||
|   position: IVec2; | ||||
|   direction: IVec2; | ||||
| } | ||||
|  | ||||
| export interface IVec2 { | ||||
|   x: number; | ||||
|   y: number | ||||
|   y: number; | ||||
| } | ||||
|  | ||||
| export interface IState { | ||||
|   players: IActor[], | ||||
|   bullets: IBullet[], | ||||
|   nextBulletId: number | ||||
|   actors: IActor[]; | ||||
|   bullets: IBullet[]; | ||||
|   nextBulletId: number; | ||||
| } | ||||
|  | ||||
| export type IClientInput = IActorMove | IWeaponShoot | ITimePast | ||||
| export type IClientInput = IActorMove | IWeaponShoot | ITimePast; | ||||
|  | ||||
| export interface IActorMove { | ||||
|   type: InputTypeEnum.ActorMove | ||||
|   type: InputTypeEnum.ActorMove; | ||||
|   id: number; | ||||
|   direction: IVec2; | ||||
|   dt: number; | ||||
| } | ||||
|  | ||||
| export interface IWeaponShoot { | ||||
|   type: InputTypeEnum.WeaponShoot | ||||
|   type: InputTypeEnum.WeaponShoot; | ||||
|   owner: number; | ||||
|   position: IVec2; | ||||
|   direction: IVec2; | ||||
| @@ -52,5 +52,5 @@ export interface IWeaponShoot { | ||||
|  | ||||
| export interface ITimePast { | ||||
|   type: InputTypeEnum.TimePast; | ||||
|   dt: number | ||||
| } | ||||
|   dt: number; | ||||
| } | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| export const toFixed = (num: number, digit: number = 4): number => Math.floor(num * 10 ** digit) / 10 ** digit | ||||
| export const toFixed = (num: number, digit: number = 4): number => Math.floor(num * 10 ** digit) / 10 ** digit; | ||||
|  | ||||
| export const strencode = (str: string) => { | ||||
|   let byteArray: number[] = []; | ||||
| @@ -11,11 +11,16 @@ export const strencode = (str: string) => { | ||||
|     } 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)); | ||||
|       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[] = []; | ||||
| @@ -33,10 +38,14 @@ export const strdecode = (bytes: Uint8Array) => { | ||||
|       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); | ||||
|       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); | ||||
| } | ||||
| }; | ||||
|   | ||||
| @@ -1,64 +1,63 @@ | ||||
| import { EventEmitter } from 'stream'; | ||||
| import WebSocket, { WebSocketServer } from 'ws'; | ||||
| import { ApiMsgEnum } from '../Common'; | ||||
| import { Connection, ConnectionEventEnum } from './Connection'; | ||||
| import { EventEmitter } from "stream"; | ||||
| import WebSocket, { WebSocketServer } from "ws"; | ||||
| import { ApiMsgEnum } from "../Common"; | ||||
| import { Connection, ConnectionEventEnum } from "./Connection"; | ||||
|  | ||||
| export interface IMyServerOptions { | ||||
|   port: number | ||||
|   port: number; | ||||
| } | ||||
|  | ||||
| export enum MyServerEventEnum { | ||||
|   Connect = 'Connect', | ||||
|   DisConnect = 'DisConnect', | ||||
|   Connect = "Connect", | ||||
|   DisConnect = "DisConnect", | ||||
| } | ||||
|  | ||||
| export class MyServer extends EventEmitter { | ||||
|   wss?: WebSocketServer | ||||
|   port: number | ||||
|   connections: Set<Connection> = new Set() | ||||
|   apiMap: Map<ApiMsgEnum, Function> = new Map() | ||||
|   wss?: WebSocketServer; | ||||
|   port: number; | ||||
|   connections: Set<Connection> = new Set(); | ||||
|   apiMap: Map<ApiMsgEnum, Function> = new Map(); | ||||
|  | ||||
|   constructor({ port = 8080 }: Partial<IMyServerOptions>) { | ||||
|     super() | ||||
|     this.port = port | ||||
|     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("connection", this.handleConnect.bind(this)); | ||||
|  | ||||
|       this.wss.on("error", (e) => { | ||||
|         reject(e) | ||||
|       }) | ||||
|         reject(e); | ||||
|       }); | ||||
|  | ||||
|       this.wss.on("close", () => { | ||||
|         console.log("MyServer 服务关闭"); | ||||
|       }) | ||||
|       }); | ||||
|  | ||||
|       this.wss.on("listening", () => { | ||||
|         resolve(true) | ||||
|       }) | ||||
|     }) | ||||
|  | ||||
|         resolve(true); | ||||
|       }); | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   handleConnect(ws: WebSocket) { | ||||
|     //初始化 | ||||
|     const connection = new Connection(this, ws) | ||||
|     const connection = new Connection(this, ws); | ||||
|  | ||||
|     //向外告知有人来了 | ||||
|     this.connections.add(connection) | ||||
|     this.emit(MyServerEventEnum.Connect, connection) | ||||
|     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) | ||||
|     }) | ||||
|       this.connections.delete(connection); | ||||
|       this.emit(MyServerEventEnum.DisConnect, connection, code, reason); | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   setApi(apiName: ApiMsgEnum, cb: Function) { | ||||
|     this.apiMap.set(apiName, cb) | ||||
|     this.apiMap.set(apiName, cb); | ||||
|   } | ||||
| } | ||||
| } | ||||
|   | ||||
| @@ -1,13 +1,12 @@ | ||||
| export default class Singleton { | ||||
|   private static _instance: any = null | ||||
|   private static _instance: any = null; | ||||
|  | ||||
|   static GetInstance<T>(): T { | ||||
|     if (this._instance === null) { | ||||
|       this._instance = new this() | ||||
|       this._instance = new this(); | ||||
|     } | ||||
|     return this._instance | ||||
|     return this._instance; | ||||
|   } | ||||
|  | ||||
|   protected constructor() { | ||||
|   } | ||||
| } | ||||
|   protected constructor() {} | ||||
| } | ||||
|   | ||||
| @@ -1,16 +1,16 @@ | ||||
| import { Connection } from "../Core" | ||||
| import { Connection } from "../Core"; | ||||
|  | ||||
| export default class Player { | ||||
|   id: number | ||||
|   nickname: string | ||||
|   connection: Connection | ||||
|   rid: number | ||||
|   id: number; | ||||
|   nickname: string; | ||||
|   connection: Connection; | ||||
|   rid: number; | ||||
|  | ||||
|   constructor({ id, nickname, connection }: Pick<Player, 'id' | 'nickname' | 'connection'>) { | ||||
|     this.id = id | ||||
|     this.nickname = nickname | ||||
|     this.connection = connection | ||||
|     this.connection.playerId = this.id | ||||
|     this.rid = -1 | ||||
|   constructor({ id, nickname, connection }: Pick<Player, "id" | "nickname" | "connection">) { | ||||
|     this.id = id; | ||||
|     this.nickname = nickname; | ||||
|     this.connection = connection; | ||||
|     this.connection.playerId = this.id; | ||||
|     this.rid = -1; | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,54 +1,55 @@ | ||||
| import Singleton from '../Base/Singleton' | ||||
| import { ApiMsgEnum, IApiPlayerJoinReq } from '../Common' | ||||
| import { Connection } from '../Core' | ||||
| import Player from './Player' | ||||
| import RoomManager from './RoomManager' | ||||
| import Singleton from "../Base/Singleton"; | ||||
| import { ApiMsgEnum, IApiPlayerJoinReq } from "../Common"; | ||||
| import { Connection } from "../Core"; | ||||
| import Player from "./Player"; | ||||
| import RoomManager from "./RoomManager"; | ||||
| export default class PlayerManager extends Singleton { | ||||
|   static get Instance() { | ||||
|     return super.GetInstance<PlayerManager>() | ||||
|     return super.GetInstance<PlayerManager>(); | ||||
|   } | ||||
|  | ||||
|   playerId = 1 | ||||
|   players: Set<Player> = new Set() | ||||
|   idMapPlayer: Map<number, Player> = new Map() | ||||
|   players: Set<Player> = new Set(); | ||||
|  | ||||
|   private nextPlayerId = 1; | ||||
|   private idMapPlayer: Map<number, Player> = new Map(); | ||||
|  | ||||
|   createPlayer({ connection, nickname }: IApiPlayerJoinReq & { connection: Connection }) { | ||||
|     const player = new Player({ id: this.playerId++, connection, nickname }) | ||||
|     this.players.add(player) | ||||
|     this.idMapPlayer.set(player.id, player) | ||||
|     return player | ||||
|     const player = new Player({ id: this.nextPlayerId++, connection, nickname }); | ||||
|     this.players.add(player); | ||||
|     this.idMapPlayer.set(player.id, player); | ||||
|     return player; | ||||
|   } | ||||
|  | ||||
|   removePlayer(uid: number) { | ||||
|     const player = this.idMapPlayer.get(uid) | ||||
|     const player = this.idMapPlayer.get(uid); | ||||
|     if (player) { | ||||
|       const rid = player.rid | ||||
|       const rid = player.rid; | ||||
|       if (rid !== undefined) { | ||||
|         RoomManager.Instance.leaveRoom(rid, uid); | ||||
|         RoomManager.Instance.syncRooms(); | ||||
|         RoomManager.Instance.syncRoom(rid); | ||||
|       } | ||||
|       this.players.delete(player) | ||||
|       this.idMapPlayer.delete(uid) | ||||
|       this.syncPlayers() | ||||
|       this.players.delete(player); | ||||
|       this.idMapPlayer.delete(uid); | ||||
|       this.syncPlayers(); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   getPlayerById(uid: number) { | ||||
|     return this.idMapPlayer.get(uid) | ||||
|     return this.idMapPlayer.get(uid); | ||||
|   } | ||||
|  | ||||
|   syncPlayers() { | ||||
|     for (const player of this.players) { | ||||
|       player.connection.sendMsg(ApiMsgEnum.MsgPlayerList, { list: this.getPlayersView() }) | ||||
|       player.connection.sendMsg(ApiMsgEnum.MsgPlayerList, { list: this.getPlayersView() }); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   getPlayersView(players: Set<Player> = this.players) { | ||||
|     return [...players].map((player) => this.getPlayerView(player)) | ||||
|     return [...players].map((player) => this.getPlayerView(player)); | ||||
|   } | ||||
|  | ||||
|   getPlayerView({ id, nickname, rid }: Player) { | ||||
|     return { id, nickname, rid } | ||||
|     return { id, nickname, rid }; | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,60 +1,63 @@ | ||||
| import { ApiMsgEnum, EntityTypeEnum, IClientInput, InputTypeEnum, IState, toFixed } from '../Common' | ||||
| import type Player from './Player' | ||||
| import PlayerManager from './PlayerManager' | ||||
| import RoomManager from './RoomManager' | ||||
| import { ApiMsgEnum, EntityTypeEnum, IClientInput, IMsgClientSync, InputTypeEnum, IState, toFixed } from "../Common"; | ||||
| import { Connection } from "../Core"; | ||||
| import type Player from "./Player"; | ||||
| import PlayerManager from "./PlayerManager"; | ||||
| import RoomManager from "./RoomManager"; | ||||
|  | ||||
| export default class Room { | ||||
|   id: number | ||||
|   players: Set<Player> = new Set() | ||||
|   lastSyncTime?: number | ||||
|   id: number; | ||||
|   players: Set<Player> = new Set(); | ||||
|  | ||||
|   private timers: NodeJS.Timer[] = [] | ||||
|   private inputs: Array<IClientInput> = [] | ||||
|   private lastTime?: number; | ||||
|   private timers: NodeJS.Timer[] = []; | ||||
|   private pendingInput: Array<IClientInput> = []; | ||||
|   private lastPlayerFrameIdMap: Map<number, number> = new Map(); | ||||
|  | ||||
|   constructor(rid: number) { | ||||
|     this.id = rid | ||||
|     this.id = rid; | ||||
|   } | ||||
|  | ||||
|   join(uid: number) { | ||||
|     const player = PlayerManager.Instance.getPlayerById(uid) | ||||
|     const player = PlayerManager.Instance.getPlayerById(uid); | ||||
|     if (player) { | ||||
|       player.rid = this.id | ||||
|       this.players.add(player) | ||||
|       player.rid = this.id; | ||||
|       this.players.add(player); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   leave(uid: number) { | ||||
|     const player = PlayerManager.Instance.getPlayerById(uid) | ||||
|     const player = PlayerManager.Instance.getPlayerById(uid); | ||||
|     if (player) { | ||||
|       player.rid = -1 | ||||
|       player.connection.unlistenMsg(ApiMsgEnum.MsgClientSync, this.getInput, this) | ||||
|       this.players.delete(player) | ||||
|       player.rid = -1; | ||||
|       player.connection.unlistenMsg(ApiMsgEnum.MsgClientSync, this.getClientMsg, this); | ||||
|       this.players.delete(player); | ||||
|       if (!this.players.size) { | ||||
|         RoomManager.Instance.closeRoom(this.id) | ||||
|         RoomManager.Instance.closeRoom(this.id); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   close() { | ||||
|     this.timers.forEach(t => clearInterval(t)) | ||||
|     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) | ||||
|       player.rid = -1; | ||||
|       player.connection.sendMsg(ApiMsgEnum.MsgGameEnd, {}); | ||||
|       player.connection.unlistenMsg(ApiMsgEnum.MsgClientSync, this.getClientMsg, this); | ||||
|     } | ||||
|     this.players.clear() | ||||
|     this.players.clear(); | ||||
|   } | ||||
|  | ||||
|   sync() { | ||||
|     for (const player of this.players) { | ||||
|       player.connection.sendMsg(ApiMsgEnum.MsgRoom, { | ||||
|         room: RoomManager.Instance.getRoomView(this) | ||||
|       }) | ||||
|         room: RoomManager.Instance.getRoomView(this), | ||||
|       }); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   start() { | ||||
|     const state: IState = { | ||||
|       players: [...this.players].map((player, index) => ({ | ||||
|       actors: [...this.players].map((player, index) => ({ | ||||
|         id: player.id, | ||||
|         nickname: player.nickname, | ||||
|         position: { | ||||
| @@ -63,52 +66,56 @@ export default class Room { | ||||
|         }, | ||||
|         direction: { | ||||
|           x: 1, | ||||
|           y: 0 | ||||
|           y: 0, | ||||
|         }, | ||||
|         hp: 100, | ||||
|         type: EntityTypeEnum.Actor2, | ||||
|         weaponType: EntityTypeEnum.Weapon2, | ||||
|         bulletType: EntityTypeEnum.Bullet2, | ||||
|         type: index === 0 ? EntityTypeEnum.Actor1 : EntityTypeEnum.Actor2, | ||||
|         weaponType: index === 0 ? EntityTypeEnum.Weapon1 : EntityTypeEnum.Weapon2, | ||||
|         bulletType: index === 0 ? EntityTypeEnum.Bullet1 : EntityTypeEnum.Bullet2, | ||||
|       })), | ||||
|       bullets: [], | ||||
|       nextBulletId: 1 | ||||
|     } | ||||
|       nextBulletId: 1, | ||||
|     }; | ||||
|  | ||||
|     for (const player of this.players) { | ||||
|       player.connection.sendMsg(ApiMsgEnum.MsgGameStart, { | ||||
|         state | ||||
|       }) | ||||
|       player.connection.listenMsg(ApiMsgEnum.MsgClientSync, this.getInput, this) | ||||
|         state, | ||||
|       }); | ||||
|       player.connection.listenMsg(ApiMsgEnum.MsgClientSync, this.getClientMsg, this); | ||||
|     } | ||||
|     let t1 = setInterval(() => { | ||||
|       this.sendInput() | ||||
|     }, 100) | ||||
|       this.sendServerMsg(); | ||||
|     }, 100); | ||||
|     let t2 = setInterval(() => { | ||||
|       this.timePast() | ||||
|     }, 16) | ||||
|     this.timers = [t1, t2] | ||||
|       this.timePast(); | ||||
|     }, 16); | ||||
|     this.timers = [t1, t2]; | ||||
|   } | ||||
|  | ||||
|   getInput(input: IClientInput) { | ||||
|     this.inputs.push(input) | ||||
|   getClientMsg(connection: Connection, { frameId, input }: IMsgClientSync) { | ||||
|     this.lastPlayerFrameIdMap.set(connection.playerId, frameId); | ||||
|     this.pendingInput.push(input); | ||||
|   } | ||||
|  | ||||
|   sendInput() { | ||||
|     const inputs = this.inputs | ||||
|     this.inputs = [] | ||||
|   sendServerMsg() { | ||||
|     const pendingInput = this.pendingInput; | ||||
|     this.pendingInput = []; | ||||
|  | ||||
|     for (const player of this.players) { | ||||
|       player.connection.sendMsg(ApiMsgEnum.MsgServerSync, inputs) | ||||
|       player.connection.sendMsg(ApiMsgEnum.MsgServerSync, { | ||||
|         lastFrameId: this.lastPlayerFrameIdMap.get(player.id) ?? 0, | ||||
|         inputs: pendingInput, | ||||
|       }); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   timePast() { | ||||
|     let now = process.uptime(); | ||||
|     const dt = now - (this.lastSyncTime ?? now) | ||||
|     this.inputs.push({ | ||||
|     const dt = now - (this.lastTime ?? now); | ||||
|     this.pendingInput.push({ | ||||
|       type: InputTypeEnum.TimePast, | ||||
|       dt: toFixed(dt) | ||||
|     }) | ||||
|     this.lastSyncTime = now; | ||||
|       dt: toFixed(dt), | ||||
|     }); | ||||
|     this.lastTime = now; | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,77 +1,77 @@ | ||||
| import Singleton from '../Base/Singleton' | ||||
| import { ApiMsgEnum } from '../Common' | ||||
| import PlayerManager from './PlayerManager' | ||||
| import Room from './Room' | ||||
| import Singleton from "../Base/Singleton"; | ||||
| import { ApiMsgEnum } from "../Common"; | ||||
| import PlayerManager from "./PlayerManager"; | ||||
| import Room from "./Room"; | ||||
|  | ||||
| export default class RoomManager extends Singleton { | ||||
|   static get Instance() { | ||||
|     return super.GetInstance<RoomManager>() | ||||
|     return super.GetInstance<RoomManager>(); | ||||
|   } | ||||
|  | ||||
|   roomId = 1 | ||||
|   rooms: Set<Room> = new Set() | ||||
|   idMapRoom: Map<number, Room> = new Map() | ||||
|   nextRoomId = 1; | ||||
|   rooms: Set<Room> = new Set(); | ||||
|   idMapRoom: Map<number, Room> = new Map(); | ||||
|  | ||||
|   createRoom() { | ||||
|     const room = new Room(this.roomId++) | ||||
|     this.rooms.add(room) | ||||
|     this.idMapRoom.set(room.id, room) | ||||
|     return room | ||||
|     const room = new Room(this.nextRoomId++); | ||||
|     this.rooms.add(room); | ||||
|     this.idMapRoom.set(room.id, room); | ||||
|     return room; | ||||
|   } | ||||
|  | ||||
|   joinRoom(rid: number, uid: number) { | ||||
|     const room = this.getRoomById(rid) | ||||
|     const room = this.getRoomById(rid); | ||||
|     if (room) { | ||||
|       room.join(uid) | ||||
|       return room | ||||
|       room.join(uid); | ||||
|       return room; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   leaveRoom(rid: number, uid: number) { | ||||
|     const room = this.getRoomById(rid) | ||||
|     const room = this.getRoomById(rid); | ||||
|     if (room) { | ||||
|       room.leave(uid) | ||||
|       room.leave(uid); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   closeRoom(rid: number) { | ||||
|     const room = this.getRoomById(rid) | ||||
|     const room = this.getRoomById(rid); | ||||
|     if (room) { | ||||
|       room.close() | ||||
|       this.rooms.delete(room) | ||||
|       this.idMapRoom.delete(rid) | ||||
|       room.close(); | ||||
|       this.rooms.delete(room); | ||||
|       this.idMapRoom.delete(rid); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   startRoom(rid: number) { | ||||
|     const room = this.getRoomById(rid) | ||||
|     const room = this.getRoomById(rid); | ||||
|     if (room) { | ||||
|       room.start() | ||||
|       room.start(); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   getRoomById(id: number) { | ||||
|     return this.idMapRoom.get(id) | ||||
|     return this.idMapRoom.get(id); | ||||
|   } | ||||
|  | ||||
|   syncRooms() { | ||||
|     for (const player of PlayerManager.Instance.players) { | ||||
|       player.connection.sendMsg(ApiMsgEnum.MsgRoomList, { list: this.getRoomsView() }) | ||||
|       player.connection.sendMsg(ApiMsgEnum.MsgRoomList, { list: this.getRoomsView() }); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   syncRoom(rid: number) { | ||||
|     const room = this.idMapRoom.get(rid) | ||||
|     const room = this.idMapRoom.get(rid); | ||||
|     if (room) { | ||||
|       room.sync() | ||||
|       room.sync(); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   getRoomsView(rooms: Set<Room> = this.rooms) { | ||||
|     return [...rooms].map((room) => this.getRoomView(room)) | ||||
|     return [...rooms].map((room) => this.getRoomView(room)); | ||||
|   } | ||||
|  | ||||
|   getRoomView({ id, players }: Room) { | ||||
|     return { id, players: PlayerManager.Instance.getPlayersView(players) } | ||||
|     return { id, players: PlayerManager.Instance.getPlayersView(players) }; | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,8 +1,7 @@ | ||||
| export * from './Api' | ||||
| export * from './Msg' | ||||
| export * from './Enum' | ||||
| export * from './Model' | ||||
| export * from './State' | ||||
| export * from './Utils' | ||||
| export * from './Binary' | ||||
|  | ||||
| export * from "./Api"; | ||||
| export * from "./Msg"; | ||||
| export * from "./Enum"; | ||||
| export * from "./Model"; | ||||
| export * from "./State"; | ||||
| export * from "./Utils"; | ||||
| export * from "./Binary"; | ||||
|   | ||||
| @@ -1,12 +1,12 @@ | ||||
| import WebSocket from 'ws'; | ||||
| import { EventEmitter } from 'stream'; | ||||
| import { MyServer } from './MyServer'; | ||||
| import { getTime, toArrayBuffer } from '../Utils'; | ||||
| import { ApiMsgEnum, IModel } from '../Common'; | ||||
| import { binaryEncode, binaryDecode } from '../Common/Binary'; | ||||
| import WebSocket from "ws"; | ||||
| import { EventEmitter } from "stream"; | ||||
| import { MyServer } from "./MyServer"; | ||||
| import { getTime, buffer2ArrayBuffer } from "../Utils"; | ||||
| import { ApiMsgEnum, IModel } from "../Common"; | ||||
| import { binaryEncode, binaryDecode } from "../Common/Binary"; | ||||
|  | ||||
| export enum ConnectionEventEnum { | ||||
|   Close = 'Close', | ||||
|   Close = "Close", | ||||
| } | ||||
|  | ||||
| interface IItem { | ||||
| @@ -15,80 +15,84 @@ interface IItem { | ||||
| } | ||||
|  | ||||
| export class Connection extends EventEmitter { | ||||
|   server: MyServer | ||||
|   ws: WebSocket | ||||
|   msgMap: Map<ApiMsgEnum, Array<IItem>> = new Map() | ||||
|   server: MyServer; | ||||
|   ws: WebSocket; | ||||
|   msgMap: Map<ApiMsgEnum, Array<IItem>> = new Map(); | ||||
|   playerId?: number; | ||||
|  | ||||
|   constructor(server: MyServer, ws: WebSocket) { | ||||
|     super() | ||||
|     super(); | ||||
|  | ||||
|     this.server = server | ||||
|     this.ws = ws | ||||
|     this.ws.on('close', (code: number, reason: Buffer) => { | ||||
|       this.emit(ConnectionEventEnum.Close, code, reason.toString()) | ||||
|     }) | ||||
|     this.server = server; | ||||
|     this.ws = ws; | ||||
|     this.ws.on("close", (code: number, reason: Buffer) => { | ||||
|       this.emit(ConnectionEventEnum.Close, code, reason.toString()); | ||||
|     }); | ||||
|  | ||||
|     this.ws.on('message', (buffer: Buffer) => { | ||||
|     this.ws.on("message", (buffer: Buffer) => { | ||||
|       // const str = buffer.toString() | ||||
|       try { | ||||
|         const json = binaryDecode(toArrayBuffer(buffer)) | ||||
|         const { name, data } = json | ||||
|         const json = binaryDecode(buffer2ArrayBuffer(buffer)); | ||||
|         const { name, data } = json; | ||||
|         // console.log(`${getTime()}接收|字节数${buffer.length}|${this.playerId || -1}|${str}`) | ||||
|         console.log(`${getTime()}接收|字节数${buffer.length}|${this.playerId || -1}|${JSON.stringify(json)}`) | ||||
|         console.log(`${getTime()}接收|字节数${buffer.length}|${this.playerId || -1}|${JSON.stringify(json)}`); | ||||
|         if (this.server.apiMap.has(name)) { | ||||
|           try { | ||||
|             const cb = this.server.apiMap.get(name) | ||||
|             const res = cb.call(null, data) | ||||
|             const cb = this.server.apiMap.get(name); | ||||
|             const res = cb.call(null, this, data); | ||||
|             this.sendMsg(name, { | ||||
|               success: true, | ||||
|               res, | ||||
|             }) | ||||
|             }); | ||||
|           } catch (error) { | ||||
|             this.sendMsg(name, { | ||||
|               success: false, | ||||
|               error: (error as Error)?.message, | ||||
|             }) | ||||
|             }); | ||||
|           } | ||||
|         } else { | ||||
|           try { | ||||
|             if (this.msgMap.has(name)) { | ||||
|               this.msgMap.get(name)?.forEach(({ cb, ctx }) => cb.call(ctx, data)) | ||||
|               this.msgMap.get(name).forEach(({ cb, ctx }) => cb.call(ctx, this, data)); | ||||
|             } | ||||
|           } catch (error) { | ||||
|             console.log(error) | ||||
|             console.log(error); | ||||
|           } | ||||
|         } | ||||
|       } catch (error) { | ||||
|         console.log(`解析失败,不是合法的JSON格式:`, error) | ||||
|         console.log(`解析失败,不是合法的JSON格式:`, error); | ||||
|       } | ||||
|     }) | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   listenMsg<T extends keyof IModel['msg']>(name: T, cb: (args: IModel['msg'][T]) => void, ctx: unknown) { | ||||
|   listenMsg<T extends keyof IModel["msg"]>(name: T, cb: (connection: Connection, arg: IModel["msg"][T]) => void, ctx: unknown) { | ||||
|     if (this.msgMap.has(name)) { | ||||
|       this.msgMap.get(name)?.push({ cb, ctx }) | ||||
|       this.msgMap.get(name).push({ cb, ctx }); | ||||
|     } else { | ||||
|       this.msgMap.set(name, [{ cb, ctx }]) | ||||
|       this.msgMap.set(name, [{ cb, ctx }]); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   unlistenMsg<T extends keyof IModel['msg']>(name: T, cb: (args: IModel['msg'][T]) => void, ctx: unknown) { | ||||
|   unlistenMsg<T extends keyof IModel["msg"]>(name: T, cb: (connection: Connection, arg: IModel["msg"][T]) => void, ctx: unknown) { | ||||
|     if (this.msgMap.has(name)) { | ||||
|       const items = this.msgMap.get(name) | ||||
|       const index = items.findIndex(i => cb === i.cb && i.ctx === ctx); | ||||
|       index > -1 && items.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); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   sendMsg<T extends keyof IModel['msg']>(name: T, data: IModel['msg'][T]) { | ||||
|   sendMsg<T extends keyof IModel["msg"]>(name: T, data: IModel["msg"][T]) { | ||||
|     const msg = JSON.stringify({ | ||||
|       name, | ||||
|       data | ||||
|     }) | ||||
|     const view = binaryEncode(name, data) | ||||
|     const buffer = Buffer.from(view.buffer) | ||||
|     console.log(`${getTime()}发送|字节数${buffer.length}|${this.playerId || -1}|内存${(process.memoryUsage().heapUsed / 1024 / 1024).toFixed(2)}MB|${msg}`) | ||||
|     this.ws.send(buffer) | ||||
|       data, | ||||
|     }); | ||||
|     const view = binaryEncode(name, data); | ||||
|     const buffer = Buffer.from(view.buffer); | ||||
|     console.log( | ||||
|       `${getTime()}发送|字节数${buffer.length}|${this.playerId || -1}|内存${(process.memoryUsage().heapUsed / 1024 / 1024).toFixed( | ||||
|         2 | ||||
|       )}MB|${msg}` | ||||
|     ); | ||||
|     this.ws.send(buffer); | ||||
|   } | ||||
| } | ||||
| } | ||||
|   | ||||
| @@ -1,2 +1,2 @@ | ||||
| export * from './MyServer' | ||||
| export * from './Connection' | ||||
| export * from "./MyServer"; | ||||
| export * from "./Connection"; | ||||
|   | ||||
| @@ -1,144 +1,165 @@ | ||||
| import { Connection, MyServer, MyServerEventEnum } from './Core'; | ||||
| import PlayerManager from './Biz/PlayerManager'; | ||||
| import RoomManager from './Biz/RoomManager'; | ||||
| import { getTime, symlinkCommon } from './Utils'; | ||||
| import { ApiMsgEnum, IApiGameEndReq, IApiGameEndRes, IApiGameStartReq, IApiGameStartRes, IApiPlayerJoinReq, IApiPlayerJoinRes, IApiPlayerListReq, IApiPlayerListRes, IApiRoomCreateReq, IApiRoomCreateRes, IApiRoomJoinReq, IApiRoomJoinRes, IApiRoomLeaveReq, IApiRoomLeaveRes, IApiRoomListReq, IApiRoomListRes, IModel } from './Common'; | ||||
| import { Connection, MyServer, MyServerEventEnum } from "./Core"; | ||||
| import PlayerManager from "./Biz/PlayerManager"; | ||||
| import RoomManager from "./Biz/RoomManager"; | ||||
| import { getTime, symlinkCommon } from "./Utils"; | ||||
| 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 }) | ||||
| const server = new MyServer({ port: 8888 }); | ||||
|  | ||||
| // event | ||||
| server.on(MyServerEventEnum.Connect, (connection: Connection) => { | ||||
|   console.log(`${getTime()}来人|人数|${server.connections.size}`) | ||||
| }) | ||||
|   console.log(`${getTime()}来人|人数|${server.connections.size}`); | ||||
| }); | ||||
|  | ||||
| server.on(MyServerEventEnum.DisConnect, (connection: Connection) => { | ||||
|   console.log(`${getTime()}走人|人数|${server.connections.size}`) | ||||
|   console.log(`${getTime()}走人|人数|${server.connections.size}`); | ||||
|   if (connection.playerId) { | ||||
|     PlayerManager.Instance.removePlayer(connection.playerId) | ||||
|     PlayerManager.Instance.removePlayer(connection.playerId); | ||||
|   } | ||||
| }) | ||||
| }); | ||||
|  | ||||
| // api | ||||
| server.setApi(ApiMsgEnum.ApiPlayerList, (connection: Connection, data: IApiPlayerListReq): IApiPlayerListRes => { | ||||
|   return { list: PlayerManager.Instance.getPlayersView() } | ||||
| }) | ||||
|   return { list: PlayerManager.Instance.getPlayersView() }; | ||||
| }); | ||||
|  | ||||
| server.setApi(ApiMsgEnum.ApiPlayerJoin, (connection: Connection, { nickname }: IApiPlayerJoinReq): IApiPlayerJoinRes => { | ||||
|   const player = PlayerManager.Instance.createPlayer({ connection, nickname }) | ||||
|   PlayerManager.Instance.syncPlayers() | ||||
|   const player = PlayerManager.Instance.createPlayer({ connection, nickname }); | ||||
|   PlayerManager.Instance.syncPlayers(); | ||||
|   return { | ||||
|     player: PlayerManager.Instance.getPlayerView(player) | ||||
|   } | ||||
| }) | ||||
|     player: PlayerManager.Instance.getPlayerView(player), | ||||
|   }; | ||||
| }); | ||||
|  | ||||
| server.setApi(ApiMsgEnum.ApiRoomList, (connection: Connection, data: IApiRoomListReq): IApiRoomListRes => { | ||||
|   return { list: RoomManager.Instance.getRoomsView() } | ||||
| }) | ||||
|   return { list: RoomManager.Instance.getRoomsView() }; | ||||
| }); | ||||
|  | ||||
| server.setApi(ApiMsgEnum.ApiRoomCreate, (connection: Connection, data: IApiRoomCreateReq): IApiRoomCreateRes => { | ||||
|   if (connection.playerId) { | ||||
|     const room = RoomManager.Instance.joinRoom(RoomManager.Instance.createRoom().id, connection.playerId) | ||||
|     const room = RoomManager.Instance.joinRoom(RoomManager.Instance.createRoom().id, connection.playerId); | ||||
|     if (room) { | ||||
|       RoomManager.Instance.syncRooms() | ||||
|       PlayerManager.Instance.syncPlayers() | ||||
|       RoomManager.Instance.syncRooms(); | ||||
|       PlayerManager.Instance.syncPlayers(); | ||||
|       return { | ||||
|         room: RoomManager.Instance.getRoomView(room) | ||||
|       } | ||||
|         room: RoomManager.Instance.getRoomView(room), | ||||
|       }; | ||||
|     } else { | ||||
|       throw new Error("ApiRoomCreate room不存在") | ||||
|       throw new Error("ApiRoomCreate room不存在"); | ||||
|     } | ||||
|   } else { | ||||
|     throw new Error("ApiRoomCreate 玩家未登录") | ||||
|     throw new Error("ApiRoomCreate 玩家未登录"); | ||||
|   } | ||||
| }) | ||||
| }); | ||||
|  | ||||
| server.setApi(ApiMsgEnum.ApiRoomJoin, (connection: Connection, data: IApiRoomJoinReq): IApiRoomJoinRes => { | ||||
|   if (connection.playerId) { | ||||
|     const room = RoomManager.Instance.joinRoom(data.rid, connection.playerId) | ||||
|     const room = RoomManager.Instance.joinRoom(data.rid, connection.playerId); | ||||
|     if (room) { | ||||
|       RoomManager.Instance.syncRooms() | ||||
|       PlayerManager.Instance.syncPlayers() | ||||
|       RoomManager.Instance.syncRoom(room.id) | ||||
|       RoomManager.Instance.syncRooms(); | ||||
|       PlayerManager.Instance.syncPlayers(); | ||||
|       RoomManager.Instance.syncRoom(room.id); | ||||
|       return { | ||||
|         room: RoomManager.Instance.getRoomView(room) | ||||
|       } | ||||
|         room: RoomManager.Instance.getRoomView(room), | ||||
|       }; | ||||
|     } else { | ||||
|       throw new Error("ApiRoomJoin room不存在") | ||||
|       throw new Error("ApiRoomJoin room不存在"); | ||||
|     } | ||||
|   } else { | ||||
|     throw new Error("ApiRoomJoin 玩家未登录") | ||||
|     throw new Error("ApiRoomJoin 玩家未登录"); | ||||
|   } | ||||
| }) | ||||
| }); | ||||
|  | ||||
| server.setApi(ApiMsgEnum.ApiRoomLeave, (connection: Connection, data: IApiRoomLeaveReq): IApiRoomLeaveRes => { | ||||
|   if (connection.playerId) { | ||||
|     const player = PlayerManager.Instance.getPlayerById(connection.playerId) | ||||
|     const player = PlayerManager.Instance.getPlayerById(connection.playerId); | ||||
|     if (player) { | ||||
|       const rid = player.rid | ||||
|       const rid = player.rid; | ||||
|       if (rid) { | ||||
|         RoomManager.Instance.leaveRoom(rid, player.id) | ||||
|         PlayerManager.Instance.syncPlayers() | ||||
|         RoomManager.Instance.syncRooms() | ||||
|         RoomManager.Instance.syncRoom(rid) | ||||
|         return {} | ||||
|         RoomManager.Instance.leaveRoom(rid, player.id); | ||||
|         PlayerManager.Instance.syncPlayers(); | ||||
|         RoomManager.Instance.syncRooms(); | ||||
|         RoomManager.Instance.syncRoom(rid); | ||||
|         return {}; | ||||
|       } else { | ||||
|         throw new Error("ApiRoomLeave 玩家不在房间") | ||||
|         throw new Error("ApiRoomLeave 玩家不在房间"); | ||||
|       } | ||||
|     } else { | ||||
|       throw new Error("ApiRoomLeave 玩家不存在") | ||||
|       throw new Error("ApiRoomLeave 玩家不存在"); | ||||
|     } | ||||
|   } else { | ||||
|     throw new Error("ApiRoomLeave 玩家未登录") | ||||
|     throw new Error("ApiRoomLeave 玩家未登录"); | ||||
|   } | ||||
| }) | ||||
| }); | ||||
|  | ||||
| server.setApi(ApiMsgEnum.ApiGameStart, (connection: Connection, data: IApiGameStartReq): IApiGameStartRes => { | ||||
|   if (connection.playerId) { | ||||
|     const player = PlayerManager.Instance.getPlayerById(connection.playerId) | ||||
|     const player = PlayerManager.Instance.getPlayerById(connection.playerId); | ||||
|     if (player) { | ||||
|       const rid = player.rid | ||||
|       const rid = player.rid; | ||||
|       if (rid) { | ||||
|         RoomManager.Instance.startRoom(rid) | ||||
|         // PlayerManager.Instance.syncPlayers() | ||||
|         // RoomManager.Instance.syncRooms() | ||||
|         return {} | ||||
|         RoomManager.Instance.startRoom(rid); | ||||
|         PlayerManager.Instance.syncPlayers(); | ||||
|         RoomManager.Instance.syncRooms(); | ||||
|         return {}; | ||||
|       } else { | ||||
|         throw new Error("ApiRoomLeave 玩家不在房间") | ||||
|         throw new Error("ApiRoomLeave 玩家不在房间"); | ||||
|       } | ||||
|     } else { | ||||
|       throw new Error("ApiRoomLeave 玩家不存在") | ||||
|       throw new Error("ApiRoomLeave 玩家不存在"); | ||||
|     } | ||||
|   } else { | ||||
|     throw new Error("ApiRoomLeave 玩家未登录") | ||||
|     throw new Error("ApiRoomLeave 玩家未登录"); | ||||
|   } | ||||
| }) | ||||
| }); | ||||
|  | ||||
| server.setApi(ApiMsgEnum.ApiGameEnd, (connection: Connection, data: IApiGameEndReq): IApiGameEndRes => { | ||||
|   if (connection.playerId) { | ||||
|     const player = PlayerManager.Instance.getPlayerById(connection.playerId) | ||||
|     const player = PlayerManager.Instance.getPlayerById(connection.playerId); | ||||
|     if (player) { | ||||
|       const rid = player.rid | ||||
|       const rid = player.rid; | ||||
|       if (rid) { | ||||
|         RoomManager.Instance.closeRoom(rid) | ||||
|         PlayerManager.Instance.syncPlayers() | ||||
|         RoomManager.Instance.syncRooms() | ||||
|         return {} | ||||
|         RoomManager.Instance.closeRoom(rid); | ||||
|         PlayerManager.Instance.syncPlayers(); | ||||
|         RoomManager.Instance.syncRooms(); | ||||
|         return {}; | ||||
|       } else { | ||||
|         throw new Error("ApiGameEnd 玩家不在房间") | ||||
|         throw new Error("ApiGameEnd 玩家不在房间"); | ||||
|       } | ||||
|     } else { | ||||
|       throw new Error("ApiGameEnd 玩家不存在") | ||||
|       throw new Error("ApiGameEnd 玩家不存在"); | ||||
|     } | ||||
|   } else { | ||||
|     throw new Error("ApiGameEnd 玩家未登录") | ||||
|     throw new Error("ApiGameEnd 玩家未登录"); | ||||
|   } | ||||
| }) | ||||
|  | ||||
| }); | ||||
|  | ||||
| // start!! | ||||
| server.start().then(() => { | ||||
|   symlinkCommon() | ||||
|   console.log("服务启动!") | ||||
| }).catch((e) => { | ||||
|   console.log("服务异常", e) | ||||
| }) | ||||
| server | ||||
|   .start() | ||||
|   .then(() => { | ||||
|     symlinkCommon(); | ||||
|     console.log("服务启动!"); | ||||
|   }) | ||||
|   .catch((e) => { | ||||
|     console.log("服务异常", e); | ||||
|   }); | ||||
|   | ||||
| @@ -1,821 +0,0 @@ | ||||
| /** | ||||
|  * 类似于protobuffer,但此库及其精简且专为js打造 | ||||
|  * @author zp | ||||
|  * @version 1.1.0 | ||||
|  */ | ||||
|  | ||||
| /** | ||||
|  * [注] nodejs环境下通过Uint8Array将ArrayBuffer和Buffer互相转换 | ||||
|  * @example | ||||
|  * // Buffer ---> ArrayBuffer | ||||
|  * function toArrayBuffer(buf) { | ||||
|  *     var ab = new ArrayBuffer(buf.length); | ||||
|  *     var view = new Uint8Array(ab); | ||||
|  *     for (var i = 0; i < buf.length; ++i) { | ||||
|  *         view[i] = buf[i]; | ||||
|  *     } | ||||
|  *     return ab; | ||||
|  * } | ||||
|  * // ArrayBuffer ---> Buffer | ||||
|  * function toBuffer(ab) { | ||||
|  *     var buf = new Buffer(ab.byteLength); | ||||
|  *     var view = new Uint8Array(ab); | ||||
|  *     for (var i = 0; i < buf.length; ++i) { | ||||
|  *         buf[i] = view[i]; | ||||
|  *     } | ||||
|  *     return buf; | ||||
|  * } | ||||
|  */ | ||||
|  | ||||
| /** | ||||
|  * @example | ||||
|  * var { registerProto, Type, encode, decode, singleArray } = binary; | ||||
|  * registerProto(1001, { | ||||
|  *     name: Type.String, | ||||
|  *     age: Type.Uint8, | ||||
|  *     sex: Type.Uint8 | ||||
|  * }) | ||||
|  * registerProto(1002, { | ||||
|  *     info: 1001, | ||||
|  *     gold: Type.Uint16, | ||||
|  *     items: [Type.Uint16, Type.String] | ||||
|  * }) | ||||
|  * registerProto(1003, { | ||||
|  *     array0: Type.Array, | ||||
|  *     array1: singleArray(1002), | ||||
|  *     array2: singleArray([1001, 1002]), | ||||
|  *     array3: singleArray(Type.Uint16), | ||||
|  *     array4: singleArray([Type.Uint16, Type.String]) | ||||
|  * }) | ||||
|  | ||||
|  * var buffer = encode({ name: 'Mary', age: 18, sex: 0 }, 1001); | ||||
|  * decode(buffer); | ||||
|  | ||||
|  * var buffer = encode({ info: { name: 'Mary', age: 18, sex: 0 }, gold: 10, array: [100, 2, 3] }, 1002); | ||||
|  * decode(buffer); | ||||
|  | ||||
|  * var buffer = encode({ | ||||
|  *     array0: ['你好啊','我很好'], | ||||
|  *     array1: [{ info: { name: 'James', age: 30, sex: 1 }, gold: 10, array: [100, 2, 3] }], | ||||
|  *     array2: [[{}, { info: { name: 'Mary', age: 18, sex: 0 }, gold: 10, array: [100, 2, 3] }]], | ||||
|  *     array3: [568], | ||||
|  *     array4: [[0, '零'], [1, '一'], [2, '二'], [3, '三']] | ||||
|  * }, 1003); | ||||
|  * decode(buffer); | ||||
|  */ | ||||
|  | ||||
| /** | ||||
|  * https://segmentfault.com/a/1190000014533505 中提到如果服务器开启了压缩了话,需要进行解压操作,并推荐了pako.js | ||||
|  * (具体也不知道这个压缩怎么回事,测试的时候也没遇到这个问题,先写注释记下来) | ||||
|  * @see https://github.com/nodeca/pako/edit/master/dist/pako.js | ||||
|  * @example | ||||
|  * let compressdata = new Uint8Array(buffer, byteOff, length); | ||||
|  * let uncompress = pako.inflate(compressdata);//解压数据 | ||||
|  * let uncompressdata = uncompress.buffer;// ArrayBuffer {} | ||||
|  * let dataViewData = new DataView(uncompressdata, 0);//解压后数据 | ||||
|  */ | ||||
|  | ||||
|  | ||||
| /** | ||||
| * A minimal base64 implementation for number arrays. | ||||
| * @memberof util | ||||
| * @namespace | ||||
| */ | ||||
| class Base64 { | ||||
|     // Base64 encoding table | ||||
|     private b64 = new Array(64); | ||||
|     // Base64 decoding table | ||||
|     private s64 = new Array(123); | ||||
|     private invalidEncoding = "invalid encoding"; | ||||
|  | ||||
|     constructor() { | ||||
|         // 65..90, 97..122, 48..57, 43, 47 | ||||
|         for (var i = 0; i < 64;) { | ||||
|             this.s64[this.b64[i] = i < 26 ? i + 65 : i < 52 ? i + 71 : i < 62 ? i - 4 : i - 59 | 43] = i++; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Calculates the byte length of a base64 encoded string. | ||||
|      * @param {string} string Base64 encoded string | ||||
|      * @returns {number} Byte length | ||||
|      */ | ||||
|     length(string: string): number { | ||||
|         var p = string.length; | ||||
|         if (!p) | ||||
|             return 0; | ||||
|         var n = 0; | ||||
|         while (--p % 4 > 1 && string.charAt(p) === "=") | ||||
|             ++n; | ||||
|         return Math.ceil(string.length * 3) / 4 - n; | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Encodes a buffer to a base64 encoded string. | ||||
|      * @param {DataView} buffer Source buffer | ||||
|      * @param {number} start Source start | ||||
|      * @param {number} end Source end | ||||
|      * @returns {string} Base64 encoded string | ||||
|      */ | ||||
|     read(buffer: DataView, start: number, end: number): string { | ||||
|         var parts = null, | ||||
|             chunk = []; | ||||
|         var i = 0, // output index | ||||
|             j = 0, // goto index | ||||
|             t;     // temporary | ||||
|         while (start < end) { | ||||
|             var b = buffer.getUint8(start++); | ||||
|             switch (j) { | ||||
|                 case 0: | ||||
|                     chunk[i++] = this.b64[b >> 2]; | ||||
|                     t = (b & 3) << 4; | ||||
|                     j = 1; | ||||
|                     break; | ||||
|                 case 1: | ||||
|                     chunk[i++] = this.b64[t | b >> 4]; | ||||
|                     t = (b & 15) << 2; | ||||
|                     j = 2; | ||||
|                     break; | ||||
|                 case 2: | ||||
|                     chunk[i++] = this.b64[t | b >> 6]; | ||||
|                     chunk[i++] = this.b64[b & 63]; | ||||
|                     j = 0; | ||||
|                     break; | ||||
|             } | ||||
|             if (i > 8191) { | ||||
|                 (parts || (parts = [])).push(String.fromCharCode.apply(String, chunk)); | ||||
|                 i = 0; | ||||
|             } | ||||
|         } | ||||
|         if (j) { | ||||
|             chunk[i++] = this.b64[t]; | ||||
|             chunk[i++] = 61; | ||||
|             if (j === 1) | ||||
|                 chunk[i++] = 61; | ||||
|         } | ||||
|         if (parts) { | ||||
|             if (i) | ||||
|                 parts.push(String.fromCharCode.apply(String, chunk.slice(0, i))); | ||||
|             return parts.join(""); | ||||
|         } | ||||
|         return String.fromCharCode.apply(String, chunk.slice(0, i)); | ||||
|     }; | ||||
|  | ||||
|  | ||||
|     /** | ||||
|      * Decodes a base64 encoded string to a buffer. | ||||
|      * @param {string} string Source string | ||||
|      * @param {DataView} buffer Destination buffer | ||||
|      * @param {number} offset Destination offset | ||||
|      * @returns {number} Number of bytes written | ||||
|      * @throws {Error} If encoding is invalid | ||||
|      */ | ||||
|     write(string: string, buffer: DataView, offset: number): number { | ||||
|         var start = offset; | ||||
|         var j = 0, // goto index | ||||
|             t;     // temporary | ||||
|         for (var i = 0; i < string.length;) { | ||||
|             var c = string.charCodeAt(i++); | ||||
|             if (c === 61 && j > 1) | ||||
|                 break; | ||||
|             if ((c = this.s64[c]) === undefined) | ||||
|                 throw Error(this.invalidEncoding); | ||||
|             switch (j) { | ||||
|                 case 0: | ||||
|                     t = c; | ||||
|                     j = 1; | ||||
|                     break; | ||||
|                 case 1: | ||||
|                     buffer.setUint8(offset++, t << 2 | (c & 48) >> 4); | ||||
|                     t = c; | ||||
|                     j = 2; | ||||
|                     break; | ||||
|                 case 2: | ||||
|                     buffer.setUint8(offset++, (t & 15) << 4 | (c & 60) >> 2); | ||||
|                     t = c; | ||||
|                     j = 3; | ||||
|                     break; | ||||
|                 case 3: | ||||
|                     buffer.setUint8(offset++, (t & 3) << 6 | c); | ||||
|                     j = 0; | ||||
|                     break; | ||||
|             } | ||||
|         } | ||||
|         if (j === 1) | ||||
|             throw Error(this.invalidEncoding); | ||||
|         return offset - start; | ||||
|     }; | ||||
| } | ||||
|  | ||||
| const base64 = new Base64(); | ||||
|  | ||||
|  | ||||
| /** | ||||
| * A minimal UTF8 implementation for number arrays. | ||||
| * @memberof util | ||||
| * @namespace | ||||
| */ | ||||
| class UTF8 { | ||||
|     /** | ||||
|      * Calculates the UTF8 byte length of a string. | ||||
|      */ | ||||
|     length(string: string): number { | ||||
|         var len = 0, | ||||
|             c = 0; | ||||
|         for (var i = 0; i < string.length; ++i) { | ||||
|             c = string.charCodeAt(i); | ||||
|             if (c < 128) | ||||
|                 len += 1; | ||||
|             else if (c < 2048) | ||||
|                 len += 2; | ||||
|             else if ((c & 0xFC00) === 0xD800 && (string.charCodeAt(i + 1) & 0xFC00) === 0xDC00) { | ||||
|                 ++i; | ||||
|                 len += 4; | ||||
|             } else | ||||
|                 len += 3; | ||||
|         } | ||||
|         return len; | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Reads UTF8 bytes as a string. | ||||
|      */ | ||||
|     read(buffer: DataView, start: number, end: number): string { | ||||
|         var len = end - start; | ||||
|         if (len < 1) | ||||
|             return ""; | ||||
|         var parts = null, | ||||
|             chunk = [], | ||||
|             i = 0, // char offset | ||||
|             t;     // temporary | ||||
|         while (start < end) { | ||||
|             t = buffer.getUint8(start++); | ||||
|             if (t < 128) | ||||
|                 chunk[i++] = t; | ||||
|             else if (t > 191 && t < 224) | ||||
|                 chunk[i++] = (t & 31) << 6 | buffer.getUint8(start++) & 63; | ||||
|             else if (t > 239 && t < 365) { | ||||
|                 t = ((t & 7) << 18 | (buffer.getUint8(start++) & 63) << 12 | (buffer.getUint8(start++) & 63) << 6 | buffer.getUint8(start++) & 63) - 0x10000; | ||||
|                 chunk[i++] = 0xD800 + (t >> 10); | ||||
|                 chunk[i++] = 0xDC00 + (t & 1023); | ||||
|             } else | ||||
|                 chunk[i++] = (t & 15) << 12 | (buffer.getUint8(start++) & 63) << 6 | buffer.getUint8(start++) & 63; | ||||
|             if (i > 8191) { | ||||
|                 (parts || (parts = [])).push(String.fromCharCode.apply(String, chunk)); | ||||
|                 i = 0; | ||||
|             } | ||||
|         } | ||||
|         if (parts) { | ||||
|             if (i) | ||||
|                 parts.push(String.fromCharCode.apply(String, chunk.slice(0, i))); | ||||
|             return parts.join(""); | ||||
|         } | ||||
|         return String.fromCharCode.apply(String, chunk.slice(0, i)); | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Writes a string as UTF8 bytes. | ||||
|      */ | ||||
|     write(string: string, buffer: DataView, offset: number): number { | ||||
|         var start = offset, | ||||
|             c1, // character 1 | ||||
|             c2; // character 2 | ||||
|         for (var i = 0; i < string.length; ++i) { | ||||
|             c1 = string.charCodeAt(i); | ||||
|             if (c1 < 128) { | ||||
|                 buffer.setUint8(offset++, c1); | ||||
|             } else if (c1 < 2048) { | ||||
|                 buffer.setUint8(offset++, c1 >> 6 | 192); | ||||
|                 buffer.setUint8(offset++, c1 & 63 | 128); | ||||
|             } else if ((c1 & 0xFC00) === 0xD800 && ((c2 = string.charCodeAt(i + 1)) & 0xFC00) === 0xDC00) { | ||||
|                 c1 = 0x10000 + ((c1 & 0x03FF) << 10) + (c2 & 0x03FF); | ||||
|                 ++i; | ||||
|                 buffer.setUint8(offset++, c1 >> 18 | 240); | ||||
|                 buffer.setUint8(offset++, c1 >> 12 & 63 | 128); | ||||
|                 buffer.setUint8(offset++, c1 >> 6 & 63 | 128); | ||||
|                 buffer.setUint8(offset++, c1 & 63 | 128); | ||||
|             } else { | ||||
|                 buffer.setUint8(offset++, c1 >> 12 | 224); | ||||
|                 buffer.setUint8(offset++, c1 >> 6 & 63 | 128); | ||||
|                 buffer.setUint8(offset++, c1 & 63 | 128); | ||||
|             } | ||||
|         } | ||||
|         return offset - start; | ||||
|     }; | ||||
| } | ||||
|  | ||||
| const utf8 = new UTF8(); | ||||
|  | ||||
| class Encode { | ||||
|     private buffer: ArrayBuffer = null; | ||||
|     private view: DataView = null; | ||||
|     private index: number = 0; | ||||
|  | ||||
|     constructor(length: number) { | ||||
|         this.buffer = new ArrayBuffer(length) | ||||
|         this.view = new DataView(this.buffer); | ||||
|         this.index = 0; | ||||
|     } | ||||
|  | ||||
|     Int8(data: number) { | ||||
|         if (!isNumber(data)) data = 0; | ||||
|         return this.view.setInt8(this.index++, data); | ||||
|     } | ||||
|  | ||||
|     Uint8(data: number) { | ||||
|         if (!isNumber(data)) data = 0; | ||||
|         return this.view.setUint8(this.index++, data); | ||||
|     } | ||||
|  | ||||
|     Int16(data: number) { | ||||
|         if (!isNumber(data)) data = 0; | ||||
|         var value = this.view.setInt16(this.index, data); | ||||
|         this.index += 2; | ||||
|         return value; | ||||
|     } | ||||
|  | ||||
|     Uint16(data: number) { | ||||
|         if (!isNumber(data)) data = 0; | ||||
|         var value = this.view.setUint16(this.index, data); | ||||
|         this.index += 2; | ||||
|         return value; | ||||
|     } | ||||
|  | ||||
|     Int32(data: number) { | ||||
|         if (!isNumber(data)) data = 0; | ||||
|         var value = this.view.setInt32(this.index, data); | ||||
|         this.index += 4; | ||||
|         return value; | ||||
|     } | ||||
|  | ||||
|     Uint32(data: number) { | ||||
|         if (!isNumber(data)) data = 0; | ||||
|         var value = this.view.setUint32(this.index, data); | ||||
|         this.index += 4; | ||||
|         return value; | ||||
|     } | ||||
|  | ||||
|     Float32(data: number) { | ||||
|         if (!isNumber(data)) data = 0; | ||||
|         var value = this.view.setFloat32(this.index, data); | ||||
|         this.index += 4; | ||||
|         return value; | ||||
|     } | ||||
|  | ||||
|     Float64(data: number) { | ||||
|         if (!isNumber(data)) data = 0; | ||||
|         var value = this.view.setFloat64(this.index, data); | ||||
|         this.index += 8; | ||||
|         return value; | ||||
|     } | ||||
|  | ||||
|     Boolean(data) { | ||||
|         return this.Uint8(data ? 1 : 0); | ||||
|     } | ||||
|  | ||||
|     String(string) { | ||||
|         if (!isString(string)) string = ''; | ||||
|  | ||||
|         const len = utf8.write(string, this.view, this.index + 2); | ||||
|         this.Uint16(len); | ||||
|         this.index += len; | ||||
|     } | ||||
|  | ||||
|     Base64(string) { | ||||
|         if (!isBase64(string)) string = ''; | ||||
|  | ||||
|         const len = base64.write(string, this.view, this.index + 2); | ||||
|         this.Uint16(len); | ||||
|         this.index += len; | ||||
|     } | ||||
|  | ||||
|     Array(array) { | ||||
|         if (isArray(array) && !isEmpty(array)) { | ||||
|             return this.String(JSON.stringify(array)); | ||||
|         } else { | ||||
|             return this.String(''); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     Object(obj) { | ||||
|         if (isMap(obj) && !isEmpty(obj)) { | ||||
|             return this.String(JSON.stringify(obj)); | ||||
|         } else { | ||||
|             return this.String(''); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     Buffer() { | ||||
|         return this.buffer; | ||||
|     } | ||||
| } | ||||
|  | ||||
| class Decode { | ||||
|     private view: DataView = null; | ||||
|     private index: number = 0; | ||||
|  | ||||
|     constructor(buffer: ArrayBuffer) { | ||||
|         this.view = new DataView(buffer); | ||||
|         this.index = 0; | ||||
|     } | ||||
|  | ||||
|     Int8() { | ||||
|         return this.view.getInt8(this.index++); | ||||
|     } | ||||
|  | ||||
|     Uint8() { | ||||
|         return this.view.getUint8(this.index++); | ||||
|     } | ||||
|  | ||||
|     Int16() { | ||||
|         const value = this.view.getInt16(this.index); | ||||
|         this.index += 2; | ||||
|         return value; | ||||
|     } | ||||
|  | ||||
|     Uint16() { | ||||
|         const value = this.view.getUint16(this.index); | ||||
|         this.index += 2; | ||||
|         return value; | ||||
|     } | ||||
|  | ||||
|     Int32() { | ||||
|         const value = this.view.getInt32(this.index); | ||||
|         this.index += 4; | ||||
|         return value; | ||||
|     } | ||||
|  | ||||
|     Uint32() { | ||||
|         const value = this.view.getUint32(this.index); | ||||
|         this.index += 4; | ||||
|         return value; | ||||
|     } | ||||
|  | ||||
|     Float32() { | ||||
|         const value = this.view.getFloat32(this.index); | ||||
|         this.index += 4; | ||||
|         return value; | ||||
|     } | ||||
|  | ||||
|     Float64() { | ||||
|         const value = this.view.getFloat64(this.index); | ||||
|         this.index += 8; | ||||
|         return value; | ||||
|     } | ||||
|  | ||||
|     Boolean() { | ||||
|         return !!this.Uint8(); | ||||
|     } | ||||
|  | ||||
|     String() { | ||||
|         const len = this.Uint16(); | ||||
|         this.index += len; | ||||
|         return utf8.read(this.view, this.index - len, this.index); | ||||
|     } | ||||
|  | ||||
|     Base64() { | ||||
|         const len = this.Uint16(); | ||||
|         this.index += len; | ||||
|         return base64.read(this.view, this.index - len, this.index); | ||||
|     } | ||||
|  | ||||
|     Array() { | ||||
|         const str = this.String(); | ||||
|         return str ? JSON.parse(str) : []; | ||||
|     } | ||||
|  | ||||
|     Object() { | ||||
|         const str = this.String(); | ||||
|         return str ? JSON.parse(str) : {}; | ||||
|     } | ||||
| } | ||||
|  | ||||
| const getType = function (param) { | ||||
|     return Object.prototype.toString.call(param).slice(8, -1).toLowerCase(); | ||||
| } | ||||
|  | ||||
| const isObject = function (param) { | ||||
|     return param && typeof param === 'object'; | ||||
| } | ||||
|  | ||||
| const isArray = function (param) { | ||||
|     return getType(param) === 'array'; | ||||
| } | ||||
|  | ||||
| const isMap = function (param) { | ||||
|     return getType(param) === 'object'; | ||||
| } | ||||
|  | ||||
| const isString = function (param) { | ||||
|     return getType(param) === 'string'; | ||||
| } | ||||
|  | ||||
| const isNumber = function (param) { | ||||
|     return getType(param) === 'number'; | ||||
| } | ||||
|  | ||||
| const isBoolean = function (param) { | ||||
|     return getType(param) === 'boolean'; | ||||
| } | ||||
|  | ||||
| const isBase64 = function (param) { | ||||
|     return isString(param) && /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(param); | ||||
| } | ||||
|  | ||||
| function stringStartsWith(str1: string, str2: string) { | ||||
|     if (str1 === str2) { | ||||
|         return true; | ||||
|     } | ||||
|     for (let index = 0; index < str2.length; index++) { | ||||
|         if (str1[index] !== str2[index]) { | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| function isEmpty(obj) { | ||||
|     if (isArray(obj)) { | ||||
|         return !obj.length; | ||||
|     } else if (isMap(obj)) { | ||||
|         for (const key in obj) { | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| function compareStr(str1: string, str2: string) { | ||||
|     if (str1 === str2) { | ||||
|         return 0; | ||||
|     } | ||||
|     if (str1.length > str2.length) { | ||||
|         return 1; | ||||
|     } | ||||
|     if (str1.length < str2.length) { | ||||
|         return -1; | ||||
|     } | ||||
|  | ||||
|     for (let i = 0, code1 = 0, code2 = 0; i < str1.length; i++) { | ||||
|         if (str2.length <= i) { | ||||
|             return 1; | ||||
|         } else { | ||||
|             code1 = str1.charCodeAt(i); | ||||
|             code2 = str2.charCodeAt(i); | ||||
|             if (code1 > code2) { | ||||
|                 return 1; | ||||
|             } else if (code1 < code2) { | ||||
|                 return -1; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
| function sortKeys(obj) { | ||||
|     if (isMap(obj)) { | ||||
|         let index = 0; | ||||
|         const keys: string[] = []; | ||||
|         for (const key in obj) { | ||||
|             for (index = keys.length - 1; index >= 0; index--) { | ||||
|                 if (compareStr(key, keys[index]) >= 0) { | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|             if (index === keys.length - 1) { | ||||
|                 keys.push(key); | ||||
|             } else { | ||||
|                 keys.splice(index + 1, 0, key); | ||||
|             } | ||||
|         } | ||||
|         return keys; | ||||
|     } else if (isArray(obj)) { | ||||
|         return obj.map(function (v, k) { | ||||
|             return k; | ||||
|         }) | ||||
|     } | ||||
|  | ||||
|     return []; | ||||
| } | ||||
|  | ||||
| function realType(type) { | ||||
|     if (isObject(type)) { | ||||
|         return type; | ||||
|     } | ||||
|     return protoCache[type] || type; | ||||
| } | ||||
|  | ||||
| const singleArrayPrefix = 'SingleArray'; | ||||
|  | ||||
| function isSingleArray(str: string) { | ||||
|     return isString(str) && stringStartsWith(str, singleArrayPrefix); | ||||
| } | ||||
|  | ||||
| function SingleArrayProto(str: string) { | ||||
|     const stringify = str.slice(singleArrayPrefix.length + 1, -1); | ||||
|     return JSON.parse(stringify); | ||||
| } | ||||
|  | ||||
| /** | ||||
| * 标记单一类型的数组 | ||||
| * @param proto  | ||||
| */ | ||||
| export const singleArray = function (proto) { | ||||
|     return `${singleArrayPrefix}(${JSON.stringify(proto)})`; | ||||
| } | ||||
|  | ||||
| function DataLen(data: any, proto: any) { | ||||
|     proto = realType(proto); | ||||
|  | ||||
|     let length = 0; | ||||
|     if (isMap(proto)) { | ||||
|         if (!isMap(data)) data = {}; | ||||
|         for (const key in proto) { | ||||
|             length += DataLen(data[key], proto[key]); | ||||
|         } | ||||
|     } else if (isArray(proto)) { | ||||
|         if (!isArray(data)) data = []; | ||||
|         proto.forEach(function (type, index) { | ||||
|             length += DataLen(data[index], type); | ||||
|         }) | ||||
|     } else if (proto === 'String') { | ||||
|         // 如果是String的话,固定开头有2字节记录字符串长度 | ||||
|         length += 2; | ||||
|         if (isString(data)) length += utf8.length(data); | ||||
|     } else if (proto === 'Object' || proto === 'Array') { | ||||
|         // Object和Array类型也会将数据通过JSON.stringify转成String格式 | ||||
|         length += 2; | ||||
|         if (!isEmpty(data)) length += utf8.length(JSON.stringify(data)); | ||||
|     } else if (proto === 'Base64') { | ||||
|         // 如果是Base64的话,固定开头有2字节记录字符串长度 | ||||
|         length += 2; | ||||
|         if (isBase64(data)) length += base64.length(data); | ||||
|     } else if (isSingleArray(proto)) { | ||||
|         // 如果是SingleArray的话,固定开头有2字节记录数组长度 | ||||
|         length += 2; | ||||
|         if (!isArray(data)) data = []; | ||||
|         proto = realType(SingleArrayProto(proto)); | ||||
|         data.forEach(function (value) { | ||||
|             length += DataLen(value, proto); | ||||
|         }) | ||||
|     } else if (TypeByte[proto]) { | ||||
|         length += TypeByte[proto]; | ||||
|     } else { | ||||
|         throw new Error("'proto' is bad"); | ||||
|     } | ||||
|  | ||||
|     return length; | ||||
| } | ||||
|  | ||||
| function encodeData(encode: Encode, data: any, proto: any) { | ||||
|     proto = realType(proto); | ||||
|  | ||||
|     if (isMap(proto)) { | ||||
|         if (!isMap(data)) data = {}; | ||||
|         sortKeys(proto).forEach(function (key) { | ||||
|             encodeData(encode, data[key], proto[key]); | ||||
|         }) | ||||
|     } else if (isArray(proto)) { | ||||
|         if (!isArray(data)) data = []; | ||||
|         proto.forEach(function (type, index) { | ||||
|             encodeData(encode, data[index], type); | ||||
|         }) | ||||
|     } else if (isSingleArray(proto)) { | ||||
|         if (!isArray(data)) data = []; | ||||
|         encode.Uint16(data.length); | ||||
|         proto = realType(SingleArrayProto(proto)); | ||||
|         data.forEach(function (value) { | ||||
|             encodeData(encode, value, proto); | ||||
|         }) | ||||
|     } else { | ||||
|         encode[proto](data); | ||||
|     } | ||||
| } | ||||
|  | ||||
| function decodeData(decode: Decode, proto: any) { | ||||
|     proto = realType(proto); | ||||
|  | ||||
|     if (isMap(proto)) { | ||||
|         const obj = {}; | ||||
|         sortKeys(proto).forEach(function (key) { | ||||
|             obj[key] = decodeData(decode, proto[key]); | ||||
|         }); | ||||
|         return obj; | ||||
|     } else if (isArray(proto)) { | ||||
|         return proto.map(function (type) { | ||||
|             return decodeData(decode, type); | ||||
|         }); | ||||
|     } else if (isSingleArray(proto)) { | ||||
|         const arr = []; | ||||
|         const len = decode.Uint16(); | ||||
|         proto = realType(SingleArrayProto(proto)); | ||||
|         for (let index = 0; index < len; index++) { | ||||
|             arr.push(decodeData(decode, proto)); | ||||
|         } | ||||
|         return arr; | ||||
|     } else { | ||||
|         return decode[proto](); | ||||
|     } | ||||
| } | ||||
|  | ||||
| const TypeByte = { | ||||
|     'Int8': 1, | ||||
|     'Uint8': 1, | ||||
|     'Int16': 2, | ||||
|     'Uint16': 2, | ||||
|     'Int32': 4, | ||||
|     'Uint32': 4, | ||||
|     'Float32': 4, | ||||
|     'Float64': 8, | ||||
|     'BigInt64': 8, | ||||
|     'BigUint64': 8, | ||||
|     'Boolean': 1, | ||||
|     'String': 1, | ||||
|     'Base64': 1, | ||||
|     'Array': 1, | ||||
|     'Object': 1 | ||||
| } | ||||
|  | ||||
| export const Type = { | ||||
|     'Int8': 'Int8',                 // 1byte  -128 to 127 | ||||
|     'Uint8': 'Uint8',               // 1byte  0 to 255 | ||||
|     'Uint8Clamped': 'Uint8',        // 1byte  0 to 255 | ||||
|     'Int16': 'Int16',               // 2byte  -32768 to 32767 | ||||
|     'Uint16': 'Uint16',             // 2byte  0 to 65535 | ||||
|     'Int32': 'Int32',               // 4byte  -2147483648 to 2147483647 | ||||
|     'Uint32': 'Uint32',             // 4byte  0 to 4294967295 | ||||
|     'Float32': 'Float32',           // 4byte  1.2x10^-38 to 3.4x10^38 | ||||
|     'Float64': 'Float64',           // 8byte  5.0x10^-324 to 1.8x10^308 | ||||
|     'BigInt64': 'BigInt64',         // 8byte  -2^63 to (2^63)-1 | ||||
|     'BigUint64': 'BigUint64',       // 8byte  0 to (2^64)-1 | ||||
|     'Boolean': 'Boolean',           // 1byte  0 to 255 | ||||
|     'String': 'String',             // 1byte  0 to 255 | ||||
|     'Base64': 'Base64',             // 1byte  0 to 255 | ||||
|     'Array': 'Array',               // 1byte  0 to 255 | ||||
|     'Object': 'Object'              // 1byte  0 to 255 | ||||
| } | ||||
|  | ||||
| /** | ||||
| * 序列化 | ||||
| * 开头2字节用来存储proto的id | ||||
| */ | ||||
| export const encode = function (obj: Object, id: number | string) { | ||||
|     const proto = protoCache[id]; | ||||
|     if (proto) { | ||||
|         const len = DataLen(obj, proto); | ||||
|         const encode = new Encode(len + 2); | ||||
|         encode.Uint16(Number(id)); | ||||
|         encodeData(encode, obj, proto); | ||||
|         return encode.Buffer(); | ||||
|     } else { | ||||
|         throw new Error("encode error: 'id' is bad"); | ||||
|     } | ||||
| } | ||||
|  | ||||
| /** | ||||
| * 反序列化 | ||||
| * 开头2字节代表proto的id | ||||
| */ | ||||
| export const decode = function (buffer: ArrayBuffer) { | ||||
|     const decode = new Decode(buffer); | ||||
|     const id = decode.Uint16(); | ||||
|     const proto = protoCache[id]; | ||||
|     if (proto) { | ||||
|         return decodeData(decode, proto); | ||||
|     } else { | ||||
|         throw new Error("decode error: 'buffer' is bad"); | ||||
|     } | ||||
| } | ||||
|  | ||||
| /** | ||||
| * proto缓存 | ||||
| */ | ||||
| const protoCache = {} | ||||
|  | ||||
| /** | ||||
| * 注册proto | ||||
| * id: 必须是个正整数(或正整数字符串), 取值范围[0,65535] | ||||
| */ | ||||
| export const registerProto = function (id: number | string, proto: any) { | ||||
|     if (typeof id === 'string') id = Number(id); | ||||
|  | ||||
|     if (isNumber(id) && Math.floor(id) === id && id >= 0 && id <= 65535 && !Type[id]) { | ||||
|         protoCache[id] = proto; | ||||
|     } else { | ||||
|         throw new Error("registerProto error: 'id' is bad"); | ||||
|     } | ||||
| } | ||||
|  | ||||
| export const registerProtoMap = function (protoMap: any) { | ||||
|     if (isMap(protoMap)) { | ||||
|         for (const id in protoMap) { | ||||
|             registerProto(id, protoMap[id]); | ||||
|         } | ||||
|     } else { | ||||
|         throw new Error("registerProtoMap error: 'protoMap' is bad"); | ||||
|     } | ||||
| } | ||||
|  | ||||
| export const protoToJson = function () { | ||||
|     return JSON.stringify(protoCache); | ||||
| } | ||||
| @@ -1,402 +0,0 @@ | ||||
| /** | ||||
|  * @author zp | ||||
|  */ | ||||
| /** | ||||
|  * 多平台一致精确计算库,比decimal更轻量更快 | ||||
|  * [Math.round、Math.min、Math.max,Math.floor、Math.ceil,这些系统方法一般情况下是可以放心使用的] | ||||
|  */ | ||||
|  const exactMath = Object.create(null); | ||||
|  module.exports = exactMath; | ||||
|   | ||||
|  // 计算精度 | ||||
|  // sin、cos、tan方法的误差小数点后16位 | ||||
|  const ACCURACY_SIN_ERROR = 1e-16; | ||||
|  const ACCURACY_COS_ERROR = 1e-16; | ||||
|  const ACCURACY_TAN_ERROR = 1e-16; | ||||
|   | ||||
|  // 角度弧度常量 | ||||
|  const DEG = 57.29577951308232; | ||||
|  const RAD = 0.017453292519943295; | ||||
|   | ||||
|  // 系统常量 | ||||
|  exactMath.PI = 3.141592653589793; | ||||
|  exactMath.E = 2.718281828459045; | ||||
|  exactMath.LN2 = 0.6931471805599453; | ||||
|  exactMath.LN10 = 2.302585092994046; | ||||
|  exactMath.LOG2E = 1.4426950408889634; | ||||
|  exactMath.LOG10E = 0.4342944819032518; | ||||
|  exactMath.SQRT1_2 = 0.7071067811865476; | ||||
|  exactMath.SQRT2 = 1.4142135623730951; | ||||
|   | ||||
|  /** | ||||
|   * 链式调用 | ||||
|   * @example | ||||
|   * const value = exactMath.value(10).add(20.123).mul(2).sqrt().value; | ||||
|   */ | ||||
|  let chain = null; | ||||
|  exactMath.value = function (value) { | ||||
|      if (!chain) { | ||||
|          chain = { | ||||
|              value: 0, | ||||
|              valueOf() { return this.value; }, | ||||
|              toString() { return String(this.value); } | ||||
|          } | ||||
|          for (const key in exactMath) { | ||||
|              if (key !== 'value' && typeof exactMath[key] === 'function') { | ||||
|                  chain[key] = function (...args) { | ||||
|                      this.value = exactMath[key].call(exactMath, this.value, ...args); | ||||
|                      return this; | ||||
|                  } | ||||
|              } | ||||
|          } | ||||
|      } | ||||
|   | ||||
|      chain.value = value; | ||||
|      return chain; | ||||
|  } | ||||
|   | ||||
|  /****************************************************基础****************************************************/ | ||||
|  /** | ||||
|   * 获得小数位数 | ||||
|   * @param {Number} num 浮点数 | ||||
|   * @returns {Number} | ||||
|   */ | ||||
|  exactMath.getDecimalPlace = function (num) { | ||||
|      if (num && num !== Math.floor(num)) { | ||||
|          for (let n = 1, m = 10, temp = 0; n < 20; n += 1, m *= 10) { | ||||
|              temp = num * m; | ||||
|              if (temp == Math.floor(temp)) return n; | ||||
|          } | ||||
|          return 20; | ||||
|      } else { | ||||
|          return 0; | ||||
|      } | ||||
|  } | ||||
|  /** | ||||
|   * 保留n为小数,并四舍五入 | ||||
|   * @example | ||||
|   * (2.335).toFixed(2) | ||||
|   * exactMath.toFixed(2.335, 2) | ||||
|   * @param {Number} num 浮点数 | ||||
|   * @param {Number} n 整数 | ||||
|   * @returns {Number} | ||||
|   */ | ||||
|  exactMath.toFixed = function (num, n = 0) { | ||||
|      if (n == 0) { | ||||
|          return Math.round(num); | ||||
|      } else { | ||||
|          const m = Math.pow(10, n); | ||||
|          return Math.round(num * (m * 10) / 10) / m; | ||||
|      } | ||||
|  } | ||||
|   | ||||
|  exactMath.abs = function (x) { | ||||
|      return Math.abs(x); | ||||
|  } | ||||
|  exactMath.round = function (x) { | ||||
|      return Math.round(x); | ||||
|  } | ||||
|  exactMath.ceil = function (x) { | ||||
|      return Math.ceil(x) | ||||
|  } | ||||
|  exactMath.floor = function (x) { | ||||
|      return Math.floor(x) | ||||
|  } | ||||
|  exactMath.min = function (...args) { | ||||
|      return Math.min(...args); | ||||
|  } | ||||
|  exactMath.max = function (...args) { | ||||
|      return Math.max(...args); | ||||
|  } | ||||
|   | ||||
|  /** | ||||
|   * 小数相加 | ||||
|   * @param {Number} num1 浮点数 | ||||
|   * @param {Number} num2 浮点数 | ||||
|   * @returns {Number} | ||||
|   */ | ||||
|  exactMath.add = function (...args) { | ||||
|      if (args.length === 2) { | ||||
|          const num1 = args[0]; | ||||
|          const num2 = args[1]; | ||||
|          const m = Math.pow(10, Math.max(this.getDecimalPlace(num1), this.getDecimalPlace(num2))); | ||||
|          return (this.toFixed(num1 * m) + this.toFixed(num2 * m)) / m; | ||||
|      } else { | ||||
|          return args.reduce((a, b) => this.add(a, b)) | ||||
|      } | ||||
|  }; | ||||
|  /** | ||||
|   * 小数相减 | ||||
|   * @param {Number} num1 浮点数 | ||||
|   * @param {Number} num2 浮点数 | ||||
|   * @returns {Number} | ||||
|   */ | ||||
|  exactMath.sub = function (...args) { | ||||
|      if (args.length === 2) { | ||||
|          const num1 = args[0]; | ||||
|          const num2 = args[1]; | ||||
|          const m = Math.pow(10, Math.max(this.getDecimalPlace(num1), this.getDecimalPlace(num2))); | ||||
|   | ||||
|          return (this.toFixed(num1 * m) - this.toFixed(num2 * m)) / m; | ||||
|      } else { | ||||
|          return args.reduce((a, b) => this.sub(a, b)) | ||||
|      } | ||||
|  }; | ||||
|  /** | ||||
|   * 小数相乘 | ||||
|   * @param {Number} num1 浮点数 | ||||
|   * @param {Number} num2 浮点数 | ||||
|   * @returns {Number} | ||||
|   */ | ||||
|  exactMath.mul = function (...args) { | ||||
|      if (args.length === 2) { | ||||
|          let num1 = args[0]; | ||||
|          let num2 = args[1]; | ||||
|   | ||||
|          // 方案1: | ||||
|          // 直接相乘,但是相乘两数小数点过多会导致中间值[(n1 * m1) * (n2 * m2)]过大 | ||||
|          // const n1 = this.getDecimalPlace(num1); | ||||
|          // const n2 = this.getDecimalPlace(num2); | ||||
|          // const m1 = Math.pow(10, n1); | ||||
|          // const m2 = Math.pow(10, n2); | ||||
|          // return (n1 * m1) * (n2 * m2) / (m1 * m2); | ||||
|   | ||||
|          // 方案2: | ||||
|          // 用除法实现乘法,不会存在过大中间值 | ||||
|          let n1 = this.getDecimalPlace(num1); | ||||
|          let n2 = this.getDecimalPlace(num2); | ||||
|   | ||||
|          let m = Math.pow(10, n2); | ||||
|          num2 = m / this.toFixed(num2 * m); | ||||
|   | ||||
|          m = Math.pow(10, Math.max(n1, this.getDecimalPlace(num2))); | ||||
|          m = this.toFixed(num1 * m) / this.toFixed(num2 * m); | ||||
|   | ||||
|          let n = Math.min(this.getDecimalPlace(m), n1 + n2); | ||||
|          return this.toFixed(m, n); | ||||
|      } else { | ||||
|          return args.reduce((a, b) => this.mul(a, b)) | ||||
|      } | ||||
|  }; | ||||
|  /** | ||||
|   * 小数相除法 | ||||
|   * @param {Number} num1 浮点数 | ||||
|   * @param {Number} num2 浮点数 | ||||
|   * @returns {Number} | ||||
|   */ | ||||
|  exactMath.div = function (...args) { | ||||
|      if (args.length === 2) { | ||||
|          const num1 = args[0]; | ||||
|          const num2 = args[1]; | ||||
|   | ||||
|          const m = Math.pow(10, Math.max(this.getDecimalPlace(num1), this.getDecimalPlace(num2))); | ||||
|          return this.toFixed(num1 * m) / this.toFixed(num2 * m); | ||||
|      } else { | ||||
|          return args.reduce((a, b) => this.div(a, b)) | ||||
|      } | ||||
|  }; | ||||
|  /** | ||||
|   * 取余 | ||||
|   * @param {Number} num1 浮点数 | ||||
|   * @param {Number} num2 浮点数 | ||||
|   * @returns {Number} | ||||
|   */ | ||||
|  exactMath.rem = function (...args) { | ||||
|      if (args.length === 2) { | ||||
|          const num1 = args[0]; | ||||
|          const num2 = args[1]; | ||||
|          const m = Math.pow(10, Math.max(this.getDecimalPlace(num1), this.getDecimalPlace(num2))); | ||||
|   | ||||
|          return this.toFixed(num1 * m) % this.toFixed(num2 * m) / m; | ||||
|      } else { | ||||
|          return args.reduce((a, b) => this.rem(a, b)) | ||||
|      } | ||||
|  }; | ||||
|   | ||||
|  /** | ||||
|   * n次方,仅支持整数次方(正负都可以) | ||||
|   * @param {Number} num 浮点数 | ||||
|   * @param {Number} n 整数 | ||||
|   */ | ||||
|  exactMath.pow = function (num, n) { | ||||
|      if (num == 0 && n == 0) { | ||||
|          return 1; | ||||
|      } | ||||
|      if (num == 0 && n > 0) { | ||||
|          return 0 | ||||
|      } | ||||
|      if (num == 0 && n < 0) { | ||||
|          return Infinity; | ||||
|      } | ||||
|      // num为负数,n为负小数,返回NaN | ||||
|      if (num < 0 && n < 0 && Math.round(n) != n) { | ||||
|          return NaN; | ||||
|      } | ||||
|   | ||||
|      if (Math.round(n) != n) { | ||||
|          throw new Error('n must be an integer'); | ||||
|      } | ||||
|   | ||||
|      let result = 1; | ||||
|   | ||||
|      if (n > 0) { | ||||
|          for (let index = 0; index < n; index++) { | ||||
|              result = this.mul(result, num); | ||||
|          } | ||||
|      } else if (n < 0) { | ||||
|          for (let index = 0, len = Math.abs(n); index < len; index++) { | ||||
|              result = this.div(result, num); | ||||
|          } | ||||
|      } | ||||
|   | ||||
|      return result; | ||||
|  }; | ||||
|  /** | ||||
|   * 开方运算【牛顿迭代法】 | ||||
|   *  | ||||
|   * @param {Number} n | ||||
|   * @returns  | ||||
|   */ | ||||
|  exactMath.sqrt = function (n) { | ||||
|      if (n < 0) return NaN; | ||||
|      if (n === 0) return 0; | ||||
|      if (n === 1) return 1; | ||||
|      let last = 0; | ||||
|      let res = 1; | ||||
|      let c = 50; | ||||
|      while (res != last && --c >= 0) { | ||||
|          last = res; | ||||
|          res = this.div(this.add(res, this.div(n, res)), 2) | ||||
|      } | ||||
|      return res; | ||||
|   | ||||
|      // float InvSqrt(float x) | ||||
|      // { | ||||
|      //     float xhalf = 0.5f * x; | ||||
|      //     int i = * (int *) & x; // get bits for floating VALUE  | ||||
|      //     i = 0x5f375a86 - (i >> 1); // gives initial guess y0 | ||||
|      //     x = * (float *) & i; // convert bits BACK to float | ||||
|      //     x = x * (1.5f - xhalf * x * x); // Newton step, repeating increases accuracy | ||||
|      //     x = x * (1.5f - xhalf * x * x); // Newton step, repeating increases accuracy | ||||
|      //     x = x * (1.5f - xhalf * x * x); // Newton step, repeating increases accuracy | ||||
|      //     return 1 / x; | ||||
|      // } | ||||
|  }; | ||||
|   | ||||
|  /****************************************************随机****************************************************/ | ||||
|  function getSeed(seed) { | ||||
|      if (isNaN(seed)) { | ||||
|          seed = Math.floor(Math.random() * 233280); | ||||
|      } else { | ||||
|          seed = Math.floor(seed % 233280); | ||||
|      } | ||||
|      return seed; | ||||
|  } | ||||
|   | ||||
|  let randomSeed = getSeed(); | ||||
|   | ||||
|  /** | ||||
|   * 设置随机种子 | ||||
|   */ | ||||
|  exactMath.setSeed = function (seed) { | ||||
|      randomSeed = getSeed(seed); | ||||
|  }; | ||||
|   | ||||
|  /** | ||||
|   * 随机 | ||||
|   */ | ||||
|  exactMath.random = function () { | ||||
|      randomSeed = (randomSeed * 9301 + 49297) % 233280; | ||||
|      return randomSeed / 233280.0; | ||||
|  }; | ||||
|   | ||||
|  /** | ||||
|   * 根据随机种子随机 | ||||
|   * @param {number} seed | ||||
|   */ | ||||
|  exactMath.randomBySeed = function (seed) { | ||||
|      seed = getSeed(seed); | ||||
|      seed = (seed * 9301 + 49297) % 233280; | ||||
|      return seed / 233280.0; | ||||
|  }; | ||||
|   | ||||
|  /****************************************************角度弧度转换****************************************************/ | ||||
|  /** | ||||
|   * 弧度数转角度数 | ||||
|   * @param {Number} radians 浮点数 | ||||
|   * @returns {Numbe} 浮点数 | ||||
|   */ | ||||
|  exactMath.radiansToDegrees = function (radians) { | ||||
|      return this.div(radians, RAD); | ||||
|  }; | ||||
|  /** | ||||
|   * 角度数转弧度数 | ||||
|   * @param {Number} degrees 浮点数 | ||||
|   * @returns {Numbe} 浮点数 | ||||
|   */ | ||||
|  exactMath.degreesToRadians = function (degrees) { | ||||
|      return this.div(degrees, DEG); | ||||
|  }; | ||||
|  /** | ||||
|   * 将角度值转换到[0, 360)范围内 | ||||
|   * @param {Number} angle 浮点数 | ||||
|   * @returns {Number} 整数 | ||||
|   */ | ||||
|  exactMath.get0To360Angle = function (angle) { | ||||
|      if (angle === 0) { | ||||
|          return 0; | ||||
|      } else if (angle < 0) { | ||||
|          return this.add(this.rem(angle, 360), 360); | ||||
|      } else { | ||||
|          return this.rem(angle, 360); | ||||
|      } | ||||
|  }; | ||||
|  /****************************************************三角函数****************************************************/ | ||||
|  /** | ||||
|   * 查表 | ||||
|   */ | ||||
|  exactMath._sin = {}; | ||||
|  exactMath._cos = {}; | ||||
|  exactMath._tan = {}; | ||||
|   | ||||
|  /** | ||||
|   * 3个三角函数,根据需求自行添加 | ||||
|   * 为了效率,应该尽量使用查表法 | ||||
|   * 表内查不到的,目前使用系统方法的结果并取前4位小数 | ||||
|   */ | ||||
|  exactMath.sin = function (x) { | ||||
|      if (this._sin.hasOwnProperty(x)) { | ||||
|          return this._sin[x]; | ||||
|      } | ||||
|   | ||||
|      // if (x == 0) { | ||||
|      //     return 0; | ||||
|      // } else if (x == 90) { | ||||
|      //     return 1; | ||||
|      // } | ||||
|   | ||||
|      // let n = x, sum = 0, i = 1; | ||||
|      // do { | ||||
|      //     i++; | ||||
|      //     sum = this.add(sum, n); | ||||
|      //     // n = -n * x * x / (2 * i - 1) / (2 * i - 2); | ||||
|      //     n = this.div(this.mul(-1, n, x, x), this.sub(this.mul(2, i), 1), this.sub(this.mul(2, i), 2)); | ||||
|      // } while (Math.abs(n) >= ACCURACY_SIN_ERROR); | ||||
|      // return sum; | ||||
|   | ||||
|      return this.toFixed(Math.sin(x), 4); | ||||
|  }; | ||||
|  exactMath.cos = function (x) { | ||||
|      if (this._cos.hasOwnProperty(x)) { | ||||
|          return this._cos[x]; | ||||
|      } | ||||
|   | ||||
|      return this.toFixed(Math.cos(x), 4); | ||||
|  }; | ||||
|  exactMath.tan = function (x) { | ||||
|      if (this._tan.hasOwnProperty(x)) { | ||||
|          return this._tan[x]; | ||||
|      } | ||||
|   | ||||
|      return this.toFixed(Math.tan(x), 4); | ||||
|  }; | ||||
| @@ -1,46 +1,54 @@ | ||||
| import fs from 'fs-extra' | ||||
| import path from 'path' | ||||
| import fs from "fs-extra"; | ||||
| import path from "path"; | ||||
|  | ||||
| export const getTime = () => new Date().toLocaleString().split("├")[0] | ||||
| export const getTime = () => new Date().toLocaleString().split("├")[0]; | ||||
|  | ||||
| //symlink同步 | ||||
| export const symlinkCommon = async () => { | ||||
|   const src = path.resolve(__dirname, '../Common') | ||||
|   const dst = path.resolve(__dirname, '../../../client/assets/Scripts/Common') | ||||
|   const src = path.resolve(__dirname, "../Common"); | ||||
|   const dst = path.resolve(__dirname, "../../../client/assets/Scripts/Common"); | ||||
|  | ||||
|   if (await fs.lstat(dst).then(v => v.isSymbolicLink()).catch(() => false) && await fs.readlink(dst) === src) { | ||||
|     console.log('同步成功!') | ||||
|   if ( | ||||
|     (await fs | ||||
|       .lstat(dst) | ||||
|       .then((v) => v.isSymbolicLink()) | ||||
|       .catch(() => false)) && | ||||
|     (await fs.readlink(dst)) === src | ||||
|   ) { | ||||
|     console.log("同步成功!"); | ||||
|   } else { | ||||
|     fs.symlink(src, dst).then(() => { | ||||
|       console.log('同步成功!') | ||||
|     }).catch((e) => { | ||||
|       console.log('同步失败!', e) | ||||
|     }) | ||||
|     fs.symlink(src, dst) | ||||
|       .then(() => { | ||||
|         console.log("同步成功!"); | ||||
|       }) | ||||
|       .catch((e) => { | ||||
|         console.log("同步失败!", e); | ||||
|       }); | ||||
|   } | ||||
| } | ||||
| }; | ||||
|  | ||||
| //copy同步 | ||||
| export const copyCommon = async () => { | ||||
|   const src = path.resolve(__dirname, '../Common') | ||||
|   const dst = path.resolve(__dirname, '../../../client/assets/Scripts/Common') | ||||
|   const src = path.resolve(__dirname, "../Common"); | ||||
|   const dst = path.resolve(__dirname, "../../../client/assets/Scripts/Common"); | ||||
|   console.log(src, dst); | ||||
|  | ||||
|   // clean | ||||
|   await fs.remove(dst) | ||||
|   await fs.remove(dst); | ||||
|  | ||||
|   //create | ||||
|   await fs.ensureDir(dst) | ||||
|   await fs.ensureDir(dst); | ||||
|  | ||||
|   // copy | ||||
|   await fs.copy(src, dst) | ||||
|   console.log('同步成功!') | ||||
| } | ||||
|   await fs.copy(src, dst); | ||||
|   console.log("同步成功!"); | ||||
| }; | ||||
|  | ||||
| export const toArrayBuffer = (buffer: Buffer) => { | ||||
| export const buffer2ArrayBuffer = (buffer: Buffer) => { | ||||
|   var ab = new ArrayBuffer(buffer.length); | ||||
|   var view = new Uint8Array(ab); | ||||
|   for (var i = 0; i < buffer.length; ++i) { | ||||
|     view[i] = buffer[i]; | ||||
|   } | ||||
|   return ab; | ||||
| } | ||||
| }; | ||||
|   | ||||
| @@ -7,8 +7,6 @@ | ||||
|     "apps/*" | ||||
|   ], | ||||
|   "scripts": { | ||||
|     "lint": "eslint .", | ||||
|     "lint:fix": "eslint . --fix", | ||||
|     "dev": "yarn workspace @game/server run dev" | ||||
|   } | ||||
| } | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user