C语言手写-植物大战僵尸

news/2024/7/5 3:55:54

植物大战僵尸,是一个非常经典的小游戏,初学者从零开始,开发一个自己的植物大战僵尸,还是非常值得期待的!可以作为自己的课设,也可以用来快速提升自己的项目开发能力。

项目效果(详细视频教程点这里)

说明:因为完整动图提交后提示违规,所以这里仅截图示意。如果需要演示视频,在评论中回复即可。

项目准备

  • 安装Visual Studio的任意版本(推荐VS2019社区版、VS2022社区版)

  • 安装easyx图形库(官网下载地址)

  • 领取项目素材(回复“植物大战僵尸”,即可领取)

创建项目

使用VS创建项目,使用空项目模板:

导入素材:在项目目录下,创建res文件夹,把解压后的素材拷贝到res目录下。

实现游戏初始场景

代码如下(需要逐行代码视频讲解,可回复“代码讲解“)。

#include <stdio.h>
#include <graphics.h>
#include "tools.h"
#include <mmsystem.h>
#pragma comment(lib, "winmm.lib")

#define WIN_WIDTH    900
#define WIN_HEIGHT    600

enum { WAN_DOU, XIANG_RI_KUI, ZHI_WU_COUT };
IMAGE imgBg;
IMAGE imgBar;
IMAGE imgCards[ZHI_WU_COUT];
IMAGE* imgZhiWu[ZHI_WU_COUT][20];
int curZhiWu;
int curX, curY; //当前选中植物在移动过程中的坐标

struct zhiWu {
    int type;   // >=1  0:没有植物
    int frameIndex;
};
struct zhiWu  map[3][9];
int sunshine;
int sunshineTable[ZHI_WU_COUT] = { 100, 50 };

void gameInit() {
    loadimage(&imgBg, "res/bg.jpg");
    loadimage(&imgBar, "res/bar.png");
    sunshine = 150;
    curZhiWu = 0;
    memset(imgZhiWu, 0, sizeof(imgZhiWu));
    memset(map, 0, sizeof(map));

    char name[64];
    for (int i = 0; i < ZHI_WU_COUT; i++) {
        sprintf_s(name, sizeof(name), "res/Cards/card_%d.png", i + 1);
        loadimage(&imgCards[i], name);

        for (int j = 0; j < 20; j++) {
            sprintf_s(name, sizeof(name), "res/zhiwu/%d/%d.png", i, j + 1);
            imgZhiWu[i][j] = new IMAGE;
            loadimage(imgZhiWu[i][j], name);
            if (imgZhiWu[i][j]->getwidth() == 0) {
                delete imgZhiWu[i][j];
                imgZhiWu[i][j] = NULL;
            }
        }
    }

    initgraph(WIN_WIDTH, WIN_HEIGHT, 1);
    // 设置字体:
    LOGFONT f;
    gettextstyle(&f);                     // 获取当前字体设置
    f.lfHeight = 30;                      // 设置字体高度为 48
    f.lfWidth = 15;
    strcpy(f.lfFaceName, "Segoe UI Black");
    f.lfQuality = ANTIALIASED_QUALITY;    // 设置输出效果为抗锯齿  
    settextstyle(&f);                     // 设置字体样式
    setbkmode(TRANSPARENT);
    setcolor(BLACK);

    mciSendString("play res/bg.mp3 repeat", 0, 0, 0);
}

void updateWindow() {
    BeginBatchDraw();

    putimage(0, 0, &imgBg);
    putimagePNG(250, 0, &imgBar);

    for (int i = 0; i < ZHI_WU_COUT; i++) {
        int x = 338 + i * 64;
        int y = 6;
        putimage(x, y, &imgCards[i]);
    }

    if (curZhiWu > 0) {  // 绘制正在移动的植物
        IMAGE* img = imgZhiWu[curZhiWu - 1][0];
        putimagePNG(curX - img->getwidth() * 0.5, curY - img->getheight() * 0.5, img);
    }

    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 9; j++) {
            if (map[i][j].type > 0) {
                int x = 260 + j * 81.6;    // (msg.x - 260) / 81.6;
                int y = 180 + i * 103.6 + 14; // (msg.y - 210) / 103.6;
                int zhiWuIndex = map[i][j].type;
                int frameIndex = map[i][j].frameIndex;
                putimagePNG(x, y, imgZhiWu[zhiWuIndex - 1][frameIndex]);
            }
        }
    }

    char scoreText[8];
    sprintf_s(scoreText, sizeof(scoreText), "%d", sunshine);
    outtextxy(282 - 10 + 4, 50 + 15 + 2, scoreText);
    EndBatchDraw();
}

void userClick() {
    ExMessage msg;
    static int status = 0;
    if (peekmessage(&msg)) {
        if (msg.message == WM_LBUTTONDOWN) {
            if (msg.x > 338 && msg.x < 338 + 64 * ZHI_WU_COUT && msg.y>6 && msg.y < 96) {
                int index = (msg.x - 338) / 64;
                printf("%d\n", index);
                status = 1;
                curZhiWu = index + 1; // 1, 2 
                curX = msg.x;
                curY = msg.y;
            }
        }
        else if (msg.message == WM_MOUSEMOVE && status == 1) {
            curX = msg.x;
            curY = msg.y;
        }
        else if (msg.message == WM_LBUTTONUP && status == 1) {
            printf("up\n");
            if (msg.x > 260 && msg.y < 995 && msg.y > 180 && msg.y < 491) {
                if (sunshine >= sunshineTable[curZhiWu - 1]) {
                    sunshine -= sunshineTable[curZhiWu - 1];
                    int col = (msg.x - 260) / 81.6;
                    int row = (msg.y - 210) / 103.6;
                    printf("[%d,%d]\n", row, col);
                    if (map[row][col].type == 0) {
                        map[row][col].type = curZhiWu;
                        map[row][col].frameIndex = 0;
                    }
                }
            }
            status = 0;
            curZhiWu = 0;
        }
    }
}

void updateGame() {
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 9; j++) {
            if (map[i][j].type > 0) {
                map[i][j].frameIndex++;
                if (imgZhiWu[map[i][j].type - 1][map[i][j].frameIndex] == NULL) {
                    map[i][j].frameIndex = 0;
                }
            }
        }
    }
}

int main(void) {
    gameInit();

    int timer = 0;
    bool flag = true;
    while (1) {
        userClick();
        timer += getDelay();
        if (timer > 20) {
            timer = 0;
            flag = true;
        }
        if (flag) {
            flag = false;
            updateWindow();
            updateGame();
        }
    }

    return 0;
}

添加启动菜单

创建菜单界面,代码如下:

void startUI() {
    IMAGE imgBg, imgMenu1, imgMenu2;
    loadimage(&imgBg, "res/menu.png");
    loadimage(&imgMenu1, "res/menu1.png");
    loadimage(&imgMenu2, "res/menu2.png");
    int flag = 0;
    while (1) {
        BeginBatchDraw();
        putimage(0, 0, &imgBg);
        putimagePNG(474, 75, flag ? &imgMenu2 : &imgMenu1);

        ExMessage msg;
        if (peekmessage(&msg)) {
            if (msg.message == WM_LBUTTONDOWN &&
                msg.x > 474 && msg.x < 474 + 300 && msg.y > 75 && msg.y < 75 + 140) {
                flag = 1;
                EndBatchDraw();
            }
            else if (msg.message == WM_LBUTTONUP && flag) {
                return;
            }
        }
        EndBatchDraw();
    }
}

在main函数中调用菜单,代码如下:

int main(void) {
    gameInit();
    startUI();
    int timer = 0;
    bool flag = true;
    while (1) {
        userClick();
        timer += getDelay();
        if (timer > 20) {
            timer = 0;
            flag = true;
        }
        if (flag) {
            flag = false;
            updateWindow();
            updateGame();
        }
    }

    return 0;
}

生产阳光

熟悉植物大战僵尸的同学都知道,种植植物才能消灭僵尸,但是种植植物,需要先具备一定数量的阳光值。初始的阳光值很小。有两种方式生成阳光:第一种,随机降落少量的阳光;第二种,通过种植向日葵,让向日葵自动生产阳光。我们先实现第一种方式。

定义一个结构体,来表示阳光球。因为阳光是以旋转的方式运动的,所以定义一个图片帧数组,通过循环播放图片帧来实现旋转效果。

IMAGE imgSunshineBall[29]; 
struct sunshineBall { 
    int x, y;
    int frameIndex;
    bool used;
    int destY;
    int timer = 0;
};
struct sunshineBall balls[10];

在gameInit函数中,初始化阳光帧数组。

    memset(balls, 0, sizeof(balls));
    for (int i = 0; i < 29; i++) {
        sprintf_s(name, sizeof(name), "res/sunshine/%d.png", i + 1);
        loadimage(&imgSunshineBall[i], name);
    }

创建阳光,代码如下。

void createSunshine() {
    int ballMax = sizeof(balls) / sizeof(balls[0]);

    static int frameCount = 0;
    static int fre = 400;
    frameCount++;
    if (frameCount >= fre) {
        fre = 200 + rand() % 200;  
        frameCount = 0;
        int i;
        for (i = 0; i < ballMax && balls[i].used; i++);
        if (i >= ballMax) return;

        balls[i].used = true;
        balls[i].frameIndex = 0;
        balls[i].x = 260 + rand() % (905 - 260);
        balls[i].y = 60;
        balls[i].destY = 180 + (rand() % 4) * 90 + 20;
        balls[i].timer = 0;
    }
}

修改阳光的位置和帧序号,代码如下。

void updateSunshine() {
    int ballMax = sizeof(balls) / sizeof(balls[0]);

    for (int i = 0; i < ballMax; i++) {
        if (balls[i].used) {
            balls[i].frameIndex = (balls[i].frameIndex + 1) % 29;
            if(balls[i].timer == 0) balls[i].y += 2;
            if (balls[i].y >= balls[i].destY) {
                balls[i].timer++;
                if (balls[i].timer > 100) balls[i].used = false;
            }
        }
    }
}

在updateGame函数中调用以上两个函数 ,以创建阳光并更新阳光的状态。

createSunshine();
updateSunshine();

在updateWindow函数中,渲染阳光。

for (int i = 0; i < 10; i++) {
    if (balls[i].used) {
        putimagePNG(balls[i].x, balls[i].y, &imgSunshineBall[balls[i].frameIndex]);
    }
}


http://lihuaxi.xjx100.cn/news/836918.html

相关文章

使用Canal实现mysql binlog增量订阅数据

目录 前言 简单原理 1.mysql数据库开启Binlog模式 1.docker 安装 canal 服务端 3.实现canal客户端 前言 是由公司业务改造搜索功能&#xff0c;使用ES搜索引擎中间件&#xff0c;那么我们需要将mysql中的数据同步至ES服务中&#xff0c;最总选择使用alibaba的canal增量订…

PT100温度采集电路设计

PT100是正温度系数的热敏电阻&#xff0c;顾名思义&#xff0c;随着温度的升高&#xff0c;电阻的阻值变大&#xff1b;相反&#xff0c;如果随着温度的升高&#xff0c;电阻的阻值变小&#xff0c;就是负温度系数的热敏电阻。之所以叫做PT100&#xff0c;是因为在0度时其阻值为…

【JavaEE】Java中复杂的Synchronized关键字

目录 一、synchronized的特性 &#xff08;1&#xff09;互斥 &#xff08;2&#xff09;刷新内存 &#xff08;3&#xff09;可重入 二、synchronized的使用 &#xff08;1&#xff09;修饰普通方法 &#xff08;2&#xff09;修饰静态方法 &#xff08;3&#xff09;修…

nohup后台启动程序jar包的时候进行定时按时间日期分割日志

在springboot应用开发中&#xff0c;常用jar方式进行部署&#xff0c;用nohup后台启动&#xff0c;这样生成的日志文件会越来越大&#xff0c;导致日志文件打开很慢&#xff0c;不方便后续问题的定位和解决。所以需要对日志进行分割&#xff0c;下面主要介绍按日期分割日志。话…

Python:每日一题之观光公交(前缀和)

题目描述 风景迷人的小城 Y 市&#xff0c;拥有 n 个美丽的景点。由于慕名而来的游客越来越多&#xff0c;Y 市特意安排了一辆观光公交车&#xff0c;为游客提供更便捷的交通服务。观光公交车在第 0 分钟出现在 1 号景点&#xff0c;随后依次前往 2、3、4……n 号景点。从第 i…

Linux设置开机自启keepalived+nginx服务

目录&#xff1a; 目录 背景&#xff1a; 分析过程&#xff1a; 解决方案&#xff1a; 解决方案一&#xff1a; 解决方案二&#xff1a; 背景&#xff1a; 在工作突发遇见了Linux虚拟机所在的宿主机重启了&#xff0c;虚拟机上部署nginxkeepalived服务&#xff0c;但是…

Hudi(21):Hudi集成Flink之核心原理分析

目录 0. 相关文章链接 1. 数据去重原理 1.1. 消息版本新旧 1.2. 攒消息阶段的去重 1.3. 写 parquet 增量消息的去重 1.4. 跨 partition 的消息去重 2. 表写入原理 2.1. 数据写入分析 2.2. 数据压缩 2.3. 数据清理 2.4. Job图 3. 表读取原理 0. 相关文章链接 Hudi文…

Hadoop三大框架

一、Hadoop是什么Hadoop是一个由apache开发的分布式系统基础架构。主要解决海量数据的存储和海量数据的分析计算问题。广义上来说&#xff0c;Hadoop通胀指一个更宽泛的概念——Hadoop生态圈1、Hadoop优势高可靠性&#xff1a;Hadoop底层维护多个数据副本&#xff0c;即使Hadoo…