很多教程说帧同步的关键是“显示与逻辑分离”,但是又没有具体讲解,我起初也没有搞懂这句话的意思,就直接上手开发帧同步了。在开发的过程中,一下子就悟了,所以分享一下。

显示与逻辑未分离(单机)

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呢?可以自行推演一下,是否能够解决不同步现象

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部