C 实现植物大战僵尸(一)

devtools/2024/12/27 8:52:42/

C 实现植物大战僵尸(一)

对应资源链接,C语言项目:完整版植物大战僵尸

以下内容为个人实现版,与原 UP 主项目代码内容有出入,提高了些可读和简洁性

一 创建主场景

安装 easyx 库,easyx 官网

主场景代码

#include <stdio.h>
#include <graphics.h>        // 引用图形库头文件/*开发日志1、创建新项,导入素材,实现开始游戏场景
*/#define WIN_WIDTH 900
#define WIN_HIGHT 600
IMAGE imgBg; //背景图片void gameInit() 
{//加载背景图片loadimage(&imgBg, "res/map0.jpg");//创建游戏图形窗口initgraph(WIN_WIDTH, WIN_HIGHT);
}void updateWindow() 
{//渲染背景图至窗口putimage(0, 0, &imgBg);
}int main()
{gameInit();updateWindow();system("pause");return 0;
}

配置项目字符集

VS中多字节字符集和UNICODE字符集的使用说明,如果要兼容 C 编程,只能使用多字节字符集。这里的兼容 C 编程,主要就是指 WindowsAPI 编程

image-20241226092139164

遇到的小问题 VS 无法打开 .exe 文件进行写入

通常是因为上次运行的进程未关闭,导致文件仍被占用。当尝试运行或重新编译一个程序时,如果上一次生成的 exe 文件仍在运行,VS2022 无法对其进行写入操作‌

二 实现植物卡牌

#include <stdio.h>
#include <graphics.h>        // 引用图形库头文件
#include "tools.h"/*开发日志1、创建新项,导入素材,实现开始游戏场景2、实现游戏顶部工具栏和植物卡牌
*/#define WIN_WIDTH 900
#define WIN_HIGHT 600
enum PLANT_CARDS{ PEA, SUNFLOWER, PLANT_CNT}; //使用 PLANT_CNT 统计 PLANT 总数IMAGE imgBg; //背景图片
IMAGE imgBar; //工具栏图片
IMAGE imgCards[PLANT_CNT]; //植物卡片void gameInit() 
{//加载背景图片loadimage(&imgBg, "res/map0.jpg");loadimage(&imgBar, "res/bar5.png");//加载植物卡片char name[64];for (int i = 0;i < PLANT_CNT;++i){//获取植物卡片相对路径名称sprintf(name, "res/Cards/card_%d.png", i + 1);loadimage(&imgCards[i], name);}//创建游戏图形窗口initgraph(WIN_WIDTH, WIN_HIGHT);
}void updateWindow() 
{//渲染背景图至窗口putimage(0, 0, &imgBg);putimagePNG(250, 0, &imgBar);//渲染植物卡牌for (int i = 0;i < PLANT_CNT;++i)//卡片宽度约 65putimage(338 + i * 65, 6, &imgCards[i]);
}int main()
{gameInit();updateWindow();system("pause");return 0;
}

运行结果

image-20241226104554542

遇到的小问题 图片加载不出来,DEBUG 图片的相对路径

三 实现植物的选择和拖动

#include <stdio.h>
#include <graphics.h>        // 引用图形库头文件
#include "tools.h"/*开发日志1、创建新项,导入素材,实现开始游戏场景2、实现游戏顶部工具栏和植物卡牌3、实现植物的选择和拖动
*/#define WIN_WIDTH 900
#define WIN_HIGHT 600
#define MAX_PICTURE_NUM 20
#define PIC_LEFT_MARGIN 338
#define PIC_WIDTH 65int currX = 0, currY = 0, currIndex = -1;
enum PLANT_CARDS{ PEA, SUNFLOWER, PLANT_CNT}; //使用 PLANT_CNT 统计 PLANT 总数IMAGE imgBg; //背景图片
IMAGE imgBar; //工具栏图片
IMAGE imgCards[PLANT_CNT]; //植物卡片/* 这里也可使用二维数组, 但会存在浪费空间的问题 */
IMAGE* imgPlant[PLANT_CNT][MAX_PICTURE_NUM]; //动态植物素材bool fileExist(const char* name) 
{FILE* file = NULL;if (file = fopen(name,"r"))fclose(file);return file == NULL ? false : true;
}void gameInit() 
{//加载背景图片loadimage(&imgBg, "res/map0.jpg");loadimage(&imgBar, "res/bar5.png");//加载植物卡片char name[64];//将二维指针数组内存空间置零memset(imgPlant, 0, sizeof(imgPlant));for (int i = 0; i < PLANT_CNT; ++i){//获取植物卡片相对路径名称sprintf(name, "res/Cards/card_%d.png", i + 1);loadimage(&imgCards[i], name);for (int j = 0;i < MAX_PICTURE_NUM; ++j){//获取动态植物素材相对路径名称sprintf(name, "res/Plants/%d/%d.png", i, j + 1);if (fileExist(name)) {imgPlant[i][j] = new IMAGE;loadimage(imgPlant[i][j], name);}else break;}}//创建游戏图形窗口initgraph(WIN_WIDTH, WIN_HIGHT, 1);
}void updateWindow() 
{//使用双缓冲, 解决输出窗口闪屏BeginBatchDraw();//渲染背景图至窗口putimage(0, 0, &imgBg);putimagePNG(250, 0, &imgBar);//渲染植物卡牌for (int i = 0;i < PLANT_CNT;++i)putimage(PIC_LEFT_MARGIN + i * PIC_WIDTH, 6, &imgCards[i]);//渲染 当前拖动的植物if (currIndex >= 0){IMAGE* currImage = imgPlant[currIndex][0];putimagePNG(currX - currImage->getwidth() / 2, currY - currImage->getheight() / 2, currImage);}EndBatchDraw(); //结束双缓冲
}void userClick()
{ExMessage msg; //创建消息体/* 拖动需先左键点击再拖动 */static int status = 0; //种植植物必须先选中再拖动if (peekmessage(&msg)) //该函数用于获取一个消息,并立即返回{if (msg.message == WM_LBUTTONDOWN) //鼠标点击{if (msg.x > PIC_LEFT_MARGIN && msg.x < PIC_LEFT_MARGIN + PLANT_CNT * PIC_WIDTH &&msg.y < 96){currX = msg.x, currY = msg.y;currIndex = (msg.x - PIC_LEFT_MARGIN) / PIC_WIDTH;status = 1;}}else if (msg.message == WM_MOUSEMOVE && status == 1) //鼠标拖动{currX = msg.x, currY = msg.y; //记录当前拖动位置}else if (msg.message == WM_LBUTTONUP) //鼠标抬起{}}
}int main()
{gameInit();while (1){userClick(); //监听窗口鼠标事件updateWindow(); //更新窗口视图}system("pause");return 0;
}

运行结果

image-20241226135201780

遇到的小问题 拖动植物位置不对,检查渲染当前拖动植物的参数坐标,以及监听鼠标点击事件时,坐标需要设置

四 实现植物种植和摇摆

#include <stdio.h>
#include <graphics.h>        // 引用图形库头文件
#include "tools.h"/*开发日志1、创建新项,导入素材,实现开始游戏场景2、实现游戏顶部工具栏和植物卡牌3、实现植物的选择和拖动4、实现植物的种植和摇摆
*/#define WIN_WIDTH 900
#define WIN_HIGHT 600
#define MAX_PICTURE_NUM 20
#define PIC_LEFT_MARGIN 338
#define PIC_WIDTH 65#define GRASS_LEFT_MARGIN 252
#define GRASS_TOP_MARGIN 82#define GRASS_GRID_ROW 5
#define GRASS_GRID_COL 9
#define GRASS_GRID_HIGHT 98  // (570 - 82) / 5
#define GRASS_GRID_WIDTH 81  //(984 - 252) / 9int currX = 0, currY = 0, currIndex = -1;
enum PLANT_CARDS{ PEA, SUNFLOWER, PLANT_CNT}; //使用 PLANT_CNT 统计 PLANT 总数IMAGE imgBg; //背景图片
IMAGE imgBar; //工具栏图片
IMAGE imgCards[PLANT_CNT]; //植物卡片/* 这里也可使用二维数组, 但会存在浪费空间的问题 */
IMAGE* imgPlant[PLANT_CNT][MAX_PICTURE_NUM]; //动态植物素材typedef struct Plant
{int type;     //植物类型, -1 表示草地int frameId;  //表示植物摆动帧
}Plant;
Plant plants[GRASS_GRID_ROW][GRASS_GRID_COL];bool fileExist(const char* name) 
{FILE* file = NULL;if (file = fopen(name,"r"))fclose(file);return file == NULL ? false : true;
}void gameInit() 
{//加载背景图片loadimage(&imgBg, "res/map0.jpg");loadimage(&imgBar, "res/bar5.png");//加载植物卡片char name[64];//将二维指针数组内存空间置零memset(imgPlant, 0, sizeof(imgPlant));memset(plants, -1, sizeof(plants));for (int i = 0; i < PLANT_CNT; ++i){//获取植物卡片相对路径名称sprintf(name, "res/Cards/card_%d.png", i + 1);loadimage(&imgCards[i], name);for (int j = 0;i < MAX_PICTURE_NUM; ++j){//获取动态植物素材相对路径名称sprintf(name, "res/Plants/%d/%d.png", i, j + 1);if (fileExist(name)) {imgPlant[i][j] = new IMAGE;loadimage(imgPlant[i][j], name);}else break;}}//创建游戏图形窗口initgraph(WIN_WIDTH, WIN_HIGHT, 1);
}void updateWindow() 
{//使用双缓冲, 解决输出窗口闪屏BeginBatchDraw();//渲染背景图至窗口putimage(0, 0, &imgBg);putimagePNG(250, 0, &imgBar);//渲染植物卡牌for (int i = 0;i < PLANT_CNT;++i)putimage(PIC_LEFT_MARGIN + i * PIC_WIDTH, 6, &imgCards[i]);//渲染种植植物for (int i = 0; i < GRASS_GRID_ROW; ++i){for (int j = 0; j < GRASS_GRID_COL; ++j) {if (plants[i][j].type >= 0){putimagePNG(GRASS_LEFT_MARGIN + j * GRASS_GRID_WIDTH + 5, //微调植物种植位置GRASS_TOP_MARGIN + i * GRASS_GRID_HIGHT + 10,imgPlant[plants[i][j].type][plants[i][j].frameId]);}}}//渲染当前拖动的植物if (currIndex >= 0){IMAGE* currImage = imgPlant[currIndex][0];putimagePNG(currX - currImage->getwidth() / 2,currY - currImage->getheight() / 2, currImage);}EndBatchDraw(); //结束双缓冲
}void userClick()
{ExMessage msg; //创建消息体/* 拖动需先左键点击再拖动 */static int status = 0; //种植植物必须先选中再拖动if (peekmessage(&msg)) //该函数用于获取一个消息,并立即返回{if (msg.message == WM_LBUTTONDOWN) //鼠标点击{if (msg.x > PIC_LEFT_MARGIN && msg.x < PIC_LEFT_MARGIN + PLANT_CNT * PIC_WIDTH &&msg.y < 96){currX = msg.x, currY = msg.y;currIndex = (msg.x - PIC_LEFT_MARGIN) / PIC_WIDTH;status = 1;}}else if (msg.message == WM_MOUSEMOVE && status == 1) //鼠标拖动{currX = msg.x, currY = msg.y; //记录当前拖动位置}else if (msg.message == WM_LBUTTONUP) //鼠标抬起{//当植物拖到至草地位置终止, 则种植植物if (msg.x >= GRASS_LEFT_MARGIN &&msg.x <= GRASS_LEFT_MARGIN + GRASS_GRID_COL * GRASS_GRID_WIDTH &&msg.y >= GRASS_TOP_MARGIN &&msg.y <= GRASS_TOP_MARGIN + GRASS_GRID_ROW * GRASS_GRID_HIGHT){int x = (msg.y - GRASS_TOP_MARGIN) / GRASS_GRID_HIGHT;  //计算第几行int y = (msg.x - GRASS_LEFT_MARGIN) / GRASS_GRID_WIDTH; //计算第几列//未点击植物或当前位置已种植过植物,则不种植植物if (plants[x][y].type < 0 && status == 1){plants[x][y].type = currIndex;plants[x][y].frameId = 0;}//printf("x = %d  y = %d \n", x, y); -- DEBUG}status = 0, currIndex = -1; //停止拖动当前植物}}}void updateGame() 
{//遍历种植植物数组, 更新摆动帧for (int i = 0; i < GRASS_GRID_ROW; ++i){for (int j = 0; j < GRASS_GRID_COL; ++j){if (plants[i][j].type >= 0){if (imgPlant[plants[i][j].type][++plants[i][j].frameId] == NULL) //把 ++plants[i][j].frameId 合并在一条语句中plants[i][j].frameId = 0;}}}
}int main()
{gameInit();updateWindow(); //窗口视图展示int timer = 0; //用以计时 20 毫秒更新一次while (1){userClick(); //监听窗口鼠标事件timer += getDelay();if (timer > 20){updateWindow(); //更新窗口视图updateGame(); //更新游戏动画帧timer = 0;}}system("pause");return 0;
}

运行结果

image-20241226200606328

遇到的小问题

注意以下关于行列的偏移

//计算第几行
int x = (msg.y - GRASS_TOP_MARGIN) / GRASS_GRID_HIGHT; 
//计算第几列
int y = (msg.x - GRASS_LEFT_MARGIN) / GRASS_GRID_WIDTH;

以及渲染种植植物时

x = GRASS_LEFT_MARGIN + j * GRASS_GRID_WIDTH;
y = GRASS_TOP_MARGIN + i * GRASS_GRID_HIGHT;

非常容易搞混

小技巧

利用 timer 替代 Sleep 函数,提高程序运行效率;先渲染种植植物,再渲染拖动植物,提高游戏观感


http://www.ppmy.cn/devtools/145769.html

相关文章

Linux高并发服务器开发 第五天(压缩解压缩/vim编辑器/查找替换/分屏操作/vim的配置)

目录 1.压缩和解压缩 1.1压缩 1.2解压缩 2.vim编辑器 2.1vim的3种工作模式 2.2切换编辑模式 2.3保存和退出 2.4光标移动 2.5复制粘贴 2.6剪切、删除 2.7查找 替换 2.7.1查找 2.7.2替换 3.分屏操作 3.1快速翻屏 3.2分屏 4.vim的配置 4.1系统配置 4.2用户配置…

【UE5 C++课程系列笔记】14——GameInstanceSubsystem与动态多播的简单结合使用

效果 通过在关卡蓝图中触发GameInstanceSubsystem包含的委托&#xff0c;来触发所有绑定到这个委托的事件&#xff0c;从而实现跨蓝图通信。 步骤 1. 新建一个C类 这里命名为“SubsystemAndDelegate” 引入GameInstanceSubsystem.h&#xff0c;让“SubsystemAndDelegate”继承…

ruoyi 请求参数类型不匹配,参数[giftId]要求类型为:‘java.lang.Long‘,但输入值为:‘orderGiftUnionList

记录下自己的bug /*** 礼物订单信息** author ruoyi*/ RestController RequestMapping("/order/gift") public class OrderGiftController extends BaseController {Autowiredprivate IOrderGiftService orderGiftService;/*** 获取礼物订单列表 - 联合数据*/GetMap…

实战演练JDK的模块化机制

实战演练JDK的模块化机制--楼兰 带你聊最纯粹的Java ​ 你发任你发,我用Java8。你用的JDK到什么版本了?很多开源框架都已经开始陆续升级JDK版本了。你对于JDK8往后陆陆续续更新的这些版本有什么感觉吗? ​ 很多人会说其实并没有太多的感觉。JDK的新版本不断推出一些不痛不痒…

D类音频应用EMI管理

1、前言 对于EMI&#xff0c;首先需要理解天线。频率和波长之间的关系&#xff0c;如下图所示。   作为有效天线所需的最短长度是λ/4。在空气中&#xff0c;介电常数是1&#xff0c;但是在FR4或玻璃环氧PCB的情况下&#xff0c;介电常数大约4.8。这种效应会导致信号在FR4材…

Vue BPMN Modeler流程图

1、参考地址 git clone https://github.com/evanyangg/vue-bpmn-modeler.git 2、安装bpmn.js npm install bpmn-js --save 3、使用bpmn.js <template><div class"containers"><div class"canvas" ref"canvas"></div&g…

ReactPress 1.6.0:重塑博客体验,引领内容创新

ReactPress 是一个基于Next.js的博客&CMS系统&#xff0c; Github项目地址&#xff1a;https://github.com/fecommunity/reactpress 欢迎Star。 体验地址&#xff1a;http://blog.gaoredu.com/ 今天&#xff0c;我们自豪地宣布ReactPress 1.6.0版本的正式发布&#xff0c;…

亚远景-ISO 21434标准下的汽车网络安全测试:全面要求与实施策略

ISO 21434标准在安全测试方面有着详细且全面的要求&#xff0c;以确保车辆网络系统的安全性能得到有效验证和确认。以下是该标准在安全测试方面的主要要求&#xff1a; 一、安全测试计划的制定与执行 要求&#xff1a;制造商需要制定并执行详细的安全测试计划&#xff0c;该计…