Godot游戏练习01-第9节-游戏轮次

今天我给游戏添加了轮次, 大概是关卡的意思, 每一关有一个固定时间, 当玩家杀完轮次内生成的所有敌人, 则完成当前轮次, 游戏进入下一个轮次, 随着轮次增加, 游戏难度也会逐渐增加

看看效果

当前游戏轮次的调整不明显, 主要是增加机制, 看它是否运行正常 (可以观察日志打印)

大致实现思路

轮次时间控制

在上一次实现的EnemySpawnComponent组件基础上新增一个RoundTimer, 用于轮次计时

并且新增一些变量, 用于轮次的时间/参数调整

gdscript 复制代码
const BASE_ROUND_TIME: float = 10
const ROUND_TIME_GROWTH: float = 5
const BASE_MIN_SPAWN_INTERVAL: float = 2.0
const BASE_MAX_SPAWN_INTERVAL: float = 5.0
const SPAWN_INTERVAL_GROWTH: float = -0.2

var round_count: int = 0
var round_min_spawn_interval: float = BASE_MIN_SPAWN_INTERVAL
var round_max_spawn_interval: float = BASE_MAX_SPAWN_INTERVAL
var enemy_count: int = 0

新增的常量和变量名都比较有描述性

  • 轮次初始时间为10秒, 每完成一个轮次, 下次增加5秒时间
  • 初始的敌人生成随机最小最大间隔为2-5秒, 每增加一个轮次, 间隔时间的最小最大值均减少0.2秒 (敌人生成加快)
  • 当前轮次计数, 时间与配置基于轮次数调整
  • 记录敌人数量, 依据敌人数量判断轮次是否完成

轮次开启与结束

开启时计数加1, 并且调整Timer时间与参数, 同时开启敌人生成Timer

在ready中调用_start_round自动开启第一个轮次

gdscript 复制代码
func _start_round() -> void:
	round_count += 1
	print("Round %s start" % round_count)
	round_min_spawn_interval = BASE_MIN_SPAWN_INTERVAL + (round_count - 1) * SPAWN_INTERVAL_GROWTH
	round_max_spawn_interval = BASE_MAX_SPAWN_INTERVAL + (round_count - 1) * SPAWN_INTERVAL_GROWTH
	round_timer.start(BASE_ROUND_TIME + (round_count - 1) * ROUND_TIME_GROWTH)
	spawn_timer.start(randf_range(round_min_spawn_interval, round_max_spawn_interval))

轮次的结束是round_timer计时停止, 停止后也同时停止敌人生成计时

gdscript 复制代码
func _on_round_timer_timeout() -> void:
	print("Round %s end" % round_count)
	spawn_timer.stop()
	_check_round_completed()

轮次完成检测

判断轮次完成有两个条件

  • 轮次计时完毕
  • 该轮次的敌人被全部消灭
gdscript 复制代码
func _check_round_completed() -> void:
	if !round_timer.is_stopped():
		return
	if enemy_count == 0:
		print("Round %s completed!" % round_count)
		_start_round()

若检测到轮次完成, 则自动开始下一轮次

在轮次Timer结束时已经进行过一次检测, 还应该在每个敌人死亡时也进行计数和检测

gdscript 复制代码
func _on_enemy_died() -> void:
	enemy_count -= 1
	_check_round_completed()

这就涉及到一个问题, 在该组件中怎样获取到敌人死亡的信号?

全局信号总线

有人坚决反对使用信号总线, 说这不符合Godot的设计哲学

但是我会以务实为主, 我认为信号总线应该在必要时使用, 但要避免乱用

在这里, 敌人实例与该组件实例不是简单的上下级或者兄弟级, 如果通过祖先节点传递信号, 会相当麻烦

我认为在这里使用信号总线是非常合适的

使用Autoload实现一个全局信号总线

新建一个脚本, 在ProjectSettings中的Globals中加载并启用, 之后就可以全局访问

gdscript 复制代码
extends Node

signal enemy_died

func emit_enemy_died() -> void:
	enemy_died.emit()

脚本很简单, 主要是为了跨模块访问

敌人死亡通知

敌人的生命管理被我用一个HealthComponent组件管理了, 与之前的逻辑一样, 生命值归0之后发出信号, 封装组件都是为了方便后续复用

在生命值归0后发出全局信号, 并销毁自身

gdscript 复制代码
func _on_health_ended() -> void:
	GameEvents.emit_enemy_died()
	queue_free()

全局信号监听

与普通信号监听一样, 只是我们不用再找信号的源头是谁

gdscript 复制代码
GameEvents.enemy_died.connect(_on_enemy_died)

记得处理好服务器逻辑与客户端逻辑

这样我们的轮次结构就完成了, 后续开发中可以基于轮次让游戏内容逐渐产生一些变化, 慢慢变得好玩有趣起来

相关推荐
2301_780789663 小时前
零信任架构在云安全落地过程中的最佳实践
服务器·人工智能·游戏·架构·零信任
上海云盾-小余3 小时前
精准抵御流量攻击:高防 IP + 游戏盾组合部署实战详解
网络·tcp/ip·游戏
游乐码3 小时前
C#Queue
数据结构·游戏·c#
呆呆敲代码的小Y3 小时前
【Unity工具篇】| 游戏完整资源热更新流程,YooAsset官方示例项目
人工智能·游戏·unity·游戏引擎·热更新·yooasset·免费游戏
Kang.Charles5 小时前
UE游戏性能优化归结(基于UE5环境)
游戏·ue5
前端不太难5 小时前
鸿蒙 App、PC、游戏,本质是同一套系统吗?
游戏·状态模式·harmonyos
柚要做甚码5 小时前
godot-rust(gdext)2D游戏之旅【flappy-bird】 - 2
游戏·游戏开发
柚要做甚码5 小时前
godot-rust(gdext)2D游戏之旅【flappy-bird】 - 1
游戏·游戏开发
柚要做甚码6 小时前
godot-rust(gdext)2D游戏之旅【flappy-bird】 - 3
游戏·游戏开发
wanhengidc6 小时前
服务器租用的好处
大数据·运维·服务器·游戏·智能手机