目录
游戏简介
控制一个球,让它待在板子上,不能掉下去。木板持续上升,球也不能碰到顶。
这句话中标红的,是我们一步一步要实现的。蓝色背景的字,我们将要定义结构体。
代码思路:
1. 球和板子的结构体
2. 初始化
3. 绘制屏幕
4. 移动球
5. 移动板子
6. 判断游戏结束
7. 准备阶段
准备素材
我使用了一张墙壁的背景图(至少比纯色好):
还有一个 “Ready Go!" 的音效:https://sc.chinaz.com/yinxiao/180831571002.htm
开始编写
1 预编译
#include <stdio.h>
#include <graphics.h>
#include <time.h>
#include <conio.h>
#pragma comment(lib, "winmm.lib") //为音乐做准备
#define WIDTH 760
#define HEIGHT 640
#define BOARDNUM 11
2 球和板子的结构体
球(玩家)
x坐标 x
y坐标 y
半径 r
移动速度 speed
下落速度 dy
落在的板子的下标 index
颜色 color
// 玩家结构体
struct Player
{
int x;
int y;
int r;
int speed;
int index;
int dy;
COLORREF color;
};
板子
x坐标 x
y坐标 y
高度 h
宽度 w
颜色 color
// 板子结构体
struct Board
{
int x;
int y;
int w;
int h;
COLORREF color;
};
3 初始化
首先定义玩家和表示板子的一维数组
Player player;
Board board[BOARDNUM];
加载背景图片
注意:loadimage 函数中的文件地址参数类型为 LPCTSTR, 调整步骤如下:
① 打开解决方案管理器(Ctrl + Alt + L)
② 打开属性(Alt + Enter)
③ 选择配置属性下的高级
④ 找到字符集,将使用Unicode字符集改成使用多字节字符集
// 背景图
loadimage(&bk_img, ".\\Background.png", WIDTH, HEIGHT, true);
初始化板子
遍历每一个板子,依次从高到低设置y坐标。宽度为50~150的随机数,x坐标为0~(屏幕宽度-宽度)的随机数,高度固定为10,颜色也是随机数。
// 板子
for (int i = 0; i < BOARDNUM; i++)
{
board[i].w = rand() % 100 + 50;
board[i].x = rand() % WIDTH - board[i].w;
board[i].y = 0;
if (i == 0)
{
board[i].y = 100;
}
else
{
board[i].y = board[i - 1].y + 60;
}
board[i].h = 10;
board[i].color = RGB(rand() % 256, rand() % 256, rand() % 256);
}
初始化球(玩家)
玩家的y坐标在100,x坐标由最上面的板子的x坐标决定。
// 玩家
player.x = board[0].x + rand() % board[0].w;
player.r = 10;
player.y = board[0].y - player.r;
player.speed = 5;
player.index = -1;
player.dy = 1;
player.color = LIGHTGRAY;
封装 Init 函数
// 初始化
void Init()
{
// 背景图
loadimage(&bk_img, ".\\Background.png", WIDTH, HEIGHT, true);
// 板子
for (int i = 0; i < BOARDNUM; i++)
{
board[i].w = rand() % 100 + 50;
board[i].x = rand() % WIDTH - board[i].w;
board[i].y = 0;
if (i == 0)
{
board[i].y = 100;
}
else
{
board[i].y = board[i - 1].y + 60;
}
board[i].h = 10;
board[i].color = RGB(rand() % 256, rand() % 256, rand() % 256);
}
// 玩家
player.x = board[0].x + rand() % board[0].w;
player.r = 10;
player.y = board[0].y - player.r;
player.speed = 5;
player.index = -1;
player.dy = 1;
player.color = LIGHTGRAY;
}
4 绘制屏幕
背景图
把背景图放在 (0, 0) 的位置。
// 背景图
putimage(0, 0, &bk_img);
画球(玩家)
根据结构体中的 x, y, r 绘制一个灰色的圆当作玩家。
// 玩家
setfillcolor(player.color);
setlinecolor(WHITE);
fillcircle(player.x, player.y, player.r);
画板子
根据板子的 x, y, x+w, y+h 挨个画出长方形。
// 板子
for (int i = 0; i < BOARDNUM; i++)
{
setfillcolor(board[i].color);
setlinecolor(WHITE);
fillrectangle(board[i].x, board[i].y, board[i].x + board[i].w, board[i].y + board[i].h);
}
封装 Draw 函数
// 绘制窗口
void Draw()
{
cleardevice();
// 背景图
putimage(0, 0, &bk_img);
// 玩家
setfillcolor(player.color);
setlinecolor(WHITE);
fillcircle(player.x, player.y, player.r);
// 板子
for (int i = 0; i < BOARDNUM; i++)
{
setfillcolor(board[i].color);
setlinecolor(WHITE);
fillrectangle(board[i].x, board[i].y, board[i].x + board[i].w, board[i].y + board[i].h);
}
}
5 移动球
检测按键左右移动
通过 GetAsyncKeyState 函数异步检测按键,改变球的x坐标。
其中 VK_LEFT 和 VK_RIGHT 分别表示 ← → 键。
// 左右移动
if (GetAsyncKeyState('a') || GetAsyncKeyState('A') || GetAsyncKeyState(VK_LEFT))
{
player.x -= player.speed;
}
if (GetAsyncKeyState('d') || GetAsyncKeyState('D') || GetAsyncKeyState(VK_RIGHT))
{
player.x += player.speed;
}
边界限制
判断球的x坐标,防止它跑出窗口(左右)。
// 边界限制
if (player.x <= player.r) player.x = player.r;
if (player.x >= WIDTH - player.r) player.x = WIDTH - player.r;
落在板子上
挨个判断每一个板子,球的x坐标在板子的 x x+w 的范围内,并且 y+r 在板子 y 上。
用 index 保存它落在的板子上的下标。index = -1 时表示它正在下落。
// 跟随板子
for (int i = 0; i < BOARDNUM; i++)
{
if (player.x >= board[i].x && player.x <= board[i].x + board[i].w &&
player.y + player.r >= board[i].y && player.y + player.r <= board[i].y + 10) // x坐标
{
player.index = i;
break;
}
else player.index = -1;
}
if (player.index == -1)
{
player.y += player.dy;
if (player.dy < 10) player.dy++;
}
else
{
player.y = board[player.index].y - player.r;
player.dy = 1;
}
封装 MovePlayer 函数
void MovePlayer()
{
// 左右移动
if (GetAsyncKeyState('a') || GetAsyncKeyState('A') || GetAsyncKeyState(VK_LEFT))
{
player.x -= player.speed;
}
if (GetAsyncKeyState('d') || GetAsyncKeyState('D') || GetAsyncKeyState(VK_RIGHT))
{
player.x += player.speed;
}
// 边界限制
if (player.x <= player.r) player.x = player.r;
if (player.x >= WIDTH - player.r) player.x = WIDTH - player.r;
// 跟随板子
for (int i = 0; i < BOARDNUM; i++)
{
if (player.x >= board[i].x && player.x <= board[i].x + board[i].w &&
player.y + player.r >= board[i].y && player.y + player.r <= board[i].y + 10) // x坐标
{
player.index = i;
break;
}
else player.index = -1;
}
if (player.index == -1)
{
player.y += player.dy;
if (player.dy < 10) player.dy++;
}
else
{
player.y = board[player.index].y - player.r;
player.dy = 1;
}
}
6 移动板子
向上移动
每一个板子的 y 坐标减少。
for (int i = 0; i < BOARDNUM; i++)
{
board[i].y -= 2;
}
检测到顶
如果到顶了的话,重新初始化这块板子。
if (board[i].y + board[i].h <= 0)
{
// 重新初始化
board[i].y = BOARDNUM * 60;
board[i].w = rand() % 100 + 50;
board[i].x = rand() % WIDTH - board[i].w;
board[i].color = RGB(rand() % 256, rand() % 256, rand() % 256);
}
封装 MoveBoard 函数
// 板子移动
void MoveBoard()
{
for (int i = 0; i < BOARDNUM; i++)
{
board[i].y -= 2;
if (board[i].y + board[i].h <= 0)
{
// 重新初始化
board[i].y = BOARDNUM * 60;
board[i].w = rand() % 100 + 50;
board[i].x = rand() % WIDTH - board[i].w;
board[i].color = RGB(rand() % 256, rand() % 256, rand() % 256);
}
}
}
7 游戏结束
如果球碰到顶或掉到底下,即为游戏结束。
if (player.y - player.r >= HEIGHT || player.y + player.r <= 0)
{
MessageBox(GetHWnd(), "游戏结束!", "游戏", MB_OK | MB_ICONINFORMATION);
break;
}
8 准备阶段
等待用户按下键盘,播放 “Ready Go!" 音效。
_getch();
PlaySound(".\\Ready.wav", NULL, SND_ASYNC | SND_FILENAME);
Sleep(1000);
9 最后——封装 main 函数
int main()
{
initgraph(WIDTH, HEIGHT);
srand(time(NULL));
Init();
Draw();
_getch();
PlaySound(".\\Ready.wav", NULL, SND_ASYNC | SND_FILENAME);
Sleep(1000);
BeginBatchDraw();
while (true)
{
Draw();
MovePlayer();
MoveBoard();
if (player.y - player.r >= HEIGHT || player.y + player.r <= 0)
{
MessageBox(GetHWnd(), "游戏结束!", "游戏", MB_OK | MB_ICONINFORMATION);
break;
}
FlushBatchDraw();
Sleep(1);
}
EndBatchDraw();
closegraph();
return 0;
}
效果截图
完整代码
/*
* 项目名称:是勇士就下100层
* 开发环境:vs2022 + Easyx
* 作者:轩
* 代码长度:184 行
* 完成时间:2022.12.31
* 用时:2.3 小时
*/
#include <stdio.h>
#include <graphics.h>
#include <time.h>
#include <conio.h>
#pragma comment(lib, "winmm.lib")
#define WIDTH 760
#define HEIGHT 640
#define BOARDNUM 11
// 玩家结构体
struct Player
{
int x;
int y;
int r;
int speed;
int index;
int dy;
COLORREF color;
};
// 板子结构体
struct Board
{
int x;
int y;
int w;
int h;
COLORREF color;
};
IMAGE bk_img;
Player player;
Board board[BOARDNUM];
// 初始化
void Init()
{
// 背景图
loadimage(&bk_img, ".\\Background.png", WIDTH, HEIGHT, true);
// 板子
for (int i = 0; i < BOARDNUM; i++)
{
board[i].w = rand() % 100 + 50;
board[i].x = rand() % WIDTH - board[i].w;
board[i].y = 0;
if (i == 0)
{
board[i].y = 100;
}
else
{
board[i].y = board[i - 1].y + 60;
}
board[i].h = 10;
board[i].color = RGB(rand() % 256, rand() % 256, rand() % 256);
}
// 玩家
player.x = board[0].x + rand() % board[0].w;
player.r = 10;
player.y = board[0].y - player.r;
player.speed = 5;
player.index = -1;
player.dy = 1;
player.color = LIGHTGRAY;
}
// 绘制窗口
void Draw()
{
cleardevice();
// 背景图
putimage(0, 0, &bk_img);
// 玩家
setfillcolor(player.color);
setlinecolor(WHITE);
fillcircle(player.x, player.y, player.r);
// 板子
for (int i = 0; i < BOARDNUM; i++)
{
setfillcolor(board[i].color);
setlinecolor(WHITE);
fillrectangle(board[i].x, board[i].y, board[i].x + board[i].w, board[i].y + board[i].h);
}
}
// 玩家移动
void MovePlayer()
{
// 左右移动
if (GetAsyncKeyState('a') || GetAsyncKeyState('A') || GetAsyncKeyState(VK_LEFT))
{
player.x -= player.speed;
}
if (GetAsyncKeyState('d') || GetAsyncKeyState('D') || GetAsyncKeyState(VK_RIGHT))
{
player.x += player.speed;
}
// 边界限制
if (player.x <= player.r) player.x = player.r;
if (player.x >= WIDTH - player.r) player.x = WIDTH - player.r;
// 跟随板子
for (int i = 0; i < BOARDNUM; i++)
{
if (player.x >= board[i].x && player.x <= board[i].x + board[i].w &&
player.y + player.r >= board[i].y && player.y + player.r <= board[i].y + 10) // x坐标
{
player.index = i;
break;
}
else player.index = -1;
}
if (player.index == -1)
{
player.y += player.dy;
if (player.dy < 10) player.dy++;
}
else
{
player.y = board[player.index].y - player.r;
player.dy = 1;
}
}
// 板子移动
void MoveBoard()
{
for (int i = 0; i < BOARDNUM; i++)
{
board[i].y -= 2;
if (board[i].y + board[i].h <= 0)
{
// 重新初始化
board[i].y = BOARDNUM * 60;
board[i].w = rand() % 100 + 50;
board[i].x = rand() % WIDTH - board[i].w;
board[i].color = RGB(rand() % 256, rand() % 256, rand() % 256);
}
}
}
int main()
{
initgraph(WIDTH, HEIGHT);
srand(time(NULL));
Init();
Draw();
_getch();
PlaySound(".\\Ready.wav", NULL, SND_ASYNC | SND_FILENAME);
Sleep(1000);
BeginBatchDraw();
while (true)
{
Draw();
MovePlayer();
MoveBoard();
if (player.y - player.r >= HEIGHT || player.y + player.r <= 0)
{
MessageBox(GetHWnd(), "游戏结束!", "游戏", MB_OK | MB_ICONINFORMATION);
break;
}
FlushBatchDraw();
Sleep(1);
}
EndBatchDraw();
closegraph();
return 0;
}