目录
前言
一、准备工作
1.1 工具安装
1.2 前置知识
二、实现思路
2.1 宏定义
2.2 游戏场景的搭建
2.2.1 地图
2.2.2 箱子坐标
2.2.3 角色坐标
2.3 类的属性与行为的确定
2.4 地图行为的实现
2.4.1 初始化地图数据
2.4.2 绘制地图
2.5 箱子行为的实现
2.5.1 初始化箱子数据
2.5.2 绘制箱子
2.5.3 判断是否胜利
2.6 角色行为的实现
2.6.1 初始化角色数据
2.6.2 绘制角色
2.6.3 角色移动
三、整体代码
四、游戏下载与安装
总结
推箱子游戏最早起源于日本,由电子游戏公司Thinking Rabbit在1982年推出的《Sokoban》。这个游戏的名字来自于日语“倉庫番”,意为“仓库管理员”。游戏在休闲的同时又需要开动我们的脑筋,关卡设计既简单又富有挑战性,能够很好地锻炼我们的逻辑推理能力。下面,让我们用C++来复刻这样一款既简单又烧脑的推箱子游戏吧!
运行效果演示:
1. 安装 Visual Studio
2. 安装 easyx 图形库
1. C++的相关语法知识。比如二维数组、类、友元、文件读写操作等等。
2. 熟练使用easyx图形库提供的API。比如贴图、背景音乐、键盘消息等等。
如果大家不太了解easyx图形库的安装以及使用,可以参考以下内容:
在正式写代码之前,我们先要考虑一下哪些数据是在整个代码体系中都有可能会用的上的。在这里,我想到的是窗口的宽度和高度、每一个格子的宽度以及箱子的最大数目。具体代码如下:
//定义窗口宽度和高度
#define W 640
#define H 640
//定义每个格子的宽度
#define SIZE 40
//箱子最大数目
#define MAXNUM 30
我们知道,除了人物和箱子,这个游戏包含了墙体、目的地、空地以及游戏场景以外的背景。那么,这些数据我们应该如何存储呢?我这里采用txt文件来存储这些内容,这样做的好处就是可以动态地调整自己的地图以及创建新的地图和删除已有的地图。
我们首先建一个文件夹level,将关卡数据存在这个文件夹当中。每个关卡包含有三个文件,分别为“数字_map.txt”、“数字_box.txt”、“数字_role.txt”。比如,第1关的三个文件是1_map.txt、1_box.txt以及1_role.txt,第2关的三个文件是2_map.txt、2_box.txt以及2_role.txt......这样,我们就可以通过输入的数字动态地访问到不同的关卡了。
介绍了每个关卡所包含的三个文件,下面我们就来说说这三个文件分别是用来存储什么数据的,下面以第1关的文件为例。
在1_map.txt文件中,存储了墙体、目的地、游戏去外面、空地这四个数据。对于特定的游戏数据,我是这样定义的:
//游戏场景定义
#define AIR '0' //游戏区外面
#define SPACE '1' //空地
#define BOX '2' //箱子
#define WALL '3' //墙体
#define DEST '4' //目的地
在1_map.txt中存储的数据形状为一个矩阵,到时候初始化地图数据的时候就可以将这里面的数据读取到一个二维数组当中,然后再通过二维数组中存储的不同的数据绘制不同的图案。
1_box.txt文件中存储的是箱子的坐标,每一行存储的一个箱子的坐标,第一个表示的是x坐标,第二个表示的是y坐标。
可以看到,文件中存储了一个6和一个7。当读取到这两个数后,我们把它们乘上每个格子的宽度SIZE 就能得到箱子最终呈现在界面上的位置。
1_role.txt文件中存储的是角色初始的坐标,与1_box.txt文件中存储的内容类似,第一行存储的是人物的初始x坐标,第二行存储的是人物的初始y坐标。这两个数分别乘以每个格子的宽度SIZE 就能得到角色最终呈现在界面上的位置。
在这个游戏中,我们应该定义三个类,分别为地图类、箱子类和角色类。
在地图类中,包含的属性为一个从文件当中读取地图数据的二维数组,其行为包括初始化地图数据以及绘制地图。在箱子类中,包含的属性有箱子的数目以及每个箱子的坐标,其行为包括初始化箱子数据、绘制箱子和判断游戏是否胜利。在角色类中,包含的属性为角色的坐标,其行为包括初始化角色数据、绘制角色、移动和判断移动路径上的两个箱子是否靠在一起。具体的代码如下:
//自定义坐标类型
typedef struct {
int x;
int y;
}locate;
//存储目的地,待会用来判断是否胜利
typedef struct {
int num; //目的地的数量
locate dest[MAXNUM]; //每个目的地的坐标
}Dests;
Dests d; //目的地
int level; //关卡
//定义地图的类
class Map {
friend class Person;
public:
Map(); //初始化地图数据
void Draw(); //绘制地图
private:
char map[H / SIZE+1][W / SIZE]; //地图数据(不包括人物和箱子)
};
//定义箱子的类
class Box{
friend class Person;
public:
Box(); //初始化箱子数据
void Draw(Dests& d); //绘制箱子
bool IsWin(Map& m); //用来判断是否胜利
private:
int num; //箱子的数目
locate box[MAXNUM]; //每个箱子的坐标
};
//定义角色的类
class Person {
public:
Person(); //初始化角色数据
void Draw(); //绘制角色
void Move(Map& m, Box& b); //移动
bool IsBox(Box& b, int x, int y); //用来判断移动路径上的两个箱子是否靠在一起
private:
int x, y; //角色的坐标
};
在初始化地图数据的时候,我们需要读取前面所提到的文件当中的数据,然后将这个数据存储到地图的二维数组当中。在全局里,我们还定义了存储目的地的变量,当我们在文件中每读到一个目的地时,我们需要将这个其加入到存储目的地的变量当中去。具体代码如下:
//初始化地图数据
Map::Map() {
ifstream ifs;
char str[20];
sprintf(str, "./level/%d_map.txt", level);
ifs.open(str, ios::in);
int i = 0;
while (ifs >> this->map[i]) {
i++;
}
ifs.close();
for (int i = 0; i < H / SIZE; i++) {
for (int j = 0; j < W / SIZE; j++) {
if (this->map[i][j] == '4') {
d.num++;
d.dest[d.num - 1].x = j * SIZE;
d.dest[d.num - 1].y = i * SIZE;
}
}
}
}
在这里,我们遍历地图中的二维数组,绘制相应的图片即可。但是,需要注意一点的是,我们这里需要实现一个透明贴图的效果,否则观感会有点差。
关于透明贴图:1.关于Easyx如何显示透明无背景贴图
2.easyx图形库-----贴图技巧之透明贴图与位运算(与运算、或运算、异或运算)
我也在网上找了一份tool.h的头文件来实现透明贴图的效果。tool.h中的具体代码如下:
#pragma once
#include
void drawImg(int x, int y, IMAGE* src) {
DWORD* pwin = GetImageBuffer();
DWORD* psrc = GetImageBuffer(src);
int win_w = getwidth();
int win_h = getheight();
int src_w = src->getwidth();
int src_h = src->getheight();
//计算 贴图的实际长宽
int real_w = (x + src_w > win_w) ? win_w - x : src_w;
int real_h = (y + src_h > win_h) ? win_h - y : src_h;
if (x < 0) { psrc += -x; real_w -= -x; x = 0; }
if (y < 0) { psrc += (src_w * -y); real_h -= -y; y = 0; }
//修正贴图起始位置
pwin += (win_w * y + x);
//实现透明贴图
for (int iy = 0; iy < real_h; iy++) {
for (int ix = 0; ix < real_w; ix++) {
byte a = (byte)(psrc[ix] >> 24);
if (a > 100) {
pwin[ix] = psrc[ix];
}
}
//换到下一行
pwin += win_w;
psrc += src_w;
}
}
绘制地图的具体代码如下:
//绘制地图
void Map::Draw() {
IMAGE bk;
loadimage(&bk, "./bk.jpg", W, H);
putimage(0, 0, &bk);
for (int i = 0; i < H / SIZE; i++) {
for (int j = 0; j < W / SIZE; j++) {
int x = j * SIZE;
int y = i * SIZE;
//空地
if (this->map[i][j] == SPACE) {
setfillcolor(RGB(210, 210, 210));
fillrectangle(x, y, (x + SIZE), (y + SIZE));
}
//墙体
if (this->map[i][j] == WALL) {
IMAGE a;
loadimage(&a, "./wall.png", SIZE, SIZE);
putimage(x, y, &a);
}
//目的地
if (this->map[i][j] == DEST) {
setfillcolor(RGB(210, 210, 210));
fillrectangle(x, y, (x + SIZE), (y + SIZE));
IMAGE a;
loadimage(&a, "./dest.png", SIZE, SIZE);
drawImg(x, y, &a);//调用"tool.h"头文件当中的接口实现透明贴图
}
}
}
}
在初始化箱子数据的时候,我们需要从文件当中读取箱子的坐标。每读取到一个箱子,箱子数量加一,并且需要将这个箱子的坐标存储起来。具体代码如下:
//初始化箱子数据
Box::Box() {
this->num = 0;
ifstream ifs;
char str[20];
sprintf(str, "./level/%d_box.txt", level);
ifs.open(str, ios::in);
char tmp[20];
int i = 0;
int j = 0;
while (ifs >> tmp) {
if (i == 0) {
this->box[j].x = atoi(tmp)*SIZE;
}
if (i == 1) {
this->box[j].y = atoi(tmp)*SIZE;
}
i++;
if (i == 2) {
i = 0;
j++;
this->num++;
}
}
ifs.close();
}
箱子的绘制分为两种情况,第一种是在空地上的箱子,第二种是在目的地上的箱子,这两个箱子的贴图需要设置成不一样的。我们需要遍历存储目的地数据的全局变量,以此确定一个箱子是否在目的地。具体代码如下:
//绘制箱子
void Box::Draw(Dests& d) {
for (int i = 0; i < this->num; i++) {
IMAGE a;
int status = 0;
for (int m = 0; m < d.num; m++) {
if (d.dest[m].x == this->box[i].x && d.dest[m].y == this->box[i].y) {
loadimage(&a, "./box_ok.png", SIZE, SIZE);
drawImg(this->box[i].x, this->box[i].y, &a);
status = 1;
break;
}
}
if (status == 0) {
loadimage(&a, "./box.png", SIZE, SIZE);
drawImg(this->box[i].x, this->box[i].y, &a);
}
}
}
我们用一个局部变量count来计数,将其初始化为0,遍历箱子与目的地的坐标,如果有一个箱子与其中一个目的地重合,就让count加一,当count的值等于箱子的数目时,说明全部箱子都到达了目的地,游戏胜利,否则游戏还未胜利。具体代码如下:
//用来判断是否胜利
bool Box::IsWin(Map& m) {
int count = 0;
for (int i = 0; i < d.num; i++) {
for (int j = 0; j < this->num; j++) {
if (this->box[j].x == d.dest[i].x && this->box[j].y == d.dest[i].y) {
count++;
}
}
}
if (count == d.num) {
return true;
}
return false;
}
在每一关开始时,我们需要从文件当中读取角色的初始坐标,然后将坐标赋值给角色类中的坐标属性。具体代码如下:
//初始化角色数据
Person::Person() {
ifstream ifs;
char str[20];
sprintf(str, "./level/%d_role.txt", level);
ifs.open(str, ios::in);
char tmp[20];
int i = 0;
while (ifs >> tmp) {
if (i == 0) {
this->x = atoi(tmp) * SIZE;
}
if (i == 1) {
this->y = atoi(tmp) * SIZE;
}
i++;
}
ifs.close();
}
我们只需要知道角色的坐标,然后根据坐标将角色的贴图绘制上去即可。不过这里需要注意的是,我们在贴图是也需要用透明贴图,否则影响观感。具体代码如下:
//绘制角色
void Person::Draw() {
IMAGE a;
loadimage(&a, "./one.png", SIZE, SIZE);
drawImg(this->x, this->y, &a);//"tool.h"中的实现透明贴图的接口函数
}
角色可以移动的情况有:
举个例子,角色坐标向上移动一格会碰到箱子,这时候我们应该判断角色可不可以向上移动一格。怎么判断呢?我们需要让箱子的坐标也向上移动一格,判断移动后的坐标是否与某个箱子的坐标重合或者与墙体坐标重合,若重合,则角色和箱子不能移动;若不重合,则角色与箱子都能向上移动一格。 角色移动的具体代码如下:
/移动
void Person::Move(Map& m,Box& b) {
//获取人物在二维数组中的坐标
int j = this->x / SIZE;
int i = this->y / SIZE;
//用来判断前面是否有箱子
int status = 0;
//按下移动键
if (GetAsyncKeyState('W')) {
for(int n = 0; n < b.num; n++) {
//如果前面有箱子
if (b.box[n].x == this->x && b.box[n].y == this->y - SIZE) {
status = 1;
//如果箱子后面不靠墙也不靠箱子
if (m.map[i-2][j] != WALL&&!this->IsBox(b,b.box[n].x,b.box[n].y-SIZE)) {
b.box[n].y -= SIZE;
this->y -= SIZE;
}
}
}
//空地移动
if ((m.map[i - 1][j] == SPACE || m.map[i - 1][j] == DEST)&&(status!=1)) {
this->y -= SIZE;
}
return;
}
if (GetAsyncKeyState('S') ) {
for (int n = 0; n < b.num; n++) {
//如果前面有箱子
if (b.box[n].x == this->x && b.box[n].y == this->y + SIZE) {
status = 1;
//如果箱子后面不靠墙也不靠箱子
if (m.map[i + 2][j] != WALL && !this->IsBox(b, b.box[n].x, b.box[n].y + SIZE)) {
b.box[n].y += SIZE;
this->y += SIZE;
}
}
}
//空地移动
if ((m.map[i + 1][j] == SPACE || m.map[i + 1][j] == DEST) && (status != 1)) {
this->y += SIZE;
}
return;
}
if (GetAsyncKeyState('A')) {
for (int n = 0; n < b.num; n++) {
//如果前面有箱子
if (b.box[n].y == this->y && b.box[n].x == this->x - SIZE) {
status = 1;
//如果箱子后面不靠墙也不靠箱子
if (m.map[i][j-2] != WALL && !this->IsBox(b, b.box[n].x-SIZE, b.box[n].y )) {
b.box[n].x -= SIZE;
this->x -= SIZE;
}
}
}
//空地移动
if ((m.map[i][j-1] == SPACE || m.map[i][j-1] == DEST) && (status != 1)) {
this->x -= SIZE;
}
return;
}
if (GetAsyncKeyState('D')) {
for (int n = 0; n < b.num; n++) {
//如果前面有箱子
if (b.box[n].y == this->y && b.box[n].x == this->x + SIZE) {
status = 1;
//如果箱子后面不靠墙也不靠箱子
if (m.map[i][j + 2] != WALL && !this->IsBox(b, b.box[n].x+SIZE, b.box[n].y )) {
b.box[n].x += SIZE;
this->x += SIZE;
}
}
}
//空地移动
if ((m.map[i][j+1] == SPACE || m.map[i][j+1] == DEST) && (status != 1)) {
this->x += SIZE;
}
return;
}
}
//用来判断移动路径上的两个箱子是否靠在一起
bool Person::IsBox(Box& b, int x, int y) {
for (int i = 0; i < b.num; i++) {
if (b.box[i].x == x && b.box[i].y == y)return true;
}
return false;
}
box.cpp:
#include
using namespace std;
#include
#include
#include
#include"tool.h"
#include
#pragma comment(lib,"winmm.lib")
//游戏场景定义
#define AIR '0' //游戏区外面
#define SPACE '1' //空地
#define BOX '2' //箱子
#define WALL '3' //墙体
#define DEST '4' //目的地
//定义窗口宽度和高度
#define W 640
#define H 640
//定义每个格子的宽度
#define SIZE 40
//箱子最大数目
#define MAXNUM 30
//自定义坐标类型
typedef struct {
int x;
int y;
}locate;
//存储目的地,待会用来判断是否胜利
typedef struct {
int num; //目的地的数量
locate dest[MAXNUM]; //每个目的地的坐标
}Dests;
Dests d; //目的地
int level; //关卡
//定义地图的类
class Map {
friend class Person;
public:
Map(); //初始化地图数据
void Draw(); //绘制地图
private:
char map[H / SIZE+1][W / SIZE]; //地图数据(不包括人物和箱子)
};
//定义箱子的类
class Box{
friend class Person;
public:
Box(); //初始化箱子数据
void Draw(Dests& d); //绘制箱子
bool IsWin(Map& m); //用来判断是否胜利
private:
int num; //箱子的数目
locate box[MAXNUM]; //每个箱子的坐标
};
//定义角色的类
class Person {
public:
Person(); //初始化角色数据
void Draw(); //绘制角色
void Move(Map& m, Box& b); //移动
bool IsBox(Box& b, int x, int y); //用来判断移动路径上的两个箱子是否靠在一起
private:
int x, y; //角色的坐标
};
int main() {
int n;
a:
cout << "请输入关卡(1-6):";
cin >> n;
if (n < 1 || n>6) {
cout << "输入错误!"<> this->map[i]) {
i++;
}
ifs.close();
for (int i = 0; i < H / SIZE; i++) {
for (int j = 0; j < W / SIZE; j++) {
if (this->map[i][j] == '4') {
d.num++;
d.dest[d.num - 1].x = j * SIZE;
d.dest[d.num - 1].y = i * SIZE;
}
}
}
}
//绘制地图
void Map::Draw() {
IMAGE bk;
loadimage(&bk, "./bk.jpg", W, H);
putimage(0, 0, &bk);
for (int i = 0; i < H / SIZE; i++) {
for (int j = 0; j < W / SIZE; j++) {
int x = j * SIZE;
int y = i * SIZE;
//空地
if (this->map[i][j] == SPACE) {
setfillcolor(RGB(210, 210, 210));
fillrectangle(x, y, (x + SIZE), (y + SIZE));
}
//墙体
if (this->map[i][j] == WALL) {
IMAGE a;
loadimage(&a, "./wall.png", SIZE, SIZE);
putimage(x, y, &a);
}
//目的地
if (this->map[i][j] == DEST) {
setfillcolor(RGB(210, 210, 210));
fillrectangle(x, y, (x + SIZE), (y + SIZE));
IMAGE a;
loadimage(&a, "./dest.png", SIZE, SIZE);
drawImg(x, y, &a);//调用"tool.h"头文件当中的接口实现透明贴图
}
}
}
}
//初始化箱子数据
Box::Box() {
this->num = 0;
ifstream ifs;
char str[20];
sprintf(str, "./level/%d_box.txt", level);
ifs.open(str, ios::in);
char tmp[20];
int i = 0;
int j = 0;
while (ifs >> tmp) {
if (i == 0) {
this->box[j].x = atoi(tmp)*SIZE;
}
if (i == 1) {
this->box[j].y = atoi(tmp)*SIZE;
}
i++;
if (i == 2) {
i = 0;
j++;
this->num++;
}
}
ifs.close();
}
//绘制箱子
void Box::Draw(Dests& d) {
for (int i = 0; i < this->num; i++) {
IMAGE a;
int status = 0;
for (int m = 0; m < d.num; m++) {
if (d.dest[m].x == this->box[i].x && d.dest[m].y == this->box[i].y) {
loadimage(&a, "./box_ok.png", SIZE, SIZE);
drawImg(this->box[i].x, this->box[i].y, &a);
status = 1;
break;
}
}
if (status == 0) {
loadimage(&a, "./box.png", SIZE, SIZE);
drawImg(this->box[i].x, this->box[i].y, &a);
}
}
}
//初始化角色数据
Person::Person() {
ifstream ifs;
char str[20];
sprintf(str, "./level/%d_role.txt", level);
ifs.open(str, ios::in);
char tmp[20];
int i = 0;
while (ifs >> tmp) {
if (i == 0) {
this->x = atoi(tmp) * SIZE;
}
if (i == 1) {
this->y = atoi(tmp) * SIZE;
}
i++;
}
ifs.close();
}
//绘制角色
void Person::Draw() {
IMAGE a;
loadimage(&a, "./one.png", SIZE, SIZE);
drawImg(this->x, this->y, &a);
}
//移动
void Person::Move(Map& m,Box& b) {
//获取人物在二维数组中的坐标
int j = this->x / SIZE;
int i = this->y / SIZE;
//用来判断前面是否有箱子
int status = 0;
//按下移动键
if (GetAsyncKeyState('W')) {
for(int n = 0; n < b.num; n++) {
//如果前面有箱子
if (b.box[n].x == this->x && b.box[n].y == this->y - SIZE) {
status = 1;
//如果箱子后面不靠墙也不靠箱子
if (m.map[i-2][j] != WALL&&!this->IsBox(b,b.box[n].x,b.box[n].y-SIZE)) {
b.box[n].y -= SIZE;
this->y -= SIZE;
}
}
}
//空地移动
if ((m.map[i - 1][j] == SPACE || m.map[i - 1][j] == DEST)&&(status!=1)) {
this->y -= SIZE;
}
return;
}
if (GetAsyncKeyState('S') ) {
for (int n = 0; n < b.num; n++) {
//如果前面有箱子
if (b.box[n].x == this->x && b.box[n].y == this->y + SIZE) {
status = 1;
//如果箱子后面不靠墙也不靠箱子
if (m.map[i + 2][j] != WALL && !this->IsBox(b, b.box[n].x, b.box[n].y + SIZE)) {
b.box[n].y += SIZE;
this->y += SIZE;
}
}
}
//空地移动
if ((m.map[i + 1][j] == SPACE || m.map[i + 1][j] == DEST) && (status != 1)) {
this->y += SIZE;
}
return;
}
if (GetAsyncKeyState('A')) {
for (int n = 0; n < b.num; n++) {
//如果前面有箱子
if (b.box[n].y == this->y && b.box[n].x == this->x - SIZE) {
status = 1;
//如果箱子后面不靠墙也不靠箱子
if (m.map[i][j-2] != WALL && !this->IsBox(b, b.box[n].x-SIZE, b.box[n].y )) {
b.box[n].x -= SIZE;
this->x -= SIZE;
}
}
}
//空地移动
if ((m.map[i][j-1] == SPACE || m.map[i][j-1] == DEST) && (status != 1)) {
this->x -= SIZE;
}
return;
}
if (GetAsyncKeyState('D')) {
for (int n = 0; n < b.num; n++) {
//如果前面有箱子
if (b.box[n].y == this->y && b.box[n].x == this->x + SIZE) {
status = 1;
//如果箱子后面不靠墙也不靠箱子
if (m.map[i][j + 2] != WALL && !this->IsBox(b, b.box[n].x+SIZE, b.box[n].y )) {
b.box[n].x += SIZE;
this->x += SIZE;
}
}
}
//空地移动
if ((m.map[i][j+1] == SPACE || m.map[i][j+1] == DEST) && (status != 1)) {
this->x += SIZE;
}
return;
}
}
//用来判断移动路径上的两个箱子是否靠在一起
bool Person::IsBox(Box& b, int x, int y) {
for (int i = 0; i < b.num; i++) {
if (b.box[i].x == x && b.box[i].y == y)return true;
}
return false;
}
//用来判断是否胜利
bool Box::IsWin(Map& m) {
int count = 0;
for (int i = 0; i < d.num; i++) {
for (int j = 0; j < this->num; j++) {
if (this->box[j].x == d.dest[i].x && this->box[j].y == d.dest[i].y) {
count++;
}
}
}
if (count == d.num) {
return true;
}
return false;
}
tool.h:
#pragma once
#include
void drawImg(int x, int y, IMAGE* src) {
DWORD* pwin = GetImageBuffer();
DWORD* psrc = GetImageBuffer(src);
int win_w = getwidth();
int win_h = getheight();
int src_w = src->getwidth();
int src_h = src->getheight();
//计算 贴图的实际长宽
int real_w = (x + src_w > win_w) ? win_w - x : src_w;
int real_h = (y + src_h > win_h) ? win_h - y : src_h;
if (x < 0) { psrc += -x; real_w -= -x; x = 0; }
if (y < 0) { psrc += (src_w * -y); real_h -= -y; y = 0; }
//修正贴图起始位置
pwin += (win_w * y + x);
//实现透明贴图
for (int iy = 0; iy < real_h; iy++) {
for (int ix = 0; ix < real_w; ix++) {
byte a = (byte)(psrc[ix] >> 24);
if (a > 100) {
pwin[ix] = psrc[ix];
}
}
//换到下一行
pwin += win_w;
psrc += src_w;
}
}
由于游戏在运行的过程中需要一些贴图之类的素材,所以我们将整体的代码复制粘贴之后并不能正常运行 。我将游戏的源文件和安装包都整理出来了,具体的获取方法如下:
1. 迅雷云盘
迅雷云盘:推箱子 提取码:r6am
2. CSDN
大家如果不想通过云盘下载,也可以在我的博客上找到该资源进行下载。如果暂时没有看到该资源,那么资源可能还在审核当中哦~
文章篇幅较长,如果您能耐心地看到这里,那么给自己点一个大大的赞吧!让我们一起探索代码所带来的乐趣,共同进步吧!
如果您觉得本篇文章不错,也请点个免费的赞支持一下博主吖~~