很多教程说帧同步的关键是“显示与逻辑分离”,但是又没有具体讲解,我起初也没有搞懂这句话的意思,就直接上手开发帧同步了。在开发的过程中,一下子就悟了,所以分享一下。
显示与逻辑未分离(单机)
func _physics_process(delta):
# 一些处理(如伤害判断)
func _on_animated_sprite_2d_animation_finished() -> void:
if $AnimatedSprite2D.animation=="斩击":
set_process(false)
$AnimatedSprite2D.play("后摇")
if $AnimatedSprite2D.animation=="后摇":
queue_free()
看上面这个代码,在“斩击”动画过程中,进行伤害判断。“斩击”动画结束后,播放“后摇”动画,并停止process处理。在“后摇”动画结束后,删除节点。
这就是“显示与逻辑未分离”,每一步操作都依赖于动画的播放进程。
显示与逻辑分离(多人游戏帧同步)
1.帧同步的前置要求
简单提一下,看不懂就算了。
在帧同步中,每一个涉及到物理操作(如移动)的节点(怪物节点,子弹节点),都不应该执行自己的_physics_process()函数,而是要和核心场景的clientFrame同步的。若网络慢,核心场景clientFrame没有累加,那么该节点也不应该执行物理操作。
下面是例子,所有的process中的操作,都应该由Muti_game节点控制
func update_bullet():
var bullet_arr = get_child(2).get_children()
for _item in bullet_arr:
_item.do() # 手动执行一次物理
pass
func update_player():
···
func update_monster():
var monster_arr = get_child(1).get_node("Land/Monster").get_children()
for _item in monster_arr:
_item.get_node("FSM").do_() # 手动执行一次物理
func updateClient():
if logicFrame==lastSyncFrame:
···
if lastSyncFrame>logicFrame+1:
···
if lastSyncFrame==logicFrame+1:
clientFrame+=1 # 客户端帧
update_monster() # 更新怪物
update_bullet() # 更新子弹
update_player() # 更新玩家
if clientFrame==(logicFrame+1)*GameControl.ClientServerFrameRate:
logicFrame+=1
func _physics_process(delta):
updateClient()
sendInput()
2. 分离
多人模式下,我们禁用了节点的_physics_process(),并将其中的内容提取为do函数,供核心场景调用。
我们还把基于动画的操作步骤,替换为了基于参数atk_num和houyao_num。
var atk_num = 60 # 斩击持续的帧数
var houyao_num = 30 # 后摇持续的帧数
func do():
if houyao_num<=0:
# 切换到待机状态
pass
if atk_num<=0: # 斩击结束
$AnimatedSprite2D.play("后摇")
houyao_num-=1
return
if atk_num>0:
# 一些处理(如伤害判断)
pass
atk_num-=1 # 计数更新
func _physics_process(delta):
if 处于多人模式:
return
do()
# func _on_animated_sprite_2d_animation_finished() -> void:
# if $AnimatedSprite2D.animation=="斩击":
# set_process(false)
# $AnimatedSprite2D.play("后摇")
# if $AnimatedSprite2D.animation=="后摇":
# queue_free()
3. 分析
为什么要分离。下面举反例。
假如有两个客户端A与B。
客户端A在 0s 时刻收到了来自第n帧的“斩击”指令,创建了斩击节点,并开始播放斩击动画,持续1s。
客户端B在 0.5s 时刻收到了来自第n帧的“斩击”指令,创建了斩击节点,并开始播放斩击动画,持续1s。
然后客户端A和B都在 1.2s 时刻收到了来自第n+1帧的“do”指令。
因为客户端A的斩击动画在1s 时刻播放完毕,并开始播放了后摇动画,所以do操作没有进行伤害判断。
而客户端B的斩击动画将在 1.5s 时刻结束,此时仍在播放,故do操作进行了伤害判断。
以上过程,造成了不同步现象
如果我将斩击的持续时间,量化为atk_num呢?可以自行推演一下,是否能够解决不同步现象