This commit is contained in:
sli97 2022-12-08 21:14:02 +08:00
parent 29104d4bed
commit 81a9a5a2f8
51 changed files with 1694 additions and 2815 deletions

View File

@ -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"
}
}

View File

@ -1887,7 +1887,7 @@
}, },
"component": "", "component": "",
"_componentId": "a14b40zxfhFXKk12/1/OrYr", "_componentId": "a14b40zxfhFXKk12/1/OrYr",
"handler": "createRoom", "handler": "handleCreateRoom",
"customEventData": "" "customEventData": ""
}, },
{ {

View File

@ -1255,7 +1255,7 @@
} }
], ],
"_interactable": true, "_interactable": true,
"_transition": 0, "_transition": 3,
"_normalColor": { "_normalColor": {
"__type__": "cc.Color", "__type__": "cc.Color",
"r": 214, "r": 214,
@ -1301,7 +1301,7 @@
"__expectedType__": "cc.SpriteFrame" "__expectedType__": "cc.SpriteFrame"
}, },
"_duration": 0.1, "_duration": 0.1,
"_zoomScale": 1.2, "_zoomScale": 0.9,
"_target": { "_target": {
"__id__": 30 "__id__": 30
}, },
@ -1549,7 +1549,7 @@
} }
], ],
"_interactable": true, "_interactable": true,
"_transition": 0, "_transition": 3,
"_normalColor": { "_normalColor": {
"__type__": "cc.Color", "__type__": "cc.Color",
"r": 214, "r": 214,
@ -1595,7 +1595,7 @@
"__expectedType__": "cc.SpriteFrame" "__expectedType__": "cc.SpriteFrame"
}, },
"_duration": 0.1, "_duration": 0.1,
"_zoomScale": 1.2, "_zoomScale": 0.9,
"_target": { "_target": {
"__id__": 38 "__id__": 38
}, },

View File

@ -1,22 +1,21 @@
import { _decorator, Component } from 'cc'; import { _decorator, Component } from "cc";
import { EntityStateEnum } from '../Enum'; import { EntityStateEnum } from "../Enum";
import StateMachine from './StateMachine'; import StateMachine from "./StateMachine";
const { ccclass, property } = _decorator; const { ccclass, property } = _decorator;
@ccclass('EntityManager') @ccclass("EntityManager")
export abstract class EntityManager extends Component { export abstract class EntityManager extends Component {
fsm: StateMachine fsm: StateMachine;
private _state: EntityStateEnum private _state: EntityStateEnum;
get state() { get state() {
return this._state return this._state;
} }
set state(newState) { set state(newState) {
this._state = newState this._state = newState;
this.fsm.setParams(newState, true) this.fsm.setParams(newState, true);
} }
abstract init(...args: any[]): void abstract init(...args: any[]): void;
} }

View File

@ -1,13 +1,12 @@
export default class Singleton { export default class Singleton {
private static _instance: any = null private static _instance: any = null;
static GetInstance<T>(): T { static GetInstance<T>(): T {
if (this._instance === null) { if (this._instance === null) {
this._instance = new this() this._instance = new this();
} }
return this._instance return this._instance;
} }
protected constructor() { protected constructor() {}
} }
}

View File

@ -1,48 +1,45 @@
import { animation, AnimationClip, Sprite, SpriteFrame } from 'cc' import { animation, AnimationClip, Sprite, SpriteFrame } from "cc";
import DataManager from '../Global/DataManager' import DataManager from "../Global/DataManager";
import { ResourceManager } from '../Global/ResourceManager' import { ResourceManager } from "../Global/ResourceManager";
import { sortSpriteFrame } from '../Utils' import { sortSpriteFrame } from "../Utils";
import StateMachine from './StateMachine' import StateMachine from "./StateMachine";
/*** /***
* unit:milisecond * unit:milisecond
*/ */
export const ANIMATION_SPEED = 1 / 10 export const ANIMATION_SPEED = 1 / 10;
/*** /***
* SpriteAnimation组件执行播放 * SpriteAnimation组件执行播放
*/ */
export default class State { export default class State {
private animationClip: AnimationClip private animationClip: AnimationClip;
constructor( constructor(
private fsm: StateMachine, private fsm: StateMachine,
private path: string, private path: string,
private wrapMode: AnimationClip.WrapMode = AnimationClip.WrapMode.Normal, private wrapMode: AnimationClip.WrapMode = AnimationClip.WrapMode.Normal,
private force: boolean = false, private force: boolean = false
) { ) {
//生成动画轨道属性 //生成动画轨道属性
const track = new animation.ObjectTrack() const track = new animation.ObjectTrack();
track.path = new animation.TrackPath().toComponent(Sprite).toProperty('spriteFrame') track.path = new animation.TrackPath().toComponent(Sprite).toProperty("spriteFrame");
const spriteFrames = DataManager.Instance.textureMap.get(this.path) const spriteFrames = DataManager.Instance.textureMap.get(this.path);
const frames: Array<[number, SpriteFrame]> = sortSpriteFrame(spriteFrames).map((item, index) => [ const frames: Array<[number, SpriteFrame]> = sortSpriteFrame(spriteFrames).map((item, index) => [index * ANIMATION_SPEED, item]);
index * ANIMATION_SPEED, track.channel.curve.assignSorted(frames);
item,
])
track.channel.curve.assignSorted(frames)
//动画添加轨道 //动画添加轨道
this.animationClip = new AnimationClip() this.animationClip = new AnimationClip();
this.animationClip.name = this.path this.animationClip.name = this.path;
this.animationClip.duration = frames.length * ANIMATION_SPEED this.animationClip.duration = frames.length * ANIMATION_SPEED;
this.animationClip.addTrack(track) this.animationClip.addTrack(track);
this.animationClip.wrapMode = this.wrapMode this.animationClip.wrapMode = this.wrapMode;
} }
run() { run() {
if (this.fsm.animationComponent.defaultClip?.name === this.animationClip.name && !this.force) { if (this.fsm.animationComponent.defaultClip?.name === this.animationClip.name && !this.force) {
return return;
} }
this.fsm.animationComponent.defaultClip = this.animationClip this.fsm.animationComponent.defaultClip = this.animationClip;
this.fsm.animationComponent.play() this.fsm.animationComponent.play();
} }
} }

View File

@ -1,30 +1,30 @@
import { _decorator, Animation, Component } from 'cc' import { _decorator, Animation, Component } from "cc";
import { EntityTypeEnum } from '../Common' import { EntityTypeEnum } from "../Common";
import { FsmParamTypeEnum } from '../Enum' import { FsmParamTypeEnum } from "../Enum";
const { ccclass } = _decorator const { ccclass } = _decorator;
import State from './State' import State from "./State";
import SubStateMachine from './SubStateMachine' import SubStateMachine from "./SubStateMachine";
type ParamsValueType = boolean | number type ParamsValueType = boolean | number;
export interface IParamsValue { export interface IParamsValue {
type: FsmParamTypeEnum type: FsmParamTypeEnum;
value: ParamsValueType value: ParamsValueType;
} }
export const getInitParamsTrigger = () => { export const getInitParamsTrigger = () => {
return { return {
type: FsmParamTypeEnum.Trigger, type: FsmParamTypeEnum.Trigger,
value: false, value: false,
} };
} };
export const getInitParamsNumber = () => { export const getInitParamsNumber = () => {
return { return {
type: FsmParamTypeEnum.Number, type: FsmParamTypeEnum.Number,
value: 0, value: 0,
} };
} };
/*** /***
* *
@ -39,47 +39,47 @@ export const getInitParamsNumber = () => {
/*** /***
* *
*/ */
@ccclass('StateMachine') @ccclass("StateMachine")
export default abstract class StateMachine extends Component { export default abstract class StateMachine extends Component {
private _currentState: State | SubStateMachine = null private _currentState: State | SubStateMachine = null;
params: Map<string, IParamsValue> = new Map() params: Map<string, IParamsValue> = new Map();
stateMachines: Map<string, SubStateMachine | State> = new Map() stateMachines: Map<string, SubStateMachine | State> = new Map();
animationComponent: Animation animationComponent: Animation;
type: EntityTypeEnum type: EntityTypeEnum;
getParams(paramName: string) { getParams(paramName: string) {
if (this.params.has(paramName)) { if (this.params.has(paramName)) {
return this.params.get(paramName).value return this.params.get(paramName).value;
} }
} }
setParams(paramName: string, value: ParamsValueType) { setParams(paramName: string, value: ParamsValueType) {
if (this.params.has(paramName)) { if (this.params.has(paramName)) {
this.params.get(paramName).value = value this.params.get(paramName).value = value;
this.run() this.run();
this.resetTrigger() this.resetTrigger();
} }
} }
get currentState() { get currentState() {
return this._currentState return this._currentState;
} }
set currentState(newState) { set currentState(newState) {
if (!newState) { if (!newState) {
return return;
} }
this._currentState = newState this._currentState = newState;
this._currentState.run() this._currentState.run();
} }
/*** /***
* trigger * trigger
*/ */
resetTrigger() { resetTrigger() {
for (const [, value] of this.params) { for (const [, value] of this.params) {
if (value.type === FsmParamTypeEnum.Trigger) { if (value.type === FsmParamTypeEnum.Trigger) {
value.value = false value.value = false;
} }
} }
} }
@ -87,6 +87,6 @@ export default abstract class StateMachine extends Component {
/*** /***
* currentState * currentState
*/ */
abstract init(...args: any[]): void abstract init(...args: any[]): void;
abstract run(): void abstract run(): void;
} }

View File

@ -1,30 +1,30 @@
import State from './State' import State from "./State";
import StateMachine from './StateMachine' import StateMachine from "./StateMachine";
/*** /***
* *
* idle的statestate都封装在子状态机中 * idle的statestate都封装在子状态机中
*/ */
export default abstract class SubStateMachine { export default abstract class SubStateMachine {
private _currentState: State = null private _currentState: State = null;
stateMachines: Map<string, State> = new Map() stateMachines: Map<string, State> = new Map();
constructor(public fsm: StateMachine) {} constructor(public fsm: StateMachine) {}
get currentState() { get currentState() {
return this._currentState return this._currentState;
} }
set currentState(newState) { set currentState(newState) {
if (!newState) { if (!newState) {
return return;
} }
this._currentState = newState this._currentState = newState;
this._currentState.run() this._currentState.run();
} }
/*** /***
* *
*/ */
abstract run(): void abstract run(): void;
} }

View File

@ -1,147 +1,141 @@
import { _decorator, instantiate, ProgressBar, Label, Vec3, Tween, tween, director } from 'cc'; import { _decorator, instantiate, ProgressBar, Label, Vec3, Tween, tween } from "cc";
import { EntityManager } from '../../Base/EntityManager'; import { EntityManager } from "../../Base/EntityManager";
import { ApiMsgEnum, EntityTypeEnum, IActor, InputTypeEnum, IVec2, toFixed } from '../../Common'; import { EntityTypeEnum, IActor, InputTypeEnum, IVec2, toFixed } from "../../Common";
import { EntityStateEnum, EventEnum, SceneEnum } from '../../Enum'; import { EntityStateEnum, EventEnum, SceneEnum } from "../../Enum";
import DataManager from '../../Global/DataManager'; import DataManager from "../../Global/DataManager";
import EventManager from '../../Global/EventManager'; import EventManager from "../../Global/EventManager";
import NetworkManager from '../../Global/NetworkManager'; import { rad2Angle } from "../../Utils";
import { rad2Angle } from '../../Utils'; import { WeaponManager } from "../Weapon/WeaponManager";
import { WeaponManager } from '../Weapon/WeaponManager'; import { ActorStateMachine } from "./ActorStateMachine";
import { PlayerStateMachine } from './ActorStateMachine';
const { ccclass } = _decorator; const { ccclass } = _decorator;
@ccclass('ActorManager') @ccclass("ActorManager")
export class ActorManager extends EntityManager implements IActor { export class ActorManager extends EntityManager implements IActor {
//静态数据 //静态数据
id: number id: number;
nickname: string nickname: string;
type: EntityTypeEnum type: EntityTypeEnum;
weaponType: EntityTypeEnum weaponType: EntityTypeEnum;
bulletType: EntityTypeEnum bulletType: EntityTypeEnum;
//动态数据 //动态数据
hp: number hp: number;
position: IVec2 position: IVec2;
direction: IVec2 direction: IVec2;
private hpBar: ProgressBar private hpBar: ProgressBar;
private label: Label private label: Label;
private weapon: WeaponManager private weapon: WeaponManager;
private tw: Tween<any> private tw: Tween<unknown>;
private targetPos: Vec3 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() { this.hpBar = this.node.getComponentInChildren(ProgressBar);
return DataManager.Instance.myPlayerId === this.id 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) { if (this.hp <= 0) {
const { id, nickname, type, weaponType, bulletType } = data EventManager.Instance.emit(EventEnum.GameEnd);
this.id = id this.isDead = true;
this.nickname = nickname return;
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
} }
async tick(dt: number) { if (DataManager.Instance.jm.input.length()) {
if (!this.isSelf) { const { x, y } = DataManager.Instance.jm.input;
return EventManager.Instance.emit(EventEnum.ClientSync, {
} type: InputTypeEnum.ActorMove,
id: this.id,
if (this.hp <= 0 && !this.isDead) { direction: {
EventManager.Instance.emit(EventEnum.GameEnd) x: toFixed(x),
this.isDead = true y: toFixed(y),
return },
} dt: toFixed(dt),
});
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)
})
}
} }
}
render(data: IActor) { render(data: IActor) {
this.renderHP(data) this.renderHP(data);
this.renderPosition(data) this.renderPosition(data);
this.renderDirection(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) { renderDirection(data: IActor) {
this.hp = data.hp const { x, y } = data.direction;
this.hpBar.progress = data.hp / this.hpBar.totalLength 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 { x, y } = joystick.input
const newPos = new Vec3(data.position.x, data.position.y) // let angle: number, sign: number
if (!this.targetPos) { // if (x !== 0) {
this.node.active = true // angle = rad2Angle(Math.atan(y / x))
this.node.setPosition(newPos) // sign = joystick.input.x > 0 ? 1 : -1
this.targetPos = new Vec3(newPos) // } else {
} else if (!this.targetPos.equals(newPos)) { // angle = rad2Angle(Math.PI / 2)
this.tw?.stop() // sign = joystick.input.y > 0 ? 1 : -1
this.node.setPosition(this.targetPos) // }
this.targetPos.set(newPos) // this.node.setRotationFromEuler(0, 0, sign * angle)
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)
}
} }

View File

@ -1,48 +1,47 @@
import { _decorator, Animation, AnimationClip } from 'cc' import { _decorator, Animation, AnimationClip } from "cc";
import State from '../../Base/State' import State from "../../Base/State";
import StateMachine, { getInitParamsTrigger } from '../../Base/StateMachine' import StateMachine, { getInitParamsTrigger } from "../../Base/StateMachine";
import { EntityTypeEnum } from '../../Common' import { EntityTypeEnum } from "../../Common";
import { EntityStateEnum, ParamsNameEnum } from '../../Enum' import { EntityStateEnum, ParamsNameEnum } from "../../Enum";
const { ccclass } = _decorator const { ccclass } = _decorator;
@ccclass('PlayerStateMachine') @ccclass("ActorStateMachine")
export class PlayerStateMachine extends StateMachine { export class ActorStateMachine extends StateMachine {
init(type: EntityTypeEnum) { init(type: EntityTypeEnum) {
this.type = type this.type = type;
this.animationComponent = this.node.addComponent(Animation) this.animationComponent = this.node.addComponent(Animation);
this.initParams() this.initParams();
this.initStateMachines() this.initStateMachines();
this.initAnimationEvent() this.initAnimationEvent();
} }
initParams() { initParams() {
this.params.set(ParamsNameEnum.Idle, getInitParamsTrigger()) this.params.set(ParamsNameEnum.Idle, getInitParamsTrigger());
this.params.set(ParamsNameEnum.Run, getInitParamsTrigger()) this.params.set(ParamsNameEnum.Run, getInitParamsTrigger());
} }
initStateMachines() { initStateMachines() {
this.stateMachines.set(ParamsNameEnum.Idle, new State(this, `${this.type}${EntityStateEnum.Idle}`, 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)) this.stateMachines.set(ParamsNameEnum.Run, new State(this, `${this.type}${EntityStateEnum.Run}`, AnimationClip.WrapMode.Loop));
} }
initAnimationEvent() { initAnimationEvent() {}
}
run() { run() {
switch (this.currentState) { switch (this.currentState) {
case this.stateMachines.get(ParamsNameEnum.Idle): case this.stateMachines.get(ParamsNameEnum.Idle):
case this.stateMachines.get(ParamsNameEnum.Run): case this.stateMachines.get(ParamsNameEnum.Run):
if (this.params.get(ParamsNameEnum.Run).value) { 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) { } else if (this.params.get(ParamsNameEnum.Idle).value) {
this.currentState = this.stateMachines.get(ParamsNameEnum.Idle) this.currentState = this.stateMachines.get(ParamsNameEnum.Idle);
} else { } else {
this.currentState = this.currentState this.currentState = this.currentState;
} }
break break;
default: default:
this.currentState = this.stateMachines.get(ParamsNameEnum.Idle) this.currentState = this.stateMachines.get(ParamsNameEnum.Idle);
break break;
} }
} }
} }

View File

@ -1,79 +1,83 @@
import { Tween, tween, Vec3, _decorator } from 'cc' import { Tween, tween, Vec3, _decorator } from "cc";
import { EntityManager } from '../../Base/EntityManager' import { EntityManager } from "../../Base/EntityManager";
import { EntityTypeEnum, IBullet, IVec2 } from '../../Common' import { EntityTypeEnum, IBullet, IVec2 } from "../../Common";
import { EntityStateEnum, EventEnum } from '../../Enum' import { EntityStateEnum, EventEnum } from "../../Enum";
import DataManager from '../../Global/DataManager' import DataManager from "../../Global/DataManager";
import EventManager from '../../Global/EventManager' import EventManager from "../../Global/EventManager";
import ObjectPoolManager from '../../Global/ObjectPoolManager' import ObjectPoolManager from "../../Global/ObjectPoolManager";
import { rad2Angle } from '../../Utils' import { rad2Angle } from "../../Utils";
import { ExplosionManager } from '../Explosion/ExplosionManager' import { ExplosionManager } from "../Explosion/ExplosionManager";
import { BulletStateMachine } from './BulletStateMachine' import { BulletStateMachine } from "./BulletStateMachine";
const { ccclass } = _decorator const { ccclass } = _decorator;
@ccclass('BulletManager') @ccclass("BulletManager")
export class BulletManager extends EntityManager implements IBullet { export class BulletManager extends EntityManager implements IBullet {
//静态数据 //静态数据
id: number id: number;
owner: number owner: number;
type: EntityTypeEnum type: EntityTypeEnum;
//动态数据 //动态数据
position: IVec2 position: IVec2;
direction: IVec2 direction: IVec2;
private angle: number private angle: number;
private tw: Tween<any> private tw: Tween<any>;
private targetPos: Vec3 private targetPos: Vec3;
init({ id, owner, type }: IBullet) { init({ id, owner, type }: IBullet) {
this.id = id this.id = id;
this.owner = owner this.owner = owner;
this.type = type this.type = type;
this.fsm = this.addComponent(BulletStateMachine) this.fsm = this.addComponent(BulletStateMachine);
this.fsm.init(type) this.fsm.init(type);
this.state = EntityStateEnum.Idle this.state = EntityStateEnum.Idle;
this.node.active = false
this.targetPos = undefined
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) { handleExplosion(id: number, { x, y }: IVec2) {
if (this.id !== id) { if (this.id !== id) {
return return;
} }
const explosion = ObjectPoolManager.Instance.get(EntityTypeEnum.Explosion) const explosion = ObjectPoolManager.Instance.get(EntityTypeEnum.Explosion);
const explosionManager = explosion.getComponent(ExplosionManager) || explosion.addComponent(ExplosionManager) const explosionManager = explosion.getComponent(ExplosionManager) || explosion.addComponent(ExplosionManager);
explosionManager.init(EntityTypeEnum.Explosion, { explosionManager.init(EntityTypeEnum.Explosion, {
x, y, x,
}) y,
});
EventManager.Instance.off(EventEnum.ExplosionBorn, this.handleExplosion, this) EventManager.Instance.off(EventEnum.ExplosionBorn, this.handleExplosion, this);
ObjectPoolManager.Instance.ret(this.node) ObjectPoolManager.Instance.ret(this.node);
DataManager.Instance.bulletMap.delete(this.id) DataManager.Instance.bulletMap.delete(this.id);
this.angle = undefined
} }
render(data: IBullet) { render(data: IBullet) {
this.renderPosition(data) this.renderPosition(data);
this.renderDirection(data) this.renderDirection(data);
} }
renderPosition(data: IBullet) { 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) { if (!this.targetPos) {
this.node.active = true this.node.active = true;
this.node.setPosition(newPos) this.node.setPosition(newPos);
this.targetPos = new Vec3(newPos) this.targetPos = new Vec3(newPos);
} else if (!this.targetPos.equals(newPos)) { } else if (!this.targetPos.equals(newPos)) {
this.tw?.stop() this.tw?.stop();
this.node.setPosition(this.targetPos) this.node.setPosition(this.targetPos);
this.targetPos.set(newPos) this.targetPos.set(newPos);
this.tw = tween(this.node).to(0.1, { this.tw = tween(this.node)
position: this.targetPos .to(0.1, {
}).start() position: this.targetPos,
})
.start();
} }
// this.node.setPosition(data.position.x, data.position.y) // this.node.setPosition(data.position.x, data.position.y)
@ -81,12 +85,12 @@ export class BulletManager extends EntityManager implements IBullet {
renderDirection(data: IBullet) { renderDirection(data: IBullet) {
if (this.angle === undefined) { if (this.angle === undefined) {
const { x, y } = data.direction const { x, y } = data.direction;
const side = Math.sqrt(x * x + y * y) const side = Math.sqrt(x * x + y * y);
this.angle = x > 0 ? rad2Angle(Math.asin(y / side)) : rad2Angle(Math.asin(- y / side)) + 180 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 // let angle: number, sign: number
// if (x !== 0) { // if (x !== 0) {

View File

@ -1,45 +1,43 @@
import { _decorator, Animation } from 'cc' import { _decorator, Animation } from "cc";
import State from '../../Base/State' import State from "../../Base/State";
import StateMachine, { getInitParamsTrigger } from '../../Base/StateMachine' import StateMachine, { getInitParamsTrigger } from "../../Base/StateMachine";
import { EntityTypeEnum } from '../../Common' import { EntityTypeEnum } from "../../Common";
import { EntityStateEnum, ParamsNameEnum } from '../../Enum' import { EntityStateEnum, ParamsNameEnum } from "../../Enum";
const { ccclass } = _decorator const { ccclass } = _decorator;
@ccclass('BulletStateMachine') @ccclass("BulletStateMachine")
export class BulletStateMachine extends StateMachine { export class BulletStateMachine extends StateMachine {
init(type: EntityTypeEnum) { init(type: EntityTypeEnum) {
this.type = type this.type = type;
this.animationComponent = this.node.addComponent(Animation) this.animationComponent = this.node.addComponent(Animation);
this.initParams() this.initParams();
this.initStateMachines() this.initStateMachines();
this.initAnimationEvent() this.initAnimationEvent();
} }
initParams() { initParams() {
this.params.set(ParamsNameEnum.Idle, getInitParamsTrigger()) this.params.set(ParamsNameEnum.Idle, getInitParamsTrigger());
} }
initStateMachines() { 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() { run() {
switch (this.currentState) { switch (this.currentState) {
case this.stateMachines.get(ParamsNameEnum.Idle): case this.stateMachines.get(ParamsNameEnum.Idle):
if (this.params.get(ParamsNameEnum.Idle).value) { if (this.params.get(ParamsNameEnum.Idle).value) {
this.currentState = this.stateMachines.get(ParamsNameEnum.Idle) this.currentState = this.stateMachines.get(ParamsNameEnum.Idle);
} else { } else {
this.currentState = this.currentState this.currentState = this.currentState;
} }
break break;
default: default:
this.currentState = this.stateMachines.get(ParamsNameEnum.Idle) this.currentState = this.stateMachines.get(ParamsNameEnum.Idle);
break break;
} }
} }
} }

View File

@ -1,17 +1,16 @@
import { _decorator } from 'cc' import { _decorator } from "cc";
import { EntityManager } from '../../Base/EntityManager' import { EntityManager } from "../../Base/EntityManager";
import { EntityTypeEnum, IVec2 } from '../../Common' import { EntityTypeEnum, IVec2 } from "../../Common";
import { EntityStateEnum } from '../../Enum' import { EntityStateEnum } from "../../Enum";
import { ExplosionStateMachine } from './ExplosionStateMachine' import { ExplosionStateMachine } from "./ExplosionStateMachine";
const { ccclass, property } = _decorator const { ccclass, property } = _decorator;
@ccclass('ExplosionManager') @ccclass("ExplosionManager")
export class ExplosionManager extends EntityManager { export class ExplosionManager extends EntityManager {
init(type: EntityTypeEnum, { x, y }: IVec2) { init(type: EntityTypeEnum, { x, y }: IVec2) {
this.node.setPosition(x, y) this.node.setPosition(x, y);
this.fsm = this.addComponent(ExplosionStateMachine) this.fsm = this.addComponent(ExplosionStateMachine);
this.fsm.init(type) this.fsm.init(type);
this.state = EntityStateEnum.Idle this.state = EntityStateEnum.Idle;
} }
} }

View File

@ -1,52 +1,52 @@
import { _decorator, Animation } from 'cc' import { _decorator, Animation } from "cc";
import State from '../../Base/State' import State from "../../Base/State";
import StateMachine, { getInitParamsTrigger } from '../../Base/StateMachine' import StateMachine, { getInitParamsTrigger } from "../../Base/StateMachine";
import { EntityTypeEnum } from '../../Common' import { EntityTypeEnum } from "../../Common";
import { EntityStateEnum, ParamsNameEnum } from '../../Enum' import { EntityStateEnum, ParamsNameEnum } from "../../Enum";
import ObjectPoolManager from '../../Global/ObjectPoolManager' import ObjectPoolManager from "../../Global/ObjectPoolManager";
const { ccclass, property } = _decorator const { ccclass, property } = _decorator;
@ccclass('ExplosionStateMachine') @ccclass("ExplosionStateMachine")
export class ExplosionStateMachine extends StateMachine { export class ExplosionStateMachine extends StateMachine {
init(type: EntityTypeEnum) { init(type: EntityTypeEnum) {
this.type = type this.type = type;
this.animationComponent = this.node.addComponent(Animation) this.animationComponent = this.node.addComponent(Animation);
this.initParams() this.initParams();
this.initStateMachines() this.initStateMachines();
this.initAnimationEvent() this.initAnimationEvent();
} }
initParams() { initParams() {
this.params.set(ParamsNameEnum.Idle, getInitParamsTrigger()) this.params.set(ParamsNameEnum.Idle, getInitParamsTrigger());
} }
initStateMachines() { 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() {
this.animationComponent.on(Animation.EventType.FINISHED, () => { this.animationComponent.on(Animation.EventType.FINISHED, () => {
const whiteList = [EntityStateEnum.Idle] const whiteList = [EntityStateEnum.Idle];
const name = this.animationComponent.defaultClip.name const name = this.animationComponent.defaultClip.name;
if (whiteList.some(v => name.includes(v))) { if (whiteList.some((v) => name.includes(v))) {
ObjectPoolManager.Instance.ret(this.node) ObjectPoolManager.Instance.ret(this.node);
} }
}) });
} }
run() { run() {
switch (this.currentState) { switch (this.currentState) {
case this.stateMachines.get(ParamsNameEnum.Idle): case this.stateMachines.get(ParamsNameEnum.Idle):
if (this.params.get(ParamsNameEnum.Idle).value) { if (this.params.get(ParamsNameEnum.Idle).value) {
this.currentState = this.stateMachines.get(ParamsNameEnum.Idle) this.currentState = this.stateMachines.get(ParamsNameEnum.Idle);
} else { } else {
this.currentState = this.currentState this.currentState = this.currentState;
} }
break break;
default: default:
this.currentState = this.stateMachines.get(ParamsNameEnum.Idle) this.currentState = this.stateMachines.get(ParamsNameEnum.Idle);
break break;
} }
} }
} }

View File

@ -1,66 +1,61 @@
import { _decorator, Node, Vec2, UITransform } from 'cc' import { _decorator, Node, Vec2, UITransform } from "cc";
import { EntityManager } from '../../Base/EntityManager' import { EntityManager } from "../../Base/EntityManager";
import { ApiMsgEnum, EntityTypeEnum, InputTypeEnum, toFixed } from '../../Common' import { EntityTypeEnum, InputTypeEnum, toFixed } from "../../Common";
import { EntityStateEnum, EventEnum } from '../../Enum' import { EntityStateEnum, EventEnum } from "../../Enum";
import DataManager from '../../Global/DataManager' import DataManager from "../../Global/DataManager";
import EventManager from '../../Global/EventManager' import EventManager from "../../Global/EventManager";
import NetworkManager from '../../Global/NetworkManager' import { WeaponStateMachine } from "./WeaponStateMachine";
import { WeaponStateMachine } from './WeaponStateMachine' const { ccclass } = _decorator;
const { ccclass } = _decorator
@ccclass('WeaponManager') @ccclass("WeaponManager")
export class WeaponManager extends EntityManager { export class WeaponManager extends EntityManager {
owner: number owner: number;
type: EntityTypeEnum type: EntityTypeEnum;
private body: Node private body: Node;
private anchor: Node private anchor: Node;
private point: Node private point: Node;
get isSelf() { init({ id, weaponType }: { id: number; weaponType: EntityTypeEnum }) {
return DataManager.Instance.myPlayerId === this.owner this.owner = id;
} this.type = weaponType;
init({ id, weaponType }: { id: number, weaponType: EntityTypeEnum }) { this.node.setSiblingIndex(0);
this.owner = id this.body = this.node.getChildByName("Body");
this.type = weaponType this.anchor = this.body.getChildByName("Anchor");
this.point = this.anchor.getChildByName("Point");
this.node.setSiblingIndex(0) this.fsm = this.body.addComponent(WeaponStateMachine);
this.body = this.node.getChildByName("Body") this.fsm.init(weaponType);
this.anchor = this.body.getChildByName("Anchor") this.state = EntityStateEnum.Idle;
this.point = this.anchor.getChildByName("Point")
this.fsm = this.body.addComponent(WeaponStateMachine) EventManager.Instance.on(EventEnum.WeaponShoot, this.handleWeaponShoot, this);
this.fsm.init(weaponType) EventManager.Instance.on(EventEnum.BulletBorn, this.handleBulletBorn, this);
this.state = EntityStateEnum.Idle
EventManager.Instance.on(EventEnum.WeaponShoot, this.handleWeaponShoot, this)
EventManager.Instance.on(EventEnum.BulletBorn, this.handleBulletBorn, this)
} }
onDestroy() { onDestroy() {
EventManager.Instance.off(EventEnum.WeaponShoot, this.handleWeaponShoot, this) EventManager.Instance.off(EventEnum.WeaponShoot, this.handleWeaponShoot, this);
EventManager.Instance.off(EventEnum.BulletBorn, this.handleBulletBorn, this) EventManager.Instance.off(EventEnum.BulletBorn, this.handleBulletBorn, this);
} }
handleBulletBorn(owner: number) { handleBulletBorn(owner: number) {
if (this.owner !== owner) { if (this.owner !== owner) {
return return;
} }
this.state = EntityStateEnum.Attack this.state = EntityStateEnum.Attack;
} }
handleWeaponShoot() { handleWeaponShoot() {
if (!this.isSelf) { if (DataManager.Instance.myPlayerId !== this.owner) {
return return;
} }
const pointWorldPos = this.point.getWorldPosition() const pointWorldPos = this.point.getWorldPosition();
const pointStagePos = DataManager.Instance.stage.getComponent(UITransform).convertToNodeSpaceAR(pointWorldPos); const pointStagePos = DataManager.Instance.stage.getComponent(UITransform).convertToNodeSpaceAR(pointWorldPos);
const anchorWorldPos = this.anchor.getWorldPosition() const anchorWorldPos = this.anchor.getWorldPosition();
const directionVec2 = new Vec2(pointWorldPos.x - anchorWorldPos.x, pointWorldPos.y - anchorWorldPos.y).normalize() const directionVec2 = new Vec2(pointWorldPos.x - anchorWorldPos.x, pointWorldPos.y - anchorWorldPos.y).normalize();
NetworkManager.Instance.sendMsg(ApiMsgEnum.MsgClientSync, { EventManager.Instance.emit(EventEnum.ClientSync, {
type: InputTypeEnum.WeaponShoot, type: InputTypeEnum.WeaponShoot,
owner: this.owner, owner: this.owner,
position: { position: {
@ -71,6 +66,6 @@ export class WeaponManager extends EntityManager {
x: toFixed(directionVec2.x), x: toFixed(directionVec2.x),
y: toFixed(directionVec2.y), y: toFixed(directionVec2.y),
}, },
}) });
} }
} }

View File

@ -1,59 +1,60 @@
export enum FsmParamTypeEnum { export enum FsmParamTypeEnum {
Number = 'Number', Number = "Number",
Trigger = 'Trigger', Trigger = "Trigger",
}
export enum EntityStateEnum {
Idle = 'Idle',
Run = 'Run',
Attack = 'Attack',
} }
export enum ParamsNameEnum { export enum ParamsNameEnum {
Idle = 'Idle', Idle = "Idle",
Run = 'Run', Run = "Run",
Attack = 'Attack', Attack = "Attack",
}
export enum EntityStateEnum {
Idle = "Idle",
Run = "Run",
Attack = "Attack",
} }
export enum EventEnum { export enum EventEnum {
WeaponShoot = 'WeaponShoot', WeaponShoot = "WeaponShoot",
BulletBorn = 'BulletBorn', BulletBorn = "BulletBorn",
ExplosionBorn = 'ExplosionBorn', ExplosionBorn = "ExplosionBorn",
RoomJoin = 'RoomJoin', RoomJoin = "RoomJoin",
GameStart = 'GameStart', GameStart = "GameStart",
GameEnd = 'GameEnd', GameEnd = "GameEnd",
ClientSync = "ClientSync",
} }
export enum PrefabPathEnum { export enum PrefabPathEnum {
Map1 = 'prefab/Map1', Map1 = "prefab/Map1",
Actor1 = 'prefab/Actor', Actor1 = "prefab/Actor",
Actor2 = 'prefab/Actor', Actor2 = "prefab/Actor",
Weapon1 = 'prefab/Weapon1', Weapon1 = "prefab/Weapon1",
Weapon2 = 'prefab/Weapon2', Weapon2 = "prefab/Weapon2",
Bullet1 = 'prefab/Bullet', Bullet1 = "prefab/Bullet",
Bullet2 = 'prefab/Bullet', Bullet2 = "prefab/Bullet",
Explosion = 'prefab/Explosion', Explosion = "prefab/Explosion",
JoyStick = 'prefab/JoyStick', JoyStick = "prefab/JoyStick",
Shoot = 'prefab/Shoot', Shoot = "prefab/Shoot",
} }
export enum TexturePathEnum { export enum TexturePathEnum {
Actor1Idle = 'texture/actor/actor1/idle', Actor1Idle = "texture/actor/actor1/idle",
Actor1Run = 'texture/actor/actor1/run', Actor1Run = "texture/actor/actor1/run",
Actor2Idle = 'texture/actor/actor2/idle', Actor2Idle = "texture/actor/actor2/idle",
Actor2Run = 'texture/actor/actor2/run', Actor2Run = "texture/actor/actor2/run",
Weapon1Idle = 'texture/weapon/weapon1/idle', Weapon1Idle = "texture/weapon/weapon1/idle",
Weapon1Attack = 'texture/weapon/weapon1/attack', Weapon1Attack = "texture/weapon/weapon1/attack",
Weapon2Idle = 'texture/weapon/weapon2/idle', Weapon2Idle = "texture/weapon/weapon2/idle",
Weapon2Attack = 'texture/weapon/weapon2/attack', Weapon2Attack = "texture/weapon/weapon2/attack",
Bullet1Idle = 'texture/bullet/bullet1', Bullet1Idle = "texture/bullet/bullet1",
Bullet2Idle = 'texture/bullet/bullet2', Bullet2Idle = "texture/bullet/bullet2",
ExplosionIdle = 'texture/explosion', ExplosionIdle = "texture/explosion",
} }
export enum SceneEnum { export enum SceneEnum {
Login = 'Login', Login = "Login",
Hall = 'Hall', Hall = "Hall",
Room = 'Room', Room = "Room",
Battle = 'Battle', Battle = "Battle",
} }

View File

@ -1,151 +1,160 @@
import { Node, Prefab, SpriteFrame, clamp } from 'cc' import { Node, Prefab, SpriteFrame, clamp } from "cc";
import Singleton from '../Base/Singleton' import Singleton from "../Base/Singleton";
import { EntityTypeEnum, IBullet, IClientInput, InputTypeEnum, IRoom, IState, toFixed } from '../Common' import { EntityTypeEnum, IBullet, IClientInput, InputTypeEnum, IRoom, IState, toFixed } from "../Common";
import { ActorManager } from '../Entity/Actor/ActorManager' import { ActorManager } from "../Entity/Actor/ActorManager";
import { BulletManager } from '../Entity/Bullet/BulletManager' import { BulletManager } from "../Entity/Bullet/BulletManager";
import { EventEnum } from '../Enum' import { EventEnum } from "../Enum";
import { JoyStickManager } from '../UI/JoyStickManager' import { JoyStickManager } from "../UI/JoyStickManager";
import EventManager from './EventManager' import EventManager from "./EventManager";
const PLAYER_SPEED = 100;
const BULLET_SPEED = 600;
const PLAYER_SPEED = 100 const WEAPON_DAMAGE = 5;
const BULLET_SPEED = 600
const WEAPON_DAMAGE = 5 const PLAYER_RADIUS = 50;
const BULLET_RADIUS = 10;
const PLAYER_RADIUS = 50 const mapW = 960;
const BULLET_RADIUS = 10 const mapH = 640;
const mapW = 960
const mapH = 640
export default class DataManager extends Singleton { export default class DataManager extends Singleton {
static get Instance() { static get Instance() {
return super.GetInstance<DataManager>() return super.GetInstance<DataManager>();
} }
//登陆数据 //登陆数据
myPlayerId = 1 myPlayerId = 1;
//大厅数据 //大厅数据
roomInfo: IRoom roomInfo: IRoom;
//游戏数据 //游戏数据
stage: Node stage: Node;
jm: JoyStickManager jm: JoyStickManager;
prefabMap: Map<string, Prefab> = new Map() prefabMap: Map<string, Prefab> = new Map();
textureMap: Map<string, SpriteFrame[]> = new Map() textureMap: Map<string, SpriteFrame[]> = new Map();
actorMap: Map<number, ActorManager> = new Map() actorMap: Map<number, ActorManager> = new Map();
bulletMap: Map<number, BulletManager> = new Map() bulletMap: Map<number, BulletManager> = new Map();
reset() { reset() {
this.stage = null this.frameId = 0;
this.jm = null this.stage = null;
this.actorMap.clear() this.jm = null;
this.bulletMap.clear() this.actorMap.clear();
this.prefabMap.clear() this.bulletMap.clear();
this.textureMap.clear() this.prefabMap.clear();
this.textureMap.clear();
} }
frameId = 0;
lastState: IState;
state: IState = { state: IState = {
players: [{ actors: [
id: 2, {
nickname: "哈哈1", id: 2,
position: { nickname: "哈哈1",
x: -200, position: {
y: -200 x: -200,
y: -200,
},
direction: {
x: 1,
y: 0,
},
hp: 100,
type: EntityTypeEnum.Actor1,
weaponType: EntityTypeEnum.Weapon1,
bulletType: EntityTypeEnum.Bullet1,
}, },
direction: { {
x: 1, id: 1,
y: 0 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: [], bullets: [],
nextBulletId: 1 nextBulletId: 1,
} };
applyInput(input: IClientInput) { applyInput(input: IClientInput) {
switch (input.type) { switch (input.type) {
case InputTypeEnum.ActorMove: { case InputTypeEnum.ActorMove: {
const { direction: { x, y }, dt, id } = input const {
const player = this.state.players.find(e => e.id === id) direction: { x, y },
dt,
id,
} = input;
const player = this.state.actors.find((e) => e.id === id);
if (!player) { if (!player) {
return return;
} }
player.position.x += toFixed(x * PLAYER_SPEED * dt) player.position.x += toFixed(x * PLAYER_SPEED * dt);
player.position.y += toFixed(y * PLAYER_SPEED * dt) player.position.y += toFixed(y * PLAYER_SPEED * dt);
player.position.x = clamp(player.position.x, -mapW / 2, mapW / 2) player.position.x = clamp(player.position.x, -mapW / 2, mapW / 2);
player.position.y = clamp(player.position.y, -mapH / 2, mapH / 2) player.position.y = clamp(player.position.y, -mapH / 2, mapH / 2);
player.direction = { x, y } player.direction = { x, y };
break break;
} }
case InputTypeEnum.WeaponShoot: { case InputTypeEnum.WeaponShoot: {
const { owner, position, direction } = input const { owner, position, direction } = input;
const bullet: IBullet = { const bullet: IBullet = {
id: this.state.nextBulletId++, id: this.state.nextBulletId++,
owner, owner,
position, position,
direction, direction,
type: this.actorMap.get(owner).bulletType type: this.actorMap.get(owner).bulletType,
} };
this.state.bullets.push(bullet) this.state.bullets.push(bullet);
EventManager.Instance.emit(EventEnum.BulletBorn, owner) EventManager.Instance.emit(EventEnum.BulletBorn, owner);
break break;
} }
case InputTypeEnum.TimePast: { case InputTypeEnum.TimePast: {
const { dt } = input const { dt } = input;
const { bullets, players } = this.state const { bullets, actors } = this.state;
for (let i = bullets.length - 1; i >= 0; i--) { for (let i = bullets.length - 1; i >= 0; i--) {
const bullet = bullets[i]; const bullet = bullets[i];
for (let j = players.length - 1; j >= 0; j--) { for (let j = actors.length - 1; j >= 0; j--) {
const player = players[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) { if ((player.position.x - bullet.position.x) ** 2 + (player.position.y - bullet.position.y) ** 2 < (PLAYER_RADIUS + BULLET_RADIUS) ** 2) {
EventManager.Instance.emit(EventEnum.ExplosionBorn, bullet.id, { EventManager.Instance.emit(EventEnum.ExplosionBorn, bullet.id, {
x: toFixed((player.position.x + bullet.position.x) / 2), x: toFixed((player.position.x + bullet.position.x) / 2),
y: toFixed((player.position.y + bullet.position.y) / 2), y: toFixed((player.position.y + bullet.position.y) / 2),
}) });
player.hp -= WEAPON_DAMAGE player.hp -= WEAPON_DAMAGE;
bullets.splice(i, 1) bullets.splice(i, 1);
break break;
} }
} }
if (Math.abs(bullet.position.x) > mapW / 2 || Math.abs(bullet.position.y) > mapH / 2) { if (Math.abs(bullet.position.x) > mapW / 2 || Math.abs(bullet.position.y) > mapH / 2) {
EventManager.Instance.emit(EventEnum.ExplosionBorn, bullet.id, { EventManager.Instance.emit(EventEnum.ExplosionBorn, bullet.id, {
x: bullet.position.x, x: bullet.position.x,
y: bullet.position.y, y: bullet.position.y,
}) });
bullets.splice(i, 1) bullets.splice(i, 1);
} }
} }
for (const bullet of this.state.bullets) { for (const bullet of this.state.bullets) {
bullet.position.x += toFixed(bullet.direction.x * BULLET_SPEED * dt) bullet.position.x += toFixed(bullet.direction.x * BULLET_SPEED * dt);
bullet.position.y += toFixed(bullet.direction.y * BULLET_SPEED * dt) bullet.position.y += toFixed(bullet.direction.y * BULLET_SPEED * dt);
} }
} }
} }

View File

@ -1,5 +1,5 @@
import Singleton from '../Base/Singleton' import Singleton from "../Base/Singleton";
import { EventEnum } from '../Enum'; import { EventEnum } from "../Enum";
interface IItem { interface IItem {
cb: Function; cb: Function;
@ -23,7 +23,7 @@ export default class EventManager extends Singleton {
off(event: EventEnum, cb: Function, ctx: unknown) { off(event: EventEnum, cb: Function, ctx: unknown) {
if (this.map.has(event)) { 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); index > -1 && this.map.get(event).splice(index, 1);
} }
} }
@ -31,7 +31,7 @@ export default class EventManager extends Singleton {
emit(event: EventEnum, ...params: unknown[]) { emit(event: EventEnum, ...params: unknown[]) {
if (this.map.has(event)) { if (this.map.has(event)) {
this.map.get(event).forEach(({ cb, ctx }) => { this.map.get(event).forEach(({ cb, ctx }) => {
cb.apply(ctx, params) cb.apply(ctx, params);
}); });
} }
} }

View File

@ -1,8 +1,8 @@
import Singleton from '../Base/Singleton' import Singleton from "../Base/Singleton";
import { ApiMsgEnum, IModel } from '../Common'; import { ApiMsgEnum, IModel } from "../Common";
import { binaryEncode, binaryDecode } from '../Common/Binary'; import { binaryEncode, binaryDecode } from "../Common/Binary";
const TIMEOUT = 5000 const TIMEOUT = 5000;
interface IItem { interface IItem {
cb: Function; cb: Function;
@ -12,109 +12,107 @@ interface IItem {
export interface ICallApiRet<T> { export interface ICallApiRet<T> {
success: boolean; success: boolean;
error?: Error; error?: Error;
res?: T res?: T;
} }
type aaa = keyof IModel
export default class NetworkManager extends Singleton { export default class NetworkManager extends Singleton {
static get Instance() { static get Instance() {
return super.GetInstance<NetworkManager>() return super.GetInstance<NetworkManager>();
} }
ws: WebSocket ws: WebSocket;
port = 8888 port = 8888;
maps: Map<ApiMsgEnum, Array<IItem>> = new Map() maps: Map<ApiMsgEnum, Array<IItem>> = new Map();
isConnected = false isConnected = false;
connect() { connect() {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (this.isConnected) { if (this.isConnected) {
resolve(true) resolve(true);
return return;
} }
this.ws = new WebSocket(`ws://localhost:${this.port}`) this.ws = new WebSocket(`ws://localhost:${this.port}`);
//onmessage接受的数据类型只有在后端返回字节数组的时候才有效果 //onmessage接受的数据类型只有在后端返回字节数组的时候才有效果
this.ws.binaryType = 'arraybuffer'; this.ws.binaryType = "arraybuffer";
this.ws.onopen = () => { this.ws.onopen = () => {
this.isConnected = true this.isConnected = true;
resolve(true) resolve(true);
} };
this.ws.onerror = (e) => { this.ws.onerror = (e) => {
this.isConnected = false this.isConnected = false;
console.log(e) console.log(e);
reject("ws onerror") reject("ws onerror");
} };
this.ws.onclose = () => { this.ws.onclose = () => {
this.isConnected = false this.isConnected = false;
reject("ws onclose") reject("ws onclose");
} };
this.ws.onmessage = (e) => { this.ws.onmessage = (e) => {
try { try {
const json = binaryDecode(e.data) const json = binaryDecode(e.data);
const { name, data } = json const { name, data } = json;
try { try {
if (this.maps.has(name) && this.maps.get(name).length) { if (this.maps.has(name) && this.maps.get(name).length) {
console.log(json); 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) { } catch (error) {
console.log("onmessage:", error) console.log("onmessage:", error);
} }
} catch (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) => { return new Promise((resolve) => {
try { try {
// 超时处理 // 超时处理
const timer = setTimeout(() => { const timer = setTimeout(() => {
resolve({ success: false, error: new Error('timeout') }) resolve({ success: false, error: new Error("timeout") });
this.unlistenMsg(name as any, cb, null) this.unlistenMsg(name as any, cb, null);
}, TIMEOUT) }, TIMEOUT);
// 回调处理 // 回调处理
const cb = (res) => { const cb = (res) => {
resolve(res) resolve(res);
clearTimeout(timer) clearTimeout(timer);
this.unlistenMsg(name as any, cb, null) this.unlistenMsg(name as any, cb, null);
} };
this.listenMsg(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) { } 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]) { async sendMsg<T extends keyof IModel["msg"]>(name: T, data: IModel["msg"][T]) {
const view = binaryEncode(name, data) const view = binaryEncode(name, data);
let delay = parseInt(new URLSearchParams(location.search).get('delay') || '0') || 0; let delay = parseInt(new URLSearchParams(location.search).get("delay") || "0") || 0;
await new Promise((r) => setTimeout(r, delay)) await new Promise((r) => setTimeout(r, delay));
this.ws.send(view.buffer) 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)) { if (this.maps.has(name)) {
this.maps.get(name).push({ ctx, cb }) this.maps.get(name).push({ ctx, cb });
} else { } 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)) { if (this.maps.has(name)) {
const items = this.maps.get(name) const items = this.maps.get(name);
const index = items.findIndex(i => cb === i.cb && i.ctx === ctx); const index = items.findIndex((i) => cb === i.cb && i.ctx === ctx);
index > -1 && items.splice(index, 1) index > -1 && items.splice(index, 1);
} }
} }
} }

View File

@ -1,55 +1,55 @@
import Singleton from '../Base/Singleton' import Singleton from "../Base/Singleton";
import { instantiate, Node } from 'cc' import { instantiate, Node } from "cc";
import DataManager from './DataManager' import DataManager from "./DataManager";
import { EntityTypeEnum } from '../Common' import { EntityTypeEnum } from "../Common";
export default class ObjectPoolManager extends Singleton { export default class ObjectPoolManager extends Singleton {
static get Instance() { static get Instance() {
return super.GetInstance<ObjectPoolManager>() return super.GetInstance<ObjectPoolManager>();
} }
private objectPool: Node = null private objectPool: Node = null;
private map: Map<EntityTypeEnum, Node[]> = new Map() private map: Map<EntityTypeEnum, Node[]> = new Map();
private getContainerName(objectName: EntityTypeEnum) { private getContainerName(objectName: EntityTypeEnum) {
return objectName + 'Pool' return objectName + "Pool";
} }
reset() { reset() {
this.objectPool = null this.objectPool = null;
this.map.clear() this.map.clear();
} }
get(objectName: EntityTypeEnum) { get(objectName: EntityTypeEnum) {
if (this.objectPool === null) { if (this.objectPool === null) {
this.objectPool = new Node("ObjectPool") this.objectPool = new Node("ObjectPool");
this.objectPool.setParent(DataManager.Instance.stage) this.objectPool.setParent(DataManager.Instance.stage);
} }
if (!this.map.has(objectName)) { if (!this.map.has(objectName)) {
this.map.set(objectName, []) this.map.set(objectName, []);
const container = new Node(this.getContainerName(objectName)) const container = new Node(this.getContainerName(objectName));
container.setParent(this.objectPool) container.setParent(this.objectPool);
} }
let node: Node let node: Node;
const nodes = this.map.get(objectName) const nodes = this.map.get(objectName);
if (!nodes.length) { if (!nodes.length) {
const prefab = DataManager.Instance.prefabMap.get(objectName) const prefab = DataManager.Instance.prefabMap.get(objectName);
node = instantiate(prefab) node = instantiate(prefab);
node.name = objectName node.name = objectName;
node.setParent(this.objectPool.getChildByName(this.getContainerName(objectName))) node.setParent(this.objectPool.getChildByName(this.getContainerName(objectName)));
} else { } else {
node = nodes.pop() node = nodes.pop();
} }
node.active = true node.active = true;
return node return node;
} }
ret(object: Node) { ret(object: Node) {
object.active = false object.active = false;
const objectName = object.name as EntityTypeEnum const objectName = object.name as EntityTypeEnum;
this.map.get(objectName).push(object) this.map.get(objectName).push(object);
} }
} }

View File

@ -1,32 +1,32 @@
import { _decorator, resources, Asset } from 'cc' import { _decorator, resources, Asset } from "cc";
import Singleton from '../Base/Singleton' import Singleton from "../Base/Singleton";
export class ResourceManager extends Singleton { export class ResourceManager extends Singleton {
static get Instance() { static get Instance() {
return super.GetInstance<ResourceManager>() return super.GetInstance<ResourceManager>();
} }
loadRes<T extends Asset>(path: string, type: new (...args: any[]) => T) { loadRes<T extends Asset>(path: string, type: new (...args: any[]) => T) {
return new Promise<T>((resolve, reject) => { return new Promise<T>((resolve, reject) => {
resources.load(path, type, (err, res) => { resources.load(path, type, (err, res) => {
if (err) { if (err) {
reject(err) reject(err);
return return;
} }
resolve(res) resolve(res);
}) });
}) });
} }
loadDir<T extends Asset>(path: string, type: new (...args: any[]) => T) { loadDir<T extends Asset>(path: string, type: new (...args: any[]) => T) {
return new Promise<T[]>((resolve, reject) => { return new Promise<T[]>((resolve, reject) => {
resources.loadDir(path, type, (err, res) => { resources.loadDir(path, type, (err, res) => {
if (err) { if (err) {
reject(err) reject(err);
return return;
} }
resolve(res) resolve(res);
}) });
}) });
} }
} }

View File

@ -1,192 +1,218 @@
import { _decorator, Component, Node, Prefab, instantiate, SpriteFrame, director } from 'cc'; import { _decorator, Component, Node, Prefab, instantiate, SpriteFrame, director } from "cc";
import { ActorManager } from '../Entity/Actor/ActorManager'; import { ActorManager } from "../Entity/Actor/ActorManager";
import DataManager from '../Global/DataManager'; import DataManager from "../Global/DataManager";
import { JoyStickManager } from '../UI/JoyStickManager'; import { JoyStickManager } from "../UI/JoyStickManager";
import { ResourceManager } from '../Global/ResourceManager'; import { ResourceManager } from "../Global/ResourceManager";
import { EventEnum, PrefabPathEnum, SceneEnum, TexturePathEnum } from '../Enum'; import { EventEnum, PrefabPathEnum, SceneEnum, TexturePathEnum } from "../Enum";
import NetworkManager from '../Global/NetworkManager'; import NetworkManager from "../Global/NetworkManager";
import ObjectPoolManager from '../Global/ObjectPoolManager'; import ObjectPoolManager from "../Global/ObjectPoolManager";
import { BulletManager } from '../Entity/Bullet/BulletManager'; import { BulletManager } from "../Entity/Bullet/BulletManager";
import { ApiMsgEnum, EntityTypeEnum, IMsgServerSync, InputTypeEnum } from '../Common'; import { ApiMsgEnum, EntityTypeEnum, IClientInput, IMsgServerSync, InputTypeEnum, toFixed } from "../Common";
import EventManager from '../Global/EventManager'; import EventManager from "../Global/EventManager";
import { deepClone } from "../Utils";
const { ccclass } = _decorator; const { ccclass } = _decorator;
@ccclass('BattleManager') @ccclass("BattleManager")
export class BattleManager extends Component { export class BattleManager extends Component {
private stage: Node private stage: Node;
private ui: Node private ui: Node;
private shouldUpdate = false private shouldUpdate = false;
private pendingMsg = [];
async start() { async start() {
//清空 //清空
this.clearGame() 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.MsgServerSync, this.listenServerSync, this);
NetworkManager.Instance.listenMsg(ApiMsgEnum.MsgGameEnd, this.leaveGame, this); NetworkManager.Instance.listenMsg(ApiMsgEnum.MsgGameEnd, this.listenGameEnd, this);
EventManager.Instance.on(EventEnum.GameEnd, this.handleGameEnd, 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);
} }
for (const type in TexturePathEnum) {
clearGame() { const p = ResourceManager.Instance.loadDir(TexturePathEnum[type], SpriteFrame).then((spriteFrames) => {
//监听 DataManager.Instance.textureMap.set(type, spriteFrames);
NetworkManager.Instance.unlistenMsg(ApiMsgEnum.MsgServerSync, this.handleSync, this); });
NetworkManager.Instance.unlistenMsg(ApiMsgEnum.MsgGameEnd, this.leaveGame, this); list.push(p);
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()
} }
await Promise.all(list);
}
async loadRes() { async connectServer() {
const list = [] if (!(await NetworkManager.Instance.connect().catch(() => false))) {
for (const type in PrefabPathEnum) { await new Promise((resolve) => setTimeout(resolve, 1000));
const p = ResourceManager.Instance.loadRes(PrefabPathEnum[type], Prefab).then((prefab) => { await this.connectServer();
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() { async initGame() {
if (!await NetworkManager.Instance.connect().catch(() => false)) { this.initJoyStick();
await new Promise((resolve) => setTimeout(resolve, 1000)) this.initShoot();
await this.connectServer() 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() { tick(dt: number) {
this.clearGame() this.tickPlayer(dt);
director.loadScene(SceneEnum.Hall); // 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() { // tickGlobal(dt: number) {
const { success, res, error } = await NetworkManager.Instance.callApi(ApiMsgEnum.ApiGameEnd, { rid: DataManager.Instance.roomInfo.id }) // DataManager.Instance.applyInput({
if (!success) { // type: InputTypeEnum.TimePast,
console.log(error) // dt: toFixed(dt),
return; // })
} // }
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() { renderBullet() {
this.initJoyStick() for (const b of DataManager.Instance.state.bullets) {
this.initShoot() let bulletManager = DataManager.Instance.bulletMap.get(b.id);
this.initMap() if (!bulletManager) {
this.shouldUpdate = true 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() { handleClientSync(input: IClientInput) {
const prefab = DataManager.Instance.prefabMap.get(EntityTypeEnum.JoyStick) const msg = {
const joySitck = instantiate(prefab) frameId: DataManager.Instance.frameId++,
joySitck.setParent(this.ui) input,
const jm = DataManager.Instance.jm = joySitck.getComponent(JoyStickManager) };
jm.init() NetworkManager.Instance.sendMsg(ApiMsgEnum.MsgClientSync, msg);
//移动才做预测,射击不做
if (input.type === InputTypeEnum.ActorMove) {
DataManager.Instance.applyInput(input);
this.pendingMsg.push(msg);
} }
}
initShoot() { listenServerSync({ lastFrameId, inputs }: IMsgServerSync) {
const prefab = DataManager.Instance.prefabMap.get(EntityTypeEnum.Shoot) //回滚上次服务器状态
const shoot = instantiate(prefab) DataManager.Instance.state = DataManager.Instance.lastState;
shoot.setParent(this.ui) //应用服务器输入
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) this.pendingMsg = this.pendingMsg.filter((msg) => msg.frameId > lastFrameId);
const map = instantiate(prefab) //应用本地输入
map.setParent(this.stage) for (const msg of this.pendingMsg) {
DataManager.Instance.applyInput(msg.input);
} }
}
update(dt: number) { async handleGameEnd() {
if (!this.shouldUpdate) { const { success, res, error } = await NetworkManager.Instance.callApi(ApiMsgEnum.ApiGameEnd, { rid: DataManager.Instance.roomInfo.id });
return if (!success) {
} console.log(error);
this.render() return;
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)
}
} }
}
listenGameEnd() {
this.clearGame();
director.loadScene(SceneEnum.Hall);
}
} }

View File

@ -1,121 +1,122 @@
import { _decorator, Component, Node, Prefab, director, instantiate } from 'cc'; import { _decorator, Component, Node, Prefab, director, instantiate } from "cc";
import { ApiMsgEnum, IApiPlayerListRes, IApiRoomListRes, IMsgPlayerList, IMsgRoomList } from '../Common'; import { ApiMsgEnum, IApiPlayerListRes, IApiRoomListRes, IMsgPlayerList, IMsgRoomList } from "../Common";
import { EventEnum, SceneEnum } from '../Enum'; import { EventEnum, SceneEnum } from "../Enum";
import DataManager from '../Global/DataManager'; import DataManager from "../Global/DataManager";
import EventManager from '../Global/EventManager'; import EventManager from "../Global/EventManager";
import NetworkManager from '../Global/NetworkManager'; import NetworkManager from "../Global/NetworkManager";
import { PlayerManager } from '../UI/PlayerManager'; import { PlayerManager } from "../UI/PlayerManager";
import { RoomManager } from '../UI/RoomManager'; import { RoomManager } from "../UI/RoomManager";
const { ccclass, property } = _decorator; const { ccclass, property } = _decorator;
@ccclass('HallManager') @ccclass("HallManager")
export class HallManager extends Component { export class HallManager extends Component {
@property(Node) @property(Node)
playerContainer: Node = null; playerContainer: Node = null;
@property(Prefab) @property(Prefab)
playerPrefab: Prefab = null; playerPrefab: Prefab = null;
@property(Node) @property(Node)
roomContainer: Node = null; roomContainer: Node = null;
@property(Prefab) @property(Prefab)
roomPrefab: Prefab = null; roomPrefab: Prefab = null;
onLoad() { onLoad() {
director.preloadScene(SceneEnum.Room); director.preloadScene(SceneEnum.Room);
EventManager.Instance.on(EventEnum.RoomJoin, this.joinRoom, this) EventManager.Instance.on(EventEnum.RoomJoin, this.handleJoinRoom, this);
NetworkManager.Instance.listenMsg(ApiMsgEnum.MsgPlayerList, this.renderPlayers, this); NetworkManager.Instance.listenMsg(ApiMsgEnum.MsgPlayerList, this.renderPlayers, this);
NetworkManager.Instance.listenMsg(ApiMsgEnum.MsgRoomList, this.renderRooms, 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() { this.renderPlayers(res);
EventManager.Instance.off(EventEnum.RoomJoin, this.joinRoom, this) }
NetworkManager.Instance.unlistenMsg(ApiMsgEnum.MsgPlayerList, this.renderPlayers, this);
NetworkManager.Instance.unlistenMsg(ApiMsgEnum.MsgRoomList, this.renderRooms, this); 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() { for (let i = 0; i < list.length; i++) {
this.getPlayers() const data = list[i];
this.getRooms() 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() { this.renderRooms(res);
const { success, res, error } = await NetworkManager.Instance.callApi(ApiMsgEnum.ApiPlayerList, {}); }
if (!success) {
console.log(error)
return;
}
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 (let i = 0; i < list.length; i++) {
for (const item of this.playerContainer.children) { const data = list[i];
item.active = false const node = this.roomContainer.children[i];
} const roomItemManager = node.getComponent(RoomManager);
while (this.playerContainer.children.length < list.length) { roomItemManager.init(data);
const playerItem = instantiate(this.playerPrefab); }
playerItem.active = false };
playerItem.setParent(this.playerContainer)
}
for (let i = 0; i < list.length; i++) { async handleCreateRoom() {
const data = list[i]; const { success, res, error } = await NetworkManager.Instance.callApi(ApiMsgEnum.ApiRoomCreate, {});
const node = this.playerContainer.children[i] if (!success) {
const playerItemManager = node.getComponent(PlayerManager) console.log(error);
playerItemManager.init(data) return;
}
} }
async getRooms() { DataManager.Instance.roomInfo = res.room;
const { success, res, error } = await NetworkManager.Instance.callApi(ApiMsgEnum.ApiRoomList, {}); director.loadScene(SceneEnum.Room);
if (!success) { }
console.log(error)
return;
}
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) => { DataManager.Instance.roomInfo = res.room;
for (const item of this.roomContainer.children) { director.loadScene(SceneEnum.Room);
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);
}
} }

View File

@ -1,45 +1,44 @@
import { _decorator, Component, EditBox, director } from 'cc'; import { _decorator, Component, EditBox, director } from "cc";
import { ApiMsgEnum } from '../Common'; import { ApiMsgEnum } from "../Common";
import { SceneEnum } from '../Enum'; import { SceneEnum } from "../Enum";
import DataManager from '../Global/DataManager'; import DataManager from "../Global/DataManager";
import NetworkManager from '../Global/NetworkManager'; import NetworkManager from "../Global/NetworkManager";
const { ccclass, property } = _decorator; const { ccclass, property } = _decorator;
@ccclass('LoginManager') @ccclass("LoginManager")
export class LoginManager extends Component { export class LoginManager extends Component {
input: EditBox input: EditBox;
onLoad() { onLoad() {
this.input = this.node.getChildByName('Input').getComponent(EditBox) this.input = this.node.getChildByName("Input").getComponent(EditBox);
director.preloadScene(SceneEnum.Hall); 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() { DataManager.Instance.myPlayerId = res.player.id;
await NetworkManager.Instance.connect(); director.loadScene(SceneEnum.Hall);
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);
}
} }

View File

@ -1,76 +1,77 @@
import { _decorator, Component, Node, Prefab, director, instantiate } from 'cc'; import { _decorator, Component, Node, Prefab, director, instantiate } from "cc";
import { ApiMsgEnum, IMsgGameStart, IMsgRoom } from '../Common'; import { ApiMsgEnum, IMsgGameStart, IMsgRoom } from "../Common";
import { SceneEnum } from '../Enum'; import { SceneEnum } from "../Enum";
import DataManager from '../Global/DataManager'; import DataManager from "../Global/DataManager";
import NetworkManager from '../Global/NetworkManager'; import NetworkManager from "../Global/NetworkManager";
import { PlayerManager } from '../UI/PlayerManager'; import { PlayerManager } from "../UI/PlayerManager";
import { deepClone } from "../Utils";
const { ccclass, property } = _decorator; const { ccclass, property } = _decorator;
@ccclass('RoomManager') @ccclass("RoomManager")
export class RoomManager extends Component { export class RoomManager extends Component {
@property(Node) @property(Node)
playerContainer: Node = null; playerContainer: Node = null;
@property(Prefab) @property(Prefab)
playerPrefab: Prefab = null; playerPrefab: Prefab = null;
onLoad() { onLoad() {
director.preloadScene(SceneEnum.Battle); director.preloadScene(SceneEnum.Battle);
NetworkManager.Instance.listenMsg(ApiMsgEnum.MsgRoom, this.renderPlayers, this); NetworkManager.Instance.listenMsg(ApiMsgEnum.MsgRoom, this.renderPlayers, this);
NetworkManager.Instance.listenMsg(ApiMsgEnum.MsgGameStart, this.handleGameStart, 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() { for (let i = 0; i < list.length; i++) {
NetworkManager.Instance.unlistenMsg(ApiMsgEnum.MsgRoom, this.renderPlayers, this); const data = list[i];
NetworkManager.Instance.unlistenMsg(ApiMsgEnum.MsgGameStart, this.handleGameStart, this); 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() { DataManager.Instance.roomInfo = null;
this.renderPlayers({ director.loadScene(SceneEnum.Hall);
room: DataManager.Instance.roomInfo }
})
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) { handleGameStart({ state }: IMsgGameStart) {
for (const item of this.playerContainer.children) { DataManager.Instance.state = state;
item.active = false DataManager.Instance.lastState = deepClone(DataManager.Instance.state);
} director.loadScene(SceneEnum.Battle);
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);
}
} }

View File

@ -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; const { ccclass, property } = _decorator;
@ccclass('JoyStickManager') @ccclass("JoyStickManager")
export class JoyStickManager extends Component { export class JoyStickManager extends Component {
input: Vec2 = Vec2.ZERO input: Vec2 = Vec2.ZERO;
private body: Node private body: Node;
private stick: Node private stick: Node;
private touchStartPos: Vec2 private touchStartPos: Vec2;
private defaultPos: Vec2 private defaultPos: Vec2;
private radius: number = 0 private radius: number = 0;
init() { init() {
this.body = this.node.getChildByName("Body") this.body = this.node.getChildByName("Body");
this.stick = this.body.getChildByName("Stick") this.stick = this.body.getChildByName("Stick");
const { x, y } = this.body.position const { x, y } = this.body.position;
this.defaultPos = new Vec2(x, y) this.defaultPos = new Vec2(x, y);
this.radius = this.body.getComponent(UITransform).contentSize.x / 2 this.radius = this.body.getComponent(UITransform).contentSize.x / 2;
input.on(Input.EventType.TOUCH_START, this.onTouchMove, this); input.on(Input.EventType.TOUCH_START, this.onTouchMove, this);
input.on(Input.EventType.TOUCH_MOVE, 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_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() { const stickPos = new Vec2(touchPos.x - this.touchStartPos.x, touchPos.y - this.touchStartPos.y);
input.off(Input.EventType.TOUCH_START, this.onTouchMove, this); const len = stickPos.length();
input.off(Input.EventType.TOUCH_MOVE, this.onTouchMove, this); if (len > this.radius) {
input.off(Input.EventType.TOUCH_END, this.onTouchEnd, this); stickPos.multiplyScalar(this.radius / len);
} }
onTouchMove(e: EventTouch) { this.stick.setPosition(stickPos.x, stickPos.y);
const touchPos = e.touch.getUILocation() this.input = stickPos.clone().normalize();
if (!this.touchStartPos) { }
this.touchStartPos = touchPos.clone()
this.body.setPosition(this.touchStartPos.x, this.touchStartPos.y);
}
const stickPos = new Vec2(touchPos.x - this.touchStartPos.x, touchPos.y - this.touchStartPos.y) onTouchEnd() {
const len = stickPos.length() this.body.setPosition(this.defaultPos.x, this.defaultPos.y);
if (len > this.radius) { this.stick.setPosition(0, 0);
stickPos.multiplyScalar(this.radius / len) this.touchStartPos = undefined;
} this.input = Vec2.ZERO;
}
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
}
} }

View File

@ -1,22 +1,19 @@
import { _decorator, Component, Node, Label } from 'cc'; import { _decorator, Component, Node, Label } from "cc";
import { EventEnum } from '../Enum'; import DataManager from "../Global/DataManager";
import DataManager from '../Global/DataManager';
import EventManager from '../Global/EventManager';
const { ccclass, property } = _decorator; const { ccclass, property } = _decorator;
@ccclass('PlayerManager') @ccclass("PlayerManager")
export class PlayerManager extends Component { export class PlayerManager extends Component {
init({ id, nickname, rid }: { id: number, nickname: string, rid: number }) { init({ id, nickname, rid }: { id: number; nickname: string; rid: number }) {
const label = this.getComponent(Label) const label = this.getComponent(Label);
let str = nickname let str = nickname;
if (DataManager.Instance.myPlayerId === id) { if (DataManager.Instance.myPlayerId === id) {
str += `(我)` str += `(我)`;
} }
if (rid !== -1) { if (rid !== -1) {
str += `(房间${rid})` str += `(房间${rid})`;
} }
label.string = str label.string = str;
this.node.active = true this.node.active = true;
} }
} }

View File

@ -1,20 +1,19 @@
import { _decorator, Component, Node, Label } from 'cc'; import { _decorator, Component, Node, Label } from "cc";
import { EventEnum } from '../Enum'; import { EventEnum } from "../Enum";
import EventManager from '../Global/EventManager'; import EventManager from "../Global/EventManager";
const { ccclass, property } = _decorator; const { ccclass, property } = _decorator;
@ccclass('RoomManager') @ccclass("RoomManager")
export class RoomManager extends Component { export class RoomManager extends Component {
id: number id: number;
init({ id, players }: { id: number, players: Array<{ id: number, nickname: string }> }) { init({ id, players }: { id: number; players: Array<{ id: number; nickname: string }> }) {
this.id = id this.id = id;
const label = this.getComponent(Label) const label = this.getComponent(Label);
label.string = `房间id:${id},当前人数:${players.length}` label.string = `房间id:${id},当前人数:${players.length}`;
this.node.active = true this.node.active = true;
} }
handleClick() { handleClick() {
EventManager.Instance.emit(EventEnum.RoomJoin, this.id) EventManager.Instance.emit(EventEnum.RoomJoin, this.id);
} }
} }

View File

@ -1,13 +1,11 @@
import { _decorator, Component, Node } from 'cc'; import { _decorator, Component, Node } from "cc";
import { EventEnum } from '../Enum'; import { EventEnum } from "../Enum";
import EventManager from '../Global/EventManager'; import EventManager from "../Global/EventManager";
const { ccclass, property } = _decorator; const { ccclass, property } = _decorator;
@ccclass('ShootManager') @ccclass("ShootManager")
export class ShootManager extends Component { export class ShootManager extends Component {
shoot() { shoot() {
EventManager.Instance.emit(EventEnum.WeaponShoot) EventManager.Instance.emit(EventEnum.WeaponShoot);
} }
} }

View File

@ -1,11 +1,25 @@
import { SpriteFrame } from "cc" import { SpriteFrame } from "cc";
import { ApiMsgEnum, InputTypeEnum, strdecode } from "../Common"
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>) => 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;
};

View File

@ -1,61 +1,60 @@
export interface IPlayer { export interface IPlayer {
id: number, nickname: string, rid: number id: number;
nickname: string;
rid: number;
} }
export interface IRoom { export interface IRoom {
id: number, players: Array<IPlayer> id: number;
players: Array<IPlayer>;
} }
export interface IApiPlayerListReq { export interface IApiPlayerListReq {}
}
export interface IApiPlayerListRes { export interface IApiPlayerListRes {
list: Array<IPlayer> list: Array<IPlayer>;
} }
export interface IApiPlayerJoinReq { export interface IApiPlayerJoinReq {
nickname: string nickname: string;
} }
export interface IApiPlayerJoinRes { export interface IApiPlayerJoinRes {
player: IPlayer player: IPlayer;
} }
export interface IApiRoomListReq { export interface IApiRoomListReq {}
}
export interface IApiRoomListRes { export interface IApiRoomListRes {
list: Array<IRoom> list: Array<IRoom>;
} }
export interface IApiRoomCreateReq { export interface IApiRoomCreateReq {}
}
export interface IApiRoomCreateRes { export interface IApiRoomCreateRes {
room: IRoom room: IRoom;
} }
export interface IApiRoomJoinReq { export interface IApiRoomJoinReq {
rid: number rid: number;
} }
export interface IApiRoomJoinRes { export interface IApiRoomJoinRes {
room: IRoom room: IRoom;
} }
export interface IApiRoomLeaveReq { export interface IApiRoomLeaveReq {}
}
export interface IApiRoomLeaveRes { } export interface IApiRoomLeaveRes {}
export interface IApiGameStartReq { export interface IApiGameStartReq {
rid: number rid: number;
} }
export interface IApiGameStartRes { } export interface IApiGameStartRes {}
export interface IApiGameEndReq { export interface IApiGameEndReq {
rid: number rid: number;
} }
export interface IApiGameEndRes { } export interface IApiGameEndRes {}

View File

@ -1,91 +1,100 @@
import { ApiMsgEnum, InputTypeEnum } from "./Enum"; import { ApiMsgEnum, InputTypeEnum } from "./Enum";
import { strdecode, strencode, toFixed } from "./Utils"; import { strdecode, strencode, toFixed } from "./Utils";
const encodeActorMove = (proto: ApiMsgEnum, data: any, view: DataView, index: number) => { const encodeActorMove = (input: any, view: DataView, index: number) => {
view.setUint8(index++, data.type) view.setUint8(index++, input.type)
view.setUint8(index++, data.id) view.setUint8(index++, input.id)
view.setFloat32(index, data.direction.x) view.setFloat32(index, input.direction.x)
index += 4 index += 4
view.setFloat32(index, data.direction.y) view.setFloat32(index, input.direction.y)
index += 4 index += 4
view.setFloat32(index, data.dt) view.setFloat32(index, input.dt)
index += 4 index += 4
} }
const encodeWeaponShoot = (proto: ApiMsgEnum, data: any, view: DataView, index: number) => { const encodeWeaponShoot = (input: any, view: DataView, index: number) => {
view.setUint8(index++, data.type) view.setUint8(index++, input.type)
view.setUint8(index++, data.owner) view.setUint8(index++, input.owner)
view.setFloat32(index, data.position.x) view.setFloat32(index, input.position.x)
index += 4 index += 4
view.setFloat32(index, data.position.y) view.setFloat32(index, input.position.y)
index += 4 index += 4
view.setFloat32(index, data.direction.x) view.setFloat32(index, input.direction.x)
index += 4 index += 4
view.setFloat32(index, data.direction.y) view.setFloat32(index, input.direction.y)
index += 4 index += 4
} }
export const encdoeTimePast = (proto: ApiMsgEnum, data: any, view: DataView, index: number) => { export const encodeTimePast = (input: any, view: DataView, index: number) => {
view.setUint8(index++, data.type) view.setUint8(index++, input.type)
view.setFloat32(index, data.dt) view.setFloat32(index, input.dt)
index += 4 index += 4
} }
export const binaryEncode = (proto: ApiMsgEnum, data: any): DataView => { export const binaryEncode = (name: ApiMsgEnum, data: any): DataView => {
if (proto === ApiMsgEnum.MsgClientSync) { if (name === ApiMsgEnum.MsgClientSync) {
if (data.type === InputTypeEnum.ActorMove) { //name 1字节 + frameId 4字节 + 数据长度 n 字节
const { frameId, input } = data
if (input.type === InputTypeEnum.ActorMove) {
let index = 0 let index = 0
const ab = new ArrayBuffer(3 + 12) const ab = new ArrayBuffer(1 + 4 + 14)
const view = new DataView(ab) const view = new DataView(ab)
view.setUint8(index++, proto) view.setUint8(index++, name)
encodeActorMove(proto, data, view, index) view.setUint32(index, frameId)
index += 4
encodeActorMove(input, view, index)
return view return view
} else if (data.type === InputTypeEnum.WeaponShoot) { } else if (input.type === InputTypeEnum.WeaponShoot) {
let index = 0 let index = 0
const ab = new ArrayBuffer(3 + 16) const ab = new ArrayBuffer(1 + 4 + 18)
const view = new DataView(ab) const view = new DataView(ab)
view.setUint8(index++, proto) view.setUint8(index++, name)
encodeWeaponShoot(proto, data, view, index) view.setUint32(index, frameId)
index += 4
encodeWeaponShoot(input, view, index)
return view return view
} else { } else {
let index = 0 let index = 0
const ab = new ArrayBuffer(2 + 4) const ab = new ArrayBuffer(1 + 4 + 5)
const view = new DataView(ab) const view = new DataView(ab)
view.setUint8(index++, proto) view.setUint8(index++, name)
encdoeTimePast(proto, data, view, index) view.setUint32(index, frameId)
index += 4
encodeTimePast(input, view, index)
return view return view
} }
} else if (proto === ApiMsgEnum.MsgServerSync) { } else if (name === ApiMsgEnum.MsgServerSync) {
const { lastFrameId, inputs } = data
let total = 0 let total = 0
for (let i = 0; i < data.length; i++) { for (const input of inputs) {
const item = data[i]; if (input.type === InputTypeEnum.ActorMove) {
if (item.type === InputTypeEnum.ActorMove) {
total += 14 total += 14
} else if (item.type === InputTypeEnum.WeaponShoot) { } else if (input.type === InputTypeEnum.WeaponShoot) {
total += 18 total += 18
} else { } else {
total += 5 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) const view = new DataView(ab)
let index = 0 let index = 0
view.setUint8(index++, proto) view.setUint8(index++, name)
view.setUint8(index++, data.length) view.setUint32(index, lastFrameId)
for (let i = 0; i < data.length; i++) { index += 4
const item = data[i]; view.setUint8(index++, inputs.length)
if (item.type === InputTypeEnum.ActorMove) { for (const input of inputs) {
encodeActorMove(proto, item, view, index) if (input.type === InputTypeEnum.ActorMove) {
encodeActorMove(input, view, index)
index += 14 index += 14
} else if (item.type === InputTypeEnum.WeaponShoot) { } else if (input.type === InputTypeEnum.WeaponShoot) {
encodeWeaponShoot(proto, item, view, index) encodeWeaponShoot(input, view, index)
index += 18 index += 18
} else { } else {
encdoeTimePast(proto, item, view, index) encodeTimePast(input, view, index)
index += 5 index += 5
} }
} }
return view return view
} else { } else {
let index = 0 let index = 0
@ -93,7 +102,7 @@ export const binaryEncode = (proto: ApiMsgEnum, data: any): DataView => {
const ta = strencode(str) const ta = strencode(str)
const ab = new ArrayBuffer(ta.length + 1) const ab = new ArrayBuffer(ta.length + 1)
const view = new DataView(ab) const view = new DataView(ab)
view.setUint8(index++, proto) view.setUint8(index++, name)
for (let i = 0; i < ta.length; i++) { for (let i = 0; i < ta.length; i++) {
view.setUint8(index++, ta[i]) view.setUint8(index++, ta[i])
} }
@ -109,20 +118,17 @@ const decodeActorMove = (view: DataView, index: number) => {
index += 4 index += 4
const dt = toFixed(view.getFloat32(index)) const dt = toFixed(view.getFloat32(index))
index += 4 index += 4
const msg = { const input = {
name: ApiMsgEnum.MsgClientSync, type: InputTypeEnum.ActorMove,
data: { id,
type: InputTypeEnum.ActorMove, direction: {
id, x: directionX,
direction: { y: directionY,
x: directionX, },
y: directionY, dt
},
dt
}
} }
return msg return input
} }
const decodeWeaponShoot = (view: DataView, index: number) => { const decodeWeaponShoot = (view: DataView, index: number) => {
@ -135,76 +141,96 @@ const decodeWeaponShoot = (view: DataView, index: number) => {
index += 4 index += 4
const directionY = toFixed(view.getFloat32(index)) const directionY = toFixed(view.getFloat32(index))
index += 4 index += 4
const msg = { const input = {
name: ApiMsgEnum.MsgClientSync, type: InputTypeEnum.WeaponShoot,
data: { owner,
type: InputTypeEnum.WeaponShoot, position: {
owner, x: positionX,
position: { y: positionY,
x: positionX, },
y: positionY, direction: {
}, x: directionX,
direction: { y: directionY,
x: directionX, },
y: directionY,
},
}
} }
return input
return msg
} }
const decodeTimePast = (view: DataView, index: number) => { const decodeTimePast = (view: DataView, index: number) => {
const dt = toFixed(view.getFloat32(index)) const dt = toFixed(view.getFloat32(index))
index += 4 index += 4
const msg = { const input = {
name: ApiMsgEnum.MsgClientSync, type: InputTypeEnum.TimePast,
data: { dt,
type: InputTypeEnum.TimePast,
dt,
}
} }
return msg return input
} }
export const binaryDecode = (buffer: ArrayBuffer) => { export const binaryDecode = (buffer: ArrayBuffer) => {
let index = 0 let index = 0
const view = new DataView(buffer) 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++) const inputType = view.getUint8(index++)
if (inputType === InputTypeEnum.ActorMove) { if (inputType === InputTypeEnum.ActorMove) {
return decodeActorMove(view, index) const input = decodeActorMove(view, index)
return {
name,
data: {
frameId,
input
}
}
} else if (inputType === InputTypeEnum.WeaponShoot) { } else if (inputType === InputTypeEnum.WeaponShoot) {
return decodeWeaponShoot(view, index) const input = decodeWeaponShoot(view, index)
return {
name,
data: {
frameId,
input
}
}
} else { } 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 len = view.getUint8(index++)
const res = [] const inputs = []
for (let i = 0; i < len; i++) { for (let i = 0; i < len; i++) {
const inputType = view.getUint8(index++) const inputType = view.getUint8(index++)
if (inputType === InputTypeEnum.ActorMove) { if (inputType === InputTypeEnum.ActorMove) {
res.push(decodeActorMove(view, index).data) inputs.push(decodeActorMove(view, index))
index += 13 index += 13
} else if (inputType === InputTypeEnum.WeaponShoot) { } else if (inputType === InputTypeEnum.WeaponShoot) {
res.push(decodeWeaponShoot(view, index).data) inputs.push(decodeWeaponShoot(view, index))
index += 17 index += 17
} else { } else {
res.push(decodeTimePast(view, index).data) inputs.push(decodeTimePast(view, index))
index += 4 index += 4
} }
} }
return { return {
name: ApiMsgEnum.MsgServerSync, name: ApiMsgEnum.MsgServerSync,
data: res data: {
lastFrameId,
inputs
}
} }
} else { } else {
return { return {
name: proto, name: name,
data: JSON.parse(strdecode(new Uint8Array(buffer.slice(1)))) data: JSON.parse(strdecode(new Uint8Array(buffer.slice(1))))
} }
} }

View File

@ -38,7 +38,6 @@ export enum ApiMsgEnum {
// TimePast = 'TimePast', // TimePast = 'TimePast',
// } // }
export enum InputTypeEnum { export enum InputTypeEnum {
ActorMove, ActorMove,
WeaponShoot, WeaponShoot,
@ -46,14 +45,14 @@ export enum InputTypeEnum {
} }
export enum EntityTypeEnum { export enum EntityTypeEnum {
Map1 = 'Map1', Map1 = "Map1",
Actor1 = 'Actor1', Actor1 = "Actor1",
Actor2 = 'Actor2', Actor2 = "Actor2",
Weapon1 = 'Weapon1', Weapon1 = "Weapon1",
Weapon2 = 'Weapon2', Weapon2 = "Weapon2",
Bullet1 = 'Bullet1', Bullet1 = "Bullet1",
Bullet2 = 'Bullet2', Bullet2 = "Bullet2",
Explosion = 'Explosion', Explosion = "Explosion",
JoyStick = 'JoyStick', JoyStick = "JoyStick",
Shoot = 'Shoot', Shoot = "Shoot",
} }

View File

@ -1,50 +1,66 @@
import { IApiGameEndReq, IApiGameStartReq, IApiGameStartRes, IApiPlayerJoinReq, IApiPlayerJoinRes, IApiPlayerListReq, IApiPlayerListRes, IApiRoomCreateReq, IApiRoomCreateRes, IApiRoomJoinReq, IApiRoomJoinRes, IApiRoomLeaveReq, IApiRoomLeaveRes, IApiRoomListReq, IApiRoomListRes, IApiGameEndRes } from './Api' import {
import { ApiMsgEnum } from './Enum' IApiGameEndReq,
import { IMsgClientSync, IMsgGameEnd, IMsgGameStart, IMsgPlayerList, IMsgRoom, IMsgRoomList, IMsgServerSync } from './Msg' 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 { export interface IModel {
api: { api: {
[ApiMsgEnum.ApiPlayerJoin]: { [ApiMsgEnum.ApiPlayerJoin]: {
req: IApiPlayerJoinReq, req: IApiPlayerJoinReq;
res: IApiPlayerJoinRes, res: IApiPlayerJoinRes;
} };
[ApiMsgEnum.ApiPlayerList]: { [ApiMsgEnum.ApiPlayerList]: {
req: IApiPlayerListReq, req: IApiPlayerListReq;
res: IApiPlayerListRes, res: IApiPlayerListRes;
} };
[ApiMsgEnum.ApiRoomList]: { [ApiMsgEnum.ApiRoomList]: {
req: IApiRoomListReq, req: IApiRoomListReq;
res: IApiRoomListRes, res: IApiRoomListRes;
} };
[ApiMsgEnum.ApiRoomCreate]: { [ApiMsgEnum.ApiRoomCreate]: {
req: IApiRoomCreateReq, req: IApiRoomCreateReq;
res: IApiRoomCreateRes, res: IApiRoomCreateRes;
} };
[ApiMsgEnum.ApiRoomJoin]: { [ApiMsgEnum.ApiRoomJoin]: {
req: IApiRoomJoinReq, req: IApiRoomJoinReq;
res: IApiRoomJoinRes, res: IApiRoomJoinRes;
} };
[ApiMsgEnum.ApiRoomLeave]: { [ApiMsgEnum.ApiRoomLeave]: {
req: IApiRoomLeaveReq, req: IApiRoomLeaveReq;
res: IApiRoomLeaveRes, res: IApiRoomLeaveRes;
} };
[ApiMsgEnum.ApiGameStart]: { [ApiMsgEnum.ApiGameStart]: {
req: IApiGameStartReq, req: IApiGameStartReq;
res: IApiGameStartRes, res: IApiGameStartRes;
}, };
[ApiMsgEnum.ApiGameEnd]: { [ApiMsgEnum.ApiGameEnd]: {
req: IApiGameEndReq, req: IApiGameEndReq;
res: IApiGameEndRes, res: IApiGameEndRes;
} };
}, };
msg: { msg: {
[ApiMsgEnum.MsgPlayerList]: IMsgPlayerList [ApiMsgEnum.MsgPlayerList]: IMsgPlayerList;
[ApiMsgEnum.MsgRoomList]: IMsgRoomList, [ApiMsgEnum.MsgRoomList]: IMsgRoomList;
[ApiMsgEnum.MsgRoom]: IMsgRoom, [ApiMsgEnum.MsgRoom]: IMsgRoom;
[ApiMsgEnum.MsgGameStart]: IMsgGameStart, [ApiMsgEnum.MsgGameStart]: IMsgGameStart;
[ApiMsgEnum.MsgGameEnd]: IMsgGameEnd, [ApiMsgEnum.MsgGameEnd]: IMsgGameEnd;
[ApiMsgEnum.MsgClientSync]: IMsgClientSync, [ApiMsgEnum.MsgClientSync]: IMsgClientSync;
[ApiMsgEnum.MsgServerSync]: IMsgServerSync, [ApiMsgEnum.MsgServerSync]: IMsgServerSync;
} };
} }

View File

@ -1,31 +1,34 @@
import { IPlayer, IRoom } from "./Api" import { IPlayer, IRoom } from "./Api";
import { IClientInput, IState } from "./State" import { IClientInput, IState } from "./State";
export interface IMsgPlayerList { export interface IMsgPlayerList {
list: Array<IPlayer> list: Array<IPlayer>;
} }
export interface IMsgRoomList { export interface IMsgRoomList {
list: Array<IRoom> list: Array<IRoom>;
} }
export interface IMsgRoom { export interface IMsgRoom {
room: IRoom room: IRoom;
} }
export interface IMsgGameStart { export interface IMsgGameStart {
state: IState state: IState;
} }
export interface IMsgGameStart { export interface IMsgGameStart {
state: IState state: IState;
} }
export interface IMsgGameEnd { export interface IMsgGameEnd {}
export interface IMsgClientSync {
frameId: number;
input: IClientInput;
} }
export interface IMsgServerSync {
export type IMsgClientSync = IClientInput lastFrameId: number;
inputs: Array<IClientInput>;
export type IMsgServerSync = Array<IClientInput> }

View File

@ -1,50 +1,50 @@
import { EntityTypeEnum, InputTypeEnum } from "./Enum" import { EntityTypeEnum, InputTypeEnum } from "./Enum";
export interface IActor { export interface IActor {
id: number id: number;
nickname: string nickname: string;
type: EntityTypeEnum type: EntityTypeEnum;
weaponType: EntityTypeEnum weaponType: EntityTypeEnum;
bulletType: EntityTypeEnum bulletType: EntityTypeEnum;
//动态数据 //动态数据
hp: number hp: number;
position: IVec2 position: IVec2;
direction: IVec2 direction: IVec2;
} }
export interface IBullet { export interface IBullet {
id: number id: number;
owner: number owner: number;
type: EntityTypeEnum type: EntityTypeEnum;
//动态数据 //动态数据
position: IVec2 position: IVec2;
direction: IVec2 direction: IVec2;
} }
export interface IVec2 { export interface IVec2 {
x: number; x: number;
y: number y: number;
} }
export interface IState { export interface IState {
players: IActor[], actors: IActor[];
bullets: IBullet[], bullets: IBullet[];
nextBulletId: number nextBulletId: number;
} }
export type IClientInput = IActorMove | IWeaponShoot | ITimePast export type IClientInput = IActorMove | IWeaponShoot | ITimePast;
export interface IActorMove { export interface IActorMove {
type: InputTypeEnum.ActorMove type: InputTypeEnum.ActorMove;
id: number; id: number;
direction: IVec2; direction: IVec2;
dt: number; dt: number;
} }
export interface IWeaponShoot { export interface IWeaponShoot {
type: InputTypeEnum.WeaponShoot type: InputTypeEnum.WeaponShoot;
owner: number; owner: number;
position: IVec2; position: IVec2;
direction: IVec2; direction: IVec2;
@ -52,5 +52,5 @@ export interface IWeaponShoot {
export interface ITimePast { export interface ITimePast {
type: InputTypeEnum.TimePast; type: InputTypeEnum.TimePast;
dt: number dt: number;
} }

View File

@ -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) => { export const strencode = (str: string) => {
let byteArray: number[] = []; let byteArray: number[] = [];
@ -11,11 +11,16 @@ export const strencode = (str: string) => {
} else if (charCode <= 0xffff) { } else if (charCode <= 0xffff) {
byteArray.push(0xe0 | (charCode >> 12), 0x80 | ((charCode & 0xfc0) >> 6), 0x80 | (charCode & 0x3f)); byteArray.push(0xe0 | (charCode >> 12), 0x80 | ((charCode & 0xfc0) >> 6), 0x80 | (charCode & 0x3f));
} else { } 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); return new Uint8Array(byteArray);
} };
export const strdecode = (bytes: Uint8Array) => { export const strdecode = (bytes: Uint8Array) => {
let array: number[] = []; 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); charCode = ((bytes[offset] & 0x0f) << 12) + ((bytes[offset + 1] & 0x3f) << 6) + (bytes[offset + 2] & 0x3f);
offset += 3; offset += 3;
} else { } 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; offset += 4;
} }
array.push(charCode); array.push(charCode);
} }
return String.fromCharCode.apply(null, array); return String.fromCharCode.apply(null, array);
} };

View File

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

View File

@ -1,13 +1,12 @@
export default class Singleton { export default class Singleton {
private static _instance: any = null private static _instance: any = null;
static GetInstance<T>(): T { static GetInstance<T>(): T {
if (this._instance === null) { if (this._instance === null) {
this._instance = new this() this._instance = new this();
} }
return this._instance return this._instance;
} }
protected constructor() { protected constructor() {}
} }
}

View File

@ -1,16 +1,16 @@
import { Connection } from "../Core" import { Connection } from "../Core";
export default class Player { export default class Player {
id: number id: number;
nickname: string nickname: string;
connection: Connection connection: Connection;
rid: number rid: number;
constructor({ id, nickname, connection }: Pick<Player, 'id' | 'nickname' | 'connection'>) { constructor({ id, nickname, connection }: Pick<Player, "id" | "nickname" | "connection">) {
this.id = id this.id = id;
this.nickname = nickname this.nickname = nickname;
this.connection = connection this.connection = connection;
this.connection.playerId = this.id this.connection.playerId = this.id;
this.rid = -1 this.rid = -1;
} }
} }

View File

@ -1,54 +1,55 @@
import Singleton from '../Base/Singleton' import Singleton from "../Base/Singleton";
import { ApiMsgEnum, IApiPlayerJoinReq } from '../Common' import { ApiMsgEnum, IApiPlayerJoinReq } from "../Common";
import { Connection } from '../Core' import { Connection } from "../Core";
import Player from './Player' import Player from "./Player";
import RoomManager from './RoomManager' import RoomManager from "./RoomManager";
export default class PlayerManager extends Singleton { export default class PlayerManager extends Singleton {
static get Instance() { static get Instance() {
return super.GetInstance<PlayerManager>() return super.GetInstance<PlayerManager>();
} }
playerId = 1 players: Set<Player> = new Set();
players: Set<Player> = new Set()
idMapPlayer: Map<number, Player> = new Map() private nextPlayerId = 1;
private idMapPlayer: Map<number, Player> = new Map();
createPlayer({ connection, nickname }: IApiPlayerJoinReq & { connection: Connection }) { createPlayer({ connection, nickname }: IApiPlayerJoinReq & { connection: Connection }) {
const player = new Player({ id: this.playerId++, connection, nickname }) const player = new Player({ id: this.nextPlayerId++, connection, nickname });
this.players.add(player) this.players.add(player);
this.idMapPlayer.set(player.id, player) this.idMapPlayer.set(player.id, player);
return player return player;
} }
removePlayer(uid: number) { removePlayer(uid: number) {
const player = this.idMapPlayer.get(uid) const player = this.idMapPlayer.get(uid);
if (player) { if (player) {
const rid = player.rid const rid = player.rid;
if (rid !== undefined) { if (rid !== undefined) {
RoomManager.Instance.leaveRoom(rid, uid); RoomManager.Instance.leaveRoom(rid, uid);
RoomManager.Instance.syncRooms(); RoomManager.Instance.syncRooms();
RoomManager.Instance.syncRoom(rid); RoomManager.Instance.syncRoom(rid);
} }
this.players.delete(player) this.players.delete(player);
this.idMapPlayer.delete(uid) this.idMapPlayer.delete(uid);
this.syncPlayers() this.syncPlayers();
} }
} }
getPlayerById(uid: number) { getPlayerById(uid: number) {
return this.idMapPlayer.get(uid) return this.idMapPlayer.get(uid);
} }
syncPlayers() { syncPlayers() {
for (const player of this.players) { 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) { 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) { getPlayerView({ id, nickname, rid }: Player) {
return { id, nickname, rid } return { id, nickname, rid };
} }
} }

View File

@ -1,60 +1,63 @@
import { ApiMsgEnum, EntityTypeEnum, IClientInput, InputTypeEnum, IState, toFixed } from '../Common' import { ApiMsgEnum, EntityTypeEnum, IClientInput, IMsgClientSync, InputTypeEnum, IState, toFixed } from "../Common";
import type Player from './Player' import { Connection } from "../Core";
import PlayerManager from './PlayerManager' import type Player from "./Player";
import RoomManager from './RoomManager' import PlayerManager from "./PlayerManager";
import RoomManager from "./RoomManager";
export default class Room { export default class Room {
id: number id: number;
players: Set<Player> = new Set() players: Set<Player> = new Set();
lastSyncTime?: number
private timers: NodeJS.Timer[] = [] private lastTime?: number;
private inputs: Array<IClientInput> = [] private timers: NodeJS.Timer[] = [];
private pendingInput: Array<IClientInput> = [];
private lastPlayerFrameIdMap: Map<number, number> = new Map();
constructor(rid: number) { constructor(rid: number) {
this.id = rid this.id = rid;
} }
join(uid: number) { join(uid: number) {
const player = PlayerManager.Instance.getPlayerById(uid) const player = PlayerManager.Instance.getPlayerById(uid);
if (player) { if (player) {
player.rid = this.id player.rid = this.id;
this.players.add(player) this.players.add(player);
} }
} }
leave(uid: number) { leave(uid: number) {
const player = PlayerManager.Instance.getPlayerById(uid) const player = PlayerManager.Instance.getPlayerById(uid);
if (player) { if (player) {
player.rid = -1 player.rid = -1;
player.connection.unlistenMsg(ApiMsgEnum.MsgClientSync, this.getInput, this) player.connection.unlistenMsg(ApiMsgEnum.MsgClientSync, this.getClientMsg, this);
this.players.delete(player) this.players.delete(player);
if (!this.players.size) { if (!this.players.size) {
RoomManager.Instance.closeRoom(this.id) RoomManager.Instance.closeRoom(this.id);
} }
} }
} }
close() { close() {
this.timers.forEach(t => clearInterval(t)) this.timers.forEach((t) => clearInterval(t));
for (const player of this.players) { for (const player of this.players) {
player.connection.sendMsg(ApiMsgEnum.MsgGameEnd, {}) player.rid = -1;
player.connection.unlistenMsg(ApiMsgEnum.MsgClientSync, this.getInput, this) player.connection.sendMsg(ApiMsgEnum.MsgGameEnd, {});
player.connection.unlistenMsg(ApiMsgEnum.MsgClientSync, this.getClientMsg, this);
} }
this.players.clear() this.players.clear();
} }
sync() { sync() {
for (const player of this.players) { for (const player of this.players) {
player.connection.sendMsg(ApiMsgEnum.MsgRoom, { player.connection.sendMsg(ApiMsgEnum.MsgRoom, {
room: RoomManager.Instance.getRoomView(this) room: RoomManager.Instance.getRoomView(this),
}) });
} }
} }
start() { start() {
const state: IState = { const state: IState = {
players: [...this.players].map((player, index) => ({ actors: [...this.players].map((player, index) => ({
id: player.id, id: player.id,
nickname: player.nickname, nickname: player.nickname,
position: { position: {
@ -63,52 +66,56 @@ export default class Room {
}, },
direction: { direction: {
x: 1, x: 1,
y: 0 y: 0,
}, },
hp: 100, hp: 100,
type: EntityTypeEnum.Actor2, type: index === 0 ? EntityTypeEnum.Actor1 : EntityTypeEnum.Actor2,
weaponType: EntityTypeEnum.Weapon2, weaponType: index === 0 ? EntityTypeEnum.Weapon1 : EntityTypeEnum.Weapon2,
bulletType: EntityTypeEnum.Bullet2, bulletType: index === 0 ? EntityTypeEnum.Bullet1 : EntityTypeEnum.Bullet2,
})), })),
bullets: [], bullets: [],
nextBulletId: 1 nextBulletId: 1,
} };
for (const player of this.players) { for (const player of this.players) {
player.connection.sendMsg(ApiMsgEnum.MsgGameStart, { player.connection.sendMsg(ApiMsgEnum.MsgGameStart, {
state state,
}) });
player.connection.listenMsg(ApiMsgEnum.MsgClientSync, this.getInput, this) player.connection.listenMsg(ApiMsgEnum.MsgClientSync, this.getClientMsg, this);
} }
let t1 = setInterval(() => { let t1 = setInterval(() => {
this.sendInput() this.sendServerMsg();
}, 100) }, 100);
let t2 = setInterval(() => { let t2 = setInterval(() => {
this.timePast() this.timePast();
}, 16) }, 16);
this.timers = [t1, t2] this.timers = [t1, t2];
} }
getInput(input: IClientInput) { getClientMsg(connection: Connection, { frameId, input }: IMsgClientSync) {
this.inputs.push(input) this.lastPlayerFrameIdMap.set(connection.playerId, frameId);
this.pendingInput.push(input);
} }
sendInput() { sendServerMsg() {
const inputs = this.inputs const pendingInput = this.pendingInput;
this.inputs = [] this.pendingInput = [];
for (const player of this.players) { 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() { timePast() {
let now = process.uptime(); let now = process.uptime();
const dt = now - (this.lastSyncTime ?? now) const dt = now - (this.lastTime ?? now);
this.inputs.push({ this.pendingInput.push({
type: InputTypeEnum.TimePast, type: InputTypeEnum.TimePast,
dt: toFixed(dt) dt: toFixed(dt),
}) });
this.lastSyncTime = now; this.lastTime = now;
} }
} }

View File

@ -1,77 +1,77 @@
import Singleton from '../Base/Singleton' import Singleton from "../Base/Singleton";
import { ApiMsgEnum } from '../Common' import { ApiMsgEnum } from "../Common";
import PlayerManager from './PlayerManager' import PlayerManager from "./PlayerManager";
import Room from './Room' import Room from "./Room";
export default class RoomManager extends Singleton { export default class RoomManager extends Singleton {
static get Instance() { static get Instance() {
return super.GetInstance<RoomManager>() return super.GetInstance<RoomManager>();
} }
roomId = 1 nextRoomId = 1;
rooms: Set<Room> = new Set() rooms: Set<Room> = new Set();
idMapRoom: Map<number, Room> = new Map() idMapRoom: Map<number, Room> = new Map();
createRoom() { createRoom() {
const room = new Room(this.roomId++) const room = new Room(this.nextRoomId++);
this.rooms.add(room) this.rooms.add(room);
this.idMapRoom.set(room.id, room) this.idMapRoom.set(room.id, room);
return room return room;
} }
joinRoom(rid: number, uid: number) { joinRoom(rid: number, uid: number) {
const room = this.getRoomById(rid) const room = this.getRoomById(rid);
if (room) { if (room) {
room.join(uid) room.join(uid);
return room return room;
} }
} }
leaveRoom(rid: number, uid: number) { leaveRoom(rid: number, uid: number) {
const room = this.getRoomById(rid) const room = this.getRoomById(rid);
if (room) { if (room) {
room.leave(uid) room.leave(uid);
} }
} }
closeRoom(rid: number) { closeRoom(rid: number) {
const room = this.getRoomById(rid) const room = this.getRoomById(rid);
if (room) { if (room) {
room.close() room.close();
this.rooms.delete(room) this.rooms.delete(room);
this.idMapRoom.delete(rid) this.idMapRoom.delete(rid);
} }
} }
startRoom(rid: number) { startRoom(rid: number) {
const room = this.getRoomById(rid) const room = this.getRoomById(rid);
if (room) { if (room) {
room.start() room.start();
} }
} }
getRoomById(id: number) { getRoomById(id: number) {
return this.idMapRoom.get(id) return this.idMapRoom.get(id);
} }
syncRooms() { syncRooms() {
for (const player of PlayerManager.Instance.players) { 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) { syncRoom(rid: number) {
const room = this.idMapRoom.get(rid) const room = this.idMapRoom.get(rid);
if (room) { if (room) {
room.sync() room.sync();
} }
} }
getRoomsView(rooms: Set<Room> = this.rooms) { getRoomsView(rooms: Set<Room> = this.rooms) {
return [...rooms].map((room) => this.getRoomView(room)) return [...rooms].map((room) => this.getRoomView(room));
} }
getRoomView({ id, players }: Room) { getRoomView({ id, players }: Room) {
return { id, players: PlayerManager.Instance.getPlayersView(players) } return { id, players: PlayerManager.Instance.getPlayersView(players) };
} }
} }

View File

@ -1,8 +1,7 @@
export * from './Api' export * from "./Api";
export * from './Msg' export * from "./Msg";
export * from './Enum' export * from "./Enum";
export * from './Model' export * from "./Model";
export * from './State' export * from "./State";
export * from './Utils' export * from "./Utils";
export * from './Binary' export * from "./Binary";

View File

@ -1,12 +1,12 @@
import WebSocket from 'ws'; import WebSocket from "ws";
import { EventEmitter } from 'stream'; import { EventEmitter } from "stream";
import { MyServer } from './MyServer'; import { MyServer } from "./MyServer";
import { getTime, toArrayBuffer } from '../Utils'; import { getTime, buffer2ArrayBuffer } from "../Utils";
import { ApiMsgEnum, IModel } from '../Common'; import { ApiMsgEnum, IModel } from "../Common";
import { binaryEncode, binaryDecode } from '../Common/Binary'; import { binaryEncode, binaryDecode } from "../Common/Binary";
export enum ConnectionEventEnum { export enum ConnectionEventEnum {
Close = 'Close', Close = "Close",
} }
interface IItem { interface IItem {
@ -15,80 +15,84 @@ interface IItem {
} }
export class Connection extends EventEmitter { export class Connection extends EventEmitter {
server: MyServer server: MyServer;
ws: WebSocket ws: WebSocket;
msgMap: Map<ApiMsgEnum, Array<IItem>> = new Map() msgMap: Map<ApiMsgEnum, Array<IItem>> = new Map();
playerId?: number; playerId?: number;
constructor(server: MyServer, ws: WebSocket) { constructor(server: MyServer, ws: WebSocket) {
super() super();
this.server = server this.server = server;
this.ws = ws this.ws = ws;
this.ws.on('close', (code: number, reason: Buffer) => { this.ws.on("close", (code: number, reason: Buffer) => {
this.emit(ConnectionEventEnum.Close, code, reason.toString()) this.emit(ConnectionEventEnum.Close, code, reason.toString());
}) });
this.ws.on('message', (buffer: Buffer) => { this.ws.on("message", (buffer: Buffer) => {
// const str = buffer.toString() // const str = buffer.toString()
try { try {
const json = binaryDecode(toArrayBuffer(buffer)) const json = binaryDecode(buffer2ArrayBuffer(buffer));
const { name, data } = json const { name, data } = json;
// console.log(`${getTime()}接收|字节数${buffer.length}|${this.playerId || -1}|${str}`) // 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)) { if (this.server.apiMap.has(name)) {
try { try {
const cb = this.server.apiMap.get(name) const cb = this.server.apiMap.get(name);
const res = cb.call(null, data) const res = cb.call(null, this, data);
this.sendMsg(name, { this.sendMsg(name, {
success: true, success: true,
res, res,
}) });
} catch (error) { } catch (error) {
this.sendMsg(name, { this.sendMsg(name, {
success: false, success: false,
error: (error as Error)?.message, error: (error as Error)?.message,
}) });
} }
} else { } else {
try { try {
if (this.msgMap.has(name)) { 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) { } catch (error) {
console.log(error) console.log(error);
} }
} }
} catch (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)) { if (this.msgMap.has(name)) {
this.msgMap.get(name)?.push({ cb, ctx }) this.msgMap.get(name).push({ cb, ctx });
} else { } 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)) { if (this.msgMap.has(name)) {
const items = this.msgMap.get(name) const items = this.msgMap.get(name);
const index = items.findIndex(i => cb === i.cb && i.ctx === ctx); const index = items.findIndex((i) => cb === i.cb && i.ctx === ctx);
index > -1 && items.splice(index, 1) 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({ const msg = JSON.stringify({
name, name,
data data,
}) });
const view = binaryEncode(name, data) const view = binaryEncode(name, data);
const buffer = Buffer.from(view.buffer) const buffer = Buffer.from(view.buffer);
console.log(`${getTime()}发送|字节数${buffer.length}|${this.playerId || -1}|内存${(process.memoryUsage().heapUsed / 1024 / 1024).toFixed(2)}MB|${msg}`) console.log(
this.ws.send(buffer) `${getTime()}发送|字节数${buffer.length}|${this.playerId || -1}|内存${(process.memoryUsage().heapUsed / 1024 / 1024).toFixed(
2
)}MB|${msg}`
);
this.ws.send(buffer);
} }
} }

View File

@ -1,2 +1,2 @@
export * from './MyServer' export * from "./MyServer";
export * from './Connection' export * from "./Connection";

View File

@ -1,144 +1,165 @@
import { Connection, MyServer, MyServerEventEnum } from './Core'; import { Connection, MyServer, MyServerEventEnum } from "./Core";
import PlayerManager from './Biz/PlayerManager'; import PlayerManager from "./Biz/PlayerManager";
import RoomManager from './Biz/RoomManager'; import RoomManager from "./Biz/RoomManager";
import { getTime, symlinkCommon } from './Utils'; 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 {
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 // event
server.on(MyServerEventEnum.Connect, (connection: Connection) => { server.on(MyServerEventEnum.Connect, (connection: Connection) => {
console.log(`${getTime()}来人|人数|${server.connections.size}`) console.log(`${getTime()}来人|人数|${server.connections.size}`);
}) });
server.on(MyServerEventEnum.DisConnect, (connection: Connection) => { server.on(MyServerEventEnum.DisConnect, (connection: Connection) => {
console.log(`${getTime()}走人|人数|${server.connections.size}`) console.log(`${getTime()}走人|人数|${server.connections.size}`);
if (connection.playerId) { if (connection.playerId) {
PlayerManager.Instance.removePlayer(connection.playerId) PlayerManager.Instance.removePlayer(connection.playerId);
} }
}) });
// api // api
server.setApi(ApiMsgEnum.ApiPlayerList, (connection: Connection, data: IApiPlayerListReq): IApiPlayerListRes => { 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 => { server.setApi(ApiMsgEnum.ApiPlayerJoin, (connection: Connection, { nickname }: IApiPlayerJoinReq): IApiPlayerJoinRes => {
const player = PlayerManager.Instance.createPlayer({ connection, nickname }) const player = PlayerManager.Instance.createPlayer({ connection, nickname });
PlayerManager.Instance.syncPlayers() PlayerManager.Instance.syncPlayers();
return { return {
player: PlayerManager.Instance.getPlayerView(player) player: PlayerManager.Instance.getPlayerView(player),
} };
}) });
server.setApi(ApiMsgEnum.ApiRoomList, (connection: Connection, data: IApiRoomListReq): IApiRoomListRes => { 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 => { server.setApi(ApiMsgEnum.ApiRoomCreate, (connection: Connection, data: IApiRoomCreateReq): IApiRoomCreateRes => {
if (connection.playerId) { 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) { if (room) {
RoomManager.Instance.syncRooms() RoomManager.Instance.syncRooms();
PlayerManager.Instance.syncPlayers() PlayerManager.Instance.syncPlayers();
return { return {
room: RoomManager.Instance.getRoomView(room) room: RoomManager.Instance.getRoomView(room),
} };
} else { } else {
throw new Error("ApiRoomCreate room不存在") throw new Error("ApiRoomCreate room不存在");
} }
} else { } else {
throw new Error("ApiRoomCreate 玩家未登录") throw new Error("ApiRoomCreate 玩家未登录");
} }
}) });
server.setApi(ApiMsgEnum.ApiRoomJoin, (connection: Connection, data: IApiRoomJoinReq): IApiRoomJoinRes => { server.setApi(ApiMsgEnum.ApiRoomJoin, (connection: Connection, data: IApiRoomJoinReq): IApiRoomJoinRes => {
if (connection.playerId) { if (connection.playerId) {
const room = RoomManager.Instance.joinRoom(data.rid, connection.playerId) const room = RoomManager.Instance.joinRoom(data.rid, connection.playerId);
if (room) { if (room) {
RoomManager.Instance.syncRooms() RoomManager.Instance.syncRooms();
PlayerManager.Instance.syncPlayers() PlayerManager.Instance.syncPlayers();
RoomManager.Instance.syncRoom(room.id) RoomManager.Instance.syncRoom(room.id);
return { return {
room: RoomManager.Instance.getRoomView(room) room: RoomManager.Instance.getRoomView(room),
} };
} else { } else {
throw new Error("ApiRoomJoin room不存在") throw new Error("ApiRoomJoin room不存在");
} }
} else { } else {
throw new Error("ApiRoomJoin 玩家未登录") throw new Error("ApiRoomJoin 玩家未登录");
} }
}) });
server.setApi(ApiMsgEnum.ApiRoomLeave, (connection: Connection, data: IApiRoomLeaveReq): IApiRoomLeaveRes => { server.setApi(ApiMsgEnum.ApiRoomLeave, (connection: Connection, data: IApiRoomLeaveReq): IApiRoomLeaveRes => {
if (connection.playerId) { if (connection.playerId) {
const player = PlayerManager.Instance.getPlayerById(connection.playerId) const player = PlayerManager.Instance.getPlayerById(connection.playerId);
if (player) { if (player) {
const rid = player.rid const rid = player.rid;
if (rid) { if (rid) {
RoomManager.Instance.leaveRoom(rid, player.id) RoomManager.Instance.leaveRoom(rid, player.id);
PlayerManager.Instance.syncPlayers() PlayerManager.Instance.syncPlayers();
RoomManager.Instance.syncRooms() RoomManager.Instance.syncRooms();
RoomManager.Instance.syncRoom(rid) RoomManager.Instance.syncRoom(rid);
return {} return {};
} else { } else {
throw new Error("ApiRoomLeave 玩家不在房间") throw new Error("ApiRoomLeave 玩家不在房间");
} }
} else { } else {
throw new Error("ApiRoomLeave 玩家不存在") throw new Error("ApiRoomLeave 玩家不存在");
} }
} else { } else {
throw new Error("ApiRoomLeave 玩家未登录") throw new Error("ApiRoomLeave 玩家未登录");
} }
}) });
server.setApi(ApiMsgEnum.ApiGameStart, (connection: Connection, data: IApiGameStartReq): IApiGameStartRes => { server.setApi(ApiMsgEnum.ApiGameStart, (connection: Connection, data: IApiGameStartReq): IApiGameStartRes => {
if (connection.playerId) { if (connection.playerId) {
const player = PlayerManager.Instance.getPlayerById(connection.playerId) const player = PlayerManager.Instance.getPlayerById(connection.playerId);
if (player) { if (player) {
const rid = player.rid const rid = player.rid;
if (rid) { if (rid) {
RoomManager.Instance.startRoom(rid) RoomManager.Instance.startRoom(rid);
// PlayerManager.Instance.syncPlayers() PlayerManager.Instance.syncPlayers();
// RoomManager.Instance.syncRooms() RoomManager.Instance.syncRooms();
return {} return {};
} else { } else {
throw new Error("ApiRoomLeave 玩家不在房间") throw new Error("ApiRoomLeave 玩家不在房间");
} }
} else { } else {
throw new Error("ApiRoomLeave 玩家不存在") throw new Error("ApiRoomLeave 玩家不存在");
} }
} else { } else {
throw new Error("ApiRoomLeave 玩家未登录") throw new Error("ApiRoomLeave 玩家未登录");
} }
}) });
server.setApi(ApiMsgEnum.ApiGameEnd, (connection: Connection, data: IApiGameEndReq): IApiGameEndRes => { server.setApi(ApiMsgEnum.ApiGameEnd, (connection: Connection, data: IApiGameEndReq): IApiGameEndRes => {
if (connection.playerId) { if (connection.playerId) {
const player = PlayerManager.Instance.getPlayerById(connection.playerId) const player = PlayerManager.Instance.getPlayerById(connection.playerId);
if (player) { if (player) {
const rid = player.rid const rid = player.rid;
if (rid) { if (rid) {
RoomManager.Instance.closeRoom(rid) RoomManager.Instance.closeRoom(rid);
PlayerManager.Instance.syncPlayers() PlayerManager.Instance.syncPlayers();
RoomManager.Instance.syncRooms() RoomManager.Instance.syncRooms();
return {} return {};
} else { } else {
throw new Error("ApiGameEnd 玩家不在房间") throw new Error("ApiGameEnd 玩家不在房间");
} }
} else { } else {
throw new Error("ApiGameEnd 玩家不存在") throw new Error("ApiGameEnd 玩家不存在");
} }
} else { } else {
throw new Error("ApiGameEnd 玩家未登录") throw new Error("ApiGameEnd 玩家未登录");
} }
}) });
// start!! // start!!
server.start().then(() => { server
symlinkCommon() .start()
console.log("服务启动!") .then(() => {
}).catch((e) => { symlinkCommon();
console.log("服务异常", e) console.log("服务启动!");
}) })
.catch((e) => {
console.log("服务异常", e);
});

View File

@ -1,821 +0,0 @@
/**
* protobufferjs打造
* @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
}
/**
*
* 2proto的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");
}
}
/**
*
* 2proto的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);
}

View File

@ -1,402 +0,0 @@
/**
* @author zp
*/
/**
* decimal更轻量更快
* [Math.roundMath.minMath.maxMath.floorMath.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);
};

View File

@ -1,46 +1,54 @@
import fs from 'fs-extra' import fs from "fs-extra";
import path from 'path' import path from "path";
export const getTime = () => new Date().toLocaleString().split("├")[0] export const getTime = () => new Date().toLocaleString().split("├")[0];
//symlink同步 //symlink同步
export const symlinkCommon = async () => { export const symlinkCommon = async () => {
const src = path.resolve(__dirname, '../Common') const src = path.resolve(__dirname, "../Common");
const dst = path.resolve(__dirname, '../../../client/assets/Scripts/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) { if (
console.log('同步成功!') (await fs
.lstat(dst)
.then((v) => v.isSymbolicLink())
.catch(() => false)) &&
(await fs.readlink(dst)) === src
) {
console.log("同步成功!");
} else { } else {
fs.symlink(src, dst).then(() => { fs.symlink(src, dst)
console.log('同步成功!') .then(() => {
}).catch((e) => { console.log("同步成功!");
console.log('同步失败!', e) })
}) .catch((e) => {
console.log("同步失败!", e);
});
} }
} };
//copy同步 //copy同步
export const copyCommon = async () => { export const copyCommon = async () => {
const src = path.resolve(__dirname, '../Common') const src = path.resolve(__dirname, "../Common");
const dst = path.resolve(__dirname, '../../../client/assets/Scripts/Common') const dst = path.resolve(__dirname, "../../../client/assets/Scripts/Common");
console.log(src, dst); console.log(src, dst);
// clean // clean
await fs.remove(dst) await fs.remove(dst);
//create //create
await fs.ensureDir(dst) await fs.ensureDir(dst);
// copy // copy
await fs.copy(src, dst) await fs.copy(src, dst);
console.log('同步成功!') console.log("同步成功!");
} };
export const toArrayBuffer = (buffer: Buffer) => { export const buffer2ArrayBuffer = (buffer: Buffer) => {
var ab = new ArrayBuffer(buffer.length); var ab = new ArrayBuffer(buffer.length);
var view = new Uint8Array(ab); var view = new Uint8Array(ab);
for (var i = 0; i < buffer.length; ++i) { for (var i = 0; i < buffer.length; ++i) {
view[i] = buffer[i]; view[i] = buffer[i];
} }
return ab; return ab;
} };

View File

@ -7,8 +7,6 @@
"apps/*" "apps/*"
], ],
"scripts": { "scripts": {
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"dev": "yarn workspace @game/server run dev" "dev": "yarn workspace @game/server run dev"
} }
} }