基于Easyx的打字游戏
一、效果展示
1.游戏开始时的界面:

2.游戏中的画面

3.游戏失败后的界面

二、准备开发环境
- 在官网上下载Easyx安装包文件(https://easyx.cn/)
- 双击exe文件,点击下一步

- 选择VC++2022

- 弹出安装成功提示

- 在VS2022上新建一个空项目工程

二、实现思路
1.引入库头文件
代码如下:
#include<easyx.h>
AI生成项目c
2.游戏对象类的创立
类声明
class Letter
{
private:
int x;
int y;
int speed;
char ch;
public:
//字母偏移量
static int dx[26];
//获取字母接口
char get_ch();
//获取纵坐标接口
int get_y();
//指向下一个结点
Letter* next;
//构造函数
Letter(Letter* LetterList);
//渲染自己
void drawLetter();
//更新自身坐标
void modifyPos();
};
AI生成项目cpp

类成员函数实现
#include<iostream>
#include "Letter.h"
#define FPS 90//帧率不能超过90
int Letter::dx[26] = { 22, 22, 22, 22, 24, 24, 21, 20, 26, 22, 22, 24, 17,
20, 20, 23, 21, 22, 24, 22, 20, 21, 16, 21, 22, 22 };
extern IMAGE LetterImg;
char Letter::get_ch() {
return this->ch;
}
int Letter::get_y() {
return this->y;
}
Letter::Letter(Letter* LetterList) {
this->y = 0;
this->speed = rand() % 2 + 1;
//不允许创建对象时字母一样以及萝卜重合
end:
this->ch = 'A' + rand() % 26;
this->x = rand() % (900 - 40) + 40;
for (Letter* p = LetterList; p; p = p->next) {
if (p->ch == this->ch || fabs(p->x - this->x) <= 50) {
goto end;
}
}
}
void Letter::drawLetter() {
//绘制字体
DWORD* dst = GetImageBuffer();
DWORD* draw = GetImageBuffer();
DWORD* src = GetImageBuffer(&LetterImg);
int picture_width = (&LetterImg)->getwidth();
int picture_height = (&LetterImg)->getheight();
int graphWidth = getwidth();
int graphHeight = getheight();
int dstX = 0;
for (int iy = 0; iy < picture_height; iy++)
{
for (int ix = 0; ix < picture_width; ix++)
{
int srcX = ix + iy * picture_width;
int sa = ((src[srcX] & 0xff000000) >> 24);
int sr = ((src[srcX] & 0xff0000) >> 16);
int sg = ((src[srcX] & 0xff00) >> 8);
int sb = src[srcX] & 0xff;
if (ix >= 0 && ix <= graphWidth && iy >= 0 && iy <= graphHeight && dstX <= graphWidth * graphHeight)
{
dstX = (ix + this->x) + (iy + this->y) * graphWidth;
int dr = ((dst[dstX] & 0xff0000) >> 16);
int dg = ((dst[dstX] & 0xff00) >> 8);
int db = dst[dstX] & 0xff;
draw[dstX] = ((sr * sa / 255 + dr * (255 - sa) / 255) << 16)
| ((sg * sa / 255 + dg * (255 - sa) / 255) << 8)
| (sb * sa / 255 + db * (255 - sa) / 255);
}
}
}
//绘制字母
LOGFONT f1;//设置字体
gettextstyle(&f1);
f1.lfHeight = 40;
f1.lfWeight = 30;
strcpy(f1.lfFaceName, "Segoe UI Black"); //字体名字
f1.lfQuality = ANTIALIASED_QUALITY; //抗锯齿
settextstyle(&f1);
setbkmode(TRANSPARENT); //字体的背景透明
settextcolor(BLACK); //字体颜色
outtextxy(this->x + dx[this->ch - 'A'], this->y + 40, this->ch);
}
void Letter::modifyPos() {
this->y += this->speed * 90 / FPS;
}
AI生成项目cpp

成员变量:
1、int x,y 用于定位萝卜在游戏界面上的坐标
2、int speed 是萝卜往下掉落的速度
3、char ch 是用来决定生成萝卜上的字母
本项目使用链表数据结构对对象进行组织与管理,并在类中的每个对象中分配一个Letter指针next成员变量用于指向下一个结点
成员函数:
static int dx[26]:以确保各字母在萝卜模型中居中显示,并需测定各字母相对于萝卜模型横向坐标轴的最佳偏移量;以便各对象均基于此固定偏移量进行创建

经过测试得到26个字母每个的最佳偏移值:

将变量x、y和speed设为私有属性以防止外界访问,并为此实现了int get_x()以及char get_ch()的接口以供外界进行读取操作
3、调用drawLetter()接口时程序会在(x, y)处渲染出萝卜
4、每次调用modifyPos()时都会更新萝卜的位置坐标
3.程序入口
int main() {
initgraph(1000, 600);
gameInit();//初始化
gameLoop();//进入游戏循环
closegraph();//关闭绘图窗口
return 0;
}
AI生成项目cpp
通过initgraph()函数创建一个1000×600像素的游戏界面
4.游戏数据初始化
IMAGE bg;
IMAGE gameover;
IMAGE sta;
IMAGE LetterImg;
//游戏初始化
void gameInit() {
loadimage(&bg, "./bg.jpg", 1000, 600);
loadimage(&gameover, "./gameover.jpeg", 1000, 600);
loadimage(&sta, "./sta.jpeg", 1000, 600);
loadimage(&LetterImg, "./letter.png", 60, 90);
putimage(0, 0, &sta);
srand((unsigned)time(NULL));
}
AI生成项目cpp

利用IMAGE变量类型构建了背景图景、游戏结局画面、游戏启动界面画面以及角色图像
loadimage()函数可以加载指定路径上的图片到IMAGE类型的变量当中
游戏初始化时通过putimage()函数将游戏开始时的界面渲染出
5.游戏循环
1.按键监听部分
//按键消息处理
bool Key(Letter* LetterList, int& Score) {
if (_kbhit()) {
bool isDownError = true;
char dowsKey = _getch();
//删除萝卜
deleteNode(LetterList, dowsKey, isDownError);
//删除后又创建萝卜增加可玩性
getLetter(LetterList);
if (!isDownError) {
Score++;
}
return isDownError;
}
return false;
}
AI生成项目cpp

该函数用来检测用户按下输入的字母是否存在于当前画面上。如果在当前画面上找到了该字母,则会删除对应的对象并返回false;否则则会返回true并结束游戏流程。
删除萝卜对象代码
//删除对象
void deleteNode(Letter* LetterList, char key, bool& isDownError) {
Letter* current = LetterList;
Letter* prev = nullptr;
while (current) {
if (current->get_ch() == key) {
isDownError = false;
if (prev) {
prev->next = current->next;
}
else {
LetterList = current->next;
}
Letter* temp = current;
current = current->next;
delete temp;
}
else {
prev = current;
current = current->next;
}
}
}
AI生成项目cpp

2.鼠标信息监听部分
//处理鼠标点击事件
void Message_processing() {
ExMessage msg;
static bool isRun = false;//游戏是否
static int cout = 5;//暂停次数
//没有产生鼠标消息
while (!isRun) {
//鼠标左键按下
if (peekmessage(&msg) && msg.message == WM_LBUTTONDOWN) {
//鼠标点击了开始按钮
if (msg.x > 1 && msg.x < 139 && msg.y>530 && msg.y < 568) {
isRun = true;
}
}
}
if (peekmessage(&msg) && msg.message == WM_LBUTTONDOWN) {
//鼠标点击了暂停游戏
if (msg.x > 193 && msg.x < 324 && msg.y>535 && msg.y < 566 && cout) {
cout--;
isRun = false;
}
}
}
AI生成项目cpp

监听用户是否按了暂停键或者开始键
3.游戏数据更新
//游戏数据更新
void update(Letter* LetterList) {
//生成萝卜
getLetter(LetterList);
//更新萝卜
for (Letter* p = LetterList->next; p; p = p->next) {
p->modifyPos();
}
}
AI生成项目cpp
用于创建新对象以及更新现有对象的坐标
getLetter():
//创建萝卜
void getLetter(Letter* LetterList) {
static int timer = 0;
static int randTime = rand() % (200 * FPS / 90);
Letter* p = LetterList;
if (timer == randTime) {//200 * 10 ms = 2秒 //每间隔 2-3秒 之间生成一个对象
timer = 0;
randTime = rand() % (100 * FPS / 90) + (100 * FPS / 90);
for (int i = 0; i < NUM; i++) {
while (p->next) {
p = p->next;
}
Letter* LetterTemp = new Letter(LetterList);
p->next = LetterTemp;
p = p->next;
p->next = NULL;
}
}
timer++;
}
AI生成项目cpp

声明静态变量timer用于计数。每当函数update()被调用时,计数值将增加1次。当计数值达到预定值randTime时,则会生成一个萝卜并清空计数器;此时会将该值重置为新的随机数randTime以准备下次循环。
此游戏运行时每秒可执行90次更新操作(实现原理在后面),即每次update()函数被调用的时间间隔约为1/90秒,在每次更新操作中都会基于当前状态计算出下一状态的数据。通常情况下,在大约每隔2秒的时间段内会生成3个萝卜(其中宏NUM决定了生成的数量)。
4.检测萝卜是否越界
//检测萝卜是否越界
bool detection(Letter* LetterList) {
for (Letter* p = LetterList->next; p; p = p->next) {
if (p->get_y() >= 510) {
return true;
}
}
return false;
}
AI生成项目cpp
依次访问萝卜对象的列表结构,并对其中是否存在纵坐标超过510的萝卜进行检查;如果发现存在,则导致游戏结束。
5.渲染游戏界面
//渲染画面
void draw(Letter* LetterList, const int& Score) {
BeginBatchDraw();
cleardevice();
//背景图片
putimage(0, 0, &bg);
//渲染得分
drawScore(Score);
//绘制萝卜
for (Letter* p = LetterList->next; p; p = p->next) {
p->drawLetter();
}
FlushBatchDraw();
}
AI生成项目cpp

循环遍历萝卜对象链表,并依次调用每个萝卜对象的drawLetter()方法来绘制自身,并同时生成相应的分数结果
该函数也是1秒钟调用90次(90帧),每帧调用更新当前帧画面
6.游戏循环部分完整代码
//游戏循环
void gameLoop() {
end:
int timer = 0;
int Score = 0;
Letter* LetterList = new Letter(NULL);//创建萝卜链表
LetterList->next = NULL;
while (1) {
//按的字母屏幕上没有
if (Key(LetterList, Score)) {
break;
}
Message_processing();//处理鼠标点击事件
if (timer > 1000 / FPS) {
timer = 0;
update(LetterList);
if (detection(LetterList)) {
break;
}
draw(LetterList, Score);
}
timer += getDelay();
}
//游戏结束
BeginBatchDraw();
cleardevice();
putimage(0, 0, &gameover);
drawScore(Score);//渲染得分
FlushBatchDraw();
while (1) {
if (_getch() == 32) {
goto end;
}
}
}
AI生成项目cpp

getDelay()返回上一次调用与当前调用的时间差(ms)
通过定期计数器timer每次运行时累加一次的时间差值,在累计值超过每秒(即1秒=1000/FPS)时触发执行一帧的操作,并最终使update()与draw()函数每秒可完成90次调用(生成90帧)
6.游戏得分渲染
BeginBatchDraw();
cleardevice();
putimage(0, 0, &gameover);
drawScore(Score);//渲染得分
FlushBatchDraw();
while (1) {
if (_getch() == 32) {
goto end;
}
}
AI生成项目cpp

游戏结束后退出游戏循环,渲染得分,若按空格则重新开始游戏
三、完整代码
main.cpp
#include<easyx.h>
#include<iostream>
#include<conio.h>
#include<ctime>
#include<string>
#include "Letter.h"
#define FPS 90//帧率不能超过90
#define NUM 3 //每次往下掉的数量
using namespace std;
IMAGE bg;
IMAGE gameover;
IMAGE sta;
IMAGE LetterImg;
//定时器
int getDelay()
{
static unsigned long long lasttime = 0;
unsigned long long curr = GetTickCount();
if (lasttime == 0)
{
lasttime = curr;
return 0;
}
else
{
int ret = curr - lasttime;
lasttime = curr;
return ret;
}
}
//游戏初始化
void gameInit() {
loadimage(&bg, "./bg.jpg", 1000, 600);
loadimage(&gameover, "./gameover.jpeg", 1000, 600);
loadimage(&sta, "./sta.jpeg", 1000, 600);
loadimage(&LetterImg, "./letter.png", 60, 90);
putimage(0, 0, &sta);
srand((unsigned)time(NULL));
}
//删除对象
void deleteNode(Letter* LetterList, char key, bool& isDownError) {
Letter* current = LetterList;
Letter* prev = nullptr;
while (current) {
if (current->get_ch() == key) {
isDownError = false;
if (prev) {
prev->next = current->next;
}
else {
LetterList = current->next;
}
Letter* temp = current;
current = current->next;
delete temp;
}
else {
prev = current;
current = current->next;
}
}
}
//创建萝卜
void getLetter(Letter* LetterList) {
static int timer = 0;
static int randTime = rand() % (200 * FPS / 90);
Letter* p = LetterList;
if (timer == randTime) {//200 * 10 ms = 2秒 //每间隔 2-3秒 之间生成一个对象
timer = 0;
randTime = rand() % (100 * FPS / 90) + (100 * FPS / 90);
for (int i = 0; i < NUM; i++) {
while (p->next) {
p = p->next;
}
Letter* LetterTemp = new Letter(LetterList);
p->next = LetterTemp;
p = p->next;
p->next = NULL;
}
}
timer++;
}
//按键消息处理
bool Key(Letter* LetterList, int& Score) {
if (_kbhit()) {
bool isDownError = true;
char dowsKey = _getch();
//删除萝卜
deleteNode(LetterList, dowsKey, isDownError);
//删除后又创建萝卜增加可玩性
getLetter(LetterList);
if (!isDownError) {
Score++;
}
return isDownError;
}
return false;
}
//游戏数据更新
void update(Letter* LetterList) {
//生成萝卜
getLetter(LetterList);
//更新萝卜
for (Letter* p = LetterList->next; p; p = p->next) {
p->modifyPos();
}
}
//检测萝卜是否越界
bool detection(Letter* LetterList) {
for (Letter* p = LetterList->next; p; p = p->next) {
if (p->get_y() >= 510) {
return true;
}
}
return false;
}
//渲染得分
void drawScore(const int& Score) {
LOGFONT f1;//设置字体
gettextstyle(&f1);
f1.lfHeight = 100;
f1.lfWeight = 80;
strcpy(f1.lfFaceName, "Segoe UI Black"); //字体名字
f1.lfQuality = ANTIALIASED_QUALITY; //抗锯齿
settextstyle(&f1);
setbkmode(TRANSPARENT); //字体的背景透明
settextcolor(BLACK); //字体颜色
char scoreStr[8];
sprintf_s(scoreStr, "%d", Score);
if (Score >= 100) {
outtextxy(835, 420, scoreStr);
}
if (Score >= 10 && Score < 100) {
outtextxy(855, 420, scoreStr);
}
else {
outtextxy(875, 420, scoreStr);
}
}
//渲染画面
void draw(Letter* LetterList, const int& Score) {
BeginBatchDraw();
cleardevice();
//背景图片
putimage(0, 0, &bg);
//渲染得分
drawScore(Score);
//绘制萝卜
for (Letter* p = LetterList->next; p; p = p->next) {
p->drawLetter();
}
FlushBatchDraw();
}
//处理鼠标点击事件
void Message_processing() {
ExMessage msg;
static bool isRun = false;//游戏是否
static int cout = 5;//暂停次数
//没有产生鼠标消息
while (!isRun) {
//鼠标左键按下
if (peekmessage(&msg) && msg.message == WM_LBUTTONDOWN) {
//鼠标点击了开始按钮
if (msg.x > 1 && msg.x < 139 && msg.y>530 && msg.y < 568) {
isRun = true;
}
}
}
if (peekmessage(&msg) && msg.message == WM_LBUTTONDOWN) {
//鼠标点击了暂停游戏
if (msg.x > 193 && msg.x < 324 && msg.y>535 && msg.y < 566 && cout) {
cout--;
isRun = false;
}
}
}
//游戏循环
void gameLoop() {
end:
int timer = 0;
int Score = 0;
Letter* LetterList = new Letter(NULL);//创建萝卜链表
LetterList->next = NULL;
while (1) {
//按的字母屏幕上没有
if (Key(LetterList, Score)) {
break;
}
Message_processing();//处理鼠标点击事件
if (timer > 1000 / FPS) {
timer = 0;
update(LetterList);
if (detection(LetterList)) {
break;
}
draw(LetterList, Score);
}
timer += getDelay();
}
//游戏结束
BeginBatchDraw();
cleardevice();
putimage(0, 0, &gameover);
drawScore(Score);//渲染得分
FlushBatchDraw();
while (1) {
if (_getch() == 32) {
goto end;
}
}
}
int main() {
initgraph(1000, 600);
gameInit();//初始化
gameLoop();//进入游戏循环
closegraph();//关闭绘图窗口
return 0;
}
AI生成项目cpp

Letter.h
#pragma once
#include<easyx.h>
class Letter
{
private:
int x;
int y;
int speed;
char ch;
public:
//字母偏移量
static int dx[26];
//获取字母接口
char get_ch();
//获取纵坐标接口
int get_y();
//指向下一个结点
Letter* next;
//构造函数
Letter(Letter* LetterList);
//渲染自己
void drawLetter();
//更新自身坐标
void modifyPos();
};
AI生成项目cpp

Letter.cpp
#include<iostream>
#include "Letter.h"
#define FPS 90//帧率不能超过90
int Letter::dx[26] = { 22, 22, 22, 22, 24, 24, 21, 20, 26, 22, 22, 24, 17,
20, 20, 23, 21, 22, 24, 22, 20, 21, 16, 21, 22, 22 };
extern IMAGE LetterImg;
char Letter::get_ch() {
return this->ch;
}
int Letter::get_y() {
return this->y;
}
Letter::Letter(Letter* LetterList) {
this->y = 0;
this->speed = rand() % 2 + 1;
//不允许创建对象时字母一样以及萝卜重合
end:
this->ch = 'A' + rand() % 26;
this->x = rand() % (900 - 40) + 40;
for (Letter* p = LetterList; p; p = p->next) {
if (p->ch == this->ch || fabs(p->x - this->x) <= 50) {
goto end;
}
}
}
void Letter::drawLetter() {
//绘制字体
DWORD* dst = GetImageBuffer();
DWORD* draw = GetImageBuffer();
DWORD* src = GetImageBuffer(&LetterImg);
int picture_width = (&LetterImg)->getwidth();
int picture_height = (&LetterImg)->getheight();
int graphWidth = getwidth();
int graphHeight = getheight();
int dstX = 0;
for (int iy = 0; iy < picture_height; iy++)
{
for (int ix = 0; ix < picture_width; ix++)
{
int srcX = ix + iy * picture_width;
int sa = ((src[srcX] & 0xff000000) >> 24);
int sr = ((src[srcX] & 0xff0000) >> 16);
int sg = ((src[srcX] & 0xff00) >> 8);
int sb = src[srcX] & 0xff;
if (ix >= 0 && ix <= graphWidth && iy >= 0 && iy <= graphHeight && dstX <= graphWidth * graphHeight)
{
dstX = (ix + this->x) + (iy + this->y) * graphWidth;
int dr = ((dst[dstX] & 0xff0000) >> 16);
int dg = ((dst[dstX] & 0xff00) >> 8);
int db = dst[dstX] & 0xff;
draw[dstX] = ((sr * sa / 255 + dr * (255 - sa) / 255) << 16)
| ((sg * sa / 255 + dg * (255 - sa) / 255) << 8)
| (sb * sa / 255 + db * (255 - sa) / 255);
}
}
}
//绘制字母
LOGFONT f1;//设置字体
gettextstyle(&f1);
f1.lfHeight = 40;
f1.lfWeight = 30;
strcpy(f1.lfFaceName, "Segoe UI Black"); //字体名字
f1.lfQuality = ANTIALIASED_QUALITY; //抗锯齿
settextstyle(&f1);
setbkmode(TRANSPARENT); //字体的背景透明
settextcolor(BLACK); //字体颜色
outtextxy(this->x + dx[this->ch - 'A'], this->y + 40, this->ch);
}
void Letter::modifyPos() {
this->y += this->speed * 90 / FPS;
}
AI生成项目cpp

创建下图所示的文件夹名称

将游戏素材放到项目文件夹目录下(图片名称要和我一致)

必须确保字符集设置与我的配置一致以便正常运行代码。将导航栏属性中的高级选项中的字符集字段更改为多字节编码,并将编程语言从c/c++更改为SDL库。


素材地址可通过百度网盘下载的电子资料包名称为"打字游戏"获取 下载页面链接为 https://pan.baidu.com/s/19j9pKwLlNUN1j7ST7RlQTw?pwd=9dn4 并在跳出页面中输入提取编码 [此处填写字符] 即可下载
四、总结
以链表为数据结构设计的类,在接收到玩家交互返回的指令时会对链表上的萝卜对象进行增减,并具体实现萝卜对象的行为。
