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, 下次处理

相关推荐
临水逸2 小时前
AI游戏 《博弈法则:辞退谈判》
游戏
D_C_tyu3 小时前
HTML | 结合Canvas开发具有智能寻路功能的贪吃蛇小游戏实战详解
javascript·算法·游戏·html·bfs
云边有个稻草人4 小时前
重温童年游戏时光:RetroArch-web 使用体验与部署分享
游戏
林鸿群4 小时前
.NET 10 打造 Google Play 风格游戏商城首页 - 完整实战
游戏·.net
MORE_774 小时前
leecode100-跳跃游戏-贪心算法
算法·游戏·贪心算法
前端不太难4 小时前
AI + 鸿蒙游戏,会不会是下一个爆点?
人工智能·游戏·harmonyos
MORE_775 小时前
leecode100-跳跃游戏2-贪心算法
算法·游戏·贪心算法
做cv的小昊19 小时前
结合代码读3DGS论文(10)——ICLR 2025 3DGS加速&压缩新工作Sort-Free 3DGS论文及代码解读
论文阅读·人工智能·游戏·计算机视觉·3d·图形渲染·3dgs
风酥糖1 天前
Godot游戏练习01-第17节-状态机管理的敌人
算法·游戏·godot