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

news2025/3/10 11:10:30

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++学习Day01之using声明以及using编译指令

目录 一、程序1.1 using声明1.2 using声明与就近原则1.3 using编译指令与就近原则1.4 多个using编译指令 二、分析与总结 一、程序 1.1 using声明 #include<iostream> using namespace std;namespace KingGlory {int sunwukongId 1; } void test01() {//1、using声明u…

立体车库行业分析:未来3-5年将保持每年25%-40%左右的增速

机械式的停车库的采用从节省土地资源、有效利用空间为目的的单一需求&#xff0c;向节能、环保、美化环境、节省投资等很多的有用方式来进行用途性转变&#xff0c;从被动的形式到主动的来进行一定的变化&#xff0c;也因此提高了这种停车形式的使用性价值点。 中国机械停车设备…

vit细粒度图像分类(六)FBSD学习笔记

1.摘要 从判别局部区域学习特征表示在细粒度视觉分类中起着关键作用。利用注意机制提取零件特征已成为一种趋势。然而&#xff0c;这些方法有两个主要的局限性:第一&#xff0c;它们往往只关注最突出的部分&#xff0c;而忽略了其他不明显但可区分的部分。其次&#xff0c;他们…

MySQL亿级数据的查询优化-历史表该如何建

前端时间在知乎上看到一个问题&#xff0c;今天有空整理并测试了一下&#xff1a; 这个问题很具体&#xff0c;所以还是可以去尝试优化一下&#xff0c;我们基于InnoDB并使用自增主键来讲。 比较简单的做法是将历史数据存放到另一个表中&#xff0c;与最近的数据分开。那是不是…

解决nginx: [error] open() "/usr/local/nginx/logs/nginx.pid" failed错误

在往nginx.conf文件中添加tcp负载均衡的配置之后&#xff0c;使用./nginx -s reload启动&#xff0c;发现报错。 遂搜寻解决方法&#xff0c;最后通过nginx -c指定nginx.conf文件的路径&#xff0c;解决了问题。 /usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/nginx.…

2024 高级前端面试题之 HTTP模块 「精选篇」

该内容主要整理关于 HTTP模块 的相关面试题&#xff0c;其他内容面试题请移步至 「最新最全的前端面试题集锦」 查看。 HTTP模块精选篇 1. HTTP 报文的组成部分2. 常见状态码3. 从输入URL到呈现页面过程3.1 简洁3.2 详细 4. TCP、UDP相关5. HTTP2相关6. https相关7. WebSocket的…

数据库建模之PowerDesigner创建概念模型

数据模型&#xff08;Data Model&#xff09;是数据特征的抽象&#xff0c;它从抽象层次上描述了系统的静态特征、动态行为和约束条件&#xff0c;为数据库系统的信息表示与操作提供一个抽象的框架。数据模型所描述的内容有三部分&#xff0c;分别是数据结构、数据操作和数据约…

Qwen-VL 技术报告总结

感谢如此优秀的开源工作,仓库链接 Qwen-VL 权重分为 Qwen-VL && Qwen-VL-Chat,区别文档稍后介绍 训练过程 在第一阶段中主要使用224X224分辨率训练,训练数据主要来源是公开数据集,经过清洗,数据总量大约是1.4B,中文数据和英文j训练目标是视觉语言和文本语言对齐。…

【MySQL】——用SQL语句实现数据库和基本表的创建

&#x1f383;个人专栏&#xff1a; &#x1f42c; 算法设计与分析&#xff1a;算法设计与分析_IT闫的博客-CSDN博客 &#x1f433;Java基础&#xff1a;Java基础_IT闫的博客-CSDN博客 &#x1f40b;c语言&#xff1a;c语言_IT闫的博客-CSDN博客 &#x1f41f;MySQL&#xff1a…

Windows Server 2019 Web服务器搭建

系列文章目录 提示&#xff1a;这里可以添加系列文章的所有文章的目录&#xff0c;目录需要自己手动添加 例如&#xff1a;第一章 Python 机器学习入门之pandas的使用 提示&#xff1a;写完文章后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目…

【Java】实现图书管理系统

文章目录 1. 设计背景2. 需求分析3. 设计思路4. 实现4.1 book包4.1.1 Book类4.1.2 BookList类(书架) 4.2 user包4.2.1 User 类4.2.2 AdminUser类&#xff08;管理员用户&#xff09;4.2.3 NormalUser类&#xff08;普通用户&#xff09; 4.3 operation包4.3.1 IOPeration接口4.…

产学研交流 | 广东轻工职业技术学院莅临调研

产学研融合是当前教育发展的重要趋势&#xff0c;通过学校与企业之间的深度合作&#xff0c;提高学生的实践能力和创新精神&#xff0c;同时促进企业的技术进步和产业升级。 近日&#xff0c;广东轻工职业技术学院杨军主任、骨干教师丁向荣莅临科东软件&#xff0c;双方就“产学…

spring-security 默认登录页面

Spring Security是一个强大且高度可定制的身份验证和访问控制框架。天然与Spring整合&#xff0c;易扩展&#xff0c;引入jar包就可以用了&#xff0c;在boot自动装载下&#xff0c;不需要任何配置就可以控制资源访问。那么默认登录页是如何产生的呢&#xff1f; 版本信息 内…

使用 git 将本地文件上传到 gitee 远程仓库中,推送失败

项目场景&#xff1a; 背景&#xff1a; 使用 git 想要push 本地文件 到 另一个远程仓库&#xff0c;执行 git push origin master后此时报错 问题描述 问题&#xff1a; git push 本地文件 到 另一个远程仓库时&#xff0c;运行 git push origin master ,push文件失败&…

k8s之基础组件说明

前言 K8S&#xff0c;全称 Kubernetes&#xff0c;是一个用于管理容器的开源平台。它可以让用户更加方便地部署、扩展和管理容器化应用程序&#xff0c;并通过自动化的方式实现负载均衡、服务发现和自动弹性伸缩等功能。 具体来说&#xff0c;Kubernetes 可以将应用程序打包成…

异构计算关键技术之多线程技术(三)

异构计算关键技术之多线程技术&#xff08;三&#xff09; 一、多线程概述 1. 多线程的概念与优劣 多线程是指在程序中同时运行多个线程&#xff0c;每个线程都可以独立执行不同的代码段&#xff0c;且各个线程之间共享程序的数据空间和资源。 优劣&#xff1a; 优点&#…

Leetcode29-最大频率元素计数(3005)

1、题目 给你一个由 正整数 组成的数组 nums 。 返回数组 nums 中所有具有 最大 频率的元素的 总频率 。 元素的 频率 是指该元素在数组中出现的次数。 示例 1&#xff1a; 输入&#xff1a;nums [1,2,2,3,1,4] 输出&#xff1a;4 解释&#xff1a;元素 1 和 2 的频率为 …

模板讲解之进阶

在之前的C入门的博客中我们就学习到了模板初阶&#xff0c;今天我们来学习模板的进阶&#xff0c;以便于更好地将模板运用到代码中 非类型模板参数 模板参数分类类型形参与非类型形参。 类型形参即&#xff1a;出现在模板参数列表中&#xff0c;跟在class或者typename之类的…

使用Docker部署DashDot服务器仪表盘并结合cpolar实现公网监测服务器

最近&#xff0c;我发现了一个超级强大的人工智能学习网站。它以通俗易懂的方式呈现复杂的概念&#xff0c;而且内容风趣幽默。我觉得它对大家可能会有所帮助&#xff0c;所以我在此分享。点击这里跳转到网站。 文章目录 1. 本地环境检查1.1 安装docker1.2 下载Dashdot镜像 2.…

氟化硼二吡咯甲基四嗪,BDP-FL-methyltetrazine,能够提高检测的灵敏度和特异性

BDP FL methyltetrazine&#xff0c;氟化硼二吡咯甲基四嗪&#xff0c;BDP-FL-methyltetrazine&#xff0c;能够提高检测的灵敏度和特异性 您好&#xff0c;欢迎来到新研之家 文章关键词&#xff1a;BDP FL methyltetrazine&#xff0c;氟化硼二吡咯甲基四嗪&#xff0c;BDP-…