本教程将讲解弹幕系统示例游戏 BulletHellSample 的整体结构与实现机制。
◆ 游戏概述
这是一个使用 射击 和 炸弹 来击败一名 HP 为 500、会释放弹幕的 Boss 的游戏。
由于这是示例项目,Boss 被击败后游戏即结束。
操作方式
- 方向键:移动
- Z 键:射击
- X 键:炸弹
实现了弹幕游戏中常见的机制,例如 擦弹(Graze) 和 炸弹消弹。
游戏场景结构
游戏仅使用一个场景:test_scene.tscn。
不存在场景切换。
对象结构
玩家
player.tscn 是玩家角色的场景。
其子节点包括:
GrazeObject.tscn:用于擦弹判定player_hit_checker.tscn:用于受击判定
敌人
enemy.tscn 是敌人(Boss)的场景。
其子节点包括:
damage_checker.tscn:用于受击判定
各功能的实现说明
敌人的弹幕
敌人采用“按固定时间间隔发射弹幕”的简单实现方式。
弹幕内容如下:
- 会加速的全方向弹
- 结合移动的霰弹枪式弹幕
- 从关卡边缘发射、以玩家为目标的分裂弹
- 由全方向弹组合而成的花形弹幕
- 使用转向弹绘制的简单几何图形
由于弹幕需要重复播放音效,因此通过 AnimationPlayer 节点 来触发 AudioStreamPlayer 播放声音。
目前由于缺少合适的音效资源,采用了这种实现方式。
理想情况下,应为每种弹幕准备合适的音效,并在弹幕发射时直接播放。
- 当前的弹幕系统 没有停止机制,因此即使敌人被击败,已经开始生成的弹幕仍会继续发射。
这一问题计划在后续更新中通过添加弹幕停止功能来解决。
在现阶段,可以通过 不使用重复处理,而是拆分状态并手动实现循环逻辑来规避。
玩家普通射击弹幕
玩家在 同一个状态中同时发射 4 种弹幕。
目前,速射类型的弹幕无法直接指定发射方向。
因此通过 朝非常远的全局坐标发射 的方式,来模拟方向射击的效果。
- 后续更新中计划支持直接指定方向发射。
擦弹处理
通过将 GrazeObject.tscn 作为玩家的子节点来实现擦弹功能。
该对象拥有 比玩家受击判定更大的碰撞范围。
当它与敌方弹幕发生接触时:
- 播放擦弹粒子效果
- 增加分数
受伤处理
玩家与敌人的受伤处理均通过 独立的子对象 来实现。
主要原因有两点:
- 避免因受伤而中断当前正在执行的状态
- 明确区分 擦弹判定 与 受击判定
- 在 ACTION GAME MAKER 中,HitArea2D 会检测根节点以下所有 HitArea 的碰撞,因此也会拾取子节点的判定。
该问题可以通过“不作为子节点,而是作为子对象生成”来避免。
但为了便于目视确认判定位置,本示例中采用了子节点的方式。
弹幕的层级(Layer)结构
层级设置如下:
- 玩家:Layer 1
- 敌人:Layer 2
- 擦弹判定:Layer 3
弹幕层级:
- 敌人弹幕:Layer 1、3
- 玩家弹幕:Layer 2
炸弹的实现
炸弹并非作为弹幕系统的一部分,而是作为 普通子弹 来实现。
bomb.tscn 是炸弹的场景。
功能如下:
-
设置覆盖整个画面的碰撞判定
-
清除命中的弹幕
-
以 10% 的概率 将被清除的弹幕转换为分数道具
-
同时对敌人本体造成伤害
-
转换为分数道具会涉及 对象生成,这是相对较重的处理。
如果将转换概率设为 100%,会导致性能下降。
对此问题的优化方案正在另行研究中。
分数道具
为了降低对象生成带来的性能负担,分数道具使用 GDScript 实现。
- 场景:
score_test.tscn - 行为:
- 向玩家靠近
- 距离足够近时消失
- 增减指定的变量值
可在 Score 节点的 Inspector 中自定义以下参数:
- PickUpRadius:拾取判定距离
- PullSpeed:移动速度
- Group:靠近的目标组
- Project Variable:要修改的项目变量名
- Variable Add:增加的数值