优化 Arrow 同步
This commit is contained in:
parent
50c8976712
commit
906c355348
@ -46,13 +46,8 @@ export class GameSystem {
|
|||||||
this._state.arrows.push({
|
this._state.arrows.push({
|
||||||
id: this._state.nextArrowId++,
|
id: this._state.nextArrowId++,
|
||||||
fromPlayerId: input.playerId,
|
fromPlayerId: input.playerId,
|
||||||
startPos: { ...player.pos },
|
targetPos: { ...input.targetPos },
|
||||||
startTime: this._state.now,
|
targetTime: input.targetTime
|
||||||
targetPos: {
|
|
||||||
x: player.pos.x + input.offset.x,
|
|
||||||
y: player.pos.y + input.offset.y
|
|
||||||
},
|
|
||||||
targetTime: this._state.now + gameConfig.arrowFlyTime
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -112,7 +107,10 @@ export interface PlayerMove {
|
|||||||
export interface PlayerAttack {
|
export interface PlayerAttack {
|
||||||
type: 'PlayerAttack',
|
type: 'PlayerAttack',
|
||||||
playerId: number,
|
playerId: number,
|
||||||
offset: { x: number, y: number },
|
// 落点坐标
|
||||||
|
targetPos: { x: number, y: number },
|
||||||
|
// 落点时间(游戏时间)
|
||||||
|
targetTime: number
|
||||||
}
|
}
|
||||||
export interface PlayerJoin {
|
export interface PlayerJoin {
|
||||||
type: 'PlayerJoin',
|
type: 'PlayerJoin',
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
export const gameConfig = {
|
export const gameConfig = {
|
||||||
syncRate: 10,
|
syncRate: 10,
|
||||||
// 网络延迟
|
// 网络延迟(同时在服务端和客户端生效,所以实际是双倍延迟)
|
||||||
networkLag: 0,
|
networkLag: 200,
|
||||||
|
|
||||||
// 攻击技能的冷却时间(毫秒)
|
// 攻击技能的冷却时间(毫秒)
|
||||||
attackCD: 1000,
|
attackCD: 1000,
|
||||||
@ -10,6 +10,6 @@ export const gameConfig = {
|
|||||||
|
|
||||||
arrowFlyTime: 500,
|
arrowFlyTime: 500,
|
||||||
arrowDistance: 8,
|
arrowDistance: 8,
|
||||||
arrowAttackRadius: 3,
|
arrowAttackRadius: 2,
|
||||||
arrowDizzyTime: 1000
|
arrowDizzyTime: 1000
|
||||||
}
|
}
|
@ -1,8 +1,6 @@
|
|||||||
export type ArrowState = {
|
export type ArrowState = {
|
||||||
id: number,
|
id: number,
|
||||||
fromPlayerId: number,
|
fromPlayerId: number,
|
||||||
startTime: number,
|
|
||||||
startPos: { x: number, y: number },
|
|
||||||
targetTime: number,
|
targetTime: number,
|
||||||
targetPos: { x: number, y: number }
|
targetPos: { x: number, y: number }
|
||||||
}
|
}
|
@ -17,7 +17,6 @@ export interface ServiceType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const serviceProto: ServiceProto<ServiceType> = {
|
export const serviceProto: ServiceProto<ServiceType> = {
|
||||||
"version": 2,
|
|
||||||
"services": [
|
"services": [
|
||||||
{
|
{
|
||||||
"id": 0,
|
"id": 0,
|
||||||
@ -159,8 +158,8 @@ export const serviceProto: ServiceProto<ServiceType> = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 3,
|
"id": 2,
|
||||||
"name": "offset",
|
"name": "targetPos",
|
||||||
"type": {
|
"type": {
|
||||||
"type": "Interface",
|
"type": "Interface",
|
||||||
"properties": [
|
"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",
|
"name": "arrows",
|
||||||
"type": {
|
"type": {
|
||||||
"type": "Array",
|
"type": "Array",
|
||||||
@ -309,43 +315,13 @@ export const serviceProto: ServiceProto<ServiceType> = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 2,
|
"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",
|
"name": "targetTime",
|
||||||
"type": {
|
"type": {
|
||||||
"type": "Number"
|
"type": "Number"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 5,
|
"id": 3,
|
||||||
"name": "targetPos",
|
"name": "targetPos",
|
||||||
"type": {
|
"type": {
|
||||||
"type": "Interface",
|
"type": "Interface",
|
||||||
|
@ -12,31 +12,44 @@ export class Arrow extends Component {
|
|||||||
id!: number;
|
id!: number;
|
||||||
state!: ArrowState;
|
state!: ArrowState;
|
||||||
|
|
||||||
|
// 开始位置(场景坐标)
|
||||||
private _startPos = new Vec3;
|
private _startPos = new Vec3;
|
||||||
|
// 落点位置(场景坐标)
|
||||||
private _endPos = 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.id = state.id;
|
||||||
this.state = state;
|
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._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) {
|
update() {
|
||||||
let percent = MathUtil.limit((now - state.startTime) / (state.targetTime - state.startTime), 0, 1);
|
|
||||||
|
|
||||||
//下一个目标位置
|
//下一个目标位置
|
||||||
let newPos = this._startPos.clone().lerp(this._endPos, percent)
|
let percent = MathUtil.limit((Date.now() - this._startTime) / (this._endTime - this._startTime), 0, 1);
|
||||||
//下一个目标位置的高度
|
this._updatePosAndForward(percent);
|
||||||
newPos.y = ARROW_TOP * Math.cos(percent * Math.PI - Math.PI / 2);
|
}
|
||||||
|
|
||||||
|
private _updatePosAndForward(percent: number) {
|
||||||
|
let nextPos = this._getPos(percent);
|
||||||
|
this.node.position = nextPos;
|
||||||
|
|
||||||
//武器朝向下一个目标位置, 形成曲线飞行的感觉
|
//武器朝向下一个目标位置, 形成曲线飞行的感觉
|
||||||
let newForward = newPos.clone().subtract(this.node.position).normalize();
|
let lastPos = this._getPos(percent - 0.01)
|
||||||
if (!newForward.equals(Vec3.ZERO)) {
|
this.node.forward = nextPos.clone().subtract(lastPos).normalize();
|
||||||
this.node.forward = newForward;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
||||||
|
}
|
||||||
}
|
}
|
@ -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 { gameConfig } from '../../scripts/shared/game/gameConfig';
|
||||||
import { PlayerState } from '../../scripts/shared/game/state/PlayerState';
|
import { PlayerState } from '../../scripts/shared/game/state/PlayerState';
|
||||||
const { ccclass, property } = _decorator;
|
const { ccclass, property } = _decorator;
|
||||||
@ -22,7 +23,7 @@ export class Player extends Component {
|
|||||||
state!: PlayerState;
|
state!: PlayerState;
|
||||||
now: number = 0;
|
now: number = 0;
|
||||||
|
|
||||||
private _tweens: Tween<any>[] = [];
|
private _tweens = new TweenPool;
|
||||||
private _targetPos = new Vec3;
|
private _targetPos = new Vec3;
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
@ -73,14 +74,12 @@ export class Player extends Component {
|
|||||||
// 更新位置
|
// 更新位置
|
||||||
let newPos = new Vec3(state.pos.x, 0, -state.pos.y);
|
let newPos = new Vec3(state.pos.x, 0, -state.pos.y);
|
||||||
if (!this._targetPos.equals(newPos)) {
|
if (!this._targetPos.equals(newPos)) {
|
||||||
// 清理 Tween
|
this._tweens.clear();
|
||||||
this._tweens?.forEach(v => v.stop());
|
|
||||||
this._tweens = [];
|
|
||||||
this.node.setPosition(this._targetPos);
|
this.node.setPosition(this._targetPos);
|
||||||
|
|
||||||
// 插值朝向
|
// 插值朝向
|
||||||
let newForward = new Vec3(newPos).subtract(this.node.position).normalize();
|
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) => {
|
onUpdate: (v: any) => {
|
||||||
this.node.forward = new Vec3(v.forward);
|
this.node.forward = new Vec3(v.forward);
|
||||||
}
|
}
|
||||||
@ -89,7 +88,7 @@ export class Player extends Component {
|
|||||||
// 新的位置
|
// 新的位置
|
||||||
this._targetPos.set(newPos)
|
this._targetPos.set(newPos)
|
||||||
this.setAni('run');
|
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
|
position: this._targetPos
|
||||||
}).call(() => {
|
}).call(() => {
|
||||||
this.setAni('idle')
|
this.setAni('idle')
|
||||||
|
@ -39,7 +39,7 @@ export class GameScene extends Component {
|
|||||||
@property(FollowCamera)
|
@property(FollowCamera)
|
||||||
camera: FollowCamera = null as any;
|
camera: FollowCamera = null as any;
|
||||||
|
|
||||||
gameManager = new GameManager();
|
gameManager!: GameManager;
|
||||||
|
|
||||||
private _playerInstances: { [playerId: number]: Player } = {};
|
private _playerInstances: { [playerId: number]: Player } = {};
|
||||||
private _arrowInstances: { [arrowId: number]: Arrow } = {};
|
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 => {
|
this.gameManager.client.flows.postDisconnectFlow.push(v => {
|
||||||
location.reload()
|
location.reload()
|
||||||
return v;
|
return v;
|
||||||
})
|
});
|
||||||
|
|
||||||
this.gameManager.join();
|
this.gameManager.join();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,13 +125,17 @@ export class GameScene extends Component {
|
|||||||
for (let arrowState of arrowStates) {
|
for (let arrowState of arrowStates) {
|
||||||
let arrow: Arrow = this._arrowInstances[arrowState.id];
|
let arrow: Arrow = this._arrowInstances[arrowState.id];
|
||||||
if (!arrow) {
|
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);
|
let node = instantiate(this.prefabArrow);
|
||||||
this.arrows.addChild(node);
|
this.arrows.addChild(node);
|
||||||
arrow = this._arrowInstances[arrowState.id] = node.getComponent(Arrow)!;
|
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
|
// Clear left players
|
||||||
@ -145,11 +149,21 @@ export class GameScene extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onBtnAttack() {
|
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({
|
this.gameManager.sendClientInput({
|
||||||
type: 'PlayerAttack',
|
type: 'PlayerAttack',
|
||||||
offset: { x: offset.x, y: -offset.z }
|
// 显示坐标 —> 逻辑坐标
|
||||||
|
targetPos: { x: targetPos.x, y: targetPos.y },
|
||||||
|
targetTime: this.gameManager.state.now + gameConfig.arrowFlyTime
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -23,7 +23,7 @@ export class GameManager {
|
|||||||
let client = this.client = new WsClient(serviceProto, {
|
let client = this.client = new WsClient(serviceProto, {
|
||||||
server: `ws://${location.hostname}:3000`,
|
server: `ws://${location.hostname}:3000`,
|
||||||
json: true,
|
json: true,
|
||||||
// logger: console
|
logger: console
|
||||||
});;
|
});;
|
||||||
client.listenMsg('server/Frame', msg => { this._onServerSync(msg) });
|
client.listenMsg('server/Frame', msg => { this._onServerSync(msg) });
|
||||||
|
|
||||||
@ -96,12 +96,18 @@ export class GameManager {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('sendClientInput', input, this.state.now);
|
||||||
|
|
||||||
let msg: MsgClientInput = {
|
let msg: MsgClientInput = {
|
||||||
sn: ++this.lastSN,
|
sn: ++this.lastSN,
|
||||||
inputs: [input]
|
inputs: [input]
|
||||||
}
|
}
|
||||||
this.pendingInputMsgs.push(msg);
|
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({
|
this.gameSystem.applyInput({
|
||||||
|
@ -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 = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"ver": "4.0.22",
|
||||||
|
"importer": "typescript",
|
||||||
|
"imported": true,
|
||||||
|
"uuid": "11f8c73c-26e5-4da9-b5b9-4e9223b2c79a",
|
||||||
|
"files": [],
|
||||||
|
"subMetas": {},
|
||||||
|
"userData": {}
|
||||||
|
}
|
@ -7,6 +7,6 @@
|
|||||||
"version": "3.3.2"
|
"version": "3.3.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tsrpc-browser": "^3.1.2"
|
"tsrpc-browser": "^3.1.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user