项目简介 (Introduction)
这是一个基于 C++ 和 ncurses 库开发的终端海战游戏。本项目不仅是一个简单的游戏,更是一个面向对象编程 (OOP) 的教学案例。它展示了如何使用 C++ 构建一个具有完整生命周期、即时渲染和交互逻辑的系统。
核心特性:
- 面向对象设计: 充分利用继承 (
Inheritance) 和多态 (Polymorphism) 管理游戏实体。 - 内存管理: 使用智能指针 (
std::unique_ptr) 自动管理对象生命周期,杜绝内存泄漏。 - 即时渲染: 使用
ncurses库实现终端下的高性能字符绘图。 - 游戏架构: 经典的
Input->Update->Render游戏循环模式。
Github仓库:https://github.com/ty-yqs/Cpp-Terminal-Naval-War-Game
源码下载:https://github.com/ty-yqs/Cpp-Terminal-Naval-War-Game/archive/refs/heads/main.zip
项目结构 (Project Structure)
.
├── Makefile # 自动化编译脚本
├── README.md # 项目文档
├── ClassDiagram.md # 类图设计文档
├── main.cpp # 程序入口
├── game.h/cpp # 游戏核心控制器 (上帝类)
├── world.h/cpp # 地图与环境数据
├── renderer.h/cpp # 负责屏幕绘制 (ncurses 封装)
├── input_manager.h/cpp # 输入处理
├── entity.h/cpp # 所有物体的基类
├── ship.h/cpp # 飞船基类 (继承自 Entity)
├── player_ship.h/cpp # 玩家飞船 (继承自 Ship)
├── enemy_ship.h/cpp # 敌人飞船 (继承自 Ship)
├── projectile.h/cpp # 子弹/导弹 (继承自 Entity)
└── pickup.h/cpp # 掉落道具 (继承自 Entity)
环境准备与安装 (Prerequisites)
macOS
macOS 通常预装了 ncurses 库。你需要安装编译器:
xcode-select --install
Linux (Ubuntu/Debian)
需要安装 ncurses 开发库:
sudo apt-get update
sudo apt-get install libncurses5-dev libncursesw5-dev g++ make
Windows
Windows 原生不支持 ncurses。推荐使用 WSL (Windows Subsystem for Linux) 安装 Ubuntu 子系统,然后按照 Linux 步骤操作。
编译与运行 (Build & Run)
本项目使用 Makefile 管理编译过程,自动处理依赖关系。
# 1. 编译项目
make
# 2. 运行游戏
./game
# 3. 清理编译产生的临时文件 (.o 文件)
make clean
Makefile 原理解析:
clang++ -std=c++17: 指定使用 C++17 标准。-lncurses: 链接 ncurses 库,这是绘图的关键。-o game: 输出可执行文件名为game。
从零开始:构建教程 (Step-by-Step Guide)
如果你想亲手重写这个项目,请遵循以下逻辑路径:
第一阶段:基础架构
- 创建
main.cpp: 它是程序的入口,只负责实例化Game对象并调用runLoop()。 - 设计
Game类:- 这是游戏的“大脑”。
- 实现
runLoop():一个while(running)循环。 - 在循环中依次调用
handleInput(),update(),render()。 - 控制帧率:使用
usleep或std::this_thread::sleep_for防止 CPU 占用过高。
第二阶段:显示系统 (Renderer)
- 引入 ncurses: 在
Renderer构造函数中调用initscr(),noecho(),curs_set(0)等初始化函数。 - 封装绘图: 不要直接在游戏逻辑里写
mvaddch。- 写一个
drawEntity(const Entity& e)函数。 - 难点: 如何支持多行字符?(如轰炸机)。
- 解法: 使用
stringstream按\n分割字符串,逐行绘制。
- 写一个
第三阶段:实体系统 (Entity Hierarchy)
这是 OOP 的核心。
Entity(基类):- 属性:
row,col,glyph(显示字符),color。 - 方法:
virtual void update() = 0;(纯虚函数,强制子类实现)。
- 属性:
Ship(战斗单位):- 继承自
Entity。 - 新增:
hp(血量),spawnProjectile()(发射子弹)。 - 设计模式: 飞船发射子弹时,不能直接把子弹加到
Game的列表里(因为Ship不知道Game的存在)。 - 解法:
Ship将子弹存入自己的newProjectiles_队列,Game在每帧结束时提取它们。
- 继承自
第四阶段:游戏逻辑实现
- 玩家控制 (
PlayerShip):- 在
handleInput中根据InputState修改row/col。 - 实现边界检查:不能飞出屏幕。
- 在
- 敌人 AI (
EnemyShip):- 使用
rand()决定移动方向。 - 使用计时器 (
timer) 控制移动频率(例如每 10 帧移动一次),避免移动太快。
- 使用
- 碰撞检测 (
Game::checkCollisions):- 双重循环遍历:
ProjectilesvsEnemies,PlayervsEnemies。 - 如果坐标重叠 (
r1==r2 && c1==c2),则判定命中,扣血并标记死亡。
- 双重循环遍历:
核心类详细说明 (Class Details)
Entity (entity.h/cpp)
- 定位: 抽象基类。
- 关键点: 使用
std::string存储glyph_而不是char,这使得我们可以轻松实现像轰炸机那样复杂的图形。
Game (game.h/cpp)
- 定位: 中央控制器。
- 容器管理:
std::unique_ptr <PlayerShip> player_; std::vector<std::unique_ptr<EnemyShip>> enemies_; std::vector<std::unique_ptr<Projectile>> projectiles_;使用
vector+unique_ptr是现代 C++ 管理对象集合的最佳实践,当对象从 vector 移除时,内存自动释放。
InputManager (input_manager.h/cpp)
- 定位: 输入抽象层。
- 设计: 将 ncurses 的原始键值 (如
KEY_UP,104) 转换为游戏语义 (dRow = -1,fireShell = true)。这样如果以后想改键位,只需改这一个文件。
Renderer (renderer.h/cpp)
- 定位: 视图层。
- 多行渲染实现:
void drawEntity(...) { stringstream ss(entity.getGlyph()); string line; int r = entity.getRow(); while(getline(ss, line)) { mvaddstr(r++, entity.getCol(), line.c_str()); } }这段代码让我们可以给实体设置 “^\n=\nV” 这样的字符串,它会自动被画成 3 行。
PlayerShip (player_ship.h/cpp)
- 动态外观: 在
handleInput中,根据移动方向 (dRow) 实时修改glyph_,实现飞船转向的视觉效果。
EnemyShip (enemy_ship.h/cpp)
- 工厂模式雏形: 构造函数根据
EnemyType枚举初始化不同的属性(血量、颜色、符号),这是一种简单的工厂模式实现。
类图 (Class Diagram)
正文完


