联机帧同步射击类游戏终于上线了。再次分享并请教各位。

浏览器最小化,这时候update是停止的,会有问题吧?你的游戏我也玩了,最小化再回来,貌似游戏是暂停的。我是这么写的,渲染帧的帧数和逻辑帧无关,逻辑帧收到消息之后直接设置逻辑位置,然后根据移动速度和方向弧度设置一个逻辑帧移动所需耗费时间,然后渲染帧里面根据update的deltaTime,换算出渲染一次所需时间,然后进行lerp。我现在的表现是,浏览器最小化,逻辑帧继续跑,回复浏览器,渲染帧会直接设置人物位置到最新位置。以下是代码,不知道有没有帮到你:

逻辑帧update:

public update(delta: number) {
if (this.moveRadian != -9999) {
let oldPosition = this.position.clone();
let position = oldPosition.clone().add(this.getV3(this.moveRadian, delta));//目标位置
if (!this.battle.navmesh.isPointInMesh(position)) {//如果目标点是障碍
const dx = position.x - oldPosition.x;
const dy = position.y - oldPosition.y;
let findResult = this.battle.navmesh.findClosestMeshPoint(new Vector2(position.x, position.y), Math.sqrt(dx * dx + dy * dy));
if (!findResult.point) {
return;
}
let radian = Math.atan2(findResult.point.y - oldPosition.y, findResult.point.x - oldPosition.x);
position = oldPosition.clone().add(this.getV3(radian, delta));//重设目标位置
}
this.position = position;
this.direction = this.position.clone().subtract(oldPosition);
this.totalTime = this.direction.length() / this.moveSpeed;
this.walkTime = 0;
}
}

渲染帧update:

private update(delta: number) {
if (this.hero.walkTime >= this.hero.totalTime) {
return;
}
this.hero.walkTime += delta;
if (this.hero.walkTime >= this.hero.totalTime) {
this.hero.walkTime = this.hero.totalTime;
}
let ratio = Math.floor(this.hero.walkTime / this.hero.totalTime * 1000) / 1000;
this.node.position = this.node.position.lerp(this.hero.position, ratio);
}

谢谢:pray: 我的邮箱392883202@qq.com

十分感谢啦,我因为这个物理困扰了好久。有几个疑问向您请教:
1没用引擎自带的物理,碰撞是怎么写的,贴墙滑动是怎么实现的呢?
2玩家操作人物移动会先表现出来吗,我有点不太明白同步玩家的位置 是玩家已经发生位移后再上传到服务器的吗?
3同步玩家子弹是怎么做的呢?

你的update算法里面没有用上自带参数dt对吧,这样的话在不同的手机,每次执行update的时间都是不一致的,是不是就是因为这点导致有的手机会卡一下

严格一点说我的是状态同步,即同步玩家位置的xy坐标信息
1、就是在update中,你先计算人物本帧移动后的位置坐标xy,如果xy不在障碍物内,将人物坐标发送给服务端,如果在障碍物内,此时,可以2种操作方法,一种是人物停止不动,一种是让人物沿着障碍物贴墙滑动,一般来说2d游戏我们障碍物比较简单,都是长方形,贴墙滑动要不就是x=x+v,要不就y=y+v,v是人物移动速度,具体是x还是y要自己判断,另外,如果复杂点,还要继续判断x+v或y+v后是否还在障碍物内。
2、如第一点,玩家移动流程是这样的:玩家位置信息xy计算后将结果发给服务器(玩家的node此时并不移动),玩家真正的位置移动需要在收到帧消息并处理好帧逻辑后,在玩家node的update里渲染的。
3、子弹同步:玩家在客户端点击射击键后,此时客户端将这个开枪信号flag=1传送给服务端,服务端广播给所有客户端,客户端收到后判断玩家开枪信号flag是否等于1,决定玩家是否射出子弹,然后子弹飞行时间是1个逻辑帧还是多少个逻辑帧需要你自己处理,并且还要计算该逻辑帧下是否和玩家、障碍物碰撞了。flag只是举例,现实可能flag很有可能包含比如子弹射击的方向、速度等,具体看你情况。

我之所以用位置同步,是因为如果只同步玩家输入,那么每个玩家和障碍物的碰撞都要在每个客户端计算,我的10人游戏就要计算10遍,机器效率跟不上,所以改成了每个客户端只计算自己和障碍物的计算,这样效率提高了很多,不会这么卡,如果你也是多人游戏,建议你用这个方式。
另外,定点数在这个地址下载,我的跟他几乎完全一样的,我邮箱也不发你了。

是的,是状态同步

mark,我先看看

谢谢。有个疑问,比如2个玩家,在第0帧的时候,大家都没有移动。到第1帧时,玩家1移动了,到第6帧同步到服务器,这个时候服务器正好第100ms下发给其他玩家,玩家2在第9帧收到了玩家1的移动,而玩家1在这个时候已经又发生了移动,玩家2在第10帧向玩家1发射了一发子弹,而这个时候玩家2看到的玩家1的位置和玩家1看到自己的位置是一致的吗?

我现在是以帧数,也就是6作为除数来平滑移动,我之前也用过dt作为除数来平滑移动,但是发现这样卡顿更严重,我现在的方法反而流畅多了

在第1帧移动了,那么就会在第1帧同步,不可能到第6帧才同步的。
第一帧移动:收到服务端下发的第0帧–>验证操作,如果没有则无视,获取摇杆方向、A键按下并发送到服务端–>服务端下发第1帧–>验证操作,得到摇杆方向和A键按下,移动人物1帧,创建子弹(假设A键是射击)

以上的帧是指逻辑帧(根据服务端帧频,一般15fps-30fps,固定的),不是渲染帧(客户端帧频一般60fps,根据性能不固定)

dt是不可能卡顿的,等于把帧跟帧之间不固定的时间差给平滑碎片化了,如果缓存逻辑帧几帧,那么不可能不平滑。。我现在不缓存也非常平滑。

你是没有区分好逻辑帧和渲染帧update的区别。逻辑帧做计算,而渲染帧只处理渲染不做任何计算,任何计算都在逻辑帧中处理,比如说玩家在该逻辑帧后的位置、子弹是否与人物碰撞,将计算结果放到渲染帧update里面显示就可以了。由于逻辑帧都是每100ms同步一次,每100ms计算一次位置、碰撞,所以所有客户端可以在同一个逻辑帧保持同步,这就是帧同步、状态同步的原理。你这里的描述是明显都是在渲染帧update处理了计算了,所以没法同步。

你的游戏摄像机会跟随主角玩家移动吗

抱歉,回复错人了

您的玩家移动是先表现出来,再上传给服务器的吗?

玩了一下,穿墙很频繁,有时都穿墙到地图外面去了

现在这样的体验也就ok了,要再优化,就得网络延时的时候做预测,而预测就会有纠正,纠正又涉及到平滑修复,涉及的点还是很多的。

你看上面几楼回复你的第二点,是先将位置上传给服务端,服务端广播帧消息到客户端,然后客户端再表现

感谢提醒,我也有发现这个bug,但是好像并不频繁吧