10. 打磨与发布
2026/4/14大约 5 分钟
10. 雷霆战机——打磨与发布
10.1 难度曲线设计
一个好的射击游戏需要合理的难度曲线——太简单会让玩家无聊,太难会让玩家放弃。
难度递进原则
难度
│ ★ Boss
│ ╱╲
│ ╱╲ ╱ ╲
│ ╱╲ ╱ ╲ ╱ ╲
│ ╱╲ ╱ ╲╱ ╲╱
│ ╱╲ ╱ ╲╱
│ ╱╲ ╱ ╲╱
│╱ ╲╱
└──────────────────────────────────→ 时间
热身 提升 挑战 Boss| 阶段 | 时间占比 | 敌人数量 | 弹幕密度 | 目的 |
|---|---|---|---|---|
| 热身 | 0-15% | 少,弱 | 稀疏 | 让玩家熟悉操作 |
| 提升 | 15-40% | 逐渐增多 | 中等 | 提升紧张感 |
| 挑战 | 40-80% | 多,强 | 密集 | 考验技术 |
| Boss | 80-100% | 1个Boss | 极密 | 最终挑战 |
C
/// <summary>
/// 难度控制器——根据游戏时间动态调整难度
/// </summary>
public partial class DifficultyController : Node
{
private float _gameTime = 0f;
private float _maxGameTime = 180f; // 3分钟一个关卡
// 难度参数
public float EnemySpawnRate { get; private set; }
public float EnemySpeedMult { get; private set; }
public float EnemyBulletSpeedMult { get; private set; }
public float EnemyHPMult { get; private set; }
// 难度等级(0-1)
public float DifficultyLevel { get; private set; }
public override void _Process(double delta)
{
_gameTime += (float)delta;
UpdateDifficulty();
}
private void UpdateDifficulty()
{
// 使用S曲线(Sigmoid)计算难度等级
// 开始平缓,中间加速,最后趋于平稳
float t = Mathf.Clamp(_gameTime / _maxGameTime, 0f, 1f);
DifficultyLevel = t * t * (3f - 2f * t); // SmoothStep
// 根据难度等级调整参数
EnemySpawnRate = Mathf.Lerp(2.0f, 0.5f, DifficultyLevel);
EnemySpeedMult = Mathf.Lerp(0.8f, 1.5f, DifficultyLevel);
EnemyBulletSpeedMult = Mathf.Lerp(0.7f, 1.3f, DifficultyLevel);
EnemyHPMult = Mathf.Lerp(0.8f, 2.0f, DifficultyLevel);
}
/// <summary>
/// 重置难度
/// </summary>
public void Reset()
{
_gameTime = 0f;
UpdateDifficulty();
}
}GDScript
extends Node
## 难度控制器——根据游戏时间动态调整难度
var _game_time: float = 0.0
var _max_game_time: float = 180.0 # 3分钟一个关卡
# 难度参数
var enemy_spawn_rate: float:
get: return _enemy_spawn_rate
var enemy_speed_mult: float:
get: return _enemy_speed_mult
var enemy_bullet_speed_mult: float:
get: return _enemy_bullet_speed_mult
var enemy_hp_mult: float:
get: return _enemy_hp_mult
var difficulty_level: float:
get: return _difficulty_level
var _enemy_spawn_rate: float = 2.0
var _enemy_speed_mult: float = 0.8
var _enemy_bullet_speed_mult: float = 0.7
var _enemy_hp_mult: float = 0.8
var _difficulty_level: float = 0.0
func _process(delta: float) -> void:
_game_time += delta
_update_difficulty()
func _update_difficulty() -> void:
var t = clampf(_game_time / _max_game_time, 0.0, 1.0)
_difficulty_level = t * t * (3.0 - 2.0 * t) # SmoothStep
_enemy_spawn_rate = lerpf(2.0, 0.5, _difficulty_level)
_enemy_speed_mult = lerpf(0.8, 1.5, _difficulty_level)
_enemy_bullet_speed_mult = lerpf(0.7, 1.3, _difficulty_level)
_enemy_hp_mult = lerpf(0.8, 2.0, _difficulty_level)
func reset() -> void:
_game_time = 0.0
_update_difficulty()10.2 性能优化清单
射击游戏对性能要求较高,需要注意以下优化点:
| 优化项 | 方法 | 效果 |
|---|---|---|
| 子弹对象池 | 预分配200个子弹 | 避免GC卡顿 |
| 粒子数量限制 | 最大100个粒子/效果 | 防止GPU过载 |
| 碰撞检测优化 | 距离预筛选+空间分区 | 减少计算量 |
| 节点数量控制 | 及时销毁离屏对象 | 减少场景复杂度 |
| 纹理压缩 | 使用压缩格式 | 减少显存占用 |
| 音效池 | 10个播放器循环使用 | 避免音频节点过多 |
C
/// <summary>
/// 性能监控器——开发时用,发布前移除
/// </summary>
public partial class PerformanceMonitor : Node
{
private Label _fpsLabel;
private Label _nodeLabel;
private Label _bulletLabel;
private float _updateTimer = 0f;
public override void _Ready()
{
// 仅在调试模式显示
if (!OS.IsDebugBuild())
{
QueueFree();
return;
}
_fpsLabel = new Label();
_nodeLabel = new Label();
_bulletLabel = new Label();
var vbox = new VBoxContainer();
vbox.AddChild(_fpsLabel);
vbox.AddChild(_nodeLabel);
vbox.AddChild(_bulletLabel);
var canvas = new CanvasLayer();
canvas.Layer = 100;
AddChild(canvas);
canvas.AddChild(vbox);
vbox.Position = new Vector2(5, 5);
}
public override void _Process(double delta)
{
_updateTimer += (float)delta;
if (_updateTimer < 0.5f) return;
_updateTimer = 0f;
_fpsLabel.Text = $"FPS: {Engine.GetFramesPerSecond()}";
var root = GetTree().CurrentScene;
int nodeCount = CountNodes(root);
_nodeLabel.Text = $"节点数: {nodeCount}";
}
private int CountNodes(Node node)
{
int count = 1;
foreach (var child in node.GetChildren())
count += CountNodes(child);
return count;
}
}GDScript
extends Node
## 性能监控器——开发时用
var _fps_label: Label
var _node_label: Label
var _update_timer: float = 0.0
func _ready() -> void:
if not OS.is_debug_build():
queue_free()
return
_fps_label = Label.new()
_node_label = Label.new()
var vbox = VBoxContainer.new()
vbox.add_child(_fps_label)
vbox.add_child(_node_label)
var canvas = CanvasLayer.new()
canvas.layer = 100
add_child(canvas)
canvas.add_child(vbox)
vbox.position = Vector2(5, 5)
func _process(delta: float) -> void:
_update_timer += delta
if _update_timer < 0.5:
return
_update_timer = 0.0
_fps_label.text = "FPS: %d" % Engine.get_frames_per_second()
_node_label.text = "节点数: %d" % _count_nodes(get_tree().current_scene)
func _count_nodes(node: Node) -> int:
var count = 1
for child in node.get_children():
count += _count_nodes(child)
return count10.3 Bug检查清单
| Bug | 检查方法 | 修复方案 |
|---|---|---|
| 子弹不消失 | 玩家子弹飞出屏幕不回收 | 检查边界检测 |
| 敌人穿屏 | 敌人飞过屏幕底部不消失 | 添加底部检测 |
| 道具无法拾取 | 碰撞距离太小 | 调大拾取范围 |
| Boss卡住 | Boss血量不降低 | 检查碰撞检测 |
| 内存增长 | 长时间玩越来越卡 | 检查节点是否正确销毁 |
| 音效中断 | 多个音效同时播放 | 增大对象池 |
| 触摸无响应 | 移动端控制不工作 | 检查触摸区域 |
| 帧率不稳 | 偶尔掉帧 | 优化碰撞和粒子 |
10.4 项目导出
平台适配
| 平台 | 特殊处理 |
|---|---|
| Windows | 窗口模式+全屏模式 |
| Android | 触摸控制、竖屏锁定、性能降级 |
| iOS | 触摸控制、App Store审核 |
| HTML5 | 加载画面、触控支持 |
移动端适配要点
// 检测平台并调整参数
public override void _Ready()
{
bool isMobile = OS.GetName() == "Android"
|| OS.GetName() == "iOS";
if (isMobile)
{
// 降低粒子数量
ProjectSettings.SetSetting(
"rendering/quality/particle_quality", 2);
// 显示触摸控制
GetNode<Control>("TouchControls").Visible = true;
// 禁用鼠标光标
Input.SetMouseMode(Input.MouseModeEnum.Hidden);
}
}10.5 后续扩展方向
| 扩展功能 | 难度 | 说明 |
|---|---|---|
| 多关卡 | 中 | 不同背景、敌人组合、Boss |
| 升级商店 | 中 | 关卡间购买升级 |
| 多种战机 | 中 | 不同战机有不同的武器特性 |
| 协力模式 | 高 | 两个玩家同时游戏 |
| 每日挑战 | 低 | 固定种子生成相同的关卡 |
| 成就系统 | 低 | 解锁各种成就 |
10.6 完整项目回顾
回顾一下我们在这10章中完成的全部内容:
| 章节 | 内容 |
|---|---|
| 1 | 分析纵版射击游戏的核心玩法和设计要点 |
| 2 | 搭建项目结构、配置输入、创建GameManager |
| 3 | 实现玩家移动(双速)和自动射击(5级武器) |
| 4 | 实现子弹对象池、5种敌人弹幕模式 |
| 5 | 实现敌人基类、运动模式、波次管理器 |
| 6 | 实现6种道具、护盾系统、炸弹清屏效果 |
| 7 | 实现Boss多阶段攻击、血条、弹幕模式 |
| 8 | 搭建HUD、Boss血条、排行榜 |
| 9 | 添加音效系统、爆炸粒子、屏幕震动 |
| 10 | 难度曲线、性能优化、多平台导出 |
你现在拥有了一个功能完整的纵版射击游戏,包含:
- 流畅的玩家操控和射击系统
- 多种敌人和弹幕模式
- 精彩的Boss多阶段战
- 完整的道具和升级系统
- 丰富的音效和视觉特效
- 可调节的难度曲线
恭喜你完成了两个完整的Godot游戏实战项目——俄罗斯方块和雷霆战机!这些知识为你进一步学习更复杂的游戏开发打下了坚实的基础。
