优化 Arrow 同步

This commit is contained in:
k8w 2021-12-03 13:53:53 +08:00
parent 50c8976712
commit 906c355348
11 changed files with 110 additions and 81 deletions

View File

@ -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',

View File

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

View File

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

View File

@ -17,7 +17,6 @@ export interface ServiceType {
}
export const serviceProto: ServiceProto<ServiceType> = {
"version": 2,
"services": [
{
"id": 0,
@ -159,8 +158,8 @@ export const serviceProto: ServiceProto<ServiceType> = {
}
},
{
"id": 3,
"name": "offset",
"id": 2,
"name": "targetPos",
"type": {
"type": "Interface",
"properties": [
@ -180,6 +179,13 @@ export const serviceProto: ServiceProto<ServiceType> = {
}
]
}
},
{
"id": 3,
"name": "targetTime",
"type": {
"type": "Number"
}
}
]
},
@ -228,7 +234,7 @@ export const serviceProto: ServiceProto<ServiceType> = {
}
},
{
"id": 4,
"id": 2,
"name": "arrows",
"type": {
"type": "Array",
@ -309,43 +315,13 @@ export const serviceProto: ServiceProto<ServiceType> = {
},
{
"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",

View File

@ -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;
let lastPos = this._getPos(percent - 0.01)
this.node.forward = nextPos.clone().subtract(lastPos).normalize();
}
this.node.position = newPos;
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;
}
}

View File

@ -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<any>[] = [];
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')

View File

@ -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);
this.gameManager.sendClientInput({
type: 'PlayerAttack',
offset: { x: offset.x, y: -offset.z }
})
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',
// 显示坐标 —> 逻辑坐标
targetPos: { x: targetPos.x, y: targetPos.y },
targetTime: this.gameManager.state.now + gameConfig.arrowFlyTime
})
}
}

View File

@ -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({

View File

@ -0,0 +1,16 @@
import { Tween } from "cc";
export class TweenPool {
private _tweens: Tween<any>[] = [];
add(tween: Tween<any>) {
this._tweens.push(tween);
}
clear() {
this._tweens?.forEach(v => v.stop());
this._tweens = [];
}
}

View File

@ -0,0 +1,9 @@
{
"ver": "4.0.22",
"importer": "typescript",
"imported": true,
"uuid": "11f8c73c-26e5-4da9-b5b9-4e9223b2c79a",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@ -7,6 +7,6 @@
"version": "3.3.2"
},
"dependencies": {
"tsrpc-browser": "^3.1.2"
"tsrpc-browser": "^3.1.3"
}
}