Merge pull request 'sdl2_version' (#2) from sdl2_version into main

Reviewed-on: #2
This commit was merged in pull request #2.
This commit is contained in:
2026-05-03 11:56:14 +08:00
27 changed files with 909 additions and 1868 deletions
+18
View File
@@ -432,3 +432,21 @@ FodyWeavers.xsd
# JetBrains Rider # JetBrains Rider
*.sln.iml *.sln.iml
# ---> CMake
build/
cmake-build-*/
CMakeCache.txt
CMakeFiles/
CMakeScripts/
*.cmake
!CMakeLists.txt
Makefile
*.make
session-*.md
.hermes/
# CMake 构建产物
build_Debug/
build_Release/
build_*/
+57
View File
@@ -0,0 +1,57 @@
cmake_minimum_required(VERSION 3.10)
project(tank_battles_on_the_scrap_paper
VERSION 1.0
LANGUAGES CXX
)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
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/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/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
SDL2::SDL2
SDL2_image::SDL2_image
SDL2_mixer::SDL2_mixer
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/
)
file(COPY
tank_battles_on_the_scrap_paper/music/
DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/music/
)
+2
View File
@@ -3,3 +3,5 @@
v16: 添加了全局背景音效 v16: 添加了全局背景音效
v17: 修改了一点bug,在返回菜单后调用InitMap(),避免再次进入游戏界面时有脏数据导致显示出错 v17: 修改了一点bug,在返回菜单后调用InitMap(),避免再次进入游戏界面时有脏数据导致显示出错
v18: 从 Windows/EasyX 移植到 SDL2,支持 Linux;修复启动黑屏问题(自动设置工作目录为可执行文件所在目录)
-31
View File
@@ -1,31 +0,0 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.5.33530.505
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "tank_battles_on_the_scrap_paper", "tank_battles_on_the_scrap_paper\tank_battles_on_the_scrap_paper.vcxproj", "{AC8C4A04-1650-473C-8E9A-3B7CDE2AD5CC}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{AC8C4A04-1650-473C-8E9A-3B7CDE2AD5CC}.Debug|x64.ActiveCfg = Debug|x64
{AC8C4A04-1650-473C-8E9A-3B7CDE2AD5CC}.Debug|x64.Build.0 = Debug|x64
{AC8C4A04-1650-473C-8E9A-3B7CDE2AD5CC}.Debug|x86.ActiveCfg = Debug|Win32
{AC8C4A04-1650-473C-8E9A-3B7CDE2AD5CC}.Debug|x86.Build.0 = Debug|Win32
{AC8C4A04-1650-473C-8E9A-3B7CDE2AD5CC}.Release|x64.ActiveCfg = Release|x64
{AC8C4A04-1650-473C-8E9A-3B7CDE2AD5CC}.Release|x64.Build.0 = Release|x64
{AC8C4A04-1650-473C-8E9A-3B7CDE2AD5CC}.Release|x86.ActiveCfg = Release|Win32
{AC8C4A04-1650-473C-8E9A-3B7CDE2AD5CC}.Release|x86.Build.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {4E9B74CF-EE0B-430C-8380-34BE94639065}
EndGlobalSection
EndGlobal
-24
View File
@@ -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
+29 -45
View File
@@ -1,52 +1,36 @@
// 其他配置定义 // 项目配置常量
#ifndef DATA_CONFIG_H #ifndef DATA_CONFIG_H
#define DATA_CONFIG_H #define DATA_CONFIG_H
// 导入 tank 和 bullet 定义头文件
#include "tank.h"
#include "bullet.h"
// 常量定义 // 网格
const int CELL_SIZE = 30; // 每个单元格(坦克、墙、空地等)的大小(例如:像素) constexpr int CELL_SIZE = 30;
const int MAP_CELL_NUM = 20; // 地图大小为MAP_CELL_NUM^2 constexpr int MAP_CELL_NUM = 20;
const int ENEMIES_NUM = 10; // 地图上最多的敌人数量
const int BULLET_NUM = 20; // 地图上最多的子弹数量
const int SINGLE_BULLET = 3; // 每个坦克同时存在的最多子弹数量
const int White = 0; // 灰度图-白
const int Black = 1; // 灰度图-黑
#define PIEXL1 30 //像素常量 // 偏移
#define PIEXL2 4 constexpr int X_OFFSET = 50;
#define X_OFFSET 50 //以左上第一块墙为基准的偏移量 constexpr int Y_OFFSET = 45;
#define Y_OFFSET 45 //以左上第一块墙为基准的偏移量
/* // 窗口
地图: constexpr int WINDOW_W = 700;
使用数组存储地图信息 constexpr int WINDOW_H = 700;
map[x][y] 存储当前坦克信息
BLANK 空地 // 实体数量
RED_TANK 红方坦克 constexpr int ENEMIES_NUM = 10;
RED_DEAD_TANK 红方阵亡坦克 constexpr int MAX_PLAYER_BULLETS = 3;
BLUE_TANK, 蓝方坦克 constexpr int MAX_ENEMY_BULLETS = ENEMIES_NUM;
BLUE_DEAD_TANK 蓝方阵亡坦克
WALL 墙 // 方向
BULLET 子弹 enum Direction { UP, DOWN, LEFT, RIGHT };
*/
// 地图单元类型
enum CELL_Type { enum CELL_Type {
BLANK, // 空地 BLANK,
RED_TANK, // 红方坦克 RED_TANK,
RED_DEAD_TANK, // 红方报废坦克 RED_DEAD_TANK,
BLUE_TANK, // 蓝方坦克 BLUE_TANK,
BLUE_DEAD_TANK, // 蓝方报废坦克 BLUE_DEAD_TANK,
WALL, // 墙 WALL,
BULLET, // 子弹 BULLET
}; };
int map[MAP_CELL_NUM][MAP_CELL_NUM]; //在map.h预设好
int kill = 5; //击杀数 #endif
tank player; //玩家
tank enemies[ENEMIES_NUM]; //敌人
bullet E_bullets[BULLET_NUM * ENEMIES_NUM]; //敌方坦克子弹数组
bullet P_bullets[BULLET_NUM]; //玩家子弹数组
tank enemy; // 用于更改敌人的属性(难度设置用到)
#endif // !DATA_CONFIG_H
File diff suppressed because it is too large Load Diff
-54
View File
@@ -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;
}
}
}
}
-16
View File
@@ -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
@@ -1,16 +0,0 @@
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ 生成的包含文件。
// 供 resource.rc 使用
//
#define IDR_WAVE1 101
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 102
#define _APS_NEXT_COMMAND_VALUE 40001
#define _APS_NEXT_CONTROL_VALUE 1001
#define _APS_NEXT_SYMED_VALUE 101
#endif
#endif
Binary file not shown.
@@ -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;
}
+17
View File
@@ -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
+32
View File
@@ -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
-33
View File
@@ -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
@@ -1,147 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>16.0</VCProjectVersion>
<Keyword>Win32Proj</Keyword>
<ProjectGuid>{ac8c4a04-1650-473c-8e9a-3b7cde2ad5cc}</ProjectGuid>
<RootNamespace>tankbattlesonthescrappaper</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="main.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="bullet.h" />
<ClInclude Include="data_config.h" />
<ClInclude Include="resource.h" />
<ClInclude Include="tank.h" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="resource.rc" />
</ItemGroup>
<ItemGroup>
<Media Include="music\bgm.wav" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>
@@ -1,46 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="源文件">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="头文件">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
</Filter>
<Filter Include="资源文件">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="main.cpp">
<Filter>源文件</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="tank.h">
<Filter>头文件</Filter>
</ClInclude>
<ClInclude Include="bullet.h">
<Filter>头文件</Filter>
</ClInclude>
<ClInclude Include="data_config.h">
<Filter>头文件</Filter>
</ClInclude>
<ClInclude Include="resource.h">
<Filter>头文件</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="resource.rc">
<Filter>资源文件</Filter>
</ResourceCompile>
</ItemGroup>
<ItemGroup>
<Media Include="music\bgm.wav">
<Filter>资源文件</Filter>
</Media>
</ItemGroup>
</Project>