diff --git a/examples/cocos-creator-multiplayer/backend/src/shared/game/GameSystem.ts b/examples/cocos-creator-multiplayer/backend/src/shared/game/GameSystem.ts index 805b5ac..e73064b 100644 --- a/examples/cocos-creator-multiplayer/backend/src/shared/game/GameSystem.ts +++ b/examples/cocos-creator-multiplayer/backend/src/shared/game/GameSystem.ts @@ -46,13 +46,8 @@ export class GameSystem { this._state.arrows.push({ id: this._state.nextArrowId++, fromPlayerId: input.playerId, - startPos: { ...player.pos }, - startTime: this._state.now, - targetPos: { - x: player.pos.x + input.offset.x, - y: player.pos.y + input.offset.y - }, - targetTime: this._state.now + gameConfig.arrowFlyTime + targetPos: { ...input.targetPos }, + targetTime: input.targetTime }); } } @@ -112,7 +107,10 @@ export interface PlayerMove { export interface PlayerAttack { type: 'PlayerAttack', playerId: number, - offset: { x: number, y: number }, + // 落点坐标 + targetPos: { x: number, y: number }, + // 落点时间(游戏时间) + targetTime: number } export interface PlayerJoin { type: 'PlayerJoin', diff --git a/examples/cocos-creator-multiplayer/backend/src/shared/game/gameConfig.ts b/examples/cocos-creator-multiplayer/backend/src/shared/game/gameConfig.ts index 7f973d2..5b3fef2 100644 --- a/examples/cocos-creator-multiplayer/backend/src/shared/game/gameConfig.ts +++ b/examples/cocos-creator-multiplayer/backend/src/shared/game/gameConfig.ts @@ -1,7 +1,7 @@ export const gameConfig = { syncRate: 10, - // 网络延迟 - networkLag: 0, + // 网络延迟(同时在服务端和客户端生效,所以实际是双倍延迟) + networkLag: 200, // 攻击技能的冷却时间(毫秒) attackCD: 1000, @@ -10,6 +10,6 @@ export const gameConfig = { arrowFlyTime: 500, arrowDistance: 8, - arrowAttackRadius: 3, + arrowAttackRadius: 2, arrowDizzyTime: 1000 } \ No newline at end of file diff --git a/examples/cocos-creator-multiplayer/backend/src/shared/game/state/ArrowState.ts b/examples/cocos-creator-multiplayer/backend/src/shared/game/state/ArrowState.ts index e746b7b..b2d9531 100644 --- a/examples/cocos-creator-multiplayer/backend/src/shared/game/state/ArrowState.ts +++ b/examples/cocos-creator-multiplayer/backend/src/shared/game/state/ArrowState.ts @@ -1,8 +1,6 @@ export type ArrowState = { id: number, fromPlayerId: number, - startTime: number, - startPos: { x: number, y: number }, targetTime: number, targetPos: { x: number, y: number } } \ No newline at end of file diff --git a/examples/cocos-creator-multiplayer/backend/src/shared/protocols/serviceProto.ts b/examples/cocos-creator-multiplayer/backend/src/shared/protocols/serviceProto.ts index b9fdcbb..09ed1e8 100644 --- a/examples/cocos-creator-multiplayer/backend/src/shared/protocols/serviceProto.ts +++ b/examples/cocos-creator-multiplayer/backend/src/shared/protocols/serviceProto.ts @@ -17,7 +17,6 @@ export interface ServiceType { } export const serviceProto: ServiceProto = { - "version": 2, "services": [ { "id": 0, @@ -159,8 +158,8 @@ export const serviceProto: ServiceProto = { } }, { - "id": 3, - "name": "offset", + "id": 2, + "name": "targetPos", "type": { "type": "Interface", "properties": [ @@ -180,6 +179,13 @@ export const serviceProto: ServiceProto = { } ] } + }, + { + "id": 3, + "name": "targetTime", + "type": { + "type": "Number" + } } ] }, @@ -228,7 +234,7 @@ export const serviceProto: ServiceProto = { } }, { - "id": 4, + "id": 2, "name": "arrows", "type": { "type": "Array", @@ -309,43 +315,13 @@ export const serviceProto: ServiceProto = { }, { "id": 2, - "name": "startTime", - "type": { - "type": "Number" - } - }, - { - "id": 3, - "name": "startPos", - "type": { - "type": "Interface", - "properties": [ - { - "id": 0, - "name": "x", - "type": { - "type": "Number" - } - }, - { - "id": 1, - "name": "y", - "type": { - "type": "Number" - } - } - ] - } - }, - { - "id": 4, "name": "targetTime", "type": { "type": "Number" } }, { - "id": 5, + "id": 3, "name": "targetPos", "type": { "type": "Interface", diff --git a/examples/cocos-creator-multiplayer/frontend/assets/prefabs/Arrow/Arrow.ts b/examples/cocos-creator-multiplayer/frontend/assets/prefabs/Arrow/Arrow.ts index 6d8b8d7..4fc551c 100644 --- a/examples/cocos-creator-multiplayer/frontend/assets/prefabs/Arrow/Arrow.ts +++ b/examples/cocos-creator-multiplayer/frontend/assets/prefabs/Arrow/Arrow.ts @@ -12,31 +12,44 @@ export class Arrow extends Component { id!: number; state!: ArrowState; + // 开始位置(场景坐标) private _startPos = new Vec3; + // 落点位置(场景坐标) private _endPos = new Vec3; + // 开始时间(场景时间) + private _startTime = 0; + // 结束时间(场景时间) + private _endTime = 0; - init(state: ArrowState) { + init(state: ArrowState, startPos: Vec3, now: number) { this.id = state.id; this.state = state; - this._startPos.set(state.startPos.x, 0, -state.startPos.y); + this._startPos.set(startPos); this._endPos.set(state.targetPos.x, 0, -state.targetPos.y); + this._startTime = Date.now(); + this._endTime = this._startTime + state.targetTime - now; + + this._updatePosAndForward(0); } - updateState(state: ArrowState, now: number) { - let percent = MathUtil.limit((now - state.startTime) / (state.targetTime - state.startTime), 0, 1); - + update() { //下一个目标位置 - let newPos = this._startPos.clone().lerp(this._endPos, percent) - //下一个目标位置的高度 - newPos.y = ARROW_TOP * Math.cos(percent * Math.PI - Math.PI / 2); + let percent = MathUtil.limit((Date.now() - this._startTime) / (this._endTime - this._startTime), 0, 1); + this._updatePosAndForward(percent); + } + + private _updatePosAndForward(percent: number) { + let nextPos = this._getPos(percent); + this.node.position = nextPos; //武器朝向下一个目标位置, 形成曲线飞行的感觉 - let newForward = newPos.clone().subtract(this.node.position).normalize(); - if (!newForward.equals(Vec3.ZERO)) { - this.node.forward = newForward; - } - - this.node.position = newPos; + let lastPos = this._getPos(percent - 0.01) + this.node.forward = nextPos.clone().subtract(lastPos).normalize(); } + private _getPos(percent: number) { + let pos = this._startPos.clone().lerp(this._endPos, percent) + pos.y = ARROW_TOP * Math.cos(percent * Math.PI - Math.PI / 2); + return pos; + } } \ No newline at end of file diff --git a/examples/cocos-creator-multiplayer/frontend/assets/prefabs/Player/Player.ts b/examples/cocos-creator-multiplayer/frontend/assets/prefabs/Player/Player.ts index 5af1a66..f027080 100644 --- a/examples/cocos-creator-multiplayer/frontend/assets/prefabs/Player/Player.ts +++ b/examples/cocos-creator-multiplayer/frontend/assets/prefabs/Player/Player.ts @@ -1,5 +1,6 @@ -import { Component, MeshRenderer, SkeletalAnimation, Texture2D, tween, Tween, Vec3, _decorator } from 'cc'; +import { Component, MeshRenderer, SkeletalAnimation, Texture2D, tween, Vec3, _decorator } from 'cc'; +import { TweenPool } from '../../scripts/models/TweenPool'; import { gameConfig } from '../../scripts/shared/game/gameConfig'; import { PlayerState } from '../../scripts/shared/game/state/PlayerState'; const { ccclass, property } = _decorator; @@ -22,7 +23,7 @@ export class Player extends Component { state!: PlayerState; now: number = 0; - private _tweens: Tween[] = []; + private _tweens = new TweenPool; private _targetPos = new Vec3; start() { @@ -73,14 +74,12 @@ export class Player extends Component { // 更新位置 let newPos = new Vec3(state.pos.x, 0, -state.pos.y); if (!this._targetPos.equals(newPos)) { - // 清理 Tween - this._tweens?.forEach(v => v.stop()); - this._tweens = []; + this._tweens.clear(); this.node.setPosition(this._targetPos); // 插值朝向 let newForward = new Vec3(newPos).subtract(this.node.position).normalize(); - this._tweens.push(tween({ forward: this.node.forward }).to(0.1, { forward: newForward }, { + this._tweens.add(tween({ forward: this.node.forward }).to(0.1, { forward: newForward }, { onUpdate: (v: any) => { this.node.forward = new Vec3(v.forward); } @@ -89,7 +88,7 @@ export class Player extends Component { // 新的位置 this._targetPos.set(newPos) this.setAni('run'); - this._tweens.push(tween(this.node).to(1 / gameConfig.syncRate, { + this._tweens.add(tween(this.node).to(1 / gameConfig.syncRate, { position: this._targetPos }).call(() => { this.setAni('idle') diff --git a/examples/cocos-creator-multiplayer/frontend/assets/scenes/GameScene/GameScene.ts b/examples/cocos-creator-multiplayer/frontend/assets/scenes/GameScene/GameScene.ts index 6718246..ada0915 100644 --- a/examples/cocos-creator-multiplayer/frontend/assets/scenes/GameScene/GameScene.ts +++ b/examples/cocos-creator-multiplayer/frontend/assets/scenes/GameScene/GameScene.ts @@ -39,7 +39,7 @@ export class GameScene extends Component { @property(FollowCamera) camera: FollowCamera = null as any; - gameManager = new GameManager(); + gameManager!: GameManager; private _playerInstances: { [playerId: number]: Player } = {}; private _arrowInstances: { [arrowId: number]: Arrow } = {}; @@ -60,11 +60,11 @@ export class GameScene extends Component { } } + this.gameManager = new GameManager(); this.gameManager.client.flows.postDisconnectFlow.push(v => { location.reload() return v; - }) - + }); this.gameManager.join(); } @@ -125,13 +125,17 @@ export class GameScene extends Component { for (let arrowState of arrowStates) { let arrow: Arrow = this._arrowInstances[arrowState.id]; if (!arrow) { + let playerState = this.gameManager.state.players.find(v => v.id === arrowState.fromPlayerId); + if (!playerState) { + continue; + } + let playerNode = this._playerInstances[playerState.id].node; + let node = instantiate(this.prefabArrow); this.arrows.addChild(node); arrow = this._arrowInstances[arrowState.id] = node.getComponent(Arrow)!; - arrow.init(arrowState) + arrow.init(arrowState, playerNode.position, this.gameManager.state.now); } - - arrow.updateState(arrowState, this.gameManager.state.now); } // Clear left players @@ -145,11 +149,21 @@ export class GameScene extends Component { } onBtnAttack() { - let offset = this._playerInstances[this.gameManager.selfPlayerId].node.forward.clone().normalize().multiplyScalar(gameConfig.arrowDistance); + let playerState = this.gameManager.state.players.find(v => v.id === this.gameManager.selfPlayerId); + if (!playerState) { + return; + } + + let playerNode = this._playerInstances[this.gameManager.selfPlayerId].node; + // 攻击落点偏移(表现层坐标) + let sceneOffset = playerNode.forward.clone().normalize().multiplyScalar(gameConfig.arrowDistance); + // 攻击落点(逻辑层坐标) + let targetPos = new Vec2(playerState.pos.x, playerState.pos.y).add2f(sceneOffset.x, -sceneOffset.z); this.gameManager.sendClientInput({ type: 'PlayerAttack', - offset: { x: offset.x, y: -offset.z } + // 显示坐标 —> 逻辑坐标 + targetPos: { x: targetPos.x, y: targetPos.y }, + targetTime: this.gameManager.state.now + gameConfig.arrowFlyTime }) } - } \ No newline at end of file diff --git a/examples/cocos-creator-multiplayer/frontend/assets/scripts/models/GameManager.ts b/examples/cocos-creator-multiplayer/frontend/assets/scripts/models/GameManager.ts index 5dd50ca..4546077 100644 --- a/examples/cocos-creator-multiplayer/frontend/assets/scripts/models/GameManager.ts +++ b/examples/cocos-creator-multiplayer/frontend/assets/scripts/models/GameManager.ts @@ -23,7 +23,7 @@ export class GameManager { let client = this.client = new WsClient(serviceProto, { server: `ws://${location.hostname}:3000`, json: true, - // logger: console + logger: console });; client.listenMsg('server/Frame', msg => { this._onServerSync(msg) }); @@ -96,12 +96,18 @@ export class GameManager { return; } + console.log('sendClientInput', input, this.state.now); + let msg: MsgClientInput = { sn: ++this.lastSN, inputs: [input] } this.pendingInputMsgs.push(msg); - this.client.sendMsg('client/ClientInput', msg); + this.client.sendMsg('client/ClientInput', msg).then(v => { + if (!v.isSucc) { + console.error('xxxxxxxxxxxxxx', msg, v) + } + }); // 预测 this.gameSystem.applyInput({ diff --git a/examples/cocos-creator-multiplayer/frontend/assets/scripts/models/TweenPool.ts b/examples/cocos-creator-multiplayer/frontend/assets/scripts/models/TweenPool.ts new file mode 100644 index 0000000..59bb5c7 --- /dev/null +++ b/examples/cocos-creator-multiplayer/frontend/assets/scripts/models/TweenPool.ts @@ -0,0 +1,16 @@ +import { Tween } from "cc"; + +export class TweenPool { + + private _tweens: Tween[] = []; + + add(tween: Tween) { + this._tweens.push(tween); + } + + clear() { + this._tweens?.forEach(v => v.stop()); + this._tweens = []; + } + +} \ No newline at end of file diff --git a/examples/cocos-creator-multiplayer/frontend/assets/scripts/models/TweenPool.ts.meta b/examples/cocos-creator-multiplayer/frontend/assets/scripts/models/TweenPool.ts.meta new file mode 100644 index 0000000..ec33f2d --- /dev/null +++ b/examples/cocos-creator-multiplayer/frontend/assets/scripts/models/TweenPool.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.22", + "importer": "typescript", + "imported": true, + "uuid": "11f8c73c-26e5-4da9-b5b9-4e9223b2c79a", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/examples/cocos-creator-multiplayer/frontend/package.json b/examples/cocos-creator-multiplayer/frontend/package.json index a061085..8155950 100644 --- a/examples/cocos-creator-multiplayer/frontend/package.json +++ b/examples/cocos-creator-multiplayer/frontend/package.json @@ -7,6 +7,6 @@ "version": "3.3.2" }, "dependencies": { - "tsrpc-browser": "^3.1.2" + "tsrpc-browser": "^3.1.3" } }