1. 核心玩法
2026/4/14大约 11 分钟
1. 雷霆战机——核心玩法
本章你将掌握
可以学到的 Godot 技能
| 技能 | 说明 |
|---|---|
| 对象池(Object Pool) | 预创建子弹/敌人节点,复用而非频繁实例化,大幅提升性能 |
| 弹幕系统 | 用 Timer + 方向向量批量生成子弹,实现扇形/环形弹幕 |
| Area2D 碰撞检测 | 用 Area2D + CollisionShape2D 检测子弹命中敌人 |
| 滚动背景 | 两张背景图交替滚动,实现无限向下飞行的视觉效果 |
| 道具系统 | 敌人死亡随机掉落道具,玩家拾取后触发升级效果 |
| Boss 战设计 | Boss 有多阶段血量,每阶段切换不同的弹幕模式 |
| 屏幕震动 | 用 Camera2D 的 offset 做随机抖动,增强爆炸反馈 |
| 音效管理 | 用 AudioStreamPlayer 播放射击、爆炸、道具音效 |
关键 Node 节点
RaidenGame(Node2D)
├── Background(Node2D) ← 滚动背景
│ ├── BG1(Sprite2D) ← 背景图1
│ └── BG2(Sprite2D) ← 背景图2(交替)
├── Player(CharacterBody2D) ← 玩家飞机
│ ├── Sprite2D ← 飞机贴图
│ ├── CollisionShape2D ← 碰撞体
│ ├── ShootTimer(Timer) ← 射击间隔
│ └── HitBox(Area2D) ← 受击判定区
├── BulletPool(Node2D) ← 子弹对象池
├── EnemyPool(Node2D) ← 敌人对象池
├── ItemPool(Node2D) ← 道具对象池
├── EnemySpawner(Node2D) ← 敌人生成控制器
│ └── SpawnTimer(Timer) ← 生成间隔
├── Boss(CharacterBody2D) ← Boss节点(动态加载)
└── HUD(CanvasLayer) ← 游戏界面
├── HealthBar(TextureProgressBar)
├── ScoreLabel(Label)
└── BombCount(HBoxContainer)游戏核心节点
点击节点可跳转到对应文档查看详细说明。
游戏系统结构图
┌─────────────────────────────────────────────────────────────┐
│ 雷霆战机 系统架构 │
├──────────────┬──────────────┬──────────────┬────────────────┤
│ 玩家系统 │ 弹幕系统 │ 敌人系统 │ 道具系统 │
│ │ │ │ │
│ 移动控制 │ 子弹对象池 │ 敌人对象池 │ 随机掉落 │
│ 射击控制 │ 弹幕模式 │ 移动路径 │ 武器升级 │
│ 无敌帧 │ 伤害判定 │ AI行为 │ 炸弹道具 │
│ 死亡爆炸 │ 穿透/爆炸 │ 死亡奖励 │ 护盾道具 │
└──────────────┴──────────────┴──────────────┴────────────────┘
↓ ↓ ↓
┌─────────────────────────────────────────────────────────────┐
│ Boss 战系统 │
│ 阶段1(50%血量以上) │ 阶段2(50%以下) │ 死亡演出 │
│ 普通弹幕 │ 强化弹幕+冲刺 │ 爆炸粒子 │
└─────────────────────────────────────────────────────────────┘1.1 什么是纵版射击游戏?
想象你站在一个竖着的大屏幕前面,你的飞机在屏幕最底部,敌人从屏幕最上方不断飞下来。你需要一边躲避敌人的子弹,一边用你的子弹消灭它们——这就是纵版射击游戏(Shoot 'em Up,简称STG)。
雷霆战机就是这类游戏的经典代表。它最初是街机游戏,规则简单但极具挑战性:移动、射击、躲弹幕、打Boss。
为什么选择雷霆战机作为进阶项目?
| 原因 | 说明 |
|---|---|
| 动作密集 | 大量子弹和敌人在屏幕上运动,需要高效的对象管理 |
| 碰撞检测多 | 子弹打敌人、敌人撞玩家、道具碰撞等 |
| 节奏感强 | 波次设计、Boss战需要精心编排 |
| 技术点丰富 | 对象池、状态机、计时器、粒子系统都用得上 |
1.2 游戏核心循环
雷霆战机的核心循环和俄罗斯方块不同——它是一个实时动作循环,玩家需要时刻保持注意力:
┌────────────────────────────────────────────────┐
│ │
│ 玩家移动/射击 → 敌人出现 → 碰撞检测 │
│ ↑ │ │ │
│ │ ↓ ↓ │
│ 继续游戏 ←── 消灭敌人 ←── 计分/掉道具 │
│ │
│ 特殊事件:Boss波 → Boss血条 → 弹幕模式 │
│ │
└────────────────────────────────────────────────┘用大白话来说就是:
- 玩家操作:移动战机、自动射击、使用炸弹
- 敌人出现:按照预设的波次生成不同类型的敌人
- 战斗过程:子弹飞来飞去,碰撞检测判定谁打中了谁
- 消灭敌人:敌人被消灭后可能掉落道具
- 收集道具:升级武器、获得炸弹、恢复生命等
- Boss战:每隔几个波次出现一个Boss,血厚攻击强
如果玩家的生命值降为0,游戏就结束了。
1.3 游戏区域
纵版射击游戏的区域通常比俄罗斯方块简单得多——不需要网格,就是一个矩形自由空间。
┌──────────────────────┐
│ 敌人出现区域 │ ← 敌人从这里飞入
│ │
│ │
│ │
│ 战斗区域 │ ← 大部分战斗在这里发生
│ │
│ │
│ │
│ ← 玩家移动区域 → │ ← 玩家在底部左右移动
│ [玩家战机] │
└──────────────────────┘关键参数:
| 参数 | 值 | 说明 |
|---|---|---|
| 游戏宽度 | 360px | 标准竖屏宽度 |
| 游戏高度 | 640px | 标准竖屏高度 |
| 玩家移动区域 | 底部1/3 | 玩家只能在屏幕下方移动 |
| 安全判定区 | 3x3像素 | 玩家的"核心",被击中才算受伤 |
1.4 玩家战机
玩家战机的核心属性:
| 属性 | 说明 |
|---|---|
| 生命值(HP) | 玩家有多少条命,通常3-5条 |
| 武器等级 | 决定子弹的数量和伤害 |
| 炸弹数量 | 清屏道具,通常初始3个 |
| 无敌时间 | 被击中后短暂无敌 |
| 判定点 | 飞机中心的小点,弹幕游戏的核心 |
判定点(Hitbox)是弹幕游戏的重要概念:虽然飞机的图片可能很大,但实际被判定为"被击中"的区域只有中心的一个极小的点(通常3x3像素)。这让玩家能够在密集的弹幕中找到缝隙穿过去。
C
using Godot;
/// <summary>
/// 玩家数据定义
/// </summary>
public static class PlayerConfig
{
// ===== 基础属性 =====
/// <summary>初始生命值</summary>
public const int MaxLives = 3;
/// <summary>初始炸弹数</summary>
public const int MaxBombs = 3;
/// <summary>初始武器等级</summary>
public const int StartWeaponLevel = 1;
/// <summary>最高武器等级</summary>
public const int MaxWeaponLevel = 5;
// ===== 移动参数 =====
/// <summary>移动速度(像素/秒)</summary>
public const float MoveSpeed = 250f;
/// <summary>低速模式速度倍率(按住慢速键时)</summary>
public const float FocusSpeedMultiplier = 0.4f;
// ===== 无敌参数 =====
/// <summary>被击中后无敌时间(秒)</summary>
public const float InvincibleDuration = 2.0f;
/// <summary>无敌闪烁间隔(秒)</summary>
public const float BlinkInterval = 0.1f;
// ===== 射击参数 =====
/// <summary>射击间隔(秒)</summary>
public const float FireInterval = 0.12f;
/// <summary>子弹速度(像素/秒)</summary>
public const float BulletSpeed = 600f;
// ===== 判定点 =====
/// <summary>判定点半径</summary>
public const float HitboxRadius = 2f;
/// <summary>判定点显示半径(比实际大一点,方便玩家看到)</summary>
public const float HitboxVisualRadius = 5f;
}GDScript
## 玩家数据定义
class_name PlayerConfig
# ===== 基础属性 =====
## 初始生命值
const MAX_LIVES: int = 3
## 初始炸弹数
const MAX_BOMBS: int = 3
## 初始武器等级
const START_WEAPON_LEVEL: int = 1
## 最高武器等级
const MAX_WEAPON_LEVEL: int = 5
# ===== 移动参数 =====
## 移动速度(像素/秒)
const MOVE_SPEED: float = 250.0
## 低速模式速度倍率(按住慢速键时)
const FOCUS_SPEED_MULTIPLIER: float = 0.4
# ===== 无敌参数 =====
## 被击中后无敌时间(秒)
const INVINCIBLE_DURATION: float = 2.0
## 无敌闪烁间隔(秒)
const BLINK_INTERVAL: float = 0.1
# ===== 射击参数 =====
## 射击间隔(秒)
const FIRE_INTERVAL: float = 0.12
## 子弹速度(像素/秒)
const BULLET_SPEED: float = 600.0
# ===== 判定点 =====
## 判定点半径
const HITBOX_RADIUS: float = 2.0
## 判定点显示半径
const HITBOX_VISUAL_RADIUS: float = 5.01.5 敌人类型
雷霆战机的敌人通常分为以下几类:
| 类型 | 名称 | 特点 | 血量 |
|---|---|---|---|
| 小型 | 侦察机 | 直线飞行,不射击或偶尔射击 | 1 |
| 中型 | 战斗机 | 会射击,有简单运动模式 | 3-5 |
| 大型 | 轰炸机 | 血厚,会发射扇形弹幕 | 10-15 |
| 特殊 | 侦察无人机 | 沿特定路径飞行 | 2 |
| Boss | 关卡Boss | 多阶段,多种攻击模式 | 100-500 |
敌人运动模式
敌人不是只会直着往下飞。不同的运动模式让游戏更有趣:
直线飞行: ─────────→ 简单直接
正弦波动: ~~~~~→ 左右摇摆
弧形运动: ╭───────╮ 绕弧线进入
追踪运动: ↗↗↗→ 跟着玩家走
编队飞行: ═══════→ 整齐排列1.6 Boss战设计
Boss战是每个关卡的高潮。一个设计良好的Boss应该有:
- 多阶段:血量降到一定比例后改变攻击模式
- 血条显示:让玩家知道还差多少
- 多种弹幕:散射、追踪弹、激光等交替使用
- 弱点:某些时段会暴露弱点,鼓励玩家抓住时机攻击
- 奖励丰厚:击败Boss后获得大量分数和道具
Boss阶段示例:
阶段1(100%-60%血量):
- 散射弹幕:3发扇形弹
- 移动模式:缓慢左右移动
阶段2(60%-30%血量):
- 旋转弹幕:弹幕像风扇一样旋转
- 移动模式:加快移动速度
- 召唤小兵:每隔几秒放出小敌人
阶段3(30%-0%血量):
- 疯狂模式:所有弹幕同时使用
- 激光攻击:从Boss中心射出激光
- 移动模式:随机快速移动1.7 道具系统
敌人被消灭后可能掉落道具,玩家飞过去就能拾取。
| 道具 | 图标 | 效果 |
|---|---|---|
| P(Power) | 红色P | 武器升级 |
| B(Bomb) | 蓝色B | 炸弹+1 |
| S(Shield) | 绿色S | 护盾(挡一次伤害) |
| 1UP | 金色1UP | 生命+1 |
| 分数 | 星星 | 额外加分 |
| 炸弹补给 | 闪光 | 大范围清屏 |
1.8 弹幕模式
"弹幕"(Danmaku)是射击游戏的核心挑战——大量子弹在屏幕上形成美丽的图案,玩家需要找到安全的缝隙穿过去。
常见的弹幕模式:
| 模式 | 描述 | 图示 |
|---|---|---|
| 直线弹 | 子弹沿直线飞向玩家 | ↓↓↓↓↓ |
| 扇形弹 | 多发子弹呈扇形散开 | ⟨⟩ |
| 圆形弹 | 子弹向四面八方射出 | ✳ |
| 螺旋弹 | 子弹沿螺旋线飞出 | @ |
| 追踪弹 | 子弹会缓慢追踪玩家 | →↗↑ |
| 激光 | 一条持续的直线伤害 | │ |
1.9 游戏状态管理
和俄罗斯方块类似,雷霆战机也需要一个状态机:
┌──────────┐ 开始 ┌──────────┐
│ 主菜单 │ ────────→ │ 游戏中 │
└──────────┘ └────┬─────┘
↑ │
│ Boss战 │
│ ┌────────┐ │
│ │ Boss战 │ ←──────┘
│ └────────┘
│ │
│ 游戏结束
│ ←──────────────
│
│ 暂停
│ ┌────────┐
└──── │ 暂停 │ ←──────┘
└────────┘C
/// <summary>
/// 雷霆战机的游戏状态
/// </summary>
public enum RaidenGameState
{
Menu, // 主菜单
Playing, // 游戏中
BossFight, // Boss战
Paused, // 暂停
GameOver // 游戏结束
}
/// <summary>
/// 游戏管理器
/// </summary>
public partial class RaidenGameManager : Node
{
public RaidenGameState CurrentState { get; private set; }
= RaidenGameState.Menu;
// 游戏数据
public int Score { get; private set; }
public int Lives { get; private set; }
public int Bombs { get; private set; }
public int WeaponLevel { get; private set; }
// 信号
[Signal] public delegate void ScoreChangedEventHandler(int score);
[Signal] public delegate void LivesChangedEventHandler(int lives);
[Signal] public delegate void BombsChangedEventHandler(int bombs);
[Signal] public delegate void StateChangedEventHandler(
RaidenGameState state);
public void StartGame()
{
Score = 0;
Lives = PlayerConfig.MaxLives;
Bombs = PlayerConfig.MaxBombs;
WeaponLevel = PlayerConfig.StartWeaponLevel;
SetState(RaidenGameState.Playing);
}
public void AddScore(int points)
{
Score += points;
EmitSignal(SignalName.ScoreChanged, Score);
}
public void LoseLife()
{
Lives--;
EmitSignal(SignalName.LivesChanged, Lives);
// 降级武器
WeaponLevel = Mathf.Max(1, WeaponLevel - 1);
if (Lives <= 0)
{
SetState(RaidenGameState.GameOver);
}
}
public void UseBomb()
{
if (Bombs <= 0) return;
Bombs--;
EmitSignal(SignalName.BombsChanged, Bombs);
}
public void AddBomb(int count)
{
Bombs = Mathf.Min(Bombs + count, 5);
EmitSignal(SignalName.BombsChanged, Bombs);
}
public void UpgradeWeapon()
{
WeaponLevel = Mathf.Min(
WeaponLevel + 1,
PlayerConfig.MaxWeaponLevel);
}
private void SetState(RaidenGameState state)
{
CurrentState = state;
GetTree().Paused = (state == RaidenGameState.Paused);
EmitSignal(SignalName.StateChanged, state);
}
}GDScript
## 雷霆战机的游戏状态
enum RaidenGameState { MENU, PLAYING, BOSS_FIGHT, PAUSED, GAME_OVER }
## 游戏管理器
extends Node
var current_state: RaidenGameState = RaidenGameState.MENU
# 游戏数据
var score: int = 0
var lives: int = 3
var bombs: int = 3
var weapon_level: int = 1
# 信号
signal score_changed(score: int)
signal lives_changed(lives: int)
signal bombs_changed(bombs: int)
signal state_changed(state: int)
func start_game() -> void:
score = 0
lives = PlayerConfig.MAX_LIVES
bombs = PlayerConfig.MAX_BOMBS
weapon_level = PlayerConfig.START_WEAPON_LEVEL
set_state(RaidenGameState.PLAYING)
func add_score(points: int) -> void:
score += points
score_changed.emit(score)
func lose_life() -> void:
lives -= 1
lives_changed.emit(lives)
weapon_level = maxi(1, weapon_level - 1)
if lives <= 0:
set_state(RaidenGameState.GAME_OVER)
func use_bomb() -> void:
if bombs <= 0:
return
bombs -= 1
bombs_changed.emit(bombs)
func add_bomb(count: int) -> void:
bombs = mini(bombs + count, 5)
bombs_changed.emit(bombs)
func upgrade_weapon() -> void:
weapon_level = mini(weapon_level + 1, PlayerConfig.MAX_WEAPON_LEVEL)
func set_state(state: RaidenGameState) -> void:
current_state = state
get_tree().paused = (state == RaidenGameState.PAUSED)
state_changed.emit(state)1.10 本章小结
| 知识点 | 说明 |
|---|---|
| 游戏类型 | 纵版射击(STG),实时动作游戏 |
| 核心循环 | 移动→射击→碰撞→得分→道具 |
| 游戏区域 | 360x640的自由矩形空间 |
| 玩家属性 | 生命、武器等级、炸弹、判定点 |
| 敌人类型 | 小型/中型/大型/Boss,各有不同行为 |
| Boss战 | 多阶段、血条、多种弹幕模式 |
| 道具系统 | 武器升级、炸弹、护盾、1UP |
| 弹幕模式 | 直线、扇形、圆形、螺旋、追踪、激光 |
下一章我们将搭建雷霆战机的项目结构,创建场景骨架。
