update
This commit is contained in:
parent
29104d4bed
commit
81a9a5a2f8
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user