用C++和SFML实现俄罗斯方块小游戏

VS2019 C++ SFML 俄罗斯方块

SFML配置方法和素材:https://www.bilibili.com/video/av80956260

代码来源:https://www.bilibili.com/video/BV1uK4y187zT?from=search&seid=3828192988339151057


完整代码如下:

#include <SFML/Graphics.hpp>  //图形
#include <SFML/Audio.hpp>     //声音
#include <time.h>
using namespace sf;
//写在main函数外的是全局变量

Sound sou;

int blocks[7][4] =     //7种方块,每种4格
{
    1, 3, 5, 7,   //1型
    2, 4, 5, 7,   //Z 1型
    3, 5, 4, 6,   //Z 2型
    3, 5, 4, 7,   //T型
    2, 3, 5, 7,   //L
    3, 5, 7, 6,   //J
    2, 3, 4, 5,   //田
};  //每种方块中方格的序号

const int ROW_COUNT = 20;
const int COL_COUNT = 10;

//游戏区域的表示
//若table[i][j]==0,表示第i行第j列这个格子是空白的
//table[i][j]==1,表示有方块,且这个格子是第一种方块(第一种形式的方块);等于2就是第二种形式的方块
int table[ROW_COUNT][COL_COUNT] = { 0 };  //方块运动的区域
int blockIndex; //当前方块的种类

struct Point        //方格的坐标
{
    int x;
    int y;
} curBlock[4], bakBlock[4];     //当前的坐标,备份

//下降速度(单位秒)
const float SPEED_NORMAL = 0.5;
const float SPEED_QUICK = 0.05;

float delay = SPEED_NORMAL;

bool check() //检查当前方块合法性
{
    for (int i = 0; i < 4; i++)
    {
        if (curBlock[i].x < 0
            || curBlock[i].x >= COL_COUNT
            || curBlock[i].y >= ROW_COUNT
            || table[curBlock[i].y][curBlock[i].x] != 0)
        {
            return false;
        }
    }
    return true;
}
void moveLeftRight(int dx)
{
    for (int i = 0; i < 4; i++)
    {
        bakBlock[i] = curBlock[i];
        curBlock[i].x += dx;
    }
    if (!check())
    {
        for (int i = 0; i < 4; i++)
        {
            curBlock[i] = bakBlock[i];
        }
    }
}
//如何实现旋转?
//1.用空间换时间:把每个方块的四种变化都存起来,游戏中直接取出用
//  优点:速度快;缺点:消耗存储空间,不够灵活,如果增加一种方块的形态就要增加4个数组  
//2.用算法进行旋转
//  优点:灵活;缺点:难度大
void doRotate()
{
    if (blockIndex == 7) //如果这个方块是田字型的,不用转
        return;
    //先备份,如果旋转后的方块是非法的,就回退还原
    for (int i = 0; i < 4; i++)
    {
        bakBlock[i] = curBlock[i];
    }
    Point p = curBlock[1]; //旋转中心选为第二个方块
    //旋转后的坐标计算,见笔记
    for (int i = 0; i < 4; i++)
    {
        Point tmp = curBlock[i];//计算时要用到原来的坐标
        curBlock[i].x = p.x - tmp.y + p.y;
        curBlock[i].y = tmp.x - p.x + p.y;
    }
    //检查合法性
    if (!check())
    {
        for (int i = 0; i < 4; i++)
            curBlock[i] = bakBlock[i];
    }
}
    void keyEvent(RenderWindow * window)   //处理按键函数
    {
        Event e;//事件变量
        bool rotate = false;//是否旋转
        int dx = 0;
        while (window->pollEvent(e)) //从事件队列中取出一个事件,如果没有事件就返回false
        {
            if (e.type == Event::Closed)
                window->close();

            if (e.type == Event::KeyPressed)
            {
                switch (e.key.code)
                {
                case Keyboard::Up:
                    rotate = true;
                    break;
                case Keyboard::Left:
                    dx = -1;
                    break;
                case Keyboard::Right:
                    dx = 1;
                    break;
                default:
                    break;
                }
            }
            //处理向下
            if (Keyboard::isKeyPressed(Keyboard::Down))
            {
                delay = SPEED_QUICK;
            }
            if (dx != 0)
            {
                moveLeftRight(dx);
            }
            if (rotate)
            {
                doRotate();
            }
        }
    }
    void newBlock()   //生成方块函数
    {   //获取随机一个随机值(1到7)作为方格的序号
        blockIndex = 1 + rand() % 7;
        int n = blockIndex - 1;
        //把序号转换成坐标值:序号%2得到x坐标,序号/2得到y坐标(转换方法与坐标怎么定的有关)
        for (int i = 0; i < 4; i++)
        {
            curBlock[i].x = blocks[n][i] % 2;
            curBlock[i].y = blocks[n][i] / 2;
        }
    }
    //画方块
    void drawBlocks(Sprite * spriteBlock, RenderWindow * window)
    {//方块分两种:1.已经降落到底部的方块;
        for (int i = 0; i < ROW_COUNT; i++)
        {
            for (int j = 0; j < COL_COUNT; j++)
            {
                if (table[i][j] != 0) //若此格有小方块
                {  //画小方块
                   //需要先使用Sprite表示完整的方块图片
                    spriteBlock->setTextureRect(IntRect(table[i][j] * 18, 0, 18, 18));//因为有不同样子的方块在一个图中,所以需要切割
                    //IntRect是个模板,有四个参数,可以用来表示一个区域。 
                    spriteBlock->setPosition(j * 18, i * 18);//这个区域展示在哪个位置
                    spriteBlock->move(28, 33);//设置偏移量

                    window->draw(*spriteBlock);//在窗口中画
                }
            }
        }
        //2.正在降落过程中的方块(当前方块)(当前方块只有四个方格)
        for (int i = 0; i < 4; i++)
        {
            spriteBlock->setTextureRect(IntRect(blockIndex * 18, 0, 18, 18));
            spriteBlock->setPosition(curBlock[i].x * 18, curBlock[i].y * 18);
            spriteBlock->move(28, 31);
            window->draw(*spriteBlock);
        }
    }

    
void drop()
{
    //下降就是x坐标不变,y坐标加1
    for (int i = 0; i < 4; i++)
    {
        bakBlock[i] = curBlock[i];
        curBlock[i].y += 1;
    }
    if (check() == false)
    {
        //固化处理
        for (int i = 0; i < 4; i++)
        {
            table[bakBlock[i].y][bakBlock[i].x] = blockIndex;
        }
        newBlock();//产生一个新方块
    }
}
void clearLine()//消除满的一行,并且上层已落定的方块向下移动一行
{
    //重新写方块的行数
    int k = ROW_COUNT-1;
    for (int i = ROW_COUNT - 1; i > 0; i--)
    {
        int count = 0;
        for (int j = 0; j < COL_COUNT; j++)
        {
            if (table[i][j])
                count++;
            table[k][j] = table[i][j]; //一边统计每行每个格子里的小方块,一边重新写进去
        }
        if (count < COL_COUNT)
            k--;              //如果这一行没满,则下一行还在原来的位置
        //如果这一行满了,k不变,下一行就会覆盖掉这一行
        //else
        //    sou.play();//播放消除音效
    }
}

    int main()
    {
        srand(time(0));  //生成一个随机种子

        /*
        //背景音乐
        Music music;
        if (music.openFromFile("bg.wav"))
            return -1;
        music.setLoop(true);//音乐循环
        music.play();
        //消除音效
        SoundBuffer xiaochu;
        if (xiaochu.loadFromFile("xiaochu.wav"))
            return -1;
        sou.setBuffer(xiaochu);
        */

        //做出游戏界面
        //1.创建游戏窗口
        RenderWindow window(        //有2个参数
            VideoMode(320, 480),    //320*480是游戏背景图的大小
            "2020");          //第二个参数是窗口的标题
        //2.添加游戏背景
        Texture t1;        //纹理。就是把图片文件加载到内存
        t1.loadFromFile("images/background.png");
        Sprite spriteBG(t1);  //根据图片来创建精灵

        Texture t2;
        t2.loadFromFile("images/tiles.png"); //方块的形式(7种形式在一张图里)
        Sprite spriteBlock(t2);

            //生成第一个方块
            newBlock();

        //SMFL的定时器
        Clock clock; //生成后就启动了

        float timer=0;//总时间
        //进入游戏循环
        while (window.isOpen())   //如果窗口没有被关闭
        {
            //获取从clock被启动或重启之后,到现在的时间(单位是秒)
            float time = clock.getElapsedTime().asSeconds();
            timer += time;
            clock.restart(); //重启
            //等待用户按下按键,并处理(自定义函数)
            keyEvent(&window);
            if (timer > delay)
            {
                //降落
                drop();  //降落函数(自定义)
                timer = 0;
            }
            
            clearLine();//消除函数(自定义)

            delay = SPEED_NORMAL;

            //绘制游戏
            window.draw(spriteBG);  //背景
            //绘制方块
            drawBlocks(&spriteBlock, &window);

            window.display();      //刷新并显示窗口
        }

        system("pause");
        return 0;
    }


没有实现的功能:
显示分数;
到顶游戏失败;
游戏暂停功能;
游戏重启功能;
到一定分数后升级,下落速度变快。


一些解释:

用C++和SFML实现俄罗斯方块小游戏