手册:BulletHell(弹幕)示例项目

本教程将讲解弹幕系统示例游戏 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:用于受击判定

各功能的实现说明

敌人的弹幕

敌人采用“按固定时间间隔发射弹幕”的简单实现方式。

弹幕内容如下:

  1. 会加速的全方向弹
  2. 结合移动的霰弹枪式弹幕
  3. 从关卡边缘发射、以玩家为目标的分裂弹
  4. 由全方向弹组合而成的花形弹幕
  5. 使用转向弹绘制的简单几何图形

由于弹幕需要重复播放音效,因此通过 AnimationPlayer 节点 来触发 AudioStreamPlayer 播放声音。

目前由于缺少合适的音效资源,采用了这种实现方式。
理想情况下,应为每种弹幕准备合适的音效,并在弹幕发射时直接播放。

  • 当前的弹幕系统 没有停止机制,因此即使敌人被击败,已经开始生成的弹幕仍会继续发射。
    这一问题计划在后续更新中通过添加弹幕停止功能来解决。
    在现阶段,可以通过 不使用重复处理,而是拆分状态并手动实现循环逻辑来规避。

玩家普通射击弹幕

玩家在 同一个状态中同时发射 4 种弹幕。

目前,速射类型的弹幕无法直接指定发射方向。
因此通过 朝非常远的全局坐标发射 的方式,来模拟方向射击的效果。

  • 后续更新中计划支持直接指定方向发射。

擦弹处理

通过将 GrazeObject.tscn 作为玩家的子节点来实现擦弹功能。

该对象拥有 比玩家受击判定更大的碰撞范围。
当它与敌方弹幕发生接触时:

  • 播放擦弹粒子效果
  • 增加分数

受伤处理

玩家与敌人的受伤处理均通过 独立的子对象 来实现。

主要原因有两点:

  1. 避免因受伤而中断当前正在执行的状态
  2. 明确区分 擦弹判定 与 受击判定
  • 在 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:增加的数值