#include <graphics.h>
#include <vector>
#include <memory>
#include <unordered_map>
#include <chrono>
#include <cmath>
#include <algorithm>
#include "tools.h"
#include "vector2.h"
// 游戏常量
constexpr int WIN_WIDTH = 900;
constexpr int WIN_HEIGHT = 600;
constexpr float FPS = 60.0f;
constexpr float FRAME_TIME = 1000.0f / FPS;
constexpr int PLANT_COST[2] = {100, 50};
// 枚举类型
enum class PlantType { Peashooter, Sunflower, Count };
enum class ZombieType { Normal, Conehead, Buckethead };
enum class GameState { MainMenu, Playing, Paused, GameOver };
// 资源管理器(单例模式)
class ResourceManager {
private:
std::unordered_map<int, std::unique_ptr<IMAGE>> plantFrames;
std::unordered_map<int, std::unique_ptr<IMAGE>> zombieFrames;
std::vector<std::unique_ptr<IMAGE>> sunshineFrames;
IMAGE background;
IMAGE uiElements;
ResourceManager() { loadResources(); }
public:
static ResourceManager& instance() {
static ResourceManager rm;
return rm;
}
void loadResources() {
loadimage(&background, "res/bg.jpg");
// 加载植物帧
for (int p = 0; p < static_cast<int>(PlantType::Count); ++p) {
for (int f = 1; ; ++f) {
char path[128];
snprintf(path, sizeof(path), "res/plants/%d/%d.png", p, f);
if (!fileExist(path)) break;
auto img = std::make_unique<IMAGE>();
loadimage(img.get(), path);
plantFrames[p * 100 + f] = std::move(img);
}
}
// 加载僵尸帧
for (int z = 0; z < 3; ++z) {
for (int f = 1; ; ++f) {
char path[128];
snprintf(path, sizeof(path), "res/zombies/%d/%d.png", z, f);
if (!fileExist(path)) break;
auto img = std::make_unique<IMAGE>();
loadimage(img.get(), path);
zombieFrames[z * 100 + f] = std::move(img);
}
}
}
IMAGE* getPlantFrame(PlantType type, int frame) const {
auto key = static_cast<int>(type) * 100 + frame;
return plantFrames.at(key).get();
}
IMAGE* getZombieFrame(ZombieType type, int frame) const {
auto key = static_cast<int>(type) * 100 + frame;
return zombieFrames.at(key).get();
}
IMAGE* getBackground() const { return &background; }
};
// 动画组件
class Animation {
private:
int currentFrame = 0;
float frameDuration;
float elapsedTime = 0.0f;
int totalFrames;
public:
Animation(float fps, int frames)
: frameDuration(1.0f / fps), totalFrames(frames) {}
void update(float dt) {
elapsedTime += dt;
while (elapsedTime >= frameDuration) {
elapsedTime -= frameDuration;
currentFrame = (currentFrame + 1) % totalFrames;
}
}
int getCurrentFrame() const { return currentFrame; }
void reset() { currentFrame = 0; elapsedTime = 0.0f; }
};
// 游戏对象基类
class GameObject {
protected:
Vector2 position;
Vector2 size;
bool active = true;
public:
GameObject(Vector2 pos, Vector2 sz) : position(pos), size(sz) {}
virtual ~GameObject() = default;
virtual void update(float dt) = 0;
virtual void draw() const = 0;
bool isActive() const { return active; }
const Vector2& getPosition() const { return position; }
const Vector2& getSize() const { return size; }
};
// 植物类
class Plant : public GameObject {
private:
PlantType type;
Animation animation;
float shootTimer = 0.0f;
int health = 100;
public:
Plant(PlantType t, Vector2 pos)
: GameObject(pos, {70, 70}), type(t), animation(12.0f, getFrameCount(t)) {}
void update(float dt) override {
animation.update(dt);
if (type == PlantType::Peashooter) {
shootTimer += dt;
if (shootTimer >= 1.5f) {
shoot();
shootTimer = 0.0f;
}
}
}
void draw() const override {
int frame = animation.getCurrentFrame();
IMAGE* img = ResourceManager::instance().getPlantFrame(type, frame + 1);
putimagePNG(position.x, position.y, img);
}
private:
void shoot() {
// 创建子弹逻辑
}
static int getFrameCount(PlantType t) {
switch (t) {
case PlantType::Peashooter: return 15;
case PlantType::Sunflower: return 12;
default: return 1;
}
}
};
// 僵尸类
class Zombie : public GameObject {
private:
ZombieType type;
Animation animation;
float speed;
int health;
bool eating = false;
public:
Zombie(ZombieType t, Vector2 pos)
: GameObject(pos, {80, 100}), type(t),
animation(8.0f, getFrameCount(t)), speed(getSpeed(t)),
health(getHealth(t)) {}
void update(float dt) override {
if (!eating) {
position.x -= speed * dt;
animation.update(dt);
}
// 碰撞检测逻辑
}
void draw() const override {
int frame = animation.getCurrentFrame();
IMAGE* img = ResourceManager::instance().getZombieFrame(type, frame + 1);
putimagePNG(position.x, position.y - 30, img); // 调整绘制位置
}
private:
static float getSpeed(ZombieType t) {
switch (t) {
case ZombieType::Normal: return 20.0f;
case ZombieType::Conehead: return 18.0f;
case ZombieType::Buckethead: return 15.0f;
default: return 20.0f;
}
}
static int getHealth(ZombieType t) {
switch (t) {
case ZombieType::Normal: return 100;
case ZombieType::Conehead: return 200;
case ZombieType::Buckethead: return 300;
default: return 100;
}
}
static int getFrameCount(ZombieType t) {
return 22; // 所有僵尸类型使用相同帧数
}
};
// 游戏主系统
class GameSystem {
private:
std::vector<std::unique_ptr<GameObject>> gameObjects;
std::vector<Plant*> plants;
std::vector<Zombie*> zombies;
int sunshine = 50;
GameState state = GameState::MainMenu;
PlantType selectedPlant = PlantType::Count;
std::chrono::steady_clock::time_point lastFrameTime;
float accumulatedTime = 0.0f;
public:
void run() {
init();
while (state != GameState::GameOver) {
auto now = std::chrono::steady_clock::now();
float dt = std::chrono::duration<float>(now - lastFrameTime).count();
lastFrameTime = now;
processInput();
if (state == GameState::Playing) {
update(dt);
}
render();
controlFrameRate();
}
}
private:
void init() {
initgraph(WIN_WIDTH, WIN_HEIGHT);
lastFrameTime = std::chrono::steady_clock::now();
}
void processInput() {
ExMessage msg;
while (peekmessage(&msg)) {
if (msg.message == WM_LBUTTONDOWN) {
handleClick({static_cast<float>(msg.x), static_cast<float>(msg.y)});
}
}
}
void handleClick(Vector2 pos) {
if (state == GameState::MainMenu) {
state = GameState::Playing;
return;
}
if (pos.y < 100) { // 工具栏区域
selectPlant(pos);
} else {
placePlant(pos);
}
}
void selectPlant(Vector2 pos) {
int index = (pos.x - 300) / 80;
if (index >= 0 && index < static_cast<int>(PlantType::Count)) {
selectedPlant = static_cast<PlantType>(index);
}
}
void placePlant(Vector2 pos) {
if (selectedPlant == PlantType::Count ||
sunshine < PLANT_COST[static_cast<int>(selectedPlant)]) return;
// 网格对齐逻辑
int col = (pos.x - 250) / 80;
int row = (pos.y - 180) / 100;
if (col >= 0 && col < 9 && row >= 0 && row < 3) {
Vector2 plantPos = {250.0f + col * 80.0f, 180.0f + row * 100.0f};
auto plant = std::make_unique<Plant>(selectedPlant, plantPos);
plants.push_back(plant.get
一、环境准备
1. **安装依赖库**
- 安装EasyX图形库(官网下载地址:https://easyx.cn)
- 确保已安装C++17兼容的编译器(推荐Visual Studio 2019+)
2. **项目配置**
```properties
# 在Visual Studio项目中设置
平台工具集:Visual Studio 2019
C++语言标准:ISO C++17 标准
附加包含目录:EasyX的include目录
附加库目录:EasyX的lib目录
```
### 二、资源准备
1. **文件目录结构**
```bash
res/
├── bg.jpg # 背景图片
├── plants/
│ ├── 0/ # 豌豆射手动画帧
│ │ ├── 1.png
│ │ └── ... # 共15帧
│ └── 1/ # 向日葵动画帧
└── zombies/
├── 0/ # 普通僵尸
├── 1/ # 路障僵尸
└── 2/ # 铁桶僵尸
```
2. **资源规格要求
- 背景图:900x600像素 JPG格式
- 植物帧:建议70x70像素 PNG格式
- 僵尸帧:建议80x100像素 PNG格式
### 三、编译运行
1. **Visual Studio编译**
```bash
1. 新建空项目
2. 添加所有源代码文件
3. 配置项目属性(见环境准备)
4. 生成 -> 生成解决方案
5. 调试 -> 开始执行
```
2. **命令行编译(MinGW)**
```bash
g++ main.cpp -o PlantsVsZombies.exe
-I[easyx安装路径]/include
-L[easyx安装路径]/lib
-lgraphics -lgdi32 -lole32 -luuid -lmsimg32
```
### 四、游戏操作
1. **基本玩法**
```properties
鼠标左键点击工具栏选择植物
在草地网格(250<x<900, 180<y<489)点击种植
豌豆射手自动攻击
向日葵产生阳光
```
2. **控制台参数(扩展功能)**
```bash
# 启动时指定初始阳光值
PlantsVsZombies.exe 100
```
### 五、配置修改
1. **调整游戏参数**
```cpp
// 修改游戏常量
constexpr int WIN_WIDTH = 1280; // 窗口宽度
constexpr int PLANT_COST[2] = {75, 40}; // 植物价格
// 修改僵尸属性
float Zombie::getSpeed(ZombieType t) {
case ZombieType::Buckethead: return 12.0f; // 降低铁桶速度
}
```
2. **配置文件示例**
创建`config.ini`:
```ini
[Game]
InitialSun=200
ZombieSpawnRate=2.5
[Plants]
PeashooterDamage=20
SunflowerInterval=15
```
### 六、常见问题解决
1. **资源加载失败**
```checklist
✓ 确认res目录与可执行文件同级
✓ 检查文件名是否连续(1.png, 2.png,...)
✓ 验证图片格式是否为32位带透明通道PNG
```
2. **运行时崩溃**
```checklist
✓ 检查EasyX是否安装正确
✓ 确保没有重复的资源加载
✓ 验证所有指针访问有效性
```
### 七、扩展开发建议
1. **添加新植物**
```cpp
// 1. 扩展PlantType枚举
enum class PlantType { Peashooter, Sunflower, Wallnut, Count };
// 2. 创建新植物类
class Wallnut : public Plant {
public:
Wallnut(Vector2 pos) : Plant(PlantType::Wallnut, pos) {
health = 400; // 高生命值
}
void update(float dt) override {
// 重写特殊逻辑
}
};
```
2. **实现僵尸生成系统**
```cpp
void GameSystem::spawnZombie() {
static float spawnTimer = 0.0f;
spawnTimer += dt;
if (spawnTimer > spawnInterval) {
Vector2 pos = {WIN_WIDTH, 180 + rand()%3*100};
auto zombie = std::make_unique<Zombie>(getRandomZombieType(), pos);
zombies.push_back(zombie.get());
gameObjects.push_back(std::move(zombie));
spawnTimer = 0.0f;
}
}
```
### 八、调试技巧
1. **显示调试信息**
```cpp
void GameSystem::render() {
// 在渲染函数中添加
char debugInfo[128];
sprintf(debugInfo, "Zombies: %d Plants: %d", zombies.size(), plants.size());
outtextxy(10, 10, debugInfo);
}