【C++游戏开发-01】推箱子

C++游戏开发


文章目录

  • C++游戏开发
    • @[TOC](文章目录)
  • 前言
  • 一、逻辑分析
    • 1.1地图实现
    • 1.2人物的移动
      • 1.2.1小人移动
      • 1.2.2其他移动
    • 1.3墙壁的碰撞
    • 1.4箱子的推动
      • 1.4.1什么时候推箱子
      • 1.4.2什么情况可以推箱子
    • 1.5胜利的判断
    • 1.6卡关的处理
    • 1.7关卡的切换
  • 二、DEMO代码
    • 2.1游戏框架
    • 2.2各功能函数的实现
      • Init()
      • Paint()
      • Run()
      • Close()
    • 2.3额外添加的函数
      • Move(char key)
      • Check()
      • main()
  • 三、完整源代码
  • 四、总结

前言

推箱子为本系列的第一篇,我个人认为这是游戏开发中最基础最简单的部分,所以放在开篇,程序有些入门适合初学者阅读。对于程序方面如果大家有更好的方案欢迎在评论区留言。那么首先为大家介绍一下推箱子的游戏规则:
在这里插入图片描述
如上图所示,为某一关推箱子的地图画面;
其中黄色的圆点为目标点,带有叉的方块是箱子,玩家通过方向键控制小人移动,在移动过程中如果遇到箱子可以动箱子,但不能拉。如下情况则不可以推动箱子:
1.箱子遇到墙壁;
2.两个箱子重叠(推动两个箱子);
如果将所有的箱子都放置在了目标点处,则游戏获得胜利(过关)。


一、逻辑分析

所有的游戏开发其逻辑都基于游戏的规则,因此完整的游戏规则是必不可少的。其中最重要的规则无外乎:获胜条件、失败条件、积分规则等。
对于推箱子而言,获胜规则显而易见是将所有的箱子都放置在目标点处。而本游戏并没有失败条件,但是考虑到箱子可能会被推到墙壁处而不能通关出现卡关情况,因此对于卡关也要做出处理。
本游戏中没有积分规则,但是会有不同的关卡设计,但其底层玩法,包括人物的移动,箱子的推动,胜利的判断是没有区别的,因此我们可以在不同的关卡中只更换游戏地图来实现关卡切换。
综上,要实现的部分有:

  1. 地图的实现
  2. 人物的移动
  3. 墙壁的碰撞
  4. 箱子的推动
  5. 胜利的判断
  6. 卡关的处理
  7. 关卡的切换

1.1地图实现

对于推箱子地图的实现,我们可以用字符简单的绘制一下,如下:

      ###    
      # #     
      # #     
   #### ######
   #         #
   ##### #####
       # #    
       # #    
       ###    		

在上图中使用了字符#来模拟地图的墙壁,当然只有地图是远远不够的,还要有小人,箱子,目标点,那么使用字符H来模拟小人,使用字符O来模拟箱子,字符*来模拟目标点得到如下的图:

      ###    
      #*#     
      #O#     
   #### ######
   #*O H O  *#
   #####O#####
       # #    
       #*#    
       ###    		

有了字符组成的地图,那么显然我们可以使用C++的数据结构二维数组来存储上述字符,从而将其输出到命令行显示:

//使用二维数组定义地图
char p_map[16][16] = {"               ", 
                      "               ",
                      "      ###      ",
					  "      #*#      ",
					  "      #O#      ",
					  "   #### ###### ",
					  "   #*O H O  *# ",
					  "   #####O##### ",
				      "       # #     ",
					  "       #*#     ",
					  "       ###     ",
                      "               ",
                      "               ",
                      "               ",
                      "               ",
                      "               "
                      };

void showMap(){//显示地图
	for(int i = 0; i < 16; i ++){
		puts(p_map[i]);
	}
}

在这里插入图片描述
由此我们得到了简易的地图显示。

1.2人物的移动

在讲到人物移动的逻辑之前,我们先来聊一下视频动画是如何形成的。
如下图是一个摇头的向日葵
在这里插入图片描述
我们之所以能看到这个向日葵在摇摆,是由下面一系列图片刷新显示,当图片连续的刷新显示使人眼产生一种向日葵动起来的错觉,这也是视频播放的原理。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
每显示一张图片我们称作一帧,而每秒钟刷新的次数(显示的帧数)称为帧率,帧率越高,动态效果就越真实。
而游戏肯定离不开动态,因此几乎所有的游戏都要刷新的去显示,形成所谓的动态效果。我们可以通过一个程序来实现一个简单的动画效果。

1.2.1小人移动

我们在命令行上输出一个小人:

printf("O\nI\nH\n");

如下:

   O
   I
   H

若想让小人跑起来,即向右移动 ,我们可以在字符OIH前分别加入一个空格使其右移一格

    O
    I
    H

若想连续的跑动则将程序放入循环中,依次在每个字符左边多加入一个空格即可,但是这样会出现以下的问题:

for(int i = 0; i < 20 ; ++i){
        for(int j = 0; j < i; j ++){//加入i个空格
            printf(" ");
        }
        printf("   O\n");
        for(int j = 0; j < i; j ++){
            printf(" ");
        }
        printf("   I\n");
        for(int j = 0; j < i; j ++){
            printf(" ");
        }
        printf("   H\n");
    }

在这里插入图片描述
小人没有像我们预期的一样跑动,而是成了一条斜线。
这是因为我们只顾着去显示忘记了刷新屏幕上已显示过的小人,因此我们需要每次将小人显示前清除上一次的显示结果,从而实现刷新显示。
使用如下程序:

system("cls");

是C++清空命令框的指令,使用时需加上stdlib.h的头文件,但此时运行会发行小人跑的太快了,这也不符合我们的预期,因此可以改变小人的刷新率(帧率)
使用如下程序:

Sleep(n);

其作用为让进程休眠n毫秒,我们这里n取1000,即帧率为1(虽然很低但是为了看清小人移动的过程),使用时需加上windows.h的头文件

for(int i = 0; i < 20 ; ++i){
        system("cls");
        for(int j = 0; j < i; j ++){//加入i个空格
            printf(" ");
        }
        printf("   O\n");
        for(int j = 0; j < i; j ++){
            printf(" ");
        }
        printf("   I\n");
        for(int j = 0; j < i; j ++){
            printf(" ");
        }
        printf("   H\n");
        Sleep(1000);
    }

这时就可以看到小人一步一步的移动啦

1.2.2其他移动

综上,要显示移动效果,需要如下步骤
1.画面的清除
2.新画面的显示
3.时间的控制(用于控制刷新率、移动的快慢等)
因此我们可以将以上封装为画面显示的函数paint(),在每次画面更新的逻辑处理完后调用即可。

在推箱子中小人的移动就很简单了,我们基于其在二维数组中的坐标x,y,每次使用键盘输入后更改x,y的值,并在地图中更新,然后调用paint()函数即可。

char p_map[16][16] = {"               ", 
                      "               ",
                      "               ",
					  "               ",
					  "               ",
					  "      H        ",
					  "               ",
					  "               ",
				      "               ",
					  "               ",
					  "               ",
                      "               ",
                      "               ",
                      "               ",
                      "               ",
                      "               "
                      };

在上图中H的坐标为(6,5),我们将地图的打印、刷新、刷新率封装为一个函数:

void paint(){
    system("cls");//清空上一次图片
    for(int i = 0; i < 16; ++i){//更新地图打印
        puts(p_map[i]);
    }
    Sleep(10);//10毫秒刷新
}

使用getch()函数来获取键盘的输入,该函数需要conio.h头文件
在循环中判断输入的键,对于不同方向做相应的处理更新小人坐标和地图:

#include<iostream>
#include<stdio.h>
#include<stdlib.h>
#include<windows.h>
#include<conio.h>

void paint(){
    system("cls");//清空上一次图片
    for(int i = 0; i < 16; ++i){//更新地图打印
        puts(p_map[i]);
    }
    Sleep(10);//10毫秒刷新
}

int main(){
    //记录坐标
    int x = 6;
    int y = 5;
    while(1){
        paint();
        char z = getch();
        if(z == 'w'){//上
            p_map[y][x] = ' ';//之前的位置更新为空格
            p_map[y-1][x] = 'H';//移动后的位置变为小人,后面同理
            y--;
		}else  if(z == 'a'){//左
            p_map[y][x] = ' ';//之前的位置更新为空格
            p_map[y][x-1] = 'H';
            x--;
        }else  if(z == 's'){//下
            p_map[y][x] = ' ';//之前的位置更新为空格
            p_map[y+1][x] = 'H';
            y++;
        }else  if(z == 'd'){//右
            p_map[y][x] = ' ';//之前的位置更新为空格
            p_map[y][x+1] = 'H';
            x++;
        }

    }
    
    system("pause");
    return  0;
}

由此即实现了小人的移动,但是由于没有边界限制条件,因此当小人走出了数组的界限会因数组越界报错,所以还需做更详细的处理。

1.3墙壁的碰撞

墙壁碰撞的逻辑就很简单了,只要我们判断移动后的位置不是表示墙壁的字符即可,如:

if(z == 'w' && p_map[y-1][x]!='#'){//不为墙壁即可向上移动
            p_map[y][x] = ' ';//移动前的位置更新
            y--;//坐标更新
            p_map[y][x] = 'H';//地图更新
}

1.4箱子的推动

箱子的推动相比于小人移动的逻辑要复杂一点,我们要明确两点:
1.什么时候推箱子
2.什么情况可以推箱子

1.4.1什么时候推箱子

当小人移动后的位置如果为箱子的话,此刻判断为推箱子,如下四种情况:

HO      OH      O     H
                H     O

1.4.2什么情况可以推箱子

当箱子移动后的位置如果是空位,则可以推箱子(不能撞墙,不能重叠推两个及以上箱子)
因此我们得到如下代码:

if(z == 'w' && p_map[y-1][x]=='O' && p_map[y-2][x]==' '){
  //移动的下一位为箱子,箱子移动的下一位为空位
            p_map[y][x] = ' ';//移动前的位置更新
            y--;//坐标更新
            p_map[y][x] = 'H';//地图更新
            p_map[y-1][x] = 'O';//更新箱子
}

1.5胜利的判断

当所有箱子都处于目标点时,即获得游戏胜利,因此我们需要遍历所有的目标点,这时要考虑,对于不同的关卡,目标点的个数也是不同的,因此我们考虑两种解决办法:
1.定义数组p_win[DEF_LENGTH],DEF_LENGTH要尽量大一些,保证所有关卡的目标点数不超过它。
2.使用STL中的vector
上述两种方法皆可,但如果使用方法1,在传参时除了要传入数组指针还要传入目标点个数,为了减少传参,我们直接使用vector作为容器。

1.6卡关的处理

当箱子处于下面的情况时,箱子将无法被推动到任何其他位置,也没有处于目标点处,这时既不会宣布游戏胜利,也不会宣布游戏失败,即出现了卡关。为了解决这一问题,我们可以加入重置关卡的功能:比如当我们按下R键,所有箱子和小人的位置就会复原到初始位置,这便解决了卡关的问题。

#######
#O H *#
#######

1.7关卡的切换

为了方便关卡的切换,我们将游戏的运行封装在一个函数中,而每一关我们只需传入关卡地图、判断目标点的容器、小人起始坐标三个变量即可。这样每当通关后,就自动切换到下一关。

以上就是推箱子游戏的基本逻辑,下面我们来做出推箱子的简单DEMO:

二、DEMO代码

2.1游戏框架

一般的游戏运行过程大致分为一下几步:
1.游戏初始化
2.游戏运行
3.游戏画面显示
4.游戏结束
那么我们先简单定义一个游戏类的接口,以便规范今后我们再去开发其他游戏。
定义抽象类GameFrame,并给出四个纯虚函数:

class GameFrame{
public:
    GameFrame(){}
    ~GameFrame(){}
    virtual void Init() = 0;//游戏初始化
    virtual void Close() = 0;//游戏结束
    virtual void Paint() = 0;//游戏画面绘制
    virtual void Run() = 0;//游戏运行
};

接下来定义坐标结构体,方便后续使用

struct Point{
        int x;
        int y;
        void set(int px,int py){//给出设置坐标的set函数
            x = px;
            y = py;
        }
};//玩家坐标

定义推箱子类并继承GameFrame

class PushBox : public GameFrame{
public:
	PushBox(){}
	~PushBox (){}
	void Init();
    void Close();
    void Paint();
    void Run();
};

加入类成员变量:

#include<vector>

using namespace std;

class PushBox : public GameFrame{
public:
	PushBox(){}
	~PushBox (){}
	void Init();
    void Close();
    void Paint();
    void Run();
private:
	int p_id;
    Point p_point;
    vector<Point> p_check;
    char p_map[16][16];
};

其中p_id是用于分辨当前的关卡,以及后续关卡切换的操作;p_point是小人当前坐标;p_check用于存储所有的目标点坐标,以便遍历目标点判断是否获胜;p_map是当前关卡地图,在切换关卡和重置关卡时都要对其进行改变。

2.2各功能函数的实现

Init()

初始化函数的功能是设置小人初始坐标,将目标点容器清空并填充并设置地图。为了后续的关卡切换功能,我们在该函数中传入参数p_id,通过判断其值来初始化不同的关卡:

void Init(int id){
        switch (id)
        {
        case 1:
        {
            p_check.clear();//容器清空
            char temp[16][16] = {"               ", 
                                "               ",
                                "      ###      ",
	 	 			            "      #*#      ",
	 				            "      # #      ",
	 				            "   ####O###### ",
	   				            "   #*O H O  *# ",
					            "   #####O##### ",
				                "       # #     ",
					            "       #*#     ",
					            "       ###     ",
                                "               ",
                                "               ",
                                "               ",
                                "               ",
                                "               "};

            for(int  i = 0; i < 16;i ++){//地图初始化
                for(int j = 0; j < 16; j ++){
                    p_map[i][j] = temp[i][j];
                }
            }
            //填充四个目标点
            p_point.set(7,6);
            Point t_point;
            t_point.set(4,6);
            p_check.push_back(t_point);
            t_point.set(12,6);
            p_check.push_back(t_point);
            t_point.set(8,9);
            p_check.push_back(t_point);
            t_point.set(7,3);
            p_check.push_back(t_point);
            break;
        }
        case 2:
        {
        //TODO:对第二关进行初始化设置
		}
	        break;
        default:
            break;
       }
}//游戏初始化

Paint()

该函数在1.2.2小结已经讲过,主要作用就是刷新显示画面

void Paint() {//游戏画面绘制
        system("cls");//清空上一次图片
        for(int i = 0; i < 16; ++i){//更新地图打印
           puts(p_map[i]);
        }
        Sleep(10);//10毫秒刷新
}

Run()

该函数是推箱子的主体运行函数,为了使程序在通关前始终运行,我们需要将进程卡在循环中,而死循环可以使用while(true)的逻辑实现,具体如下:

void Run() {
        while(1){
            Paint();//刷新显示画面
            if(//判断是否通过){
                break;
            }
            char z = getch();//读取当前输入的按键
            //小人的移动处理
        }
}//游戏运行

Close()

close()函数的作用就是资源的回收处理和游戏结束处理工作,由于本DEMO并没有使用到堆区的空间,所以不需要在这部分进行资源回收,只需要显示游戏结束即可。

void Close(){
        system("cls");
        cout<<"you win!"<<endl;
}//游戏结束

2.3额外添加的函数

接下来是我们游戏框架中没有体现的部分,属于推箱子游戏特有的功能,需要我们单独定义。

Move(char key)

该函数是负责判断键盘输入的按键来进行不同的处理,包括上下左右控制小人移动,按R建重置关卡,此外也可以考虑按Q键退出(该功能暂不实现,可以自行考虑)等等。
如下:

void Move(char key){
        if(key == 'w'){
            if(p_map[p_point.y-1][p_point.x] == ' '||p_map[p_point.y-1][p_point.x] == '*'){//小人移动的判断
                p_map[p_point.y][p_point.x] = ' ';//移动前的位置更新
                p_point.y--;//坐标更新
                p_map[p_point.y][p_point.x] = 'H';//地图更新
            }
            else if(p_map[p_point.y-1][p_point.x]=='O' && (p_map[p_point.y-2][p_point.x]==' '||p_map[p_point.y-2][p_point.x]=='*')){//推箱子的判断
                p_map[p_point.y][p_point.x] = ' ';//移动前的位置更新
                p_point.y--;//坐标更新
                p_map[p_point.y][p_point.x] = 'H';//地图更新
                p_map[p_point.y-1][p_point.x] = 'O';//更新箱子
            }
        }else  if(key == 'a'){//左
            if(p_map[p_point.y][p_point.x-1] == ' '||p_map[p_point.y][p_point.x-1] == '*'){
                p_map[p_point.y][p_point.x] = ' ';//之前的位置更新为空格
                p_point.x--;
                p_map[p_point.y][p_point.x] = 'H';
            }
            else if(p_map[p_point.y][p_point.x-1]=='O' && (p_map[p_point.y][p_point.x-2]==' '||p_map[p_point.y][p_point.x-2]=='*')){
                p_map[p_point.y][p_point.x] = ' ';//之前的位置更新为空格
                p_point.x--;
                p_map[p_point.y][p_point.x] = 'H';
                p_map[p_point.y][p_point.x-1] = 'O';//更新箱子
            }
        }else  if(key == 's'){//下
            if(p_map[p_point.y+1][p_point.x] == ' '||p_map[p_point.y+1][p_point.x] == '*'){
                p_map[p_point.y][p_point.x] = ' ';//之前的位置更新为空格
                p_point.y++;
                p_map[p_point.y][p_point.x] = 'H';
            }
            else if(p_map[p_point.y+1][p_point.x]=='O' && (p_map[p_point.y+2][p_point.x]==' '||p_map[p_point.y+2][p_point.x]=='*')){
                p_map[p_point.y][p_point.x] = ' ';//移动前的位置更新
                p_point.y++;//坐标更新
                p_map[p_point.y][p_point.x] = 'H';//地图更新
                p_map[p_point.y+1][p_point.x] = 'O';//更新箱子
            }
        }else  if(key == 'd'){//右
            if(p_map[p_point.y][p_point.x+1] == ' '||p_map[p_point.y][p_point.x+1] == '*'){
                p_map[p_point.y][p_point.x] = ' ';//之前的位置更新为空格
                p_point.x++;
                p_map[p_point.y][p_point.x] = 'H';
            }
            else if(p_map[p_point.y][p_point.x+1]=='O' && (p_map[p_point.y][p_point.x+2]==' '||p_map[p_point.y][p_point.x+2]=='*')){
                p_map[p_point.y][p_point.x] = ' ';//之前的位置更新为空格
                p_point.x++;
                p_map[p_point.y][p_point.x] = 'H';
                p_map[p_point.y][p_point.x+1] = 'O';//更新箱子
            }
        }else  if(key == 'r'){//重置
            Init(p_id);
        }
    }

Check()

该函数是判断胜利的条件的关键,我们在这一部分遍历所有的目标点位置,如果所有的目标点位置都变成了箱子的字符,则返回true,否则返回false。另外我们还要处理一种情况如下:

###########       ###########
#H   *  O*#   ->  #    *  HO#
###########       ###########

如上所示,我们控制小人经过一个目标点后将箱子推至最右侧目标点,那么按照我们上述对小人移动的逻辑实现,小人经过目标点后会将其“扫空”,即当箱子和小人离开了目标点,地图上将不会再显示,因为已经将地图中目标点位置的字符换为了空格。为解决这一bug也要在本函数中作处理。
如下:

bool Check(){//用于检测游戏胜利
        int len = p_check.size();
        int flag = 1;
        for(int i = 0; i < len; i ++){
            if(p_map[p_check[i].y][p_check[i].x] == ' '){//还原目标点
                p_map[p_check[i].y][p_check[i].x] == '*';
            }
            if(p_map[p_check[i].y][p_check[i].x] != 'O'){//判断是否为箱子
                flag = 0;
            }
        }
        if(flag){
            return true;
        }else{
            return false;
        }
}

main()

最后我们在主函数中直接定义推箱子对象并调用执行:

int main(){
    PushBox* p_game = new PushBox;
    p_game->Run();//游戏进行,进程将在死循环中
    p_game->Close();//游戏结束
    delete p_game;//释放指针空间
    p_game = nullptr;//指针置空,防止野指针
    system("pause");
    return  0;
}

三、完整源代码

#include<iostream>
#include<stdio.h>
#include<stdlib.h>
#include<windows.h>
#include<vector>
#include<conio.h>

using namespace std;

class GameFrame{
public:
    GameFrame(){}
    ~GameFrame(){}
    virtual void Init() = 0;//游戏初始化
    virtual void Close() = 0;//游戏结束
    virtual void Paint() = 0;//游戏画面绘制
    virtual void Run() = 0;//游戏运行
};

struct Point{
        int x;
        int y;
        void set(int px,int py){
            x = px;
            y = py;
        }
};//玩家坐标

class PushBox : public GameFrame{
public:
    PushBox(){
        p_id = 1;
        Init(p_id);
    }
    ~PushBox(){}
    void Init(){}
    void Init(int id){
        switch (id)
        {
        case 1:
        {
            p_check.clear();
            char temp[16][16] = {"               ", 
                                "               ",
                                "      ###      ",
	 	 			            "      #*#      ",
	 				            "      # #      ",
	 				            "   ####O###### ",
	   				            "   #*O H O  *# ",
					            "   #####O##### ",
				                "       # #     ",
					            "       #*#     ",
					            "       ###     ",
                                "               ",
                                "               ",
                                "               ",
                                "               ",
                                "               "};

            for(int  i = 0; i < 16;i ++){//地图初始化
                for(int j = 0; j < 16; j ++){
                    p_map[i][j] = temp[i][j];
                }
            }
            p_point.set(7,6);
            Point t_point;
            t_point.set(4,6);
            p_check.push_back(t_point);
            t_point.set(12,6);
            p_check.push_back(t_point);
            t_point.set(8,9);
            p_check.push_back(t_point);
            t_point.set(7,3);
            p_check.push_back(t_point);
            break;
        }
        default:
            break;
        }
    }//游戏初始化
    void Close(){
        system("cls");
        cout<<"you win!"<<endl;
    }//游戏结束
    void Paint() {//游戏画面绘制
        system("cls");//清空上一次图片
        for(int i = 0; i < 16; ++i){//更新地图打印
           puts(p_map[i]);
        }
        Sleep(10);//10毫秒刷新
    }
    void Run() {
        while(1){
            Paint();
            if(Check()){
                break;
            }
            char z = getch();
            Move(z);
        }
    }//游戏运行
    void Move(char key){
        if(key == 'w'){
            if(p_map[p_point.y-1][p_point.x] == ' '||p_map[p_point.y-1][p_point.x] == '*'){
                p_map[p_point.y][p_point.x] = ' ';//移动前的位置更新
                p_point.y--;//坐标更新
                p_map[p_point.y][p_point.x] = 'H';//地图更新
            }
            else if(p_map[p_point.y-1][p_point.x]=='O' && (p_map[p_point.y-2][p_point.x]==' '||p_map[p_point.y-2][p_point.x]=='*')){
                p_map[p_point.y][p_point.x] = ' ';//移动前的位置更新
                p_point.y--;//坐标更新
                p_map[p_point.y][p_point.x] = 'H';//地图更新
                p_map[p_point.y-1][p_point.x] = 'O';//更新箱子
            }
        }else  if(key == 'a'){//左
            if(p_map[p_point.y][p_point.x-1] == ' '||p_map[p_point.y][p_point.x-1] == '*'){
                p_map[p_point.y][p_point.x] = ' ';//之前的位置更新为空格
                p_point.x--;
                p_map[p_point.y][p_point.x] = 'H';
            }
            else if(p_map[p_point.y][p_point.x-1]=='O' && (p_map[p_point.y][p_point.x-2]==' '||p_map[p_point.y][p_point.x-2]=='*')){
                p_map[p_point.y][p_point.x] = ' ';//之前的位置更新为空格
                p_point.x--;
                p_map[p_point.y][p_point.x] = 'H';
                p_map[p_point.y][p_point.x-1] = 'O';//更新箱子
            }
        }else  if(key == 's'){//下
            if(p_map[p_point.y+1][p_point.x] == ' '||p_map[p_point.y+1][p_point.x] == '*'){
                p_map[p_point.y][p_point.x] = ' ';//之前的位置更新为空格
                p_point.y++;
                p_map[p_point.y][p_point.x] = 'H';
            }
            else if(p_map[p_point.y+1][p_point.x]=='O' && (p_map[p_point.y+2][p_point.x]==' '||p_map[p_point.y+2][p_point.x]=='*')){
                p_map[p_point.y][p_point.x] = ' ';//移动前的位置更新
                p_point.y++;//坐标更新
                p_map[p_point.y][p_point.x] = 'H';//地图更新
                p_map[p_point.y+1][p_point.x] = 'O';//更新箱子
            }
        }else  if(key == 'd'){//右
            if(p_map[p_point.y][p_point.x+1] == ' '||p_map[p_point.y][p_point.x+1] == '*'){
                p_map[p_point.y][p_point.x] = ' ';//之前的位置更新为空格
                p_point.x++;
                p_map[p_point.y][p_point.x] = 'H';
            }
            else if(p_map[p_point.y][p_point.x+1]=='O' && (p_map[p_point.y][p_point.x+2]==' '||p_map[p_point.y][p_point.x+2]=='*')){
                p_map[p_point.y][p_point.x] = ' ';//之前的位置更新为空格
                p_point.x++;
                p_map[p_point.y][p_point.x] = 'H';
                p_map[p_point.y][p_point.x+1] = 'O';//更新箱子
            }
        }else  if(key == 'r'){//重置
            Init(p_id);
        }
    }
    bool Check(){//用于检测游戏胜利
        int len = p_check.size();
        int flag = 1;
        for(int i = 0; i < len; i ++){
            if(p_map[p_check[i].y][p_check[i].x] == ' '){
                p_map[p_check[i].y][p_check[i].x] == '*';
            }
            if(p_map[p_check[i].y][p_check[i].x] != 'O'){
                flag = 0;
            }
        }
        if(flag){
            return true;
        }else{
            return false;
        }
    }
private:
    int p_id;
    Point p_point;
    vector<Point> p_check;
    char p_map[16][16];//地图
};



int main(){
    PushBox* p_game = new PushBox;
    p_game->Run();
    p_game->Close();
    delete p_game;
    p_game = nullptr;
    system("pause");
    return  0;
}

四、总结

本程序只是实现推箱子逻辑的DEMO,感兴趣的同学可以自己查找推箱子的其他关卡实现并完成关卡切换的部分。代码需改进的地方还有很多,如:
1.为了方便将源码一次性列出,我没有定义头文件编写,最好是将类以及全局变量的定义放在头文件中,类成员函数在源文件中实现,由main源文件调用头文件接口运行,如下:
pushbox.h ->定义类、全局变量
pushbox.cpp ->实现类的成员函数
main.cpp ->引用头文件<pushbox.h>后只写main函数
2.if-else的过多使用,这会使代码看起来很冗长,对于这部分还有可优化的地方,后续我会配合宏定义将这部分补充。
如果有其他优化的建议欢迎大家在我的评论区留言~


下期我会将游戏框架的代码完整的进行封装,并使用QT实现推箱子的图画版,类似下面的样子也将可以实现,敬请期待。在这里插入图片描述
Codemon2024.02.02

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/366450.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【亲测有效】无法获得下列许可 SOLIDWORKS Standard 无效的(不一致的) 使用许可号码 (-8,544,0)

在观看本文章前&#xff0c;请注意看你的报错代码是否和我的一致&#xff0c;如果不是&#xff0c;直接跳过本文章。 前言&#xff1a;我安装的是SOLIDWORKS2022版&#xff0c;软件已经安装完毕&#xff0c;SolidWorks_Flexnet_Server文件夹里面的两个注册表已经安装完毕&#…

Python tkinter (7) ——Scale控件

Python的标准Tk GUI工具包的接口 tkinter系列文章 python tkinter窗口简单实现 Python tkinter (1) —— Label标签 Python tkinter (2) —— Button标签 Python tkinter (3) —— Entry标签 Python tkinter (4) —— Text控件 Python tkinter (5) 选项按钮与复选框 Pyt…

Stata收敛性分析(含详细代码说明和样例数据)

Stata收敛性分析&#xff08;含详细代码说明和样例数据&#xff09; 收敛性分析是管理科学和运筹学中重要的概念&#xff0c;是一种解决决策者对他们的管理策略的反馈的方式和手段。它的最终目的是帮助管理者从复杂的环境中筛选最优的解决方案。收敛性分析一般情况下会结合一些…

为客户解决痛点,电子纸增加制表功能

为客户解决痛点&#xff0c;电子纸增加制表功能 部分客户购买我们的电子纸后反馈效果很好&#xff0c;但是在配套组态软件制作电子纸模板时&#xff0c;遇到需要制作表格的时候比较麻烦。像是在画板作画一样&#xff0c;比较费时&#xff0c;而且效果不是很好&#xff0c;没办…

用VsCode写python

1.创建一个文件夹 2.创建.py文件 print("Hello World") print("*"*10) 4.运行 在终端版本fileName python3 app.py

MATLAB矩阵的操作(第二部分)

师从清风 矩阵的创建方法 在MATLAB中&#xff0c;矩阵的创建方法主要有三种&#xff0c;分别是&#xff1a;直接输入法、函数创建法和导入本地文件中的数据。 直接输入法 输入矩阵时要以中括号“[ ]”作为标识符号&#xff0c;矩阵的所有元素必须都在中括号内。 矩阵的同行元…

零基础学Python之核心基础知识

1.Python入门简介 &#xff08;1&#xff09;什么是Python Life is short, you need Python&#xff01;人生苦短&#xff0c;我用Python Python 是一个高层次的结合了解释性、编译性、互动性和面向对象的脚本语言。 Python 的设计具有很强的可读性&#xff0c;相比其他语言…

笔记---中国剩余定理

全程学自y总 AcWing.204.表达整数的奇怪方式 给定 2 n 2n 2n 个整数 a a a1, a a a2,…, a a an 和 m m m1, m m m2,…, m m mn&#xff0c;求一个最小的非负整数 x x x&#xff0c;满足 ∀ i ∈ [ 1 , n ] , x ≡ m ∀i∈[1,n],x≡m ∀i∈[1,n],x≡mi ( m o d a (mod a (…

SpringMVC中的文件上传与下载功能,以及虚拟目录的配置

目录 文件下载 文件上传 第一步&#xff1a;添加依赖&#xff1a; 第二步&#xff1a;在SpringMVC的配置文件中添加配置&#xff1a; 三、控制器方法&#xff1a; 虚拟目录配置方式&#xff1a; 前端代码 SpringMVC中的文件上传与下载功能是通过MultipartResolver来实现…

华为鸿蒙DevEco Studio编辑器初体验

目录 前言DevEco Studio编辑器使用准备工作应用/服务运行可视化调试DevEco Studio配置参数列表番外篇&#xff1a;参加鸿蒙生态学堂创新实训营北京站的培训结束语 前言 众所周知华为鸿蒙作为移动应用开发的第三个热门领域&#xff08;前两个热门领域iOS原生、Android原生都已…

半桥式三相无刷直流电动机不同导通角的性能的变化

半桥式三相无刷直流电动机不同导通角的性能的变化 syms Omega clear clcOmega0pi/180*120 for Omega_x[pi/180*120,pi/180*130,pi/180*140,pi/180*150,pi/180*160,pi/180*170,pi/180*180]Omega_x*180/piOmega_x_0 (4*sin(Omega_x/2)/(Omega_xsin(Omega_x)))/(4*sin(Omega0/2)/…

数据结构—基础知识:哈夫曼编码

数据结构—基础知识&#xff1a;哈夫曼编码 哈夫曼编码的主要思想 在进行数据压缩时&#xff0c;为了使压缩后的数据文件尽可能短&#xff0c;可采用不定长编码。其基本思想是&#xff1a;为出现次数较多的字符编以较短的编码。为确保对数据文件进行有效的压缩文件和对压缩文…

基于数据挖掘的微博事件分析与可视化大屏分析系统

设计原理&#xff0c;是指一个系统的设计由来&#xff0c;其将需求合理拆解成功能&#xff0c;抽象的描述系统的模块&#xff0c;以模块下的功能。功能模块化后&#xff0c;变成可组合、可拆解的单元&#xff0c;在设计时&#xff0c;会将所有信息分解存储在各个表中&#xff0…

元素的显示与隐藏,精灵图,字体图标,CSSC三角

元素的显示与隐藏 类似网站广告&#xff0c;当我们点击关闭就不见了&#xff0c;但是我们重新刷新页面&#xff0c;会重新出现 本质&#xff1a;让元素在页面中隐藏或者显示出来。 1.display显示隐藏 2.visibility显示隐藏 3.overflow溢出显示隐藏 1.display属性&#xff08;…

麒麟系统—— openKylin 安装 Maven

麒麟系统—— openKylin 安装 Maven 一、准备工作1. 确保麒麟系统 openKylin 已经安装完毕。2. 确保 java 已经安装完毕 二、下载Maven三、解压 Maven 与环境配置解压配置环境变量验证 最终&#xff1a;介绍配置的其他参数使用 本文将分享如何在麒麟操作系统 openKylin 上安装…

互补滤波算法介绍+SCL源代码(收放卷线速度处理)

工程上对测量信号进行处理,我们可以利用低通滤波器,还可以利用滑动平均值滤波等,关于低通滤波器和滑动平均值滤波器,可以参考专栏相关文章,常用链接如下: 博途PLC一阶滞后低通滤波器(支持采样频率设置) https://rxxw-control.blog.csdn.net/article/details/132972093h…

cesium-加载地形图

废话不多说 直接代码 <template><div id"cesiumContainer" style"height: 100vh;"></div><div id"toolbar" style"position: fixed;top:20px;left:220px;"><el-breadcrumb><el-breadcrumb-item>…

如何解决jenkins插件下载失败问题

&#x1f345; 视频学习&#xff1a;文末有免费的配套视频可观看 &#x1f345; 关注公众号【互联网杂货铺】&#xff0c;回复 1 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 从 jenkins 官网上下载的 jenkins&#xff0c;在安装的过程中&a…

MacBook安装虚拟机Parallels Desktop

MacBook安装虚拟机Parallels Desktop 官方下载地址: https://www.parallels.cn/pd/general/ 介绍 Parallels Desktop 被称为 macOS 上最强大的虚拟机软件。可以在 Mac 下同时模拟运行 Win、Linux、Android 等多种操作系统及软件而不必重启电脑&#xff0c;并能在不同系统间随…

Unity Shader 滚动进度条效果

Unity Shader 滚动进度条效果 前言项目场景布置导入图片修改场景设置修改图片尺寸即可调整进度 ASE连线 前言 UI要实现一个滚动进度&#xff0c;于是使用Shader制作一个。 项目 场景布置 导入图片 修改一下导入图片的格式&#xff0c;这样才能循环起来 WrapMode改为Repea…