植物大战僵尸

#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);

}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值