refactor: 项目全面重构 — 从单文件过程式重写为模块化 OOP
**问题背景**
原项目全部逻辑集中在 main.cpp(1468 行),大量全局变量、4 方向逻辑
复制粘贴 4 遍、场景通过递归函数调用跳转(栈溢出隐患),难以维护扩展。
**重构内容**
文件架构(新增 src/ 目录):
- src/Game.h/.cpp — 状态机驱动主循环,Scene 枚举消除递归跳转
- src/AssetManager.h/.cpp — 纹理/字体/BGM 加载与缓存管理
- src/Renderer.h/.cpp — SDL 渲染封装(清屏/纹理/文字/提交)
- src/Map.h/.cpp — 地图数据 + 统一碰撞检测
- src/Tank.h/.cpp — 坦克类,dx()/dy() 方向向量消除 4x 复制粘贴
- src/Bullet.h/.cpp — 子弹类,peekAt()/step() 封装移动逻辑
- src/UI.h — 按钮区域常量 + 命中检测
核心改进:
- 全局变量清零 → 全部归入 Game 类实例管理
- 场景递归跳转 → Scene 枚举状态机,主循环驱动
- 4 方向复制粘贴 → 方向向量 {dx,dy} 表统一处理
- 修复 EnemiesMove 中循环变量 i 遮蔽 bug
- clock() → SDL_GetTicks(),跨平台时间精度
- 清理死代码 map1[50]、旧注释、未使用常量
- data_config.h 精简为纯常量
文件变更:
- 新增 7 个源文件(src/下)
- 重写 main.cpp(1468 → 6 行入口)
- 重写 data_config.h、CMakeLists.txt
- 删除 tank.h、bullet.h、map.h、map.cpp(被新架构替代)
- 更新 .gitignore(build_Debug/、.hermes/ 等)
This commit is contained in:
@@ -443,3 +443,10 @@ CMakeScripts/
|
||||
Makefile
|
||||
*.make
|
||||
|
||||
session-*.md
|
||||
.hermes/
|
||||
|
||||
# CMake 构建产物
|
||||
build_Debug/
|
||||
build_Release/
|
||||
build_*/
|
||||
|
||||
+17
-8
@@ -7,26 +7,32 @@ project(tank_battles_on_the_scrap_paper
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
# ── SDL2 依赖 ──
|
||||
find_package(SDL2 REQUIRED)
|
||||
find_package(SDL2_image REQUIRED)
|
||||
find_package(SDL2_mixer REQUIRED)
|
||||
find_package(SDL2_ttf REQUIRED)
|
||||
|
||||
# ── 源文件 ──
|
||||
set(SOURCES
|
||||
tank_battles_on_the_scrap_paper/main.cpp
|
||||
tank_battles_on_the_scrap_paper/map.cpp
|
||||
tank_battles_on_the_scrap_paper/src/AssetManager.cpp
|
||||
tank_battles_on_the_scrap_paper/src/Renderer.cpp
|
||||
tank_battles_on_the_scrap_paper/src/Map.cpp
|
||||
tank_battles_on_the_scrap_paper/src/Bullet.cpp
|
||||
tank_battles_on_the_scrap_paper/src/Tank.cpp
|
||||
tank_battles_on_the_scrap_paper/src/Game.cpp
|
||||
)
|
||||
|
||||
set(HEADERS
|
||||
tank_battles_on_the_scrap_paper/bullet.h
|
||||
tank_battles_on_the_scrap_paper/tank.h
|
||||
tank_battles_on_the_scrap_paper/map.h
|
||||
tank_battles_on_the_scrap_paper/data_config.h
|
||||
tank_battles_on_the_scrap_paper/src/AssetManager.h
|
||||
tank_battles_on_the_scrap_paper/src/Renderer.h
|
||||
tank_battles_on_the_scrap_paper/src/Map.h
|
||||
tank_battles_on_the_scrap_paper/src/Bullet.h
|
||||
tank_battles_on_the_scrap_paper/src/Tank.h
|
||||
tank_battles_on_the_scrap_paper/src/Game.h
|
||||
tank_battles_on_the_scrap_paper/src/UI.h
|
||||
)
|
||||
|
||||
# ── 可执行文件 ──
|
||||
add_executable(${PROJECT_NAME} ${SOURCES} ${HEADERS})
|
||||
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE
|
||||
@@ -36,7 +42,10 @@ target_link_libraries(${PROJECT_NAME} PRIVATE
|
||||
SDL2_ttf::SDL2_ttf
|
||||
)
|
||||
|
||||
# ── 复制资源文件到构建目录 ──
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE
|
||||
tank_battles_on_the_scrap_paper
|
||||
)
|
||||
|
||||
file(COPY
|
||||
tank_battles_on_the_scrap_paper/images/
|
||||
DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/images/
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
// 子弹定义
|
||||
#ifndef BULLET_H
|
||||
#define BULLET_H
|
||||
|
||||
/*
|
||||
子弹:
|
||||
子弹坐标:x,y
|
||||
攻击力damage:表示当前子弹造成的伤害
|
||||
存活标志:is_alive
|
||||
方向dir:
|
||||
UP 上
|
||||
DOWN 下
|
||||
LEFT 左
|
||||
RIGHT 右
|
||||
*/
|
||||
struct bullet {
|
||||
int x, y;
|
||||
int damage; // 子弹攻击力
|
||||
int dir;
|
||||
bool is_live; // 是否存活
|
||||
//tank shooter; //值为enemies[i]或player
|
||||
};
|
||||
|
||||
#endif // !BULLET_H
|
||||
@@ -1,51 +1,36 @@
|
||||
// �������ö���
|
||||
// 项目配置常量
|
||||
#ifndef DATA_CONFIG_H
|
||||
#define DATA_CONFIG_H
|
||||
// ���� tank �� bullet ����ͷ�ļ�
|
||||
#include "tank.h"
|
||||
#include "bullet.h"
|
||||
|
||||
// ��������
|
||||
const int CELL_SIZE = 30; // ÿ����Ԫ��̹�ˡ�ǽ���յصȣ��Ĵ�С�����磺���أ�
|
||||
const int MAP_CELL_NUM = 20; // ��ͼ��СΪMAP_CELL_NUM^2
|
||||
const int ENEMIES_NUM = 10; // ��ͼ�����ĵ�������
|
||||
const int BULLET_NUM = 20; // ��ͼ�������ӵ�����
|
||||
const int SINGLE_BULLET = 3; // ÿ��̹��ͬʱ���ڵ�����ӵ�����
|
||||
const int White = 0; // �Ҷ�ͼ-��
|
||||
const int Black = 1; // �Ҷ�ͼ-��
|
||||
// 网格
|
||||
constexpr int CELL_SIZE = 30;
|
||||
constexpr int MAP_CELL_NUM = 20;
|
||||
|
||||
#define PIEXL1 30 //���س���
|
||||
#define PIEXL2 4
|
||||
#define X_OFFSET 50 //�����ϵ�һ��ǽΪ����ƫ����
|
||||
#define Y_OFFSET 45 //�����ϵ�һ��ǽΪ����ƫ����
|
||||
/*
|
||||
��ͼ��
|
||||
ʹ������洢��ͼ��Ϣ
|
||||
map[x][y] �洢��ǰ̹����Ϣ
|
||||
BLANK �յ�
|
||||
RED_TANK �췽̹��
|
||||
RED_DEAD_TANK �췽����̹��
|
||||
BLUE_TANK, ����̹��
|
||||
BLUE_DEAD_TANK ��������̹��
|
||||
WALL ǽ
|
||||
BULLET �ӵ�
|
||||
*/
|
||||
// 偏移
|
||||
constexpr int X_OFFSET = 50;
|
||||
constexpr int Y_OFFSET = 45;
|
||||
|
||||
// 窗口
|
||||
constexpr int WINDOW_W = 700;
|
||||
constexpr int WINDOW_H = 700;
|
||||
|
||||
// 实体数量
|
||||
constexpr int ENEMIES_NUM = 10;
|
||||
constexpr int MAX_PLAYER_BULLETS = 3;
|
||||
constexpr int MAX_ENEMY_BULLETS = ENEMIES_NUM;
|
||||
|
||||
// 方向
|
||||
enum Direction { UP, DOWN, LEFT, RIGHT };
|
||||
|
||||
// 地图单元类型
|
||||
enum CELL_Type {
|
||||
BLANK, // �յ�
|
||||
RED_TANK, // �췽̹��
|
||||
RED_DEAD_TANK, // �췽����̹��
|
||||
BLUE_TANK, // ����̹��
|
||||
BLUE_DEAD_TANK, // ��������̹��
|
||||
WALL, // ǽ
|
||||
BULLET, // �ӵ�
|
||||
BLANK,
|
||||
RED_TANK,
|
||||
RED_DEAD_TANK,
|
||||
BLUE_TANK,
|
||||
BLUE_DEAD_TANK,
|
||||
WALL,
|
||||
BULLET
|
||||
};
|
||||
// 全局变量声明(extern),定义在 main.cpp 中
|
||||
extern int map[MAP_CELL_NUM][MAP_CELL_NUM];
|
||||
extern int kill;
|
||||
extern tank player;
|
||||
extern tank enemies[ENEMIES_NUM];
|
||||
extern bullet E_bullets[BULLET_NUM * ENEMIES_NUM];
|
||||
extern bullet P_bullets[BULLET_NUM];
|
||||
extern tank enemy;
|
||||
|
||||
#endif // !DATA_CONFIG_H
|
||||
#endif
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,54 +0,0 @@
|
||||
// map.cpp
|
||||
#include "map.h"
|
||||
|
||||
// 定义两个地图数组
|
||||
int map1[50][50];
|
||||
int map2[50][50];
|
||||
|
||||
void init_map1()
|
||||
{
|
||||
for (int i = 0; i < 50; i++)
|
||||
{
|
||||
for (int j = 0; j < 50; j++)
|
||||
{
|
||||
// 四周为墙
|
||||
if (i == 0 || i == 49 || j == 0 || j == 49)
|
||||
{
|
||||
map1[i][j] = WALL;
|
||||
}
|
||||
// 构造中间的+形状
|
||||
else if (i == 24 || j == 24)
|
||||
{
|
||||
map1[i][j] = WALL;
|
||||
}
|
||||
else
|
||||
{
|
||||
map1[i][j] = BLANK;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void init_map2()
|
||||
{
|
||||
for (int i = 0; i < 50; i++)
|
||||
{
|
||||
for (int j = 0; j < 50; j++)
|
||||
{
|
||||
// 四周为墙
|
||||
if (i == 0 || i == 49 || j == 0 || j == 49)
|
||||
{
|
||||
map2[i][j] = WALL;
|
||||
}
|
||||
// 构造中间的X形状
|
||||
else if (i == j || i + j == 49)
|
||||
{
|
||||
map2[i][j] = WALL;
|
||||
}
|
||||
else
|
||||
{
|
||||
map2[i][j] = BLANK;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
// 预制地图定义
|
||||
// map.h
|
||||
#ifndef MAP_H
|
||||
#define MAP_H
|
||||
|
||||
#include "data_config.h"
|
||||
|
||||
// 声明两个地图数组
|
||||
extern int map1[50][50];
|
||||
extern int map2[50][50];
|
||||
|
||||
// 声明地图初始化函数
|
||||
void init_map1();
|
||||
void init_map2();
|
||||
|
||||
#endif // !MAP_H
|
||||
@@ -0,0 +1,52 @@
|
||||
#include "AssetManager.h"
|
||||
#include <cstdio>
|
||||
|
||||
AssetManager::AssetManager(SDL_Renderer* r) : renderer(r) {}
|
||||
|
||||
AssetManager::~AssetManager() {
|
||||
for (auto& [_, tex] : textures)
|
||||
SDL_DestroyTexture(tex);
|
||||
if (font) TTF_CloseFont(font);
|
||||
if (bgm) { Mix_HaltMusic(); Mix_FreeMusic(bgm); }
|
||||
}
|
||||
|
||||
SDL_Texture* AssetManager::getTexture(const std::string& path) {
|
||||
auto it = textures.find(path);
|
||||
if (it != textures.end())
|
||||
return it->second;
|
||||
|
||||
SDL_Surface* surf = IMG_Load(path.c_str());
|
||||
if (!surf) {
|
||||
fprintf(stderr, "IMG_Load error: %s - %s\n", path.c_str(), IMG_GetError());
|
||||
return nullptr;
|
||||
}
|
||||
SDL_Texture* tex = SDL_CreateTextureFromSurface(renderer, surf);
|
||||
SDL_FreeSurface(surf);
|
||||
if (tex) {
|
||||
SDL_SetTextureBlendMode(tex, SDL_BLENDMODE_BLEND);
|
||||
} else {
|
||||
fprintf(stderr, "CreateTexture error: %s - %s\n", path.c_str(), SDL_GetError());
|
||||
return nullptr;
|
||||
}
|
||||
textures[path] = tex;
|
||||
return tex;
|
||||
}
|
||||
|
||||
bool AssetManager::loadFont(const char* path, int size) {
|
||||
font = TTF_OpenFont(path, size);
|
||||
if (font) {
|
||||
printf("Loaded font: %s\n", path);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AssetManager::loadBGM(const char* path) {
|
||||
bgm = Mix_LoadMUS(path);
|
||||
if (bgm) {
|
||||
Mix_PlayMusic(bgm, -1);
|
||||
return true;
|
||||
}
|
||||
fprintf(stderr, "BGM load failed: %s\n", Mix_GetError());
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
#ifndef ASSET_MANAGER_H
|
||||
#define ASSET_MANAGER_H
|
||||
|
||||
#include <SDL2/SDL.h>
|
||||
#include <SDL2/SDL_image.h>
|
||||
#include <SDL2/SDL_mixer.h>
|
||||
#include <SDL2/SDL_ttf.h>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
class AssetManager {
|
||||
public:
|
||||
AssetManager(SDL_Renderer* renderer);
|
||||
~AssetManager();
|
||||
|
||||
SDL_Texture* getTexture(const std::string& path);
|
||||
TTF_Font* getFont() const { return font; }
|
||||
Mix_Music* getBGM() const { return bgm; }
|
||||
|
||||
bool loadFont(const char* path, int size);
|
||||
bool loadBGM(const char* path);
|
||||
|
||||
private:
|
||||
SDL_Renderer* renderer;
|
||||
std::unordered_map<std::string, SDL_Texture*> textures;
|
||||
TTF_Font* font = nullptr;
|
||||
Mix_Music* bgm = nullptr;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,15 @@
|
||||
#include "Bullet.h"
|
||||
#include "Tank.h"
|
||||
|
||||
void Bullet::fire(int bx, int by, Direction d, int dmg) {
|
||||
x = bx; y = by; dir = d; damage = dmg; isLive = true;
|
||||
}
|
||||
|
||||
CELL_Type Bullet::peekAt(int map[MAP_CELL_NUM][MAP_CELL_NUM]) const {
|
||||
return static_cast<CELL_Type>(map[x + dx(dir)][y + dy(dir)]);
|
||||
}
|
||||
|
||||
void Bullet::step() {
|
||||
x += dx(dir);
|
||||
y += dy(dir);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
#ifndef BULLET_H
|
||||
#define BULLET_H
|
||||
|
||||
#include "../data_config.h"
|
||||
|
||||
class Bullet {
|
||||
public:
|
||||
int x = 0, y = 0;
|
||||
int damage = 0;
|
||||
Direction dir = UP;
|
||||
bool isLive = false;
|
||||
|
||||
void fire(int bx, int by, Direction d, int dmg);
|
||||
CELL_Type peekAt(int map[MAP_CELL_NUM][MAP_CELL_NUM]) const;
|
||||
void step();
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,451 @@
|
||||
#include "Game.h"
|
||||
#include "Tank.h"
|
||||
#include "UI.h"
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <ctime>
|
||||
#include <random>
|
||||
#include <unistd.h>
|
||||
|
||||
Game::Game() {
|
||||
std::srand(static_cast<unsigned>(std::time(nullptr)));
|
||||
playerBullets.resize(MAX_PLAYER_BULLETS);
|
||||
enemyBullets.resize(MAX_ENEMY_BULLETS);
|
||||
enemies.resize(ENEMIES_NUM);
|
||||
}
|
||||
|
||||
Game::~Game() {
|
||||
delete render;
|
||||
delete assets;
|
||||
if (sdlRenderer) SDL_DestroyRenderer(sdlRenderer);
|
||||
if (window) SDL_DestroyWindow(window);
|
||||
Mix_CloseAudio();
|
||||
TTF_Quit();
|
||||
IMG_Quit();
|
||||
SDL_Quit();
|
||||
}
|
||||
|
||||
bool Game::initSDL() {
|
||||
char* base = SDL_GetBasePath();
|
||||
if (base) { chdir(base); SDL_free(base); }
|
||||
|
||||
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0) {
|
||||
fprintf(stderr, "SDL init failed: %s\n", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
if (!(IMG_Init(IMG_INIT_PNG | IMG_INIT_JPG) & (IMG_INIT_PNG | IMG_INIT_JPG))) {
|
||||
fprintf(stderr, "SDL_image init failed: %s\n", IMG_GetError());
|
||||
return false;
|
||||
}
|
||||
if (Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 2048) < 0) {
|
||||
fprintf(stderr, "SDL_mixer init failed: %s\n", Mix_GetError());
|
||||
return false;
|
||||
}
|
||||
if (TTF_Init() < 0) {
|
||||
fprintf(stderr, "SDL_ttf init failed: %s\n", TTF_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
window = SDL_CreateWindow("纸牌坦克", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
|
||||
WINDOW_W, WINDOW_H, SDL_WINDOW_SHOWN);
|
||||
if (!window) { fprintf(stderr, "Window failed: %s\n", SDL_GetError()); return false; }
|
||||
|
||||
sdlRenderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
|
||||
if (!sdlRenderer) { fprintf(stderr, "Renderer failed: %s\n", SDL_GetError()); return false; }
|
||||
|
||||
assets = new AssetManager(sdlRenderer);
|
||||
render = new Renderer(sdlRenderer, assets);
|
||||
|
||||
const char* fontPaths[] = {
|
||||
"/usr/share/fonts/wqy-microhei-fonts/wqy-microhei.ttc",
|
||||
"/usr/share/fonts/wqy-zenhei-fonts/wqy-zenhei.ttc",
|
||||
"/usr/share/fonts/google-noto-sans-cjk-vf-fonts/NotoSansCJK-VF.ttc",
|
||||
"/usr/share/fonts/google-noto-serif-cjk-vf-fonts/NotoSerifCJK-VF.ttc",
|
||||
nullptr
|
||||
};
|
||||
bool fontOk = false;
|
||||
for (int i = 0; fontPaths[i]; i++) {
|
||||
if (assets->loadFont(fontPaths[i], 30)) { fontOk = true; break; }
|
||||
}
|
||||
if (!fontOk) fprintf(stderr, "Warning: no Chinese font found\n");
|
||||
|
||||
assets->loadBGM("music/bgm.wav");
|
||||
|
||||
lastPlayerMove = lastPlayerShoot = lastEnemyMove = lastEnemyShoot = 0;
|
||||
lastBulletMove = lastEnemyBulletMove = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
int Game::run() {
|
||||
if (!initSDL()) return -1;
|
||||
|
||||
scene = Scene::MENU;
|
||||
SDL_Event e;
|
||||
while (running) {
|
||||
while (SDL_PollEvent(&e)) handleEvents(e);
|
||||
update();
|
||||
renderScene();
|
||||
SDL_Delay(16);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ------------------- event -------------------
|
||||
|
||||
void Game::handleEvents(SDL_Event& e) {
|
||||
if (e.type == SDL_QUIT) { running = false; return; }
|
||||
|
||||
if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_ESCAPE) {
|
||||
switch (scene) {
|
||||
case Scene::LEVEL:
|
||||
case Scene::MEMBER:
|
||||
case Scene::GAME:
|
||||
case Scene::WIN:
|
||||
case Scene::LOSE:
|
||||
scene = Scene::MENU;
|
||||
map.reset();
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.type != SDL_MOUSEBUTTONDOWN) return;
|
||||
int mx = e.button.x, my = e.button.y;
|
||||
|
||||
switch (scene) {
|
||||
case Scene::MENU:
|
||||
if (UI::hit(UI::BTN_START, mx, my)) { initGame(); scene = Scene::GAME; }
|
||||
if (UI::hit(UI::BTN_LEVEL, mx, my)) { scene = Scene::LEVEL; }
|
||||
if (UI::hit(UI::BTN_MEMBER, mx, my)) { scene = Scene::MEMBER; }
|
||||
if (UI::hit(UI::BTN_QUIT, mx, my)) { running = false; }
|
||||
break;
|
||||
case Scene::LEVEL:
|
||||
if (UI::hit(UI::BTN_EASY, mx, my)) {
|
||||
enemyCfg.hp = 5; enemyCfg.attack = 1; scene = Scene::MENU;
|
||||
}
|
||||
if (UI::hit(UI::BTN_MEDIUM, mx, my)) {
|
||||
enemyCfg.hp = 10; enemyCfg.attack = 2; scene = Scene::MENU;
|
||||
}
|
||||
if (UI::hit(UI::BTN_HARD, mx, my)) {
|
||||
enemyCfg.hp = 15; enemyCfg.attack = 3; scene = Scene::MENU;
|
||||
}
|
||||
break;
|
||||
case Scene::MEMBER:
|
||||
if (UI::hit(UI::BTN_BACK, mx, my)) scene = Scene::MENU;
|
||||
break;
|
||||
case Scene::WIN:
|
||||
case Scene::LOSE:
|
||||
if (UI::hit(UI::BTN_BACK, mx, my)) scene = Scene::MENU;
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------- update -------------------
|
||||
|
||||
void Game::update() {
|
||||
if (scene != Scene::GAME) return;
|
||||
updateGame();
|
||||
}
|
||||
|
||||
void Game::initGame() {
|
||||
map.reset();
|
||||
killCount = 0;
|
||||
|
||||
player = Tank(5, 5);
|
||||
player.x = MAP_CELL_NUM / 2;
|
||||
player.y = MAP_CELL_NUM / 2;
|
||||
player.dir = UP;
|
||||
|
||||
std::default_random_engine gen(std::random_device{}());
|
||||
std::uniform_int_distribution<int> dist(1, MAP_CELL_NUM - 2);
|
||||
|
||||
for (auto& e : enemies) {
|
||||
do {
|
||||
e.x = dist(gen);
|
||||
e.y = dist(gen);
|
||||
} while (map.get(e.x, e.y) != BLANK);
|
||||
e.hp = enemyCfg.hp;
|
||||
e.attack = enemyCfg.attack;
|
||||
e.dir = LEFT;
|
||||
e.isTaken = false;
|
||||
map.set(e.x, e.y, BLUE_TANK);
|
||||
}
|
||||
|
||||
for (auto& b : playerBullets) b.isLive = false;
|
||||
for (auto& b : enemyBullets) b.isLive = false;
|
||||
}
|
||||
|
||||
bool Game::isWin() const {
|
||||
for (auto& e : enemies)
|
||||
if (e.hp > 0) return false;
|
||||
return player.hp > 0;
|
||||
}
|
||||
|
||||
void Game::updateGame() {
|
||||
if (player.hp <= 0) { scene = Scene::LOSE; return; }
|
||||
if (isWin()) { scene = Scene::WIN; return; }
|
||||
|
||||
Uint32 now = SDL_GetTicks();
|
||||
|
||||
// --- 玩家移动 ---
|
||||
const Uint8* keys = SDL_GetKeyboardState(nullptr);
|
||||
bool moved = false;
|
||||
if (keys[SDL_SCANCODE_W] || keys[SDL_SCANCODE_UP]) { player.dir = UP; moved = true; }
|
||||
else if (keys[SDL_SCANCODE_S] || keys[SDL_SCANCODE_DOWN]) { player.dir = DOWN; moved = true; }
|
||||
else if (keys[SDL_SCANCODE_A] || keys[SDL_SCANCODE_LEFT]) { player.dir = LEFT; moved = true; }
|
||||
else if (keys[SDL_SCANCODE_D] || keys[SDL_SCANCODE_RIGHT]){ player.dir = RIGHT; moved = true; }
|
||||
|
||||
if (moved && now - lastPlayerMove >= 100) {
|
||||
lastPlayerMove = now;
|
||||
int tx = player.x + dx(player.dir);
|
||||
int ty = player.y + dy(player.dir);
|
||||
CELL_Type c = map.get(tx, ty);
|
||||
|
||||
if (c == BLANK) {
|
||||
player.x = tx; player.y = ty;
|
||||
} else if (c == BULLET) {
|
||||
player.x = tx; player.y = ty;
|
||||
player.hp--;
|
||||
for (auto& b : enemyBullets) {
|
||||
if (b.isLive && b.x == tx && b.y == ty) { b.isLive = false; break; }
|
||||
}
|
||||
} else if (c == BLUE_DEAD_TANK) {
|
||||
player.x = tx; player.y = ty;
|
||||
player.attack++;
|
||||
if (player.hp < 5) player.hp++;
|
||||
for (auto& e : enemies) {
|
||||
if (e.hp <= 0 && !e.isTaken && e.x == tx && e.y == ty) {
|
||||
e.isTaken = true; break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- 玩家射击 ---
|
||||
if (keys[SDL_SCANCODE_SPACE] && now - lastPlayerShoot >= 500) {
|
||||
lastPlayerShoot = now;
|
||||
for (auto& b : playerBullets) {
|
||||
if (!b.isLive) {
|
||||
b.fire(player.x, player.y, player.dir, player.attack);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- 敌人移动 ---
|
||||
if (now - lastEnemyMove >= 1000) {
|
||||
lastEnemyMove = now;
|
||||
for (auto& e : enemies) {
|
||||
if (e.hp <= 0) continue;
|
||||
Direction d = static_cast<Direction>(rand() % 4);
|
||||
e.dir = d;
|
||||
int tx = e.x + dx(d);
|
||||
int ty = e.y + dy(d);
|
||||
CELL_Type c = map.get(tx, ty);
|
||||
|
||||
if (c == BLANK) {
|
||||
e.x = tx; e.y = ty;
|
||||
} else if (c == BULLET) {
|
||||
e.x = tx; e.y = ty;
|
||||
e.hp--;
|
||||
for (auto& pb : playerBullets) {
|
||||
if (pb.isLive && pb.x == tx && pb.y == ty) { pb.isLive = false; break; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- 敌人射击 ---
|
||||
if (now - lastEnemyShoot >= 3000) {
|
||||
lastEnemyShoot = now;
|
||||
for (auto& e : enemies) {
|
||||
if (e.hp <= 0) continue;
|
||||
for (auto& b : enemyBullets) {
|
||||
if (!b.isLive) {
|
||||
b.fire(e.x, e.y, e.dir, enemyCfg.attack);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- 玩家子弹移动 ---
|
||||
if (now - lastBulletMove >= 100) {
|
||||
lastBulletMove = now;
|
||||
for (auto& b : playerBullets) {
|
||||
if (!b.isLive) continue;
|
||||
CELL_Type c = b.peekAt(map.grid);
|
||||
|
||||
if (c == BLANK) {
|
||||
b.step();
|
||||
} else if (c == WALL) {
|
||||
b.isLive = false;
|
||||
} else if (c == BLUE_TANK) {
|
||||
b.isLive = false;
|
||||
int tx = b.x + dx(b.dir), ty = b.y + dy(b.dir);
|
||||
for (auto& e : enemies) {
|
||||
if (e.hp > 0 && e.x == tx && e.y == ty) { e.hp -= b.damage; break; }
|
||||
}
|
||||
} else {
|
||||
b.isLive = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- 敌人子弹移动 ---
|
||||
if (now - lastEnemyBulletMove >= 500) {
|
||||
lastEnemyBulletMove = now;
|
||||
for (auto& eb : enemyBullets) {
|
||||
if (!eb.isLive) continue;
|
||||
CELL_Type c = eb.peekAt(map.grid);
|
||||
|
||||
if (c == BLANK) {
|
||||
eb.step();
|
||||
} else if (c == WALL) {
|
||||
eb.isLive = false;
|
||||
} else if (c == RED_TANK) {
|
||||
eb.isLive = false;
|
||||
player.hp -= eb.damage;
|
||||
} else {
|
||||
eb.step();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- 同步地图 ---
|
||||
map.reset();
|
||||
map.set(player.x, player.y, RED_TANK);
|
||||
for (auto& e : enemies) {
|
||||
if (e.hp > 0)
|
||||
map.set(e.x, e.y, BLUE_TANK);
|
||||
else if (!e.isTaken)
|
||||
map.set(e.x, e.y, BLUE_DEAD_TANK);
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------- render -------------------
|
||||
|
||||
void Game::renderScene() {
|
||||
render->clear();
|
||||
switch (scene) {
|
||||
case Scene::MENU: renderMenu(); break;
|
||||
case Scene::LEVEL: renderLevel(); break;
|
||||
case Scene::MEMBER: renderMember(); break;
|
||||
case Scene::GAME: renderGameView(); break;
|
||||
case Scene::WIN: renderWin(); break;
|
||||
case Scene::LOSE: renderLose(); break;
|
||||
default: break;
|
||||
}
|
||||
render->present();
|
||||
}
|
||||
|
||||
static SDL_Color GRAY = {157, 157, 157, 255};
|
||||
|
||||
void Game::renderMenu() {
|
||||
render->drawImage("images/菜单界面.jpg", 0, 0, WINDOW_W, WINDOW_H);
|
||||
render->drawImage("images/开始游戏按键.jpg", UI::BTN_START.x, UI::BTN_START.y, UI::BTN_START.w, UI::BTN_START.h);
|
||||
render->drawImage("images/难度设置按键.jpg", UI::BTN_LEVEL.x, UI::BTN_LEVEL.y, UI::BTN_LEVEL.w, UI::BTN_LEVEL.h);
|
||||
render->drawImage("images/相关人员按键.jpg", UI::BTN_MEMBER.x, UI::BTN_MEMBER.y, UI::BTN_MEMBER.w, UI::BTN_MEMBER.h);
|
||||
render->drawImage("images/退出游戏按键.jpg", UI::BTN_QUIT.x, UI::BTN_QUIT.y, UI::BTN_QUIT.w, UI::BTN_QUIT.h);
|
||||
}
|
||||
|
||||
void Game::renderLevel() {
|
||||
render->drawImage("images/游戏主界面背景.jpg", 0, 0, WINDOW_W, WINDOW_H);
|
||||
render->drawImage("images/简单难度.jpg", UI::BTN_EASY.x, UI::BTN_EASY.y, UI::BTN_EASY.w, UI::BTN_EASY.h);
|
||||
render->drawImage("images/中等难度.jpg", UI::BTN_MEDIUM.x, UI::BTN_MEDIUM.y, UI::BTN_MEDIUM.w, UI::BTN_MEDIUM.h);
|
||||
render->drawImage("images/困难难度.jpg", UI::BTN_HARD.x, UI::BTN_HARD.y, UI::BTN_HARD.w, UI::BTN_HARD.h);
|
||||
}
|
||||
|
||||
void Game::renderMember() {
|
||||
render->drawImage("images/开发人员界面.jpg", 0, 0, WINDOW_W, WINDOW_H);
|
||||
render->drawImage("images/返回按钮.jpg", UI::BTN_BACK.x, UI::BTN_BACK.y, UI::BTN_BACK.w, UI::BTN_BACK.h);
|
||||
}
|
||||
|
||||
void Game::renderGameView() {
|
||||
render->drawImage("images/游戏主界面背景.jpg", 0, 0, WINDOW_W, WINDOW_H);
|
||||
|
||||
SDL_Texture* hpTex = assets->getTexture("images/red_hp_bar.jpg");
|
||||
for (int i = 0; i < player.hp; i++)
|
||||
render->drawTexture(hpTex, UI::HP_BAR_X + i * CELL_SIZE, UI::HP_BAR_Y, CELL_SIZE, CELL_SIZE);
|
||||
|
||||
int dieNum = 0;
|
||||
for (auto& e : enemies) if (e.hp <= 0) dieNum++;
|
||||
render->drawText("杀敌:", 110, 660, GRAY);
|
||||
render->drawInt(dieNum, 185, 660, GRAY);
|
||||
render->drawText("/10", 210, 660, GRAY);
|
||||
std::string atk = "当前攻击力" + std::to_string(player.attack);
|
||||
render->drawText(atk, 325, 660, GRAY);
|
||||
|
||||
SDL_Texture* wallTex = assets->getTexture("images/wall.jpg");
|
||||
for (int i = 0; i < MAP_CELL_NUM; i++)
|
||||
for (int j = 0; j < MAP_CELL_NUM; j++)
|
||||
if (map.grid[i][j] == WALL)
|
||||
render->drawTexture(wallTex, j * CELL_SIZE + X_OFFSET, i * CELL_SIZE + Y_OFFSET, CELL_SIZE, CELL_SIZE);
|
||||
|
||||
// 子弹
|
||||
auto drawBullet = [&](Bullet& b) {
|
||||
if (!b.isLive) return;
|
||||
int sx = b.x * CELL_SIZE + X_OFFSET;
|
||||
int sy = b.y * CELL_SIZE + Y_OFFSET;
|
||||
bool horiz = (b.dir == LEFT || b.dir == RIGHT);
|
||||
int tx = horiz ? sx : sx + 10;
|
||||
int ty = horiz ? sy + 10 : sy;
|
||||
int bw = horiz ? CELL_SIZE / 2 : CELL_SIZE / 4;
|
||||
int bh = horiz ? CELL_SIZE / 4 : CELL_SIZE / 2;
|
||||
const char* img = nullptr;
|
||||
switch (b.dir) {
|
||||
case UP: img = "images/B_UP_b.png"; break;
|
||||
case DOWN: img = "images/B_DOWN_b.png"; break;
|
||||
case LEFT: img = "images/B_LEFT_b.png"; break;
|
||||
case RIGHT: img = "images/B_RIGHT_b.png"; break;
|
||||
}
|
||||
SDL_Texture* tex = assets->getTexture(img);
|
||||
render->drawTexture(tex, tx, ty, bw, bh);
|
||||
};
|
||||
|
||||
for (auto& b : playerBullets) drawBullet(b);
|
||||
for (auto& b : enemyBullets) drawBullet(b);
|
||||
|
||||
// 坦克
|
||||
auto drawTank = [&](Tank& t, bool isPlayer) {
|
||||
int px = t.x * CELL_SIZE + X_OFFSET;
|
||||
int py = t.y * CELL_SIZE + Y_OFFSET;
|
||||
const char* img = nullptr;
|
||||
if (isPlayer) {
|
||||
switch (t.dir) {
|
||||
case UP: img = "images/red_up_b.png"; break;
|
||||
case DOWN: img = "images/red_down_b.png"; break;
|
||||
case LEFT: img = "images/red_left_b.png"; break;
|
||||
case RIGHT: img = "images/red_right_b.png"; break;
|
||||
}
|
||||
} else {
|
||||
switch (t.dir) {
|
||||
case UP: img = "images/blue_up_b.png"; break;
|
||||
case DOWN: img = "images/blue_down_b.png"; break;
|
||||
case LEFT: img = "images/blue_left_b.png"; break;
|
||||
case RIGHT: img = "images/blue_right_b.png"; break;
|
||||
}
|
||||
}
|
||||
render->drawTexture(assets->getTexture(img), px, py, CELL_SIZE, CELL_SIZE);
|
||||
};
|
||||
|
||||
drawTank(player, true);
|
||||
for (auto& e : enemies) {
|
||||
if (e.isTaken) continue;
|
||||
if (e.hp > 0) drawTank(e, false);
|
||||
else render->drawTexture(assets->getTexture("images/dead_tank_b.png"),
|
||||
e.x * CELL_SIZE + X_OFFSET, e.y * CELL_SIZE + Y_OFFSET, CELL_SIZE, CELL_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
void Game::renderWin() {
|
||||
render->drawImage("images/胜利.png", 0, 0, WINDOW_W, WINDOW_H);
|
||||
render->drawImage("images/返回按钮.jpg", UI::BTN_BACK.x, UI::BTN_BACK.y, UI::BTN_BACK.w, UI::BTN_BACK.h);
|
||||
}
|
||||
|
||||
void Game::renderLose() {
|
||||
render->drawImage("images/失败.png", 0, 0, WINDOW_W, WINDOW_H);
|
||||
render->drawImage("images/返回按钮.jpg", UI::BTN_BACK.x, UI::BTN_BACK.y, UI::BTN_BACK.w, UI::BTN_BACK.h);
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
#ifndef GAME_H
|
||||
#define GAME_H
|
||||
|
||||
#include <SDL2/SDL.h>
|
||||
#include <vector>
|
||||
#include "../data_config.h"
|
||||
#include "Map.h"
|
||||
#include "Tank.h"
|
||||
#include "Bullet.h"
|
||||
#include "AssetManager.h"
|
||||
#include "Renderer.h"
|
||||
|
||||
enum class Scene { MENU, LEVEL, MEMBER, GAME, WIN, LOSE, QUIT };
|
||||
|
||||
class Game {
|
||||
public:
|
||||
Game();
|
||||
~Game();
|
||||
int run();
|
||||
|
||||
private:
|
||||
SDL_Window* window = nullptr;
|
||||
SDL_Renderer* sdlRenderer = nullptr;
|
||||
AssetManager* assets = nullptr;
|
||||
Renderer* render = nullptr;
|
||||
|
||||
Scene scene = Scene::MENU;
|
||||
bool running = true;
|
||||
|
||||
Map map;
|
||||
Tank player;
|
||||
std::vector<Tank> enemies;
|
||||
std::vector<Bullet> playerBullets;
|
||||
std::vector<Bullet> enemyBullets;
|
||||
Tank enemyCfg;
|
||||
int killCount = 0;
|
||||
|
||||
// 计时
|
||||
Uint32 lastPlayerMove = 0, lastPlayerShoot = 0;
|
||||
Uint32 lastEnemyMove = 0, lastEnemyShoot = 0;
|
||||
Uint32 lastBulletMove = 0, lastEnemyBulletMove = 0;
|
||||
|
||||
bool initSDL();
|
||||
bool initFont();
|
||||
void handleEvents(SDL_Event& e);
|
||||
void update();
|
||||
void renderScene();
|
||||
|
||||
void initGame();
|
||||
void updateGame();
|
||||
|
||||
void renderMenu();
|
||||
void renderLevel();
|
||||
void renderMember();
|
||||
void renderGameView();
|
||||
void renderWin();
|
||||
void renderLose();
|
||||
|
||||
bool isWin() const;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,34 @@
|
||||
#include "Map.h"
|
||||
|
||||
Map::Map() {
|
||||
reset();
|
||||
}
|
||||
|
||||
void Map::reset() {
|
||||
for (int i = 0; i < MAP_CELL_NUM; i++) {
|
||||
for (int j = 0; j < MAP_CELL_NUM; j++) {
|
||||
if (i == 0 || i == MAP_CELL_NUM - 1 || j == 0 || j == MAP_CELL_NUM - 1)
|
||||
grid[i][j] = WALL;
|
||||
else
|
||||
grid[i][j] = BLANK;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CELL_Type Map::get(int x, int y) const {
|
||||
return static_cast<CELL_Type>(grid[x][y]);
|
||||
}
|
||||
|
||||
void Map::set(int x, int y, CELL_Type type) {
|
||||
grid[x][y] = type;
|
||||
}
|
||||
|
||||
CELL_Type Map::peek(int x, int y, Direction dir) const {
|
||||
switch (dir) {
|
||||
case UP: return static_cast<CELL_Type>(grid[x][y - 1]);
|
||||
case DOWN: return static_cast<CELL_Type>(grid[x][y + 1]);
|
||||
case LEFT: return static_cast<CELL_Type>(grid[x - 1][y]);
|
||||
case RIGHT: return static_cast<CELL_Type>(grid[x + 1][y]);
|
||||
}
|
||||
return BLANK;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
#ifndef MAP_H
|
||||
#define MAP_H
|
||||
|
||||
#include "../data_config.h"
|
||||
|
||||
class Map {
|
||||
public:
|
||||
int grid[MAP_CELL_NUM][MAP_CELL_NUM];
|
||||
|
||||
Map();
|
||||
void reset();
|
||||
CELL_Type get(int x, int y) const;
|
||||
void set(int x, int y, CELL_Type type);
|
||||
CELL_Type peek(int x, int y, Direction dir) const;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,40 @@
|
||||
#include "Renderer.h"
|
||||
|
||||
Renderer::Renderer(SDL_Renderer* r, AssetManager* a) : renderer(r), assets(a) {}
|
||||
|
||||
void Renderer::clear() {
|
||||
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
|
||||
SDL_RenderClear(renderer);
|
||||
}
|
||||
|
||||
void Renderer::present() {
|
||||
SDL_RenderPresent(renderer);
|
||||
}
|
||||
|
||||
void Renderer::drawTexture(SDL_Texture* tex, int x, int y, int w, int h) {
|
||||
if (!tex) return;
|
||||
SDL_Rect dst = {x, y, w, h};
|
||||
SDL_RenderCopy(renderer, tex, nullptr, &dst);
|
||||
}
|
||||
|
||||
void Renderer::drawImage(const std::string& path, int x, int y, int w, int h) {
|
||||
drawTexture(assets->getTexture(path), x, y, w, h);
|
||||
}
|
||||
|
||||
void Renderer::drawText(const std::string& text, int x, int y, SDL_Color color) {
|
||||
TTF_Font* font = assets->getFont();
|
||||
if (!font || text.empty()) return;
|
||||
SDL_Surface* surf = TTF_RenderUTF8_Blended(font, text.c_str(), color);
|
||||
if (!surf) return;
|
||||
SDL_Texture* tex = SDL_CreateTextureFromSurface(renderer, surf);
|
||||
if (tex) {
|
||||
SDL_Rect dst = {x, y, surf->w, surf->h};
|
||||
SDL_RenderCopy(renderer, tex, nullptr, &dst);
|
||||
SDL_DestroyTexture(tex);
|
||||
}
|
||||
SDL_FreeSurface(surf);
|
||||
}
|
||||
|
||||
void Renderer::drawInt(int value, int x, int y, SDL_Color color) {
|
||||
drawText(std::to_string(value), x, y, color);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#ifndef RENDERER_H
|
||||
#define RENDERER_H
|
||||
|
||||
#include <SDL2/SDL.h>
|
||||
#include <SDL2/SDL_ttf.h>
|
||||
#include <string>
|
||||
#include "AssetManager.h"
|
||||
|
||||
class Renderer {
|
||||
public:
|
||||
Renderer(SDL_Renderer* r, AssetManager* a);
|
||||
|
||||
void clear();
|
||||
void present();
|
||||
void drawTexture(SDL_Texture* tex, int x, int y, int w, int h);
|
||||
void drawImage(const std::string& path, int x, int y, int w, int h);
|
||||
void drawText(const std::string& text, int x, int y, SDL_Color color);
|
||||
void drawInt(int value, int x, int y, SDL_Color color);
|
||||
|
||||
private:
|
||||
SDL_Renderer* renderer;
|
||||
AssetManager* assets;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,3 @@
|
||||
#include "Tank.h"
|
||||
|
||||
Tank::Tank(int h, int a) : hp(h), attack(a) {}
|
||||
@@ -0,0 +1,19 @@
|
||||
#ifndef TANK_H
|
||||
#define TANK_H
|
||||
|
||||
#include "../data_config.h"
|
||||
|
||||
class Tank {
|
||||
public:
|
||||
int x = 0, y = 0;
|
||||
int hp = 5, attack = 1;
|
||||
Direction dir = UP;
|
||||
bool isTaken = false;
|
||||
|
||||
Tank(int h = 5, int a = 1);
|
||||
};
|
||||
|
||||
inline int dx(Direction d) { return d == LEFT ? -1 : (d == RIGHT ? 1 : 0); }
|
||||
inline int dy(Direction d) { return d == UP ? -1 : (d == DOWN ? 1 : 0); }
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,32 @@
|
||||
#ifndef UI_H
|
||||
#define UI_H
|
||||
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
namespace UI {
|
||||
|
||||
inline bool hit(const SDL_Rect& r, int mx, int my) {
|
||||
return mx >= r.x && mx <= r.x + r.w && my >= r.y && my <= r.y + r.h;
|
||||
}
|
||||
|
||||
// 菜单按钮
|
||||
constexpr SDL_Rect BTN_START = {50, 250, 150, 50};
|
||||
constexpr SDL_Rect BTN_LEVEL = {50, 330, 150, 50};
|
||||
constexpr SDL_Rect BTN_MEMBER = {50, 410, 150, 50};
|
||||
constexpr SDL_Rect BTN_QUIT = {50, 490, 150, 50};
|
||||
|
||||
// 难度按钮
|
||||
constexpr SDL_Rect BTN_EASY = {275, 150, 150, 100};
|
||||
constexpr SDL_Rect BTN_MEDIUM = {275, 300, 150, 100};
|
||||
constexpr SDL_Rect BTN_HARD = {275, 450, 150, 100};
|
||||
|
||||
// 返回按钮
|
||||
constexpr SDL_Rect BTN_BACK = {30, 550, 150, 50};
|
||||
|
||||
// HP 条
|
||||
constexpr int HP_BAR_X = 100;
|
||||
constexpr int HP_BAR_Y = 10;
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,33 +0,0 @@
|
||||
// 坦克定义
|
||||
#ifndef TANK_H
|
||||
#define TANK_H
|
||||
|
||||
/*
|
||||
坦克:
|
||||
坐标:x,y
|
||||
血量hp:表示当前坦克血量
|
||||
攻击力attack:表示当前坦克攻击力
|
||||
方向dir:
|
||||
UP 上
|
||||
DOWN 下
|
||||
LEFT 左
|
||||
RIGHT 右
|
||||
*/
|
||||
enum Direction {
|
||||
UP, // 上
|
||||
DOWN, // 下
|
||||
LEFT, // 左
|
||||
RIGHT, // 右
|
||||
};
|
||||
|
||||
struct tank {
|
||||
int x, y;//坐标
|
||||
int hp; // 血量
|
||||
int attack; // 攻击力
|
||||
int dir; // 方向
|
||||
bool is_taken = false;//报销坦克是否已经被玩家捡取
|
||||
|
||||
tank(): attack(1), hp(5){}
|
||||
};
|
||||
|
||||
#endif // !TANK_H
|
||||
Reference in New Issue
Block a user