Godot游戏练习01-第18节-玩家死亡与复活,游戏失败检测

在之前的章节中我们实现了敌人与玩家的相互攻击, 碰撞判断, 但是一直未正式处理玩家的死亡事件, 也没有判定游戏失败, 今天先实现机制

看看效果

  • 玩家在多次受伤后死亡
  • 所有玩家死亡后游戏结束, 目前仅回到主菜单
  • 游戏可以再次正常开始

实现过程

玩家死亡

我们之前添加过Player的HurtboxComponent组件, 在生命值耗尽时仅打印信息

现在在生命值耗尽后添加queue_free(), 简单处理Player死亡信号, 死亡就释放节点

但是在添加简单的处理后, 实际运行时发生了报错, 当Client的Player在移动时死亡, 非常容易复现

gdscript 复制代码
E 0:00:28:334   get_node: Node not found: "Main/YSortRoot/Player219449027/PlayerInputMultiplayerSynchronizerComponent" (relative to "/root").
  <C++ Error>   Method/function failed. Returning: nullptr
  <C++ Source>  scene/main/node.cpp:1963 @ get_node()

报错如下:

  • Client的Player在移动中, Player节点的authority为1(服务器), 而Player节点下的InputComponent的authority为Client本身
  • 数据的同步方向是从authority同步到其他peer, Client的Player移动时就从其InputComponent一直同步到服务器和其他peer
  • 但所有Player节点的authority属于服务器, 也只有服务器在检测物理事件, 当服务器检测到Client的Player死亡时, 执行了queue_free()
  • 服务器上的Player节点先释放, 之后通过网络同步释放Client上的Player节点, 这中间有网络延时
  • 在该延时期间, Client上的InputComponent组件还在尝试向Server同步输入信息, 而Server上的对应节点已经不存在了, 出现报错

该问题暂时标记为问题1, 下次处理

玩家重生

首先在main.gd记录死亡的玩家

gdscript 复制代码
var died_peers: Array[int] = []

# other codes...

func _ready() -> void:
	multiplayer_spawner.spawn_function = func(data):
		print("[peer %s] Spawn player: %s, pos: %s" % [multiplayer.get_unique_id(), data.peer_id, player_spawn_marker.global_position])
		var player = PLAYER.instantiate() as Player
		player.name = "Player%s" % [data.peer_id]
		player.input_peer_id = data.peer_id
		player.global_position = player_spawn_marker.global_position
		if is_multiplayer_authority():
			player.died.connect(_on_player_died.bind(data.peer_id))
		return player
	_peer_ready.rpc_id(1)
	if is_multiplayer_authority():
		enemy_spawn_component.round_completed.connect(_on_round_completed)
	else:
		multiplayer.server_disconnected.connect(_on_server_disconnected)

# other codes...

func _on_player_died(peer_id: int) -> void:
	died_peers.append(peer_id)
	_check_game_over()

player新增died信号, 在死亡时触发, main中创建player时监听

然后在EnemySpawnComponent组件中检测round完成时发出round_completed信号, 依旧在main中监听

之后直接调用spawn函数处理所有死亡的玩家, 即可实现新一轮游戏中所有玩家复活的效果

gdscript 复制代码
func _on_round_completed() -> void:
	for peer_id in died_peers:
		multiplayer_spawner.spawn({ "peer_id": peer_id })
	died_peers.clear()

游戏失败检测

当所有Player都死亡, 则判定为失败, 在每一个Player的死亡信号触发时进行一次检测

若游戏失败, 则关闭multiplayer_peer, 并回到主菜单 (注意: 这是服务器的逻辑)

gdscript 复制代码
func _end_game() -> void:
	multiplayer.multiplayer_peer = null
	get_tree().change_scene_to_file("res://ui/menu/main_menu.tscn")


func _check_game_over() -> void:
	# multiplayer.get_peers 返回所有已连接的peer,不包含自身
	var all_peers := multiplayer.get_peers()
	all_peers.append(multiplayer.get_unique_id())
	var is_game_over := true
	for peer_id in all_peers:
		if not died_peers.has(peer_id):
			is_game_over = false
			break
	if is_game_over:
		_end_game()

服务器关闭multiplayer_peer, 实际上会导致已连接的peer收到server_disconnected信号

进行监听和处理即可

在上面的_ready中已经展现, 当Client收到该信号时也调用_end_game, 关闭peer并切换场景

gdscript 复制代码
multiplayer.server_disconnected.connect(_on_server_disconnected)

# other codes...

func _on_server_disconnected() -> void:
	_end_game()

保存, 运行, 效果可以, 但是又出现报错

gdscript 复制代码
E 0:00:18:010   main.gd:40 @ _end_game(): Removing a CollisionObject node during a physics callback is not allowed and will cause undesired behavior. Remove with call_deferred() instead.
  <C++ Source>  scene/2d/physics/collision_object_2d.cpp:98 @ _notification()
  <Stack Trace> main.gd:40 @ _end_game()
                main.gd:53 @ _check_game_over()
                main.gd:58 @ _on_player_died()
                player.gd:69 @ _on_health_depleted()
                health_component.gd:15 @ take_damage()
                hurtbox_component.gd:15 @ take_damage()
                hurtbox_component.gd:22 @ _on_area_entered()

通过调用栈和报错信息可知, 该错误是物理回调(area_entered)中经过一系列判断触发了_end_game切换场景移除了碰撞体导致

也就是说: Godot中不允许在物理回调中移除碰撞体 , 报错信息中推荐使用call_deferred处理

这里记为问题2, 下次处理

相关推荐
SmalBox6 小时前
【节点】[SplitTextureTransform节点]原理解析与实际应用
unity3d·游戏开发·图形学
SmalBox1 天前
【节点】[SampleVirtualTexture节点]原理解析与实际应用
unity3d·游戏开发·图形学
fetasty1 天前
Godot游戏练习01-第17节-状态机管理的敌人
游戏开发
云边散步1 天前
godot2D游戏教程系列二(19)
笔记·学习·游戏·游戏开发
SmalBox3 天前
【节点】[SampleTexture3D节点]原理解析与实际应用
unity3d·游戏开发·图形学
JCHwa3 天前
UE5 GAS 源码深度解析 | 第2篇:AttributeSet 源码导读
游戏开发·unreal engine
SmalBox4 天前
【节点】[SampleTexture2DLOD节点]原理解析与实际应用
unity3d·游戏开发·图形学
fetasty4 天前
Godot游戏练习01-第15节-敌人生成动画,翻转,碰撞
游戏开发
SmalBox6 天前
【节点】[SampleTexture2D节点]原理解析与实际应用
unity3d·游戏开发·图形学