语言:C 使用工具:Visual Studio2019
使用技术:C语言、easyx图形库
一、游戏运行结果展示:
二、开发过程
1、创建游戏开始界面
(1)包含头文件
#include<stdio.h>
#include<graphics.h>//easyx图形库的头文件,需要安装easyx图形库
#include "tools.h"//PNG优化文件
#include<time.h>
#include<math.h>
(2)主函数
调用游戏开始的函数
int main() {
//游戏开始界面
startUI();
system("pause");
return 0;
}
(3)启动菜单函数
1.使用initgraph(WIN_WIDTH, WIN_HEIGHT);创建游戏窗口。
2.使用loadimage进行加载不同状态下的背景图(暗、高亮)。
3.使用while(1) 循环以持续运行游戏并处理用户输入信息,创建ExMessage 类型变量,用于接收用户消息,其中PeekMessage 函数用于检查消息队列中是否有消息存在,而不会将消息从队列中移除。如果队列中有消息,PeekMessage 可以选择性地检索该消息的信息。
4.鼠标左键是点到开始冒险的位置时,//更改状态flag = 1。当鼠标左键松时且在开始冒险的位置时,退出,进入游戏主界面。
5.BeginBatchDraw() 和 EndBatchDraw() 用于实现双缓冲绘制,以减少屏幕闪烁并提高图形绘制的平滑性。在EasyX中,BeginBatchDraw() 函数用于开始这种双缓冲绘制模式,并创建一个后台缓冲区。这个函数通常会在一个绘制循环的开始处被调用,EndBatchDraw() 函数则用于结束双缓冲绘制模式,并将后台缓冲区的内容显示到屏幕上。这个函数应该在所有的绘制操作完成之后被调用。
注:双缓冲绘制是一种图形编程技术,它涉及到在内存中创建一个“后台”缓冲区,所有的绘制操作都在这个缓冲区中进行,而不是直接在屏幕上进行。一旦所有的绘制操作完成,这个后台缓冲区就会被“交换”到前台,从而一次性地在屏幕上显示所有的更新,这样用户就看不到中间的绘制过程,从而减少了闪烁。
#define WIN_WIDTH 900//窗口宽度
#define WIN_HEIGHT 600//窗口高度
//启动菜单
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;
}
//判断鼠标左键松开是否在开始冒险的位置
else if (msg.message == WM_LBUTTONUP&&flag==1)
{
EndBatchDraw();
break;
}
}
EndBatchDraw();
}
}
(4)运行截图
2、创建主场景
(1)创建全局变量
使用枚举方式创建植物类型,定义IMAGE类型的背景图片,工具栏图片、植物卡片图片
enum {
WAN_DOU, XIANG_RI_KUI, ZHI_WU_XINAG };//定义植物种类
IMAGE imgBg;//表示背景图片
IMAGE imgBar;//表示工具栏图片
IMAGE imgCards[ZHI_WU_XINAG];//植物卡片
(2)初始化游戏背景
1.加载游戏背景图和工具栏图片、并使用memset初始化数组imgZhiWu。
2.加载所有植物图片,创建name数组,将每个植物图片的路径都用字符串保留并进行初始化加载。
2.加载每种植物的图片帧,并进行初始化加载。由于每个植物图片帧数不同,可以判断文件是否存在,进行分配内存。
//游戏初始化
void GameInit()
{
//加载游戏背景图片
loadimage(&imgBg, "./res/bg.jpg");
//加载游戏工具栏图片
loadimage(&imgBar, "./res/bar5.png");
//初始化植物卡片
char name[64];
for (int i = 0;i < ZHI_WU_XINAG;i++)
{
//生成植物卡片的文件名
sprintf_s(name, sizeof(name), "./res/Cards/card_%d.png", i + 1);
loadimage(&imgCards[i], name);
}
curZhiWe = 0;
sunshine = 50;//初始化阳光值
//创建游戏窗口
initgraph(WIN_WIDTH, WIN_HEIGHT,1);
}
(3)渲染游戏背景
创建更新窗口函数updateWindow()用于管理渲染图片作用
//更新窗口
void updateWindow()
{
BeginBatchDraw();//开始缓冲
putimage(-112, 0, &imgBg);//显示背景图
putimagePNG(250, 0, &imgBar);//显示PNG格式工具栏图
//显示植物卡片
drawCards();
EndBatchDraw();//结束双缓冲
}
(4)渲染工具栏中的植物卡片
//绘制植物卡片
void drawCards()
{
for (int i = 0;i < ZHI_WU_XINAG;i++)
{
int x = 338 + i * 65;
int y = 6;
putimage(x, y, &imgCards[i]);
}
}
(5)运行截图
3、实现用户鼠标操作
(1)设置植物结构体
创建植物数据的结构体,当前成员type、frameIndex,创建struct zhiwu类型的一个二维数组map用于记录地图上的植物,GameInit()函数中初始化map数组。
int curX, curY;//当前选中的植物,移动过程中的位置
int curZhiWe;//0表示没有选中,1:表示选中第一种植物...
struct zhiwu {
int type;//0:没有植物 1:第一种植物...
int frameIndex;//序列帧数的序号
};
struct zhiwu map[3][9];//植物地图格子
void GameInit()
{
memset(map, 0, sizeof(map));
}
(2)设置鼠标消息
1.鼠标按下:用于选择植物,WM_LBUTTONDOWN鼠标左键事件,先判断鼠标位置是否正确,是否在卡片上,更新curZhiwu记录鼠标选择植物卡片是哪种卡片。
2.鼠标移动:用于移动植物,WM_MOUSEMOVE鼠标移动事件,定义全局变量curX, curY,记录当前选中的植物,移动过程中的位置。
3.鼠标松开:用于植物种植,WM_LBUTTONUP 鼠标左键松开事件,当植物种下时,使用变量row,col记录当前的行和高;同时更新map数组中的数据。
//用户鼠标操作
void userClick()
{
//获取鼠标消息
ExMessage msg;
static int status = 0;
//是否获得消息,获得返回true
if (peekmessage(&msg))
{
//鼠标左键按下--选择植物
if (msg.message == WM_LBUTTONDOWN)
{
//判断鼠标按键位置是否正确
if (msg.x > 338 && msg.x < 338 + 65 * ZHI_WU_XINAG && msg.y < 96)
{
int index = (msg.x - 338) / 65;
status = 1;
curZhiWe = index + 1;
}
//阳光收集操作
else
{
collectSunshine(&msg);
}
}
//鼠标移动--移动植物
else if (msg.message == WM_MOUSEMOVE &&status==1)
{
curX = msg.x;
curY = msg.y;
}
//鼠标左键抬起--种植植物
else if (msg.message == WM_LBUTTONUP && status == 1)
{
//在合理范围内
if (msg.x > 256-112 && msg.y > 179 && msg.y < 489)
{
//记录鼠标位置
int row = (msg.y - 179) / 102;//行
int col = (msg.x - 256+112) / 81;//列
//printf("%d %d\n", row, col);
//种植植物
if (map[row][col].type == 0)
{
map[row][col].type = curZhiWe;
map[row][col].frameIndex = 0;
map[row][col].shootTime = 0;
map[row][col].x = 256-112 + col * 81;//记录植物x坐标
map[row][col].y = 179 + row * 102 + 14;//记录植物y坐标
}
}
status = 0;
curZhiWe = 0;
}
}
}
4、渲染植物拖动和种植
(1)创建植物图片帧数组
创建全局二维数组imgZhiWu,用于存储指向图像数据的指针,同时在初始化GameInit() 函数中将数组初始化,
当图片帧存在时,进行申请内存
IMAGE *imgZhiWu[ZHI_WU_XINAG][20];//存放植物
//游戏初始化
void GameInit()
{
memset(imgZhiWu,0,sizeof(imgZhiWu));
//初始化植物卡片
char name[64];
for (int i = 0;i < ZHI_WU_XINAG;i++)
{
for (int j = 0;i < 20;j++)
{
sprintf_s(name, sizeof(name), "./res/zhiwu/%d/%d.png",i, j + 1);
//判断文件是否存在
if (fileExist(name)) {
//申请内存
imgZhiWu[i][j] = new IMAGE;
loadimage(imgZhiWu[i][j], name);
}
else {
break;
}
}
}
}
(2)渲染拖到中的植物
在更新窗口updateWindow()函数中调用渲染植物函数,使用curX - img->getwidth() / 2,curY - img->getheight() / 2确保鼠标显示在植物的中心位置,否则在左上角。
//更新窗口
void updateWindow()
{
//渲染植物
drawZhiWu();
}
void drawZhiWu()
{
//渲染移动中的植物
if (curZhiWe > 0) {
IMAGE* img = imgZhiWu[curZhiWe - 1][0];
putimagePNG(curX - img->getwidth() / 2, curY - img->getheight() / 2, img);//实现背景透明,鼠标显示在植物中心
}
}
(3)渲染种植中的植物
遍历map数组找到已经种植植物的位置,记录坐标,渲染图片帧。
//渲染植物
void drawZhiWu()
{
//渲染种植中的植物
for (int i = 0;i < 3;i++)
{
for (int j = 0;j < 9;j++)
{
if (map[i][j