← 返回项目

SDLSpaceShooter

用 C++17 + SDL2 从零手写的 2D 太空射击游戏

C++17 SDL2 游戏开发 CMake 跨平台
SDLSpaceShooter 飞船

背景

想真正搞懂一个游戏是怎么"跑起来"的,光看引擎封装好的 API 是不够的——渲染循环、帧同步、对象管理这些底层的东西,往往被 Unity、Godot 这类引擎藏在了背后。

所以我选了一条更"裸"的路:用 C++17 + SDL2,不借助任何游戏引擎,从窗口创建、事件循环到渲染,一行行自己写一个完整的 2D 太空射击游戏。

项目的起点是 B 站 UP 主 ZiyuGameDev 的 SDL2 教程,但我没有停在"跟着敲完"这一步——在教程的骨架上,我重写了架构里的几处、并加了一整套自己的玩法和工程化改造。

技术栈

我的角色

从零到打包发布全部我一个人:架构设计、玩法编码、参数调优、跨平台构建、以及最后 Windows 端的 v1.0 打包(解压即玩)。

代码上我把整个游戏组织成了两个核心抽象:

1. 单例 Game + 场景状态机

Game 用静态局部变量做单例,持有窗口、渲染器、主循环、全局分数和排行榜。游戏的不同阶段拆成三个场景,都继承自 Scene 抽象基类:

Game::changeScene() 负责切换当前场景,事件、更新、渲染三件事统一转发给 currentScene。这样每个场景只管自己的逻辑,主循环干干净净。

2. 数据驱动的对象设计

所有游戏对象(玩家、敌人、双方子弹、爆炸、掉落物、背景)都是 Object.h 里的纯数据 struct,字段就是位置、速度、血量、冷却这些。玩法逻辑集中在 SceneMain 里用 std::list 管理各类对象的生灭。数据和逻辑分离,加新对象或调数值都很轻。

难点与解决

① 帧率无关的运动
如果直接用"每帧移动 N 像素",游戏在 144Hz 和 60Hz 的机器上速度就会不一样。解决办法是引入 deltaTime:所有位移都写成 speed * deltaTime * scale,让运动跟真实时间挂钩,而不是跟帧数挂钩。这样任何刷新率下手感一致。

② 多分辨率自适应
不同显示器分辨率差异很大。我用一个统一的 scale 缩放因子:窗口按屏幕高度的 90% 生成、保持 3:4 比例,然后所有精灵尺寸、移动速度、UI 元素都乘以这个因子。一套代码在任何屏幕上比例都不崩。

③ 存档到底该放哪
最初排行榜写在项目根目录,结果一打包发布、或者重新下载,存档就丢了。后来改用 SDL_GetPrefPath 写到系统的用户数据目录(Windows 下是 AppData/Roaming)。这才是正确做法——存档跟着用户走,不跟着程序目录走。排行榜本身用 std::multimap<int, string, greater<int>>,插入即自动降序,省掉手动排序。

④ 让战斗"有感觉"
教程原版打起来偏平淡。我做了几处玩法改造:敌方子弹会计算指向玩家的方向做追踪;玩家改成三连发、形成横向弹幕;难度随时间递增(开局一分钟后刷怪率按 3 + (elapsed-60000)/5000 增长、封顶 8);再配上精灵表爆炸动画、掉落物撞墙反弹三次、两层星空视差滚动。这些叠起来,手感和节奏就明显不一样了。

成果与收获

项目基于 ZiyuGameDev 的 SDL2 教程起步,在其基础上做了架构与玩法的二次开发。