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:
+18
@@ -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_*/
|
||||||
|
|||||||
@@ -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,4 +2,6 @@
|
|||||||
|
|
||||||
v16: 添加了全局背景音效
|
v16: 添加了全局背景音效
|
||||||
|
|
||||||
v17: 修改了一点bug,在返回菜单后调用InitMap(),避免再次进入游戏界面时有脏数据导致显示出错
|
v17: 修改了一点bug,在返回菜单后调用InitMap(),避免再次进入游戏界面时有脏数据导致显示出错
|
||||||
|
|
||||||
|
v18: 从 Windows/EasyX 移植到 SDL2,支持 Linux;修复启动黑屏问题(自动设置工作目录为可执行文件所在目录)
|
||||||
@@ -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
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
// 子弹定义
|
|
||||||
#ifndef BULLET_H
|
|
||||||
#define BULLET_H
|
|
||||||
|
|
||||||
/*
|
|
||||||
子弹:
|
|
||||||
子弹坐标:x,y
|
|
||||||
攻击力damage:表示当前子弹造成的伤害
|
|
||||||
存活标志:is_alive
|
|
||||||
方向dir:
|
|
||||||
UP 上
|
|
||||||
DOWN 下
|
|
||||||
LEFT 左
|
|
||||||
RIGHT 右
|
|
||||||
*/
|
|
||||||
struct bullet {
|
|
||||||
int x, y;
|
|
||||||
int damage; // 子弹攻击力
|
|
||||||
int dir;
|
|
||||||
bool is_live; // 是否存活
|
|
||||||
//tank shooter; //值为enemies[i]或player
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // !BULLET_H
|
|
||||||
@@ -1,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
@@ -1,54 +0,0 @@
|
|||||||
// map.cpp
|
|
||||||
#include "map.h"
|
|
||||||
|
|
||||||
// 定义两个地图数组
|
|
||||||
int map1[50][50];
|
|
||||||
int map2[50][50];
|
|
||||||
|
|
||||||
void init_map1()
|
|
||||||
{
|
|
||||||
for (int i = 0; i < 50; i++)
|
|
||||||
{
|
|
||||||
for (int j = 0; j < 50; j++)
|
|
||||||
{
|
|
||||||
// 四周为墙
|
|
||||||
if (i == 0 || i == 49 || j == 0 || j == 49)
|
|
||||||
{
|
|
||||||
map1[i][j] = WALL;
|
|
||||||
}
|
|
||||||
// 构造中间的+形状
|
|
||||||
else if (i == 24 || j == 24)
|
|
||||||
{
|
|
||||||
map1[i][j] = WALL;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
map1[i][j] = BLANK;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void init_map2()
|
|
||||||
{
|
|
||||||
for (int i = 0; i < 50; i++)
|
|
||||||
{
|
|
||||||
for (int j = 0; j < 50; j++)
|
|
||||||
{
|
|
||||||
// 四周为墙
|
|
||||||
if (i == 0 || i == 49 || j == 0 || j == 49)
|
|
||||||
{
|
|
||||||
map2[i][j] = WALL;
|
|
||||||
}
|
|
||||||
// 构造中间的X形状
|
|
||||||
else if (i == j || i + j == 49)
|
|
||||||
{
|
|
||||||
map2[i][j] = WALL;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
map2[i][j] = BLANK;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
// 预制地图定义
|
|
||||||
// map.h
|
|
||||||
#ifndef MAP_H
|
|
||||||
#define MAP_H
|
|
||||||
|
|
||||||
#include "data_config.h"
|
|
||||||
|
|
||||||
// 声明两个地图数组
|
|
||||||
extern int map1[50][50];
|
|
||||||
extern int map2[50][50];
|
|
||||||
|
|
||||||
// 声明地图初始化函数
|
|
||||||
void init_map1();
|
|
||||||
void init_map2();
|
|
||||||
|
|
||||||
#endif // !MAP_H
|
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
#ifndef MAP_H
|
||||||
|
#define MAP_H
|
||||||
|
|
||||||
|
#include "../data_config.h"
|
||||||
|
|
||||||
|
class Map {
|
||||||
|
public:
|
||||||
|
int grid[MAP_CELL_NUM][MAP_CELL_NUM];
|
||||||
|
|
||||||
|
Map();
|
||||||
|
void reset();
|
||||||
|
CELL_Type get(int x, int y) const;
|
||||||
|
void set(int x, int y, CELL_Type type);
|
||||||
|
CELL_Type peek(int x, int y, Direction dir) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
#include "Renderer.h"
|
||||||
|
|
||||||
|
Renderer::Renderer(SDL_Renderer* r, AssetManager* a) : renderer(r), assets(a) {}
|
||||||
|
|
||||||
|
void Renderer::clear() {
|
||||||
|
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
|
||||||
|
SDL_RenderClear(renderer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Renderer::present() {
|
||||||
|
SDL_RenderPresent(renderer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Renderer::drawTexture(SDL_Texture* tex, int x, int y, int w, int h) {
|
||||||
|
if (!tex) return;
|
||||||
|
SDL_Rect dst = {x, y, w, h};
|
||||||
|
SDL_RenderCopy(renderer, tex, nullptr, &dst);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Renderer::drawImage(const std::string& path, int x, int y, int w, int h) {
|
||||||
|
drawTexture(assets->getTexture(path), x, y, w, h);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Renderer::drawText(const std::string& text, int x, int y, SDL_Color color) {
|
||||||
|
TTF_Font* font = assets->getFont();
|
||||||
|
if (!font || text.empty()) return;
|
||||||
|
SDL_Surface* surf = TTF_RenderUTF8_Blended(font, text.c_str(), color);
|
||||||
|
if (!surf) return;
|
||||||
|
SDL_Texture* tex = SDL_CreateTextureFromSurface(renderer, surf);
|
||||||
|
if (tex) {
|
||||||
|
SDL_Rect dst = {x, y, surf->w, surf->h};
|
||||||
|
SDL_RenderCopy(renderer, tex, nullptr, &dst);
|
||||||
|
SDL_DestroyTexture(tex);
|
||||||
|
}
|
||||||
|
SDL_FreeSurface(surf);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Renderer::drawInt(int value, int x, int y, SDL_Color color) {
|
||||||
|
drawText(std::to_string(value), x, y, color);
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
#ifndef RENDERER_H
|
||||||
|
#define RENDERER_H
|
||||||
|
|
||||||
|
#include <SDL2/SDL.h>
|
||||||
|
#include <SDL2/SDL_ttf.h>
|
||||||
|
#include <string>
|
||||||
|
#include "AssetManager.h"
|
||||||
|
|
||||||
|
class Renderer {
|
||||||
|
public:
|
||||||
|
Renderer(SDL_Renderer* r, AssetManager* a);
|
||||||
|
|
||||||
|
void clear();
|
||||||
|
void present();
|
||||||
|
void drawTexture(SDL_Texture* tex, int x, int y, int w, int h);
|
||||||
|
void drawImage(const std::string& path, int x, int y, int w, int h);
|
||||||
|
void drawText(const std::string& text, int x, int y, SDL_Color color);
|
||||||
|
void drawInt(int value, int x, int y, SDL_Color color);
|
||||||
|
|
||||||
|
private:
|
||||||
|
SDL_Renderer* renderer;
|
||||||
|
AssetManager* assets;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
#include "Tank.h"
|
||||||
|
|
||||||
|
Tank::Tank(int h, int a) : hp(h), attack(a) {}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
#ifndef TANK_H
|
||||||
|
#define TANK_H
|
||||||
|
|
||||||
|
#include "../data_config.h"
|
||||||
|
|
||||||
|
class Tank {
|
||||||
|
public:
|
||||||
|
int x = 0, y = 0;
|
||||||
|
int hp = 5, attack = 1;
|
||||||
|
Direction dir = UP;
|
||||||
|
bool isTaken = false;
|
||||||
|
|
||||||
|
Tank(int h = 5, int a = 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
inline int dx(Direction d) { return d == LEFT ? -1 : (d == RIGHT ? 1 : 0); }
|
||||||
|
inline int dy(Direction d) { return d == UP ? -1 : (d == DOWN ? 1 : 0); }
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
#ifndef UI_H
|
||||||
|
#define UI_H
|
||||||
|
|
||||||
|
#include <SDL2/SDL.h>
|
||||||
|
|
||||||
|
namespace UI {
|
||||||
|
|
||||||
|
inline bool hit(const SDL_Rect& r, int mx, int my) {
|
||||||
|
return mx >= r.x && mx <= r.x + r.w && my >= r.y && my <= r.y + r.h;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 菜单按钮
|
||||||
|
constexpr SDL_Rect BTN_START = {50, 250, 150, 50};
|
||||||
|
constexpr SDL_Rect BTN_LEVEL = {50, 330, 150, 50};
|
||||||
|
constexpr SDL_Rect BTN_MEMBER = {50, 410, 150, 50};
|
||||||
|
constexpr SDL_Rect BTN_QUIT = {50, 490, 150, 50};
|
||||||
|
|
||||||
|
// 难度按钮
|
||||||
|
constexpr SDL_Rect BTN_EASY = {275, 150, 150, 100};
|
||||||
|
constexpr SDL_Rect BTN_MEDIUM = {275, 300, 150, 100};
|
||||||
|
constexpr SDL_Rect BTN_HARD = {275, 450, 150, 100};
|
||||||
|
|
||||||
|
// 返回按钮
|
||||||
|
constexpr SDL_Rect BTN_BACK = {30, 550, 150, 50};
|
||||||
|
|
||||||
|
// HP 条
|
||||||
|
constexpr int HP_BAR_X = 100;
|
||||||
|
constexpr int HP_BAR_Y = 10;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
// 坦克定义
|
|
||||||
#ifndef TANK_H
|
|
||||||
#define TANK_H
|
|
||||||
|
|
||||||
/*
|
|
||||||
坦克:
|
|
||||||
坐标:x,y
|
|
||||||
血量hp:表示当前坦克血量
|
|
||||||
攻击力attack:表示当前坦克攻击力
|
|
||||||
方向dir:
|
|
||||||
UP 上
|
|
||||||
DOWN 下
|
|
||||||
LEFT 左
|
|
||||||
RIGHT 右
|
|
||||||
*/
|
|
||||||
enum Direction {
|
|
||||||
UP, // 上
|
|
||||||
DOWN, // 下
|
|
||||||
LEFT, // 左
|
|
||||||
RIGHT, // 右
|
|
||||||
};
|
|
||||||
|
|
||||||
struct tank {
|
|
||||||
int x, y;//坐标
|
|
||||||
int hp; // 血量
|
|
||||||
int attack; // 攻击力
|
|
||||||
int dir; // 方向
|
|
||||||
bool is_taken = false;//报销坦克是否已经被玩家捡取
|
|
||||||
|
|
||||||
tank(): attack(1), hp(5){}
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // !TANK_H
|
|
||||||
@@ -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>
|
|
||||||
Reference in New Issue
Block a user