b站有羊了个羊第二关开发视频的教学:
开发所需图片与音乐(提取码为1234)
代码讲解
首先要介绍几个概念:
1.图块
该游戏中一共有15种图块。因为每种图块都有明暗两种类型,因此我使用了IMAGE二维数组来存储他们的图片。
每个图块的具体数据在代码中使用了一个结构体来定义,内部的变量具体请查看代码,代码中有详细注释。
2.图层
其实这与盖房子很类似。盖房子都是从底层盖到高层的,每一层就类似于图片的图层,上面的图层会覆盖下面图层的内容。一个图层可以有多个图块,也可以只有一个图块。被覆盖的图块会被设置成暗色。
在代码中使用了二级结构体指针数组来定义。也许有人会疑问为什么不是用一级结构体指针数组来定义呢?每个一级指针代表一个图层,指针指向一个存储若干个图块的结构体数组,这样不是也可以吗?确实是可以的,但这样只能完成存储功能。要知道我们定义了图层与图块后是要进行调用的。图层的图块数量不是唯一的,有些图层可能只有一个,有些图层可能有六个,对一个图层的图块数量不确定我们是无法进行调用的。因此我们可以考虑使用NULL作为图层遍历完毕的一个标志。遍历时遇到NULL说明该图层的图块都已遍历完毕。而要使用NULL就必须使用一级指针数组。上述视频中有详细图解。
函数功能讲解:
void InitLayer(int row, int col, int init_x, int init_y, int layer) { /* 参数解释: 1.row:表示图层有几行 2.col:表示图层有几列 3.init_x:表示图层的第一个图块的x坐标 4.init_y:表示图层的第一个图块的y坐标 5.layer:表示该图层为整个地图的第几层 函数功能: 用于初始化一个图层 */ int i, j; //为maps数组中的每个元素分配内存 maps[layer] = (struct block**)malloc(sizeof(struct block*) * (row * col + 1));//+1是为了在最后一位存入空指针 if (maps[layer] == NULL) { exit(0);//申请内存失败 } for (i = 0; i < row; i++) { for (j = 0; j < col; j++) { maps[layer][i * col + j] = (struct block*)malloc(sizeof(struct block)); if (maps[layer][i * col + j] == NULL) {//申请内存失败 exit(0); } maps[layer][i * col + j]->init_x = init_x; maps[layer][i * col + j]->init_y = init_y; maps[layer][i * col + j]->row = row; maps[layer][i * col + j]->col = col; maps[layer][i * col + j]->type = 1;//初始化种类为1 maps[layer][i * col + j]->x = init_x + j * BLOCK_WIDTH; maps[layer][i * col + j]->y = init_y + i * BLOCK_HEIGHT; maps[layer][i * col + j]->is_not_cover = 1;//初始化图层未被覆盖 } } maps[layer][col * row] = NULL;//每个二级指针所指向的一级指针数组的最后一个元素设为空指针 }
1.为maps数组中的某一个图层申请row*col+1个连续内存,用于存储图层中图块数据的地址,最后的+1是为了存储NULL。
2.遍历所申请的连续内存,并为图块数据赋值。
void InitMap() { int layer = 0; for (; layer < 12; layer++) { InitLayer(1, 1, 55 + layer * 10, 500, layer); } for (; layer < 24; layer++) { InitLayer(1, 1, 350 - (layer - 12) * 10, 500, layer); } InitLayer(6, 1, 15, 90, layer++); InitLayer(3, 1, 77, 130, layer++); InitLayer(6, 1, 138, 45, layer++); InitLayer(6, 1, 202, 45, layer++); InitLayer(6, 1, 264, 45, layer++); InitLayer(3, 1, 326, 130, layer++); InitLayer(6, 1, 388, 90, layer++); InitLayer(4, 1, 48, 120, layer++); InitLayer(4, 1, 360, 120, layer++); InitLayer(2, 2, 174, 20, layer++); InitLayer(2, 2, 174, 345, layer++); InitLayer(1, 2, 170, 220, layer++); InitLayer(1, 2, 15, 20, layer++); InitLayer(1, 2, 326, 20, layer++); InitLayer(1, 1, 109, 120, layer++); InitLayer(1, 1, 109, 324, layer++); InitLayer(1, 1, 299, 120, layer++); InitLayer(1, 1, 299, 324, layer++); InitLayer(1, 1, 109, 431, layer++); InitLayer(1, 1, 299, 431, layer++); InitLayer(1, 1, 77, 395, layer++); InitLayer(1, 1, 326, 395, layer++); Correct_Layer();//修正图块的数据类型,使它们最终能被全部消去 }
1.对图层的布局进行构建,可以尝试DIY布局,我这里是尽可能按照原版的布局去构建的。
2.最后需要对图块的种类进行修正。在InitLayer函数中我只是将类型初始化为1,为了保证最后所有图块都能被消去,需要调用Correct_Layer函数。(视频中并没有介绍如何保证所有图块都能被消去,该函数功能为自己所构想)
void Correct_Layer() { /* 函数功能:修正图层的图块种类,确保最终能被全部消去 */ int rand_type[MAX_BLOCK];//记录图块类型 int i, j, index = 0; int temp = MAX_BLOCK;//临时变量为最大图块数 int rand_num;//随机数 srand((unsigned int)time(NULL)); for (i = 0; i < 15; i++) { freq[i].count = 0; }//计数 i = 0; while (temp != 0 && i < 15) { freq[i++].count += 3; temp -= 3;//先让每个种类的数量都至少有3种 } while (temp != 0) { freq[rand() % 15].count += 3;//然后再随机选择一个种类数量加3 temp -= 3; } for (i = 0; i < 15; i++) { freq[i].flag = 1;//标志变量初始化为1,表示数量不为0 } temp = MAX_BLOCK;//重置临时变量 i = 0;//重置i为0 while (temp > 0) { rand_num = Rand_not_zero(freq, 15);//freq计数器中一个数量不为0的随机元素 rand_type[i++] = rand_num; freq[rand_num].count--; temp--; for (j = 0; j < 15; j++) { if (freq[j].count == 0) { freq[j].flag = 0;//若计数器的值为0,则令标志变量为0 } } }//生成一个随机种类数组 for (i = 0; i < MAX_LAYER; i++) { for (j = 0; maps[i][j] != NULL; j++) { maps[i][j]->type = rand_type[index++];//将随机种类数组依次赋值给maps中的type } } }
1.freq为一个全局变量,是一个结构体数组,其定义为:
struct count { int count; int flag;//判断count是否为0 }freq[15];//结构体数组
1.对freq数组进行初始化,count表示的是此类型图块的最终数量。首先让所有种类图块都至少有3个,再让随机种类的图块加3,直到temp为0。temp的初始值即为最大图块数,每次加3,temp就会减3。
2.然后再生成一个rand_type随机种类数组,里面存储的是最大图块数个图块对应的种类数字。通过调用Rand_not_zero函数得到freq数组中随机的count不为0的元素,并将该元素存入rand_type数组中。
完整代码
#include <graphics.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <math.h>
#include <conio.h>
#include <Windows.h>//播放音乐 头文件
#pragma comment(lib,"winmm.lib")//播放音乐 库文件
#define MAX_LAYER 46//最大图层数
#define MAX_BLOCK 90//最大图块数
#define WIDTH 464
#define HEIGHT 691//窗口的宽度与高度
#define BLOCK_WIDTH 61
#define BLOCK_HEIGHT 68//图块的宽度与高度
void LoadIMG();
void LoadMusic();
void Game();
void InitMap();
void InitLayer(int row, int col, int init_x, int init_y, int layer);
int Rand_not_zero(struct count arr[], int n);
void Correct_Layer();
void Set_Cover();
int Is_Cover(struct block* b1, struct block* b2);
void Update_Window();
void Update_Cover();
void Update_groov(int groov[], int num);
int User_Click();
int Fail(int groov[], int num);
void Work();
IMAGE img_bk;//背景图片
IMAGE img_item[15][2];//图块数组,15种物品,每种物品根据是否被覆盖而有明暗两种图片
IMAGE img_fail;//失败画面
IMAGE img_success;//通关画面
struct block {
int type;//图块的种类,共有15种,当type为-1时表示被消除
int x;
int y;//图块坐标
int is_not_cover;//图块是否被覆盖
int row;
int col;//图块处于所在图层的第几行,第几列
int init_x;
int init_y;//图块所在图层的第一个图块的坐标
};
struct count {
int count;
int flag;
}freq[15];//结构体数组
struct block** maps[MAX_LAYER];//图层数组,每个二级结构体指针指向一个图层
struct block* clickBlock = NULL;
int groov[7];//槽位数组
void LoadIMG() {//加载程序所需图片
int i, j;
char filename[256];
loadimage(&img_bk, "images/蒙娜丽坤.png");//加载背景图片
for (i = 0; i < 15; i++) {
for (j = 0; j < 2; j++) {
sprintf_s(filename, sizeof(filename), "images/%d_%d.png", i + 1, j);
loadimage(&img_item[i][j], filename);//加载图块
}
}
loadimage(&img_fail, "images/失败画面.png");
loadimage(&img_success, "images/通关画面.png");
}
void LoadMusic() {//加载音乐
mciSendString("open music/背景音乐.mp3", 0, 0, 0);
mciSendString("play music/背景音乐.mp3 repeat", 0, 0, 0);//循环播放
mciSendString("open music/鸡.mp3", 0, 0, 0);
mciSendString("open music/唱跳rap篮球.mp3", 0, 0, 0);
mciSendString("open music/你干嘛哎哟.mp3", 0, 0, 0);
mciSendString("open music/厉不厉害你坤哥.mp3", 0, 0, 0);
}
void InitLayer(int row, int col, int init_x, int init_y, int layer) {
/*
参数解释:
1.row:表示图层有几行
2.col:表示图层有几列
3.init_x:表示图层的第一个图块的x坐标
4.init_y:表示图层的第一个图块的y坐标
5.layer:表示该图层为整个地图的第几层
函数功能:
用于初始化一个图层
*/
int i, j;
//为maps数组中的每个元素分配内存
maps[layer] = (struct block**)malloc(sizeof(struct block*) * (row * col + 1));//+1是为了在最后一位存入空指针
if (maps[layer] == NULL) {
exit(0);//申请内存失败
}
for (i = 0; i < row; i++) {
for (j = 0; j < col; j++) {
maps[layer][i * col + j] = (struct block*)malloc(sizeof(struct block));
if (maps[layer][i * col + j] == NULL) {//申请内存失败
exit(0);
}
maps[layer][i * col + j]->init_x = init_x;
maps[layer][i * col + j]->init_y = init_y;
maps[layer][i * col + j]->row = row;
maps[layer][i * col + j]->col = col;
maps[layer][i * col + j]->type = 1;//初始化种类为1
maps[layer][i * col + j]->x = init_x + j * BLOCK_WIDTH;
maps[layer][i * col + j]->y = init_y + i * BLOCK_HEIGHT;
maps[layer][i * col + j]->is_not_cover = 1;//初始化图层未被覆盖
}
}
maps[layer][col * row] = NULL;//每个二级指针所指向的一级指针数组的最后一个元素设为空指针
}
int Rand_not_zero(struct count arr[], int n) {
int i;
int rand_arr[1000];
static int rand_xishu = 0;//随机系数
srand((unsigned)time(NULL) + rand_xishu);//随机数种子
rand_xishu+=rand();//改变随机系数
for (i = 0; i < 1000; i++) {
rand_arr[i] = rand() % 15;//生成随机数表
}
i = 0;
while (1) {
if (arr[rand_arr[i]].count != 0) {
return rand_arr[i];//找到一个随机数使得对应的count不为0
}
i++;
}
}
void Correct_Layer() {
/*
函数功能:修正图层的图块种类,确保最终能被全部消去
*/
int rand_type[MAX_BLOCK];//记录图块类型
int i, j, index = 0;
int temp = MAX_BLOCK;//临时变量为最大图块数
int rand_num;//随机数
srand((unsigned int)time(NULL));
for (i = 0; i < 15; i++) {
freq[i].count = 0;
}//计数
i = 0;
while (temp != 0 && i < 15) {
freq[i++].count += 3;
temp -= 3;//先让每个种类的数量都至少有3种
}
while (temp != 0) {
freq[rand() % 15].count += 3;//然后再随机选择一个种类数量加3
temp -= 3;
}
for (i = 0; i < 15; i++) {
freq[i].flag = 1;//标志变量初始化为1,表示数量不为0
}
temp = MAX_BLOCK;//重置临时变量
i = 0;//重置i为0
while (temp > 0 && index < MAX_BLOCK) {
rand_num = Rand_not_zero(freq, 15);//freq计数器中一个数量不为0的随机元素
rand_type[i++] = rand_num;
freq[rand_num].count--;
temp--;
for (j = 0; j < 15; j++) {
if (freq[j].count == 0) {
freq[j].flag = 0;//若计数器的值为0,则令标志变量为0
}
}
}//生成一个随机种类数组
for (i = 0; i < MAX_LAYER; i++) {
for (j = 0; maps[i][j] != NULL; j++) {
maps[i][j]->type = rand_type[index++];//将随机种类数组依次赋值给maps中的type
}
}
}
int User_Click() {
ExMessage msg;
msg = getmessage(EX_MOUSE | EX_KEY);
int count = 0,i;
for (i = 0; i < 7; i++) {
if (groov[i] != -1) {
count++;//槽计数器
}
}
switch (msg.message) {
case WM_LBUTTONDOWN://用户点下鼠标左键
if (msg.ctrl) {
mciSendString("play music/唱跳rap篮球.mp3 from 0 ", 0, 0, 0);//彩蛋音乐
}
for (int i = 0; i < MAX_LAYER; i++) {
for (int j = 0; maps[i][j] != NULL; j++) {
struct block* p = maps[i][j];
if (p->type != -1 && p->is_not_cover == 1
&& msg.x > p->x && msg.x < p->x + BLOCK_WIDTH
&& msg.y > p->y && msg.y < p->y + BLOCK_HEIGHT) {//点击的是未被消除且未被覆盖的图块
mciSendString("play music/鸡.mp3 from 0 ", 0, 0, 0);//有效点击音效,每次有效点击都会重新播放
if (count < 7) {//槽数组中图块不满7个
clickBlock = p;//记录有效点击所指向方块,并在Work函数继续调用
return 1;//有效点击
}
else {
return 0;
}
}
}
}
}
return 0;
}
int Fail(int groov[], int num) {//判断游戏是否失败
int sum = 0, i, flag = 1;//初始化标志变量为1,表示失败
int count[15] = { 0 };//计数器
for (i = 0; i < num && groov[i] != -1; i++) {
count[groov[i]]++;//统计数量
}
for (i = 0; i < 15; i++) {
sum += count[i];//统计槽中总块数
}
for (i = 0; i < 15; i++) {
if (count[i] == 3) {//计数器存在某个元素数量为3,满足消去条件
flag = 0;
break;
}
}
if (sum == 7 && flag) {//总共7个图块并且满足失败条件
return 1;
}
else {
return 0;
}
}
void Work() {
/*
函数功能:点击后移动图块
*/
int i = 0;
while (groov[i] != -1 && i < 7) {
i++;//找到槽数组中为-1的第一位
}
groov[i] = clickBlock->type;//在此位置填入clickBlock所指向图块的类型
if (i == 6) {
clickBlock->type = -1;//将最后图块设置为被消除
Update_Window();//更新,该函数内部会对槽数组进行更新
if (Fail(groov, 7)) {//若更新之后仍满足失败条件,说明游戏已经失败了
MOUSEMSG m;//鼠标信息
mciSendString("stop music/背景音乐.mp3", 0, 0, 0);//暂停背景音乐
mciSendString("play music/你干嘛哎哟.mp3", 0, 0, 0);//播放失败音乐
Sleep(1000);//停止程序
putimage(0, 0, &img_fail);//输出失败画面
while (1) {
m = GetMouseMsg();//获取鼠标信息
switch (m.uMsg)
{
case WM_LBUTTONDOWN:
case WM_RBUTTONDOWN:
exit(0);
// 按鼠标左键或右键退出程序
}
}
}
}
//槽数组记录有效点击图块的类型
clickBlock->type = -1;//设置该图块被清除
}
int Is_Cover(struct block* b1, struct block* b2) {//判断两个图块是否相交
if (fabs(b1->x - b2->x) < BLOCK_WIDTH && fabs(b1->y - b2->y) < BLOCK_HEIGHT) {
return 1;
}
else {
return 0;
}
}
void Update_Cover() {
//将所有图块的is_not_cover更新为1
int i, j;
for (i = 0; i < MAX_LAYER; i++) {
for (j = 0; maps[i][j] != NULL; j++) {
maps[i][j]->is_not_cover = 1;
}
}
}
void Set_Cover() {
/*
函数功能:将有重叠的图块设置成明暗重叠
*/
for (int i = MAX_LAYER - 1; i >= 0; i--) {
for (int j = 0; maps[i][j] != NULL; j++) {
if (maps[i][j]->type == -1) {//该图块已经被消除
continue;
}
for (int k = 0; k < i; k++) {
for (int l = 0; maps[k][l] != NULL; l++) {
if (maps[k][l]->type == -1) {//该图块已经被消除
continue;
}
if (Is_Cover(maps[i][j], maps[k][l])) {//两图块发生覆盖
maps[k][l]->is_not_cover = 0;//将图块设置为被覆盖
}
}
}
}
}
}
void Update_groov(int groov[], int num) {
//更新槽数组,即去掉槽数组中出现次数大于等于3的元素
int count[15] = { 0 };//计数器数组
int i, j = 0;
for (i = 0; i < 7 && groov[i] != -1; i++) {
count[groov[i]]++;//计数器进行计数
}
for (i = 0; i < 7 && groov[i] != -1; i++) {
if (count[groov[i]] < 3 && count[groov[i]] != 0) {
groov[j++] = groov[i];//将出现次数小于3且大于0的元素填入
}
}
while (j < 7) {
groov[j++] = -1;//后面填补-1
}
}
void Update_Window() {
/*
函数功能:更新窗口信息
*/
BeginBatchDraw();//这个函数用于开始批量绘图。执行后,任何绘图操作都将暂时不输出到绘图窗口上,直到执行 FlushBatchDraw 或 EndBatchDraw 才将之前的绘图输出。
int i, j;
Update_Cover();//图块的is_not_cover全部重置为1,即未被覆盖
Set_Cover();//再确立重叠关系
putimage(0, 0, &img_bk);//输出背景图片覆盖上次的结果
for (i = 0; i < MAX_LAYER; i++) {
for (j = 0; maps[i][j] != NULL; j++) {
struct block* p = maps[i][j];
if (p->type != -1) {
putimage(p->x, p->y, &img_item[p->type][p->is_not_cover]);//输出图层
}
}
}
for (i = 0; groov[i] != -1 && i < 7; i++) {
putimage(15 + i * BLOCK_WIDTH, 585, &img_item[groov[i]][1]);//输出槽
}
Update_groov(groov, 7);//更新槽数组,槽中图块是否可以消去
EndBatchDraw();//缓冲防止图像抖动
}
void InitMap() {
int layer = 0;
for (; layer < 12; layer++) {
InitLayer(1, 1, 55 + layer * 10, 500, layer);
}
for (; layer < 24; layer++) {
InitLayer(1, 1, 350 - (layer - 12) * 10, 500, layer);
}
InitLayer(6, 1, 15, 90, layer++);
InitLayer(3, 1, 77, 130, layer++);
InitLayer(6, 1, 138, 45, layer++);
InitLayer(6, 1, 202, 45, layer++);
InitLayer(6, 1, 264, 45, layer++);
InitLayer(3, 1, 326, 130, layer++);
InitLayer(6, 1, 388, 90, layer++);
InitLayer(4, 1, 48, 120, layer++);
InitLayer(4, 1, 360, 120, layer++);
InitLayer(2, 2, 174, 20, layer++);
InitLayer(2, 2, 174, 345, layer++);
InitLayer(1, 2, 170, 220, layer++);
InitLayer(1, 2, 15, 20, layer++);
InitLayer(1, 2, 326, 20, layer++);
InitLayer(1, 1, 109, 120, layer++);
InitLayer(1, 1, 109, 324, layer++);
InitLayer(1, 1, 299, 120, layer++);
InitLayer(1, 1, 299, 324, layer++);
InitLayer(1, 1, 109, 431, layer++);
InitLayer(1, 1, 299, 431, layer++);
InitLayer(1, 1, 77, 395, layer++);
InitLayer(1, 1, 326, 395, layer++);
Correct_Layer();//修正图块的数据类型,使它们最终能被全部消去
}
void Game() {//游戏初始化
int i, j;
for (i = 0; i < 7; i++) {
groov[i] = -1;//槽数组初始化
}
initgraph(WIDTH, HEIGHT);
InitMap();//初始化图层
Set_Cover();//初始化明暗重叠关系
Update_Window();//初始化界面
int update_flag = 1;//更新标记
while (1) {
if (User_Click()) {//用户点击有效,则移动图块到槽中
Work();
}
if (update_flag) {
Update_Window();//更新,该函数不是只在用户点击后才能运行。在循环中程序会不断检测用户的点击操作,也就是说这个函数实际上是在不断被调用的。
}
update_flag = 0;//检测图块是否都被消除,若没有全部被消除,则继续更新,update_flag=1
for (i = 0; i < MAX_LAYER; i++) {
for (j = 0; maps[i][j] != NULL; j++) {
if (maps[i][j]->type != -1) {
update_flag = 1;
}
}
}
if (!update_flag) {//游戏通关
MOUSEMSG m;
mciSendString("stop music/背景音乐.mp3", 0, 0, 0);//暂停背景音乐
mciSendString("play music/厉不厉害你坤哥.mp3", 0, 0, 0);//通关成功音乐
putimage(0, 0, &img_success);
while (1) {
m = GetMouseMsg();
switch (m.uMsg)
{
case WM_LBUTTONDOWN:
case WM_RBUTTONDOWN:
Sleep(10);
exit(0);
// 按鼠标左键或右键退出程序
}
}
}
}
}
int main()
{
LoadIMG();
LoadMusic();
Game();
}
效果演示
后面有空再更新讲解。。。。。