zl程序教程

您现在的位置是:首页 >  后端

当前栏目

用Devc++与easyx一步一步做游戏[启动界面部分]-之按钮制作

C++游戏 启动 制作 界面 部分 一步 按钮
2023-09-27 14:27:33 时间

前面我们介绍了如何为dev c++配置好easyx,至于用easyx能够做一些什么呢?大用处我不敢说,用来学习了解消息机制还是不错的。这我们来实现一个简单的游戏启动界面的设计,主要是按钮的设计。总体设计好的效果如下:
在这里插入图片描述
GIF截图看不到鼠标移动的效果,实际上这里是随着鼠标的移动而显示的效果,鼠标移动到对应的菜单上则下面的提示文字显示对应的内容,离开按钮则恢复显示CopyRight的信息。

一、绘制简单的按钮

1、准备easyx图形环境

easyx环境不会配置的请参考我前面的博文,这里直接从图形环境的代码开始:

#include <graphics.h>      
#include <conio.h>
#include<stdio.h>
#include<easyx.h>
#include <iostream>

/* run this program using the console pauser or add your own getch, system("pause") or input loop */

int main(int argc, char** argv) {
	
	initgraph(640, 480);   
	setbkcolor(0x3B0000);//底色采用的深蓝色,这里用的是BGR的代码,和常规RGB的不同,要注意
	cleardevice();
	
	
	getchar();
	return 0;
}

setbkcolor(0x3B0000)底色采用的深蓝色,这里用的是BGR的代码,和常规RGB的顺序是不同的,要注意三个颜色的顺序。
有了上面的图形,我们至少可以启动一个空白的界面了,在这基础之上我们便有了展开拳脚的舞台了。
在这里插入图片描述
下面,我们准备开始按钮轮廓的绘制。

2、绘制按钮轮廓

我们来绘制按钮的轮廓,这里用到了几个基本的绘制函数,我简单的列在这里,想进一步了解的可以到easyx官网查阅。
setfillcolor:色彩填充函数
fillroundrect:绘制填充型倒角矩形函数
settextstyle:设置文本字体样式
settextcolor:设置字体颜色
outtextxy:绘制文字
textheight/textwidth:设置文字高宽。

int main(int argc, char** argv) {
	
	initgraph(640, 480);   
	setbkcolor(0x3B0000);
	cleardevice();
	//绘制按钮轮廓
	int x=240,y=50;
	int w=150,h=40;
	TCHAR text[20]="Begin";
    setbkmode(TRANSPARENT);   
    setfillcolor(GREEN);
    fillroundrect(x,y,x+w,y+h,10,10);
    TCHAR s1[] = "Arial BLACK";
    settextstyle(30,0,s1);
    settextcolor(WHITE); 
    int tx = x + (w - textwidth(text)) / 2;
    int ty = y + (h - textheight(text)) / 2;
    outtextxy(tx, ty, text);
    
	getchar();
	return 0;
}

绘制完毕,一个简单的按钮轮廓就有了,看起来像了。效果如下:
在这里插入图片描述
绘制完这个简单的按钮,那接下来就要封装按钮绘制函数了,否则后面还有很多的按钮需要绘制,重要的是还有按钮的几个状态也要绘制,总不可能每个都这么绘制。

二、封装按钮添加消息循环

1、封装按钮

void button(int x,int y,TCHAR* text)
{
	
		int w=200,h=44;
	    setbkmode(TRANSPARENT);
	    
	    setfillcolor(GREEN);
	    fillroundrect(x,y,x+w,y+h,10,10);
	    TCHAR s1[] = "Arial BLACK";
	    settextstyle(30,0,s1);
	    settextcolor(WHITE);
	    
	    int tx = x + (w - textwidth(text)) / 2;
	    int ty = y + (h - textheight(text)) / 2;
	
	    outtextxy(tx, ty, text);
}

2、利用封装的按钮绘制一组按钮

//增加一个间隔变量和起始Y坐标变量;
   int gap=40;
   int starty=50;
   int height=50;
    
   //用循环来实现
   TCHAR title[4][50] = {"Load Game","Start Game","Quit Game", "Game Help"};  
   for(int i=0;i<4;i++)
	button(220, starty+i*height+i*gap,  title[i]);

在这里插入图片描述
看起来按钮的形已经有了一个粗的框架了,可惜是不能和我们的鼠标互动,中看不中用,我们接下来就是要让他对我们的鼠标动作有反应。

3、增加消息循环

easyx中的消息循环和我们在VC或者QT中用到的消息循环没有什么差别,有基础的童鞋直接可以上手,没有基础的可能就要了解一下消息循环机制。windows的消息循环我们可以理解为是一张川流不息的物流配送网,任何需要配送的物品都要先送到物流中心然后由中心再配发给各个站点,最终由站点来实现末端配送。
windows采用了一个无限循环来不停的收发来及鼠标键盘及其他输入设备的信息(这些信息就是物流要配送的物品),谁来收这些各个站点送来的物品(各输入设备输入的消息)呢?easyx提供了几个专业的消息函数,如下:
ExMessage :消息结构体。
flushmessage:清空消息缓冲区。
getmessage:获取一个消息。如果当前消息缓冲区中没有,就一直等待。
peekmessage:获取一个消息,并立即返回。
这里我们选用peekmessage,这个函数的即使反馈能力好,不会让你等太久。我们来看这个函数的用法:
bool peekmessage(ExMessage *msg, BYTE filter = -1, bool removemsg = true);
对于新童鞋,咋一看有点复杂吧,有一个消息指针msg和一个字节和一个布尔变量,细看,其实后两个参数都是有默认值的,所以调用起来很方便,最简单的调用如下:

 ExMessage *msg; //声明一个消息指针
    while (true) {
     if (peekmessage(msg, EM_MOUSE)) {
         switch (msg->message)
         {
	         case WM_LBUTTONDOWN:break;	//鼠标按键动作都在这里处理了
	         case WM_MOUSEMOVE:break;	//鼠标移动和鼠标悬停都在这里处理了
	         default:break;
         }
     }

看这个调用框架是不是感觉是否简单,对,本来就十分简单。接下来我们就要通过鼠标移动来增加按钮信息提示效果了。思路是当鼠标进入到我们绘制按钮的区域就显示对应的按钮消息,所以关键点是必须获得鼠标的位置,这个鼠标的位置其实早就被系统打包在消息里面了,也就是说鼠标位置属于消息的一个属性。看下面代码:

	ExMessage msg; //声明一个消息指针
	    while (true) {
	     if (peekmessage(&msg, EM_MOUSE)) {
	         switch (msg.message)
	         {
		         case WM_LBUTTONDOWN:break;	
		         case WM_MOUSEMOVE:
				 if (msg.x >= 220 && msg.x <= 220 + 170 && msg.y >= 50 && msg.y <= 50 + 50)
	             outtextxy(220, 420, title[0]);
	             if (msg.x >= 220 && msg.x <= 220 + 170 && msg.y >= 140 && msg.y <= 140 + 50)
	             outtextxy(220, 420, title[1]);
	             if (msg.x >= 220 && msg.x <= 220 + 170 && msg.y >= 230 && msg.y <= 230 + 50)
	             outtextxy(220, 420, title[2]);
	             if (msg.x >= 220 && msg.x <= 220 + 170 && msg.y >= 320 && msg.y <= 320 + 50)
	            outtextxy(220, 420, title[3]);
				  break;	
		         default:break;
	         }
	     }
	    }
 

上面的消息循环已经起作用了,但有个问题,那就是上一次显示的文字和当前次显示的文字会重叠在一起,如下:
在这里插入图片描述
再来一次,就叠加在一起了。
在这里插入图片描述
所以,我们必须在每一次绘制提示文字前清除文字绘制区域,这里用到了一个函数clearrectangle,这里很清楚了,是清楚一个矩形区域,我们在绘制文字前加上它即可。
上面的代码问题还很多,或者说还很粗暴,因为都是一个个坐标直接在每个分支中重复的输入,遇到调整修改那就是一场灾难(如果分支够多的话)。所这里也需要封装。
我们将提示文字的输出封装为tipInfo,如下:

void tipInfo(RECT rc,TCHAR * titles)
{
	clearrectangle(rc.left,rc.top,rc.right,rc.bottom);
	outtextxy(rc.left, rc.top, titles[0]);
}

	ExMessage msg; //声明一个消息指针
	    while (true) {
	     if (peekmessage(&msg, EM_MOUSE)) {
	         switch (msg.message)
	         {
		         case WM_LBUTTONDOWN:break;	
		         case WM_MOUSEMOVE:
				 if (msg.x >= 220 && msg.x <= 220 + 170 && msg.y >= 50 && msg.y <= 50 + 50)
	             tipInfo(tipInfoRect, title[0]);
	             if (msg.x >= 220 && msg.x <= 220 + 170 && msg.y >= 140 && msg.y <= 140 + 50)
	             tipInfo(tipInfoRect, title[1]);
	             if (msg.x >= 220 && msg.x <= 220 + 170 && msg.y >= 230 && msg.y <= 230 + 50)
	             tipInfo(tipInfoRect, title[2]);
	             if (msg.x >= 220 && msg.x <= 220 + 170 && msg.y >= 320 && msg.y <= 320 + 50)
	             tipInfo(tipInfoRect, title[3]);
				  break;	
		         default:break;
	         }
	     }
	    }

这样,上面的问题一并解决了,但鼠标移动的时候,下面的提示也会随之闪烁,这可不是我们愿意看到的。
下一篇博文,我们解决hover状态时的闪烁问题。请继续关注。