用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;
}
没有实现的功能:
显示分数;
到顶游戏失败;
游戏暂停功能;
游戏重启功能;
到一定分数后升级,下落速度变快。
一些解释: