diff --git a/.gitignore b/.gitignore index 42f2da8..7745194 100644 --- a/.gitignore +++ b/.gitignore @@ -443,3 +443,10 @@ CMakeScripts/ Makefile *.make +session-*.md +.hermes/ + +# CMake 构建产物 +build_Debug/ +build_Release/ +build_*/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 186acd5..2031d20 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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/ diff --git a/tank_battles_on_the_scrap_paper/bullet.h b/tank_battles_on_the_scrap_paper/bullet.h deleted file mode 100644 index 653f5b3..0000000 --- a/tank_battles_on_the_scrap_paper/bullet.h +++ /dev/null @@ -1,24 +0,0 @@ -// ӵ -#ifndef BULLET_H -#define BULLET_H - -/* - ӵ - ӵ꣺xy - 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 \ No newline at end of file diff --git a/tank_battles_on_the_scrap_paper/data_config.h b/tank_battles_on_the_scrap_paper/data_config.h index e170b05..018626c 100644 --- a/tank_battles_on_the_scrap_paper/data_config.h +++ b/tank_battles_on_the_scrap_paper/data_config.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 \ No newline at end of file +#endif diff --git a/tank_battles_on_the_scrap_paper/main.cpp b/tank_battles_on_the_scrap_paper/main.cpp index 631d17c..22b696c 100644 --- a/tank_battles_on_the_scrap_paper/main.cpp +++ b/tank_battles_on_the_scrap_paper/main.cpp @@ -1,1468 +1,6 @@ -// 项目:纸牌上的坦克大战 - SDL2 Linux Port -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "src/Game.h" -// 引入 data_config 等头文件 -#include "data_config.h" -// #include "map.h" // 引入预设地图头文件 -// #include "main.h" // 引入函数声明头文件 - -using namespace std; - -// -------- 全局变量定义 --------- -int map[MAP_CELL_NUM][MAP_CELL_NUM]; //用map.h预置 -int kill = 5; //玩家击杀数 -tank player; //玩家 -tank enemies[ENEMIES_NUM]; //敌人 -bullet E_bullets[BULLET_NUM * ENEMIES_NUM]; //敌方坦克子弹数量 -bullet P_bullets[BULLET_NUM]; //玩家子弹数量 -tank enemy; // 用于修改敌人的属性,难度设置时用到 - -// -------- SDL2 全局变量 -------- -SDL_Window* g_window = NULL; -SDL_Renderer* g_renderer = NULL; -TTF_Font* g_font = NULL; -Mix_Music* g_bgm = NULL; - -// -------- 辅助函数 -------- - -// 加载纹理(带缩放) -SDL_Texture* load_texture(const char* path) { - static unordered_map tex_cache; - string key(path); - auto it = tex_cache.find(key); - if (it != tex_cache.end()) { - return it->second; - } - SDL_Surface* surf = IMG_Load(path); - if (!surf) { - fprintf(stderr, "IMG_Load error: %s - %s\n", path, IMG_GetError()); - return NULL; - } - SDL_Texture* tex = SDL_CreateTextureFromSurface(g_renderer, surf); - SDL_FreeSurface(surf); - if (tex) { - SDL_SetTextureBlendMode(tex, SDL_BLENDMODE_BLEND); - } else { - fprintf(stderr, "CreateTexture error: %s - %s\n", path, SDL_GetError()); - } - tex_cache[key] = tex; - return tex; -} - -// 渲染纹理到指定位置和大小 -void render_texture(SDL_Texture* tex, int x, int y, int w, int h) { - if (!tex) return; - SDL_Rect dst = {x, y, w, h}; - SDL_RenderCopy(g_renderer, tex, NULL, &dst); -} - -// 渲染纹理到指定位置(不缩放,使用纹理原始大小) -void render_texture_raw(SDL_Texture* tex, int x, int y) { - if (!tex) return; - int w, h; - SDL_QueryTexture(tex, NULL, NULL, &w, &h); - SDL_Rect dst = {x, y, w, h}; - SDL_RenderCopy(g_renderer, tex, NULL, &dst); -} - -// 带缩放加载并渲染纹理(一次性操作) -void draw_image(const char* path, int x, int y, int w, int h) { - SDL_Texture* tex = load_texture(path); - render_texture(tex, x, y, w, h); -} - -// 渲染文本 -void render_text(const char* text, int x, int y, SDL_Color color) { - if (!g_font || !text) return; - SDL_Surface* surf = TTF_RenderUTF8_Blended(g_font, text, color); - if (!surf) return; - SDL_Texture* tex = SDL_CreateTextureFromSurface(g_renderer, surf); - if (tex) { - SDL_Rect dst = {x, y, surf->w, surf->h}; - SDL_RenderCopy(g_renderer, tex, NULL, &dst); - SDL_DestroyTexture(tex); - } - SDL_FreeSurface(surf); -} - -// 渲染int数值为文本 -void render_int(int value, int x, int y, SDL_Color color) { - string s = to_string(value); - render_text(s.c_str(), x, y, color); -} - -// 清屏 -void clear_screen() { - SDL_SetRenderDrawColor(g_renderer, 0, 0, 0, 255); - SDL_RenderClear(g_renderer); -} - -// 更新画面 -void present_screen() { - SDL_RenderPresent(g_renderer); -} - -// 事件处理辅助:等待并处理键盘和鼠标事件,返回是否退出 / 转到菜单 -// 返回: 0=继续循环, -1=退出/返回菜单 -int handle_menu_events(bool& goto_menu) { - SDL_Event e; - while (SDL_PollEvent(&e)) { - if (e.type == SDL_QUIT) { - exit(0); - } - if (e.type == SDL_KEYDOWN) { - if (e.key.keysym.sym == SDLK_ESCAPE) { - goto_menu = true; - return -1; - } - } - if (e.type == SDL_MOUSEBUTTONDOWN) { - return 1; // 鼠标点击发生,调用者需检查坐标 - } - } - return 0; -} - -// -------- 原函数声明(保持不变) -------- -void EnemyBulletMove(); -void PlayerBulletMove(); -int BulletMoveCheck(int dir, bullet bul); -void change(); -void EnemiesMove(); -void EnemyInit(); -void EnemyShoot(); -void GameView(); -void GameView_ShowBullet(); -void GameView_ShowLose(); -void GameView_ShowMap(); -void GameView_ShowTank(); -void GameView_ShowWIN(); -void InitMap(); -// void InitMap(const int preset_map[50][50]); -bool IsWin(); -void Level(); -void MemberView(); -void MenuView(); -void Move(); -void PlayerInit(); -void PlayerMove(); -void PlayerShoot(); -int TankMoveCheck(int dir, tank tank); -void Update(); - -// git 提交 - -/* - 菜单界面: - 展示开始游戏,难度设置,退出等选项 - 用户通过w/s来选择对应的按钮,回车进行选择 - - 开始游戏: - 当选中开始游戏时,跳转游戏主函数,直接进入游戏。 - - 难度设置: - 当选中难度设置时,跳转难度选择函数,进行对敌方坦克血量以及攻击力进行变化等操作。 - 调整完毕之后需要esc返回菜单然后才可以进入游戏。 - - 退出游戏: - 当选中退出时,直接退出程序。 - - 关于开发人员 - 跳转相关信息页面,展示开发人员信息 - - // 游戏进行中的开始页面 - -游戏过程 - 展示地图和坦克,玩家控制己方坦克进行自动移动和开火 - 操作wsad控制己方坦克进行上下左右移动 - 按下空格控制己方向前发射子弹 - 撞到墙,掉血 - 当杀死所有敌方坦克时,游戏胜利,返回主菜单 - - 暂停界面 - 展示继续游戏、退出游戏按钮,同时游戏暂停 -*/ - -/* - 功能模块 - 1.菜单界面 - 2.菜单界面转到游戏界面 - 3.游戏界面转到暂停界面 - 4.暂停界面转到菜单界面 -*/ - -//-----------------view------------------------ -/* - 功能描述: - 功能:展示开始游戏,难度设置,退出等选项, 关于我们 - 开始游戏: - 当选中开始游戏时,跳转游戏主函数,直接进入游戏。 - 难度设置: - 当选中难度设置时,跳转难度选择函数,进行对敌方坦克血量以及攻击力进行变化等操作。 - 调整完毕之后需要esc返回菜单然后才可以进入游戏。 - 退出游戏: - 当选中退出时,直接退出程序。 - 关于我们: - 展示团队信息 - // 游戏进行中的开始页面 -*/ -void MenuView(); - -/* -* 游戏界面 - 功能描述: -*/ -// 每一帧,地图和坦克每一帧状态渲染 -// 在一个新的地图上进行操作,包括坦克和子弹的坐标:位置和方向 -void GameView() //游戏主界面视图 -{ - InitMap(); //初始化地图 - // InitMap(map1); //初始化地图 - EnemyInit(); //初始化敌方坦克的位置 - PlayerInit(); //初始化己方坦克的位置 - - // 初始渲染 - clear_screen(); - GameView_ShowMap(); //显示地图 - GameView_ShowTank(); //显示坦克 - present_screen(); - - while (!IsWin()) // 游戏循环没有胜利 - { - if (player.hp <= 0) //判断玩家的生命值是否小于或等于 0 - { - GameView_ShowLose(); //调用 failView 函数显示失败视图 - /*MenuView();*/ //返回主菜单界面 - break; - } - - // 处理ESC和窗口事件 - SDL_Event e; - while (SDL_PollEvent(&e)) { - if (e.type == SDL_QUIT) { - exit(0); - } - if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_ESCAPE) { - MenuView(); //调用菜单 menuView 函数返回主菜单界面 - } - } - - Move(); // 调用 Move、Shoot 和 Update 函数:这些函数需要实现坦克和子弹的移动、射击和状态更新。 - //射击 - const Uint8* keys = SDL_GetKeyboardState(NULL); - if (keys[SDL_SCANCODE_SPACE]) - PlayerShoot(); - EnemyShoot(); - // Update(); - Update(); - - // 渲染帧 - clear_screen(); - GameView_ShowMap(); //显示地图的最新状态 - GameView_ShowBullet(); //再进行子弹显示更新状态 - GameView_ShowTank(); //显示坦克的最新状态 - present_screen(); - - SDL_Delay(16); // ~60 FPS,控制帧率 - } - - if (IsWin()) //如果游戏胜利,调用 winView 函数显示胜利界面,并返回主菜单界面。 - { - GameView_ShowWIN(); - /*MenuView();*/ - } -} - -/* - 创建者:桃仁和毛血旺 - 功能 - 用来判断玩家子弹是否到达上限,0-3跳过直接返回0-3并找到第一个is_live == false,break - 射击模块:子弹初始化发射 -*/ -void PlayerShoot() -{ - static clock_t start = clock() - 500; - if ((clock() - start) < 500) - return; - start = clock(); - for (int i = 0; i < SINGLE_BULLET; i++) - { - if (P_bullets[i].is_live == false) - { - //初始化子弹 - P_bullets[i].x = player.x; - P_bullets[i].y = player.y; - P_bullets[i].dir = player.dir; - P_bullets[i].damage = player.attack; - P_bullets[i].is_live = true; - break; - } - } -} - -/* - 创建者:桃仁和毛血旺 - 输入:敌人结构体数组 - 敌人发射子弹自动射击,由敌方坦克 - 先通过随机数判断是否要发射,随机数%2,返回0或1 - if 要发射 则循环子弹数组 -*/ -void EnemyShoot() -{ - static clock_t start = clock() - 3000; - if ((clock() - start) < 3000) - return; - start = clock(); - for (int i = 0; i < ENEMIES_NUM; i++) //遍历所有敌方坦克 - { - if (enemies[i].hp > 0) { - for (auto& j : E_bullets) - { - if (j.is_live == false) //只要一个子弹不存活,发射,否则若都已经存在,则不发射子弹 - { - j.x = enemies[i].x; - j.y = enemies[i].y; - j.dir = enemies[i].dir; - j.damage = enemy.attack; - j.is_live = true; - break; - } - } - } - } -} - -/* - 创建者:桃仁和毛血旺 - 功能: - 调用移动函数,子弹移动函数和碰撞检测等 -*/ -void Move() -{ - bool move = false; - const Uint8* keys = SDL_GetKeyboardState(NULL); - if (keys[SDL_SCANCODE_W] || keys[SDL_SCANCODE_UP]) - { - player.dir = UP; - move = true; - } - else if (keys[SDL_SCANCODE_S] || keys[SDL_SCANCODE_DOWN]) - { - player.dir = DOWN; - move = true; - } - else if (keys[SDL_SCANCODE_A] || keys[SDL_SCANCODE_LEFT]) - { - player.dir = LEFT; - move = true; - } - else if (keys[SDL_SCANCODE_D] || keys[SDL_SCANCODE_RIGHT]) - { - player.dir = RIGHT; - move = true; - };//设置方向 - - - - if (move) { - PlayerMove(); //player.dir 是全局变量所以能调用 - } - EnemiesMove(); //敌人移动函数 - PlayerBulletMove(); - EnemyBulletMove(); -} - -///* -//* 判断我方坦克前方是否为空地 -//*/ -//bool PlayerCollision(); - - -/* - 创建者:小 yang - 功能: - 根据map数组生成地图 -*/ -void GameView_ShowMap() // 坦克是全局的,不需要传参 -//void GameView_ShowMap(tank player) -{ - //绘制背景 - draw_image("images/游戏主界面背景.jpg", 0, 0, 700, 700); - - //红色方HP渲染 - SDL_Texture* hp_tex = load_texture("images/red_hp_bar.jpg"); - for (int i = 0; i < player.hp; i++) - { - render_texture(hp_tex, 100 + i * 30, 10, CELL_SIZE, CELL_SIZE); - } - - ////蓝色方HP渲染 - //for (int j = 0; j < player.hp; j++) - //{ - // putimage(570 - j * 30, 120, &image_RED_HP); - //} - - int dieNum = 0; - for (auto& i : enemies) { - if (i.hp <= 0) dieNum++; - } - - //文本渲染杀敌数 - SDL_Color text_color = {157, 157, 157, 255}; - render_text("杀敌:", 110, 660, text_color); - render_int(dieNum, 185, 660, text_color); - render_text("/", 210, 660, text_color); - render_text("10", 225, 660, text_color); - - //当前攻击力 - string atk_str = "当前攻击力" + to_string(player.attack); - render_text(atk_str.c_str(), 325, 660, text_color); - - //墙壁纹理渲染 - SDL_Texture* wall_tex = load_texture("images/wall.jpg"); - for (int i = 0; i < MAP_CELL_NUM; i++) - { - for (int j = 0; j < MAP_CELL_NUM; j++) - { - if (map[i][j] == WALL) - { - render_texture(wall_tex, j * CELL_SIZE + X_OFFSET, i * CELL_SIZE + Y_OFFSET, CELL_SIZE, CELL_SIZE); - } - } - } -} - - -/* - 创建者: - 功能:渲染子弹到地图 -*/ -// void GameView_ShowBullet(); -// void GameView_ShowBullet(bullet b); - -/* - 创建者:小 yang - 功能:渲染坦克到地图 - -*/ -void GameView_ShowTank() -// void GameView_ShowTank(tank player, tank enemies[]) -{ - //我方坦克纹理 - SDL_Texture* red_up = load_texture("images/red_up_b.png"); - SDL_Texture* red_down = load_texture("images/red_down_b.png"); - SDL_Texture* red_left = load_texture("images/red_left_b.png"); - SDL_Texture* red_right = load_texture("images/red_right_b.png"); - - //敌方坦克纹理 - SDL_Texture* blue_up = load_texture("images/blue_up_b.png"); - SDL_Texture* blue_down = load_texture("images/blue_down_b.png"); - SDL_Texture* blue_left = load_texture("images/blue_left_b.png"); - SDL_Texture* blue_right = load_texture("images/blue_right_b.png"); - - //死亡坦克纹理 - SDL_Texture* dead_tank = load_texture("images/dead_tank_b.png"); - - //渲染地图元素 - for (int i = 0; i < MAP_CELL_NUM; i++) - { - for (int j = 0; j < MAP_CELL_NUM; j++) - { - if (map[i][j] == RED_TANK && player.dir == LEFT) - { - render_texture(red_left, player.x * CELL_SIZE + X_OFFSET, player.y * CELL_SIZE + Y_OFFSET, CELL_SIZE, CELL_SIZE); - } - - if (map[i][j] == RED_TANK && player.dir == RIGHT) - { - render_texture(red_right, player.x * CELL_SIZE + X_OFFSET, player.y * CELL_SIZE + Y_OFFSET, CELL_SIZE, CELL_SIZE); - } - - if (map[i][j] == RED_TANK && player.dir == DOWN) - { - render_texture(red_down, player.x * CELL_SIZE + X_OFFSET, player.y * CELL_SIZE + Y_OFFSET, CELL_SIZE, CELL_SIZE); - } - - if (map[i][j] == RED_TANK && player.dir == UP) - { - render_texture(red_up, player.x * CELL_SIZE + X_OFFSET, player.y * CELL_SIZE + Y_OFFSET, CELL_SIZE, CELL_SIZE); - } - } - } - - //渲染敌方坦克 - for (int i = 0; i < ENEMIES_NUM; i++) - { - if (enemies[i].is_taken) - continue; - if (enemies[i].hp > 0) { - switch (enemies[i].dir) - { - case UP: - render_texture(blue_up, enemies[i].x * CELL_SIZE + X_OFFSET, enemies[i].y * CELL_SIZE + Y_OFFSET, CELL_SIZE, CELL_SIZE); - break; - case DOWN: - render_texture(blue_down, enemies[i].x * CELL_SIZE + X_OFFSET, enemies[i].y * CELL_SIZE + Y_OFFSET, CELL_SIZE, CELL_SIZE); - break; - case LEFT: - render_texture(blue_left, enemies[i].x * CELL_SIZE + X_OFFSET, enemies[i].y * CELL_SIZE + Y_OFFSET, CELL_SIZE, CELL_SIZE); - break; - case RIGHT: - render_texture(blue_right, enemies[i].x * CELL_SIZE + X_OFFSET, enemies[i].y * CELL_SIZE + Y_OFFSET, CELL_SIZE, CELL_SIZE); - break; - default: - break; - } - } - else { - render_texture(dead_tank, enemies[i].x * CELL_SIZE + X_OFFSET, enemies[i].y * CELL_SIZE + Y_OFFSET, CELL_SIZE, CELL_SIZE); - } - } -} - -// 子弹纹理缓存(仅加载一次) -SDL_Texture* g_bullet_tex[4] = {NULL, NULL, NULL, NULL}; - -void GameView_ShowBullet() -{ - // 首次使用时加载子弹纹理 - if (!g_bullet_tex[UP]) { - g_bullet_tex[UP] = load_texture("images/B_UP_b.png"); - g_bullet_tex[DOWN] = load_texture("images/B_DOWN_b.png"); - g_bullet_tex[LEFT] = load_texture("images/B_LEFT_b.png"); - g_bullet_tex[RIGHT] = load_texture("images/B_RIGHT_b.png"); - } - - for (int i = 0; i < BULLET_NUM; i++) { //遍历玩家子弹 - if (P_bullets[i].is_live) { - int x = P_bullets[i].x * CELL_SIZE + X_OFFSET; - int y = P_bullets[i].y * CELL_SIZE + Y_OFFSET; - int tex_x = (P_bullets[i].dir > DOWN ? x : x + 10); - int tex_y = (P_bullets[i].dir > DOWN ? y + 10 : y); - int bw = (P_bullets[i].dir > DOWN ? CELL_SIZE / 2 : CELL_SIZE / 2 / 2); - int bh = (P_bullets[i].dir > DOWN ? CELL_SIZE / 2 / 2 : CELL_SIZE / 2); - render_texture(g_bullet_tex[P_bullets[i].dir], tex_x, tex_y, bw, bh); - } - } - for (int i = 0; i < BULLET_NUM * ENEMIES_NUM; i++) { //绘制敌方子弹 - if (E_bullets[i].is_live) { - int x = E_bullets[i].x * CELL_SIZE + X_OFFSET; - int y = E_bullets[i].y * CELL_SIZE + Y_OFFSET; - int tex_x = (E_bullets[i].dir > DOWN ? x : x + 10); - int tex_y = (E_bullets[i].dir > DOWN ? y + 10 : y); - int bw = (E_bullets[i].dir > DOWN ? CELL_SIZE / 2 : CELL_SIZE / 2 / 2); - int bh = (E_bullets[i].dir > DOWN ? CELL_SIZE / 2 / 2 : CELL_SIZE / 2); - render_texture(g_bullet_tex[E_bullets[i].dir], tex_x, tex_y, bw, bh); - } - } -} - -/* - 创建者:凌零 - 功能:显示胜利界面 -*/ -void GameView_ShowWIN() -{ - clear_screen(); - draw_image("images/胜利.png", 0, 0, 700, 700); - draw_image("images/返回按钮.jpg", 30, 550, 150, 50); - present_screen(); - - bool goto_menu = false; - while (true) - { - int ev = handle_menu_events(goto_menu); - if (goto_menu) { - MenuView(); - return; - } - if (ev == 1) { - //鼠标点击 - 判断返回按钮区域 - // 用SDL_GetMouseState获取点击坐标 - int mx, my; - Uint32 buttons = SDL_GetMouseState(&mx, &my); - if (buttons & SDL_BUTTON(SDL_BUTTON_LEFT)) { - if (mx >= 30 && mx <= 180 && my >= 550 && my <= 600) - { - MenuView(); - return; - } - } - } - SDL_Delay(16); - } -} - -/* - 创建者:凌零 - 功能:显示失败界面 - -*/ - -void GameView_ShowLose() -{ - clear_screen(); - draw_image("images/失败.png", 0, 0, 700, 700); - draw_image("images/返回按钮.jpg", 30, 550, 150, 50); - present_screen(); - - bool goto_menu = false; - while (true) - { - int ev = handle_menu_events(goto_menu); - if (goto_menu) { - MenuView(); - return; - } - if (ev == 1) { - int mx, my; - Uint32 buttons = SDL_GetMouseState(&mx, &my); - if (buttons & SDL_BUTTON(SDL_BUTTON_LEFT)) { - if (mx >= 30 && mx <= 180 && my >= 550 && my <= 600) - { - MenuView(); - return; - } - } - } - SDL_Delay(16); - } -} - -//------------------view------------------- - -//-------------------service--------------- - -/* -* 刷新地图 -*/ - void Update() -{ - InitMap(); //把原有的地图数据再赋值一次map - - map[player.x][player.y] = RED_TANK; //把玩家、敌人、子弹数组,将位置信息写入到地图 - - for (int i = 0; i < ENEMIES_NUM; i++) { - if (enemies[i].hp > 0) { - map[enemies[i].x][enemies[i].y] = BLUE_TANK; - } - else if (enemies[i].hp <= 0 && enemies[i].is_taken == false) { - map[enemies[i].x][enemies[i].y] = BLUE_DEAD_TANK; - } - } - - //for (int j = 0; j < BULLET_NUM; j++) { //玩家子弹 - // if (P_bullets[j].is_live == true) { - // map[enemies[j].x][enemies[j].y] = BULLET; - // } - //} - - //for (int k = 0; k < BULLET_NUM * ENEMIES_NUM; k++) { //敌方子弹 - // if (E_bullets[k].is_live == true) { - // map[enemies[k].x][enemies[k].y] = BULLET; - // } - //} -} - -/* - 创建者:凌零 - 功能:初始化为周围是墙壁的空白地图 -*/ -void InitMap() -{ - // 初始化地图 - for (int i = 0; i < MAP_CELL_NUM; i++) { - for (int j = 0; j < MAP_CELL_NUM; j++) { - // 如果是边缘的位置,设定为 WALL - if (i == 0 || i == MAP_CELL_NUM - 1 || j == 0 || j == MAP_CELL_NUM - 1) { - map[i][j] = WALL; - } - // 其他位置设定为 BLANK - else { - map[i][j] = BLANK; - } - } - } -} - -/* - 创建者:凌零 - 功能:扩展功能的地图初始化(根据map.h文件预设地图数据) -*/ -//void InitMap(const int preset_map[MAP_CELL_NUM][MAP_CELL_NUM]) { -// for (int i = 0; i < MAP_CELL_NUM; i++) { -// for (int j = 0; j < MAP_CELL_NUM; j++) { -// map[i][j] = preset_map[i][j]; -// } -// } -//} - -/* - 创建者:丧丧的咸鱼 - 功能: - 根据传入的方向参数判断坦克移动的可行性 - 实现坦克移动 -*/ -void PlayerMove() { - static clock_t start = clock() - 100; - if ((clock() - start) < 100) - return; - start = clock(); - if (player.dir == UP) { - switch (TankMoveCheck(player.dir, player)) { //空地,敌方坦克,子弹,敌方死亡坦克,墙 - case BLANK: - player.y--; - break; - case BULLET: - player.y--; - player.hp--; - for (int i = 0; i <= BULLET_NUM * ENEMIES_NUM; i++) { //找出并干掉那颗子弹报销 - if (E_bullets[i].x == player.x && E_bullets[i].y == player.y) { - E_bullets[i].is_live = false; - } - } - break; - case BLUE_DEAD_TANK: - player.y--; - player.attack++; - if(player.hp < 5) player.hp++; - for (int i = 0; i < ENEMIES_NUM; i++) { //循环敌人坦克数组 - if (player.x == enemies[i].x && player.y == enemies[i].y) //找个位置下无敌坦克 - enemies[i].is_taken = true; - } - break; - default: - break; - } - - - } - else if (player.dir == DOWN) { - switch (TankMoveCheck(player.dir, player)) { //空地,敌方坦克,子弹,敌方死亡坦克,墙 - case BLANK: - player.y++; - break; - case BULLET: - player.y++; - player.hp--; - for (int i = 0; i <= BULLET_NUM * ENEMIES_NUM; i++) { //找出并干掉那颗子弹报销 - if (E_bullets[i].x == player.x && E_bullets[i].y == player.y) { - E_bullets[i].is_live = false; - } - } - break; - case BLUE_DEAD_TANK: - player.y++; - player.attack++; - if (player.hp < 5) player.hp++; - for (int i = 0; i < ENEMIES_NUM; i++) { //循环敌人坦克数组 - if (player.x == enemies[i].x && player.y == enemies[i].y) //找个位置下无敌坦克 - enemies[i].is_taken = true; - } - break; - default: - break; - } - } - else if (player.dir == LEFT) { - switch (TankMoveCheck(player.dir, player)) { //空地,敌方坦克,子弹,敌方死亡坦克,墙 - case BLANK: - player.x--; - break; - case BULLET: - player.x--; - player.hp--; - for (int i = 0; i <= BULLET_NUM * ENEMIES_NUM; i++) { //找出并干掉那颗子弹报销 - if (E_bullets[i].x == player.x && E_bullets[i].y == player.y) { - E_bullets[i].is_live = false; - } - } - break; - case BLUE_DEAD_TANK: - player.x--; - player.attack++; - if (player.hp < 5) player.hp++; - for (int i = 0; i < ENEMIES_NUM; i++) { //循环敌人坦克数组 - if (player.x == enemies[i].x && player.y == enemies[i].y) //找个位置下无敌坦克 - enemies[i].is_taken = true; - } - break; - default: - break; - } - } - else if (player.dir == RIGHT) { - switch (TankMoveCheck(player.dir, player)) { //空地,敌方坦克,子弹,敌方死亡坦克,墙 - case BLANK: - player.x++; - break; - case BULLET: - player.x++; - player.hp--; - for (int i = 0; i <= BULLET_NUM * ENEMIES_NUM; i++) { //找出并干掉那颗子弹报销 - if (E_bullets[i].x == player.x && E_bullets[i].y == player.y) { - E_bullets[i].is_live = false; - } - } - break; - case BLUE_DEAD_TANK: - player.x++; - player.attack++; - if (player.hp < 5) player.hp++; - for (int i = 0; i < ENEMIES_NUM; i++) { //循环敌人坦克数组 - if (player.x == enemies[i].x && player.y == enemies[i].y) //找个位置下无敌坦克 - enemies[i].is_taken = true; - } - break; - default: - break; - } - } -}//撞墙和敌方坦克坦克不移动将不会修改 - -/* - 创建者:丧丧的咸鱼 - 功能:坦克碰撞检测:检测是否为墙壁、敌方死亡坦克、敌方坦克、空地 - 对不同类型进行不同的处理 - 0->空地、1->表示墙、2->己方坦克、3->敌方坦克、4->死亡坦克 - 返回值为下一个位置的坐标(根据player.dir计算),并返回该位置存储的枚举值 -*/ -int TankMoveCheck(int dir, tank tank) { //坦克能否移动检查,返回检测位置存储的枚举值 - int ret = 0; // 默认返回值为 0 - switch (dir) { - case UP: - return map[tank.x][tank.y - 1]; - break; - case DOWN: - return map[tank.x][tank.y + 1]; - break; - case LEFT: - return map[tank.x - 1][tank.y]; - break; - case RIGHT: - return map[tank.x + 1][tank.y]; - break; - default: // 添加默认分支 - break; - } - return ret; -} - -/* - 创建者:凌零 - 功能:遍历子弹数组,对每一个子弹进行处理 - 首先肯定要用个外循环遍历所有子弹,分1,2,......1,5. 12,13,14,15,16... - 子弹碰撞检测:检测是否为墙壁、敌方死亡坦克、敌方坦克、玩家子弹、敌方子弹、空地、己方坦克。 -*/ - -/* - 创建者:凌零 -*/ -int BulletMoveCheck(int dir, bullet bul) //返回当前目标位置的map枚举值 -{ - int ret = 0; // 默认返回值为 0 - switch (dir) { - case UP: - return map[bul.x][bul.y - 1]; - break; - case DOWN: - return map[bul.x][bul.y + 1]; - break; - case LEFT: - return map[bul.x - 1][bul.y]; - break; - case RIGHT: - return map[bul.x + 1][bul.y]; - break; - default: // 添加默认分支 - return ret; - break; - } -} - -/* - 创建者:丧丧的咸鱼 - 功能: - 通过随机移动(wasd)和方向取余4取模0123 - 生成方向之后-调用检查判断下一个位置的状态然后再进行相应的操作 - 同时移动时改变方向判断下一步移动的可行性实现随机移动 -*/ -void EnemiesMove() { //敌人控制函数 - static clock_t start = clock() - 1000; - if((clock() - start) < 1000) - return; - start = clock(); - for (int i = 0; i < ENEMIES_NUM; ++i) { //n个敌方坦克移动 - if (enemies[i].hp <= 0) - continue; - int dir = rand() % 4; //随机生成敌方坦克的移动方向 - if (dir == UP) { //0向上 - enemies[i].dir = UP; // 更新当前 enemies[i] 的方向为向上 - switch (TankMoveCheck(dir, enemies[i])) { //空地,敌方坦克,子弹,墙 - case BLANK: - enemies[i].y--; - break; - case BULLET: - enemies[i].y--; - for (int i = 0; i < BULLET_NUM; i++) { //遍历玩家子弹数组 - if (P_bullets[i].x == enemies[i].x && P_bullets[i].y == enemies[i].y) { - P_bullets[i].is_live = false; - enemies[i].hp--; - } - } - break; - } - } - else if (dir == DOWN) { //1向下 - enemies[i].dir = DOWN; - switch (TankMoveCheck(dir, enemies[i])) { //空地,敌方坦克,子弹,墙 - case BLANK: - enemies[i].y++; - break; - case BULLET: - enemies[i].y++; - for (int i = 0; i < BULLET_NUM; i++) { //遍历玩家子弹数组 - if (P_bullets[i].x == enemies[i].x && P_bullets[i].y == enemies[i].y) { - P_bullets[i].is_live = false; - enemies[i].hp--; - } - } - break; - } - } - else if (dir == LEFT) { //2向左 - enemies[i].dir = LEFT; - switch (TankMoveCheck(dir, enemies[i])) { //空地,敌方坦克,子弹,墙 - case BLANK: - enemies[i].x--; - break; - case BULLET: - enemies[i].x--; - for (int i = 0; i < BULLET_NUM; i++) { //遍历玩家子弹数组 - if (P_bullets[i].x == enemies[i].x && P_bullets[i].y == enemies[i].y) { - P_bullets[i].is_live = false; - enemies[i].hp--; - } - } - break; - } - } - else if (dir == RIGHT) { //3向右 - enemies[i].dir = RIGHT; - switch (TankMoveCheck(dir, enemies[i])) { //空地,敌方坦克,子弹,墙 - case BLANK: - enemies[i].x++; - break; - case BULLET: - enemies[i].x++; - for (int i = 0; i < BULLET_NUM; i++) { //遍历玩家子弹数组 - if (P_bullets[i].x == enemies[i].x && P_bullets[i].y == enemies[i].y) { - P_bullets[i].is_live = false; - enemies[i].hp--; - } - } - break; - } - } - } -} - -/* - 创建者:凌零 - 功能: - 根据传入的方向参数调用BulletMoveCheck() - 返回的枚举值判断子弹移动结果 - 实现子弹移动 -*/ - -void PlayerBulletMove() { - static clock_t start = clock() - 100; - if ((clock() - start) < 100) - return; - start = clock(); - - int arr[4][2] = { - {0, -1}, - {0, 1}, - {-1, 0}, - {1, 0}, - }; - for (int i = 0; i < BULLET_NUM; i++) // 遍历子弹 - { - if (P_bullets[i].is_live == false) - { - continue; - } - else - { - // 子弹碰撞检测:检测是否碰到空地、墙壁、己方坦克、敌方坦克、敌方死亡坦克、玩家子弹、敌方子弹 - // 0->空地、1->表示墙、2->己方坦克、3->敌方坦克、4->死亡坦克、5->我方子弹、6->敌方子弹 - // 0 空地 1 墙 3 敌方坦克 6 敌方子弹 - int type = BulletMoveCheck(P_bullets[i].dir, P_bullets[i]); - if (type == BLANK) - { - P_bullets[i].x += arr[P_bullets[i].dir][0]; - P_bullets[i].y += arr[P_bullets[i].dir][1]; - } - else if (type == WALL) - { - P_bullets[i].is_live = false; - } - else if (type == BLUE_TANK) //敌方坦克 - { - P_bullets[i].is_live = false; //子弹消失 - for (int k = 0; k < ENEMIES_NUM; k++) //坦克爆炸 - { - switch (P_bullets[i].dir) { - case UP: - if (enemies[k].x == P_bullets[i].x && enemies[k].y == P_bullets[i].y - 1) //位于子弹上方敌方坦克受到伤害 - { - enemies[k].hp -= P_bullets[i].damage; - } - break; - case DOWN: - if (enemies[k].x == P_bullets[i].x && enemies[k].y == P_bullets[i].y + 1) //位于子弹下方敌方坦克受到伤害 - { - enemies[k].hp -= P_bullets[i].damage; - } - break; - case LEFT: - if (enemies[k].x == P_bullets[i].x - 1 && enemies[k].y == P_bullets[i].y) //位于子弹左方敌方坦克受到伤害 - { - enemies[k].hp -= P_bullets[i].damage; - } - break; - case RIGHT: - if (enemies[k].x == P_bullets[i].x + 1 && enemies[k].y == P_bullets[i].y) //位于子弹右方敌方坦克受到伤害 - { - enemies[k].hp -= P_bullets[i].damage; - } - break; - default: // 添加默认分支 - break; - } - - } - } - else - { - P_bullets[i].is_live = false; - if (BULLET_NUM == 2) - { - for (int k = 3; k <= 8; k++) - { - if (P_bullets[k].x == P_bullets[i].x && P_bullets[k].y == P_bullets[i].y) - { - P_bullets[k].is_live = false; - break; - } - } - } - else - { - for (int k = 3; k <= 12; k++) - { - if (P_bullets[k].x == P_bullets[i].x && P_bullets[k].y == P_bullets[i].y) - { - P_bullets[k].is_live = false; - break; - } - } - } - break; - } - } - } -} - -void EnemyBulletMove() { - static clock_t start = clock() - 500; - if ((clock() - start) < 500) - return; - start = clock(); - - int arr[4][2] = { - {0, -1}, - {0, 1}, - {-1, 0}, - {1, 0}, - }; - for (auto& i : E_bullets) { - if (i.is_live) { - int type = BulletMoveCheck(i.dir, i); - if (type == BLANK) - { - i.x += arr[i.dir][0]; - i.y += arr[i.dir][1]; - } - else if (type == WALL) - { - i.is_live = false; - } - else if (type == RED_TANK) - { - i.is_live = false; - player.hp -= i.damage; - } - else { - i.x += arr[i.dir][0]; - i.y += arr[i.dir][1]; - } - /*else - { - i.is_live = false; - if (BULLET_NUM == 2) - { - for (int k = 3; k <= 8; k++) - { - if (P_bullets[k].x == i.x && P_bullets[k].y == i.y) - { - P_bullets[k].is_live = false; - break; - } - } - } - else - { - for (int k = 3; k <= 12; k++) - { - if (P_bullets[k].x == i.x && P_bullets[k].y == i.y) - { - P_bullets[k].is_live = false; - break; - } - } - } - break; - }*/ - } - } -} -/* - 创建者:凌零 - 功能:初始化玩家坦克 -*/ -void PlayerInit() -{ - player.x = MAP_CELL_NUM / 2; // 玩家初始位置 - player.y = MAP_CELL_NUM / 2; - player.hp = 5; // 玩家血量初始化 - player.dir = UP; // 初始化玩家方向 - player.attack = 5; // 初始化玩家攻击力为 5 - kill = 0; // 玩家击杀数初始化为 0 - map[player.x][player.y] = RED_TANK; // 在地图上标记坦克的位置 -} - -/* - 创建者:凌零 - 功能:生成敌方坦克 -*/ -void EnemyInit() -{ - srand((unsigned int)time(NULL)); //初始化随机数种子 - // 使用 库创建随机数生成器 - //default_random_engine generator; - //uniform_int_distribution distribution(0, 49); // 创建一个均匀分布,生成的随机数范围在1-48 - - // 初始化敌方坦克 - for (int i = 0; i < ENEMIES_NUM; i++) { - enemies[i].x = rand() % (MAP_CELL_NUM - 2) + 1 ; // 使用随机数生成器生成坐标 - enemies[i].y = rand() % (MAP_CELL_NUM - 2) + 1; - - // 如果随机到的位置已经被坦克或者墙壁占据,那么重新生成 - while (map[enemies[i].x][enemies[i].y] != BLANK) { - enemies[i].x = rand() % (MAP_CELL_NUM - 2) + 1; - enemies[i].y = rand() % (MAP_CELL_NUM - 2) + 1; - } - - enemies[i].hp = enemy.hp; // 设置敌人初始血量为5 - enemies[i].attack = enemy.attack; // 设置敌人初始攻击力为1 - enemies[i].dir = LEFT; // 设置敌人初始面朝左 - map[enemies[i].x][enemies[i].y] = BLUE_TANK; // 在地图上标记敌人位置 - } -} - -/* - 创建者:凌零 - 功能: - 返回值为假:说明失败,己方坦克hp<=0,或者还没有胜利条件:己方坦克hp>0,敌方坦克数量>0 - 返回值为真:说明赢了 己方坦克hp==0,敌方坦克数量==0 -*/ -bool IsWin() { - bool all_die = true; - for (auto& i : enemies) { - if (i.hp > 0) - all_die = false; - } - if (player.hp > 0 && all_die) { - return true; // 胜利 - } - else return false; // 没胜利或失败。 还在游戏进行中。 -} - -// 难度选择 -void Level() -{ - clear_screen(); - draw_image("images/游戏主界面背景.jpg", 0, 0, 700, 700); - - draw_image("images/简单难度.jpg", 275, 150, 150, 100); - draw_image("images/中等难度.jpg", 275, 300, 150, 100); - draw_image("images/困难难度.jpg", 275, 450, 150, 100); - present_screen(); - - while (true) - { - SDL_Event e; - while (SDL_PollEvent(&e)) { - if (e.type == SDL_QUIT) exit(0); - if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_ESCAPE) { - MenuView(); - return; - } - if (e.type == SDL_MOUSEBUTTONDOWN) { - int mx = e.button.x; - int my = e.button.y; - - //选择简单难度 - if (mx >= 275 && mx <= 425 && my >= 150 && my <= 250) - { - enemy.hp = 5; - enemy.attack = 1; - MenuView(); - return; - } - //选择中等难度 - if (mx >= 275 && mx <= 425 && my >= 300 && my <= 400) - { - enemy.hp = 10; - enemy.attack = 2; - MenuView(); - return; - } - //选择困难难度 - if (mx >= 275 && mx <= 425 && my >= 450 && my <= 550) - { - enemy.hp = 15; - enemy.attack = 3; - MenuView(); - return; - } - } - } - SDL_Delay(16); - } -} - -// 开发人员 -void MemberView() -{ - clear_screen(); - draw_image("images/开发人员界面.jpg", 0, 0, 700, 700); - draw_image("images/返回按钮.jpg", 30, 550, 150, 50); - present_screen(); - - while (true) - { - SDL_Event e; - while (SDL_PollEvent(&e)) { - if (e.type == SDL_QUIT) exit(0); - if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_ESCAPE) { - MenuView(); - return; - } - if (e.type == SDL_MOUSEBUTTONDOWN) { - int mx = e.button.x; - int my = e.button.y; - - //返回主菜单界面 - if (mx >= 30 && mx <= 180 && my >= 550 && my <= 600) - { - MenuView(); - return; - } - } - } - SDL_Delay(16); - } -} - -// 菜单 -void MenuView() -{ - InitMap(); - clear_screen(); - draw_image("images/菜单界面.jpg", 0, 0, 700, 700); - draw_image("images/开始游戏按键.jpg", 50, 250, 150, 50); - draw_image("images/难度设置按键.jpg", 50, 330, 150, 50); - draw_image("images/相关人员按键.jpg", 50, 410, 150, 50); - draw_image("images/退出游戏按键.jpg", 50, 490, 150, 50); - present_screen(); - - while (true) - { - SDL_Event e; - while (SDL_PollEvent(&e)) { - if (e.type == SDL_QUIT) exit(0); - if (e.type == SDL_MOUSEBUTTONDOWN) { - int mx = e.button.x; - int my = e.button.y; - - //跳转游戏主页面 - if (mx >= 50 && mx <= 200 && my >= 250 && my <= 300) - { - GameView(); - } - //跳转至游戏难度选择 - if (mx >= 50 && mx <= 200 && my >= 330 && my <= 380) - { - Level(); - return; - } - //跳转至开发人员界面 - if (mx >= 50 && mx <= 200 && my >= 410 && my <= 460) - { - MemberView(); - return; - } - //退出游戏 - if (mx >= 50 && mx <= 200 && my >= 490 && my <= 540) - { - exit(0); - } - } - } - SDL_Delay(16); - } -} - -void change() -{ - SDL_SetWindowTitle(g_window, "纸牌坦克"); -} - - -//-------------------service--------------- - -/* - 创建者:丧丧的咸鱼 -*/ int main() { - // 将工作目录设置为可执行文件所在目录,确保相对路径资源(images/、music/)能正确加载 - char* base_path = SDL_GetBasePath(); - if (base_path) { - chdir(base_path); - SDL_free(base_path); - } - - // 初始化SDL - if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0) { - fprintf(stderr, "SDL初始化失败: %s\n", SDL_GetError()); - return -1; - } - - // 初始化SDL_image - int img_flags = IMG_INIT_PNG | IMG_INIT_JPG; - if (!(IMG_Init(img_flags) & img_flags)) { - fprintf(stderr, "SDL_image初始化失败: %s\n", IMG_GetError()); - SDL_Quit(); - return -1; - } - - // 初始化SDL_mixer - if (Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 2048) < 0) { - fprintf(stderr, "SDL_mixer初始化失败: %s\n", Mix_GetError()); - IMG_Quit(); - SDL_Quit(); - return -1; - } - - // 初始化SDL_ttf - if (TTF_Init() < 0) { - fprintf(stderr, "SDL_ttf初始化失败: %s\n", TTF_GetError()); - Mix_CloseAudio(); - IMG_Quit(); - SDL_Quit(); - return -1; - } - - // 创建窗口和渲染器 - g_window = SDL_CreateWindow("纸牌坦克", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 700, 700, SDL_WINDOW_SHOWN); - if (!g_window) { - fprintf(stderr, "窗口创建失败: %s\n", SDL_GetError()); - TTF_Quit(); - Mix_CloseAudio(); - IMG_Quit(); - SDL_Quit(); - return -1; - } - - g_renderer = SDL_CreateRenderer(g_window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); - if (!g_renderer) { - fprintf(stderr, "渲染器创建失败: %s\n", SDL_GetError()); - SDL_DestroyWindow(g_window); - TTF_Quit(); - Mix_CloseAudio(); - IMG_Quit(); - SDL_Quit(); - return -1; - } - - // 加载字体(尝试多个常用中文字体路径) - const char* font_paths[] = { - "/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", - NULL - }; - for (int i = 0; font_paths[i] != NULL; i++) { - g_font = TTF_OpenFont(font_paths[i], 30); - if (g_font) { - printf("已加载字体: %s\n", font_paths[i]); - break; - } - } - if (!g_font) { - fprintf(stderr, "警告: 未找到中文字体,文本将无法显示。请安装 wqy-microhei 或 noto-cjk 字体。\n"); - // 尝试使用任何可用TTF字体 - } - - // 加载BGM - g_bgm = Mix_LoadMUS("music/bgm.wav"); - if (!g_bgm) { - fprintf(stderr, "BGM加载失败: %s,将静音运行\n", Mix_GetError()); - } else { - Mix_PlayMusic(g_bgm, -1); // 循环播放 - } - - // 设置窗口标题 - change(); - - // 进入主菜单 - MenuView(); - - // 清理资源 - if (g_bgm) { - Mix_HaltMusic(); - Mix_FreeMusic(g_bgm); - } - if (g_font) TTF_CloseFont(g_font); - SDL_DestroyRenderer(g_renderer); - SDL_DestroyWindow(g_window); - Mix_CloseAudio(); - TTF_Quit(); - IMG_Quit(); - SDL_Quit(); - - return 0; + Game game; + return game.run(); } diff --git a/tank_battles_on_the_scrap_paper/map.cpp b/tank_battles_on_the_scrap_paper/map.cpp deleted file mode 100644 index bc78e00..0000000 --- a/tank_battles_on_the_scrap_paper/map.cpp +++ /dev/null @@ -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; - } - } - } -} diff --git a/tank_battles_on_the_scrap_paper/map.h b/tank_battles_on_the_scrap_paper/map.h deleted file mode 100644 index 580b357..0000000 --- a/tank_battles_on_the_scrap_paper/map.h +++ /dev/null @@ -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 diff --git a/tank_battles_on_the_scrap_paper/src/AssetManager.cpp b/tank_battles_on_the_scrap_paper/src/AssetManager.cpp new file mode 100644 index 0000000..2852b82 --- /dev/null +++ b/tank_battles_on_the_scrap_paper/src/AssetManager.cpp @@ -0,0 +1,52 @@ +#include "AssetManager.h" +#include + +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; +} diff --git a/tank_battles_on_the_scrap_paper/src/AssetManager.h b/tank_battles_on_the_scrap_paper/src/AssetManager.h new file mode 100644 index 0000000..714d912 --- /dev/null +++ b/tank_battles_on_the_scrap_paper/src/AssetManager.h @@ -0,0 +1,30 @@ +#ifndef ASSET_MANAGER_H +#define ASSET_MANAGER_H + +#include +#include +#include +#include +#include +#include + +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 textures; + TTF_Font* font = nullptr; + Mix_Music* bgm = nullptr; +}; + +#endif diff --git a/tank_battles_on_the_scrap_paper/src/Bullet.cpp b/tank_battles_on_the_scrap_paper/src/Bullet.cpp new file mode 100644 index 0000000..e3475b4 --- /dev/null +++ b/tank_battles_on_the_scrap_paper/src/Bullet.cpp @@ -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(map[x + dx(dir)][y + dy(dir)]); +} + +void Bullet::step() { + x += dx(dir); + y += dy(dir); +} diff --git a/tank_battles_on_the_scrap_paper/src/Bullet.h b/tank_battles_on_the_scrap_paper/src/Bullet.h new file mode 100644 index 0000000..1e4e6da --- /dev/null +++ b/tank_battles_on_the_scrap_paper/src/Bullet.h @@ -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 diff --git a/tank_battles_on_the_scrap_paper/src/Game.cpp b/tank_battles_on_the_scrap_paper/src/Game.cpp new file mode 100644 index 0000000..6f8a35c --- /dev/null +++ b/tank_battles_on_the_scrap_paper/src/Game.cpp @@ -0,0 +1,451 @@ +#include "Game.h" +#include "Tank.h" +#include "UI.h" +#include +#include +#include +#include +#include + +Game::Game() { + std::srand(static_cast(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 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(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); +} diff --git a/tank_battles_on_the_scrap_paper/src/Game.h b/tank_battles_on_the_scrap_paper/src/Game.h new file mode 100644 index 0000000..cf3f3c3 --- /dev/null +++ b/tank_battles_on_the_scrap_paper/src/Game.h @@ -0,0 +1,62 @@ +#ifndef GAME_H +#define GAME_H + +#include +#include +#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 enemies; + std::vector playerBullets; + std::vector 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 diff --git a/tank_battles_on_the_scrap_paper/src/Map.cpp b/tank_battles_on_the_scrap_paper/src/Map.cpp new file mode 100644 index 0000000..138a07f --- /dev/null +++ b/tank_battles_on_the_scrap_paper/src/Map.cpp @@ -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(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(grid[x][y - 1]); + case DOWN: return static_cast(grid[x][y + 1]); + case LEFT: return static_cast(grid[x - 1][y]); + case RIGHT: return static_cast(grid[x + 1][y]); + } + return BLANK; +} diff --git a/tank_battles_on_the_scrap_paper/src/Map.h b/tank_battles_on_the_scrap_paper/src/Map.h new file mode 100644 index 0000000..67dfd9f --- /dev/null +++ b/tank_battles_on_the_scrap_paper/src/Map.h @@ -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 diff --git a/tank_battles_on_the_scrap_paper/src/Renderer.cpp b/tank_battles_on_the_scrap_paper/src/Renderer.cpp new file mode 100644 index 0000000..d86ec21 --- /dev/null +++ b/tank_battles_on_the_scrap_paper/src/Renderer.cpp @@ -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); +} diff --git a/tank_battles_on_the_scrap_paper/src/Renderer.h b/tank_battles_on_the_scrap_paper/src/Renderer.h new file mode 100644 index 0000000..cdb93e4 --- /dev/null +++ b/tank_battles_on_the_scrap_paper/src/Renderer.h @@ -0,0 +1,25 @@ +#ifndef RENDERER_H +#define RENDERER_H + +#include +#include +#include +#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 diff --git a/tank_battles_on_the_scrap_paper/src/Tank.cpp b/tank_battles_on_the_scrap_paper/src/Tank.cpp new file mode 100644 index 0000000..348a49c --- /dev/null +++ b/tank_battles_on_the_scrap_paper/src/Tank.cpp @@ -0,0 +1,3 @@ +#include "Tank.h" + +Tank::Tank(int h, int a) : hp(h), attack(a) {} diff --git a/tank_battles_on_the_scrap_paper/src/Tank.h b/tank_battles_on_the_scrap_paper/src/Tank.h new file mode 100644 index 0000000..e85a167 --- /dev/null +++ b/tank_battles_on_the_scrap_paper/src/Tank.h @@ -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 diff --git a/tank_battles_on_the_scrap_paper/src/UI.h b/tank_battles_on_the_scrap_paper/src/UI.h new file mode 100644 index 0000000..ce59453 --- /dev/null +++ b/tank_battles_on_the_scrap_paper/src/UI.h @@ -0,0 +1,32 @@ +#ifndef UI_H +#define UI_H + +#include + +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 diff --git a/tank_battles_on_the_scrap_paper/tank.h b/tank_battles_on_the_scrap_paper/tank.h deleted file mode 100644 index 9b020d1..0000000 --- a/tank_battles_on_the_scrap_paper/tank.h +++ /dev/null @@ -1,33 +0,0 @@ -// ̹˶ -#ifndef TANK_H -#define TANK_H - -/* - ̹ˣ - ꣺xy - Ѫ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