目录
一、效果展示
二、游戏说明
三、开发环境
四、代码展示
五、代码详解
1.游戏区域表示
2.方块表示
3.方块旋转
4.消行处理
六、个性化定制
七、结语
一、效果展示
二、游戏说明
相信大家都玩过俄罗斯方块,五种按键就能带来极高的可玩性。如果想要开发属于自己的俄罗斯方块,首先就要实现这五种按键的功能,其次就是实现消行和积分等必要功能。如果想要自己的游戏更精美,就可以往里面添加优美的音乐和丰富的音效。
三、开发环境
- Visual Studio 2022
- SFML 2.5.1
四、代码展示
#include<SFML/Graphics.hpp>
#include<SFML/Audio.hpp>
#include<time.h>
#include<Windows.h>using namespace std;
using namespace sf;const int rowCount = 24;//游戏区行数
const int columnCount = 12;//游戏区列数int table[rowCount][columnCount] = { 0 };//游戏区域表示//方块表示
int block[7][4] =
{ 1, 3, 5, 7,//I字型3, 5, 4, 6,//Z字型2, 4, 5, 7,//S字型2, 3, 5, 7,//L字型3, 5, 7, 6,//J字型3, 5, 4, 7,//T字型2, 3, 4, 5,//O字型
};//隐藏控制台
void hideWindow()
{HWND hwnd = GetForegroundWindow();if (hwnd){ShowWindow(hwnd, SW_HIDE);}
}int blockIndex;//方块类型
int nextIndex;//下一方块类型bool ifNextBlock = false;//下一方块生成判断//下降速度
const float normalSpeed = 0.5;
const float fastSpeed = 0.05;
float speed = normalSpeed;int score = 0;//游戏得分
char Score[20];//得分字符串//游戏方块坐标表示
struct Point
{int x;int y;
} currentBlock[4], backBlock[4], nextBlock[4];//生成方块
void newBlock(int t)
{blockIndex = t;int n = blockIndex - 1;int dx = rand() % 11;//随机偏移//坐标转换for (int i = 0; i < 4; i++){currentBlock[i].x = block[n][i] % 2 + dx;currentBlock[i].y = block[n][i] / 2;}
}//绘制方块
void drawBlocks(Sprite * spriteFk, RenderWindow * window)
{//已经落在底部的方块for (int i = 0; i < rowCount; i++){for (int j = 0; j < columnCount; j++){if (table[i][j] != 0){spriteFk->setTextureRect(IntRect((table[i][j] - 1) * 35, 0, 35, 35));spriteFk->setPosition(j * 35 , i * 35);spriteFk->move(37,60);//偏移量window->draw(*spriteFk);}}}//正在降落的方块for (int i = 0; i < 4; i++){spriteFk->setTextureRect(IntRect((blockIndex - 1) * 35, 0, 35, 35));spriteFk->setPosition(currentBlock[i].x * 35, currentBlock[i].y * 35);spriteFk->move(37,60);window->draw(*spriteFk);}
}//绘制下一方块
void drawNextBlock(Sprite * spriteFK, RenderWindow * window)
{for (int i = 0; i < 4; i++){spriteFK->setTextureRect(IntRect((nextIndex - 1) * 35, 0, 35, 35));spriteFK->setPosition(nextBlock[i].x * 35, nextBlock[i].y * 35);if (nextIndex == 1){spriteFK->move(520, 160);}else if (nextIndex == 2){spriteFK->move(540, 140);}else if (nextIndex == 3){spriteFK->move(540, 140);}else if (nextIndex == 4){spriteFK->move(540, 140);}else if (nextIndex == 5){spriteFK->move(540, 140);}else if (nextIndex == 6){spriteFK->move(540, 140);}else if (nextIndex == 7){spriteFK->move(540, 150);}window->draw(*spriteFK);}
}//检查方块合法性
bool check()
{for (int i = 0; i < 4; i++){if (currentBlock[i].x < 0||currentBlock[i].x >= columnCount||currentBlock[i].y >= rowCount||table[currentBlock[i].y][currentBlock[i].x])//位置存在方块{return false;}}return true;
}//检查游戏失败
bool gameFailed()
{for (int i = 0; i < columnCount; i++){if (table[3][i] != 0){return true;}}return false;
}void drop()
{//下降实现for (int i = 0; i < 4; i++){backBlock[i] = currentBlock[i];currentBlock[i].y += 1;}//生成下一方块if (ifNextBlock == false){ifNextBlock = true;//随机下一方块nextIndex = rand() % 7 + 1;int n = nextIndex - 1;for (int i = 0; i < 4; i++){nextBlock[i].x = block[n][i] % 2;nextBlock[i].y = block[n][i] / 2;}}if (check() == false){//方块固化for (int i = 0; i < 4; i++){table[backBlock[i].y][backBlock[i].x] = blockIndex;}ifNextBlock = false;newBlock(nextIndex);//生成新方块}
}void leftRightMove(int dx)
{//平移实现for (int i = 0; i < 4; i++){backBlock[i] = currentBlock[i];currentBlock[i].x += dx;}//检查方块合法性if (!check()){for (int i = 0; i < 4; i++){currentBlock[i].x = backBlock[i].x;}}
}void rotatoMove()
{//O型方块特殊处理if (blockIndex == 7){return;}//还原备份for (int i = 0; i < 4; i++){backBlock[i] = currentBlock[i];}Point p = currentBlock[1];//旋转中心//顺时针旋转实现for (int i = 0; i < 4; i++){Point tmp = currentBlock[i];currentBlock[i].x = -tmp.y + p.y + p.x;currentBlock[i].y = tmp.x - p.x + p.y;}if (!check()){for (int i = 0; i < 4; i++){currentBlock[i] = backBlock[i];}}
}void downBottom()
{//落底实现while (check() == true){for (int i = 0; i < 4; i++){backBlock[i] = currentBlock[i];//还原备份currentBlock[i].y++;}}for (int i = 0; i < 4; i++){currentBlock[i] = backBlock[i];}
}//按键处理
void keyEvent(RenderWindow * window)
{int dx = 0;//平移量Event e;//事件变量while (window->pollEvent(e))//从队列事件中取一个事件{//窗口关闭if (e.type == Event::Closed){window->close();}//按键点按事件if (e.type == Event::KeyPressed){switch (e.key.code){case Keyboard::Up:rotatoMove();break;case Keyboard::Left:dx = -1;break;case Keyboard::Right:dx = 1;break;case Keyboard::Space:downBottom();default:break;}}//按键长按事件if (Keyboard::isKeyPressed(Keyboard::Down)){speed = fastSpeed;//加速实现}//平移if (dx !=0){leftRightMove(dx);}}
}void clearLine()
{int k = rowCount - 1;for (int i = rowCount - 1; i > 0; i--){int count = 0;for (int j = 0; j < columnCount; j++){if (table[i][j]){count++;}table[k][j] = table[i][j];}if (count < columnCount){k--;}else{score += 12;//得分}}
}int main(void)
{hideWindow();//隐藏控制台srand(time(0));//生成随机种子//导入背景音乐Music music;music.openFromFile("SC/Bg.ogg");music.setLoop(true);//循环播放music.play();//创建窗口RenderWindow window(VideoMode(714,960),//窗口大小"Tetris");//窗口名称//创建背景Texture Bg;Bg.loadFromFile("SC/Bg.jpg");Sprite spriteBg(Bg);//创建方块Texture Fk;Fk.loadFromFile("SC/Fk.jpg");Sprite spriteFk(Fk);//创建下一方块Texture FK;FK.loadFromFile("SC/Fk.jpg");Sprite spriteFK(FK);//导入文字Font font;//字体变量font.loadFromFile("SC/text.ttf");Text text1, text2, text3, text4, text5, text6, text7, text8, text9, text10;//文字样式text1.setPosition(475,350);//文字位置text1.setString("Score");//文字内容text1.setFont(font);//文字字体text1.setCharacterSize(30);//文字大小text1.setStyle(sf::Text::Bold);//文字格式,加粗//显示得分数值text2.setFont(font);text2.setCharacterSize(30);text2.setStyle(sf::Text::Bold);text3.setPosition(475,430);text3.setString("UP:Rotato");text3.setFont(font);text3.setCharacterSize(23);text3.setStyle(sf::Text::Bold);text4.setPosition(475,470);text4.setString("Down:Speed Up");text4.setFont(font);text4.setCharacterSize(23);text4.setStyle(sf::Text::Bold);text5.setPosition(475,510);text5.setString("Left:Left Shift");text5.setFont(font);text5.setCharacterSize(23);text5.setStyle(sf::Text::Bold);text6.setPosition(475,550);text6.setString("Right:Right Shift");text6.setFont(font);text6.setCharacterSize(23);text6.setStyle(sf::Text::Bold);text7.setPosition(475,590);text7.setString("Space:Fall");text7.setFont(font);text7.setCharacterSize(23);text7.setStyle(sf::Text::Bold);text8.setPosition(510,110);text8.setString("Next Block");text8.setFont(font);text8.setCharacterSize(25);text8.setStyle(sf::Text::Bold);text9.setPosition(110,320);text9.setString("Game Over");text9.setFont(font);text9.setCharacterSize(45);text9.setStyle(sf::Text::Bold);text10.setPosition(155,380);text10.setString("Enter:Restar");text10.setFont(font);text10.setCharacterSize(28);text10.setStyle(sf::Text::Bold);newBlock(rand() % 7 + 1);//生成新方块Clock clock;//计时器float timer = 0;int i = 0;// 游戏循环while (window.isOpen())//游戏循环{float time = clock.getElapsedTime().asSeconds();//从计时器重启到当前的时间clock.restart();//重启计时器timer += time;//得分数值显示sprintf_s(Score, "%d", score);text2.setString(Score);text2.setPosition(590, 350);keyEvent(&window);//按键事件if (timer > speed)//下降判定{drop();timer = 0;//重置时间}clearLine();//消行speed = normalSpeed;//还原速度//渲染window.draw(spriteBg);window.draw(spriteFk);window.draw(spriteFK);window.draw(text1);window.draw(text2);window.draw(text3);window.draw(text4);window.draw(text5);window.draw(text6);window.draw(text7);window.draw(text8);//绘制drawBlocks(&spriteFk, &window);drawNextBlock(&spriteFK, &window);if (gameFailed() == false){window.display();//刷新并显示窗口}else{//暂停背景音乐music.setLoop(false);music.play();//渲染window.draw(text9);window.draw(text10);//显示失败界面if (i == 0){window.display();}i = 1;Event r;//重新游戏while (window.pollEvent(r)){if (r.type == Event::KeyPressed){switch (r.key.code){case Keyboard::Enter://清屏for (int i = 0; i < rowCount; i++){for (int j = 0; j < columnCount; j++){table[i][j] = 0;}}score = 0;//重置得分i = 0;//重置window.display();break;default:break;}}}}}system("pause");return 0;
}
五、代码详解
1.游戏区域表示
游戏区域使用二维数组表示。如果数组中某位置的值不为0,表示该位置存在方块。
const int rowCount = 24;//游戏区行数
const int columnCount = 12;//游戏区列数int table[rowCount][columnCount] = { 0 };//游戏区域表示
2.方块表示
在2*4的矩阵中,可以表示俄罗斯方块中任何类型的方块。所以,方块的表示可以使用编号进行,如:I型方块表示为1,3,5,7。
//方块表示
int block[7][4] =
{ 1, 3, 5, 7,//I字型3, 5, 4, 6,//Z字型2, 4, 5, 7,//S字型2, 3, 5, 7,//L字型3, 5, 7, 6,//J字型3, 5, 4, 7,//T字型2, 3, 4, 5,//O字型
};
每种类型的方块都由四个小方块排列组合而成,所以可以使用结构体记录四个小方块的坐标,在表示游戏区域的二维数组内定位方块位置。
//游戏方块坐标表示
struct Point
{int x;int y;
} currentBlock[4], backBlock[4], nextBlock[4];
因为游戏的进行是以方块坐标的形式表示在游戏区域内,所以需要将表示方块的数组转换为方块的坐标。如:I型方块在游戏区域内应表示为(1,0),(1,1),(1,2),(1,3)。通过观察两组数据,可以发现两组数据之间的规律,即,横坐标等于方块数组除以2的余数,纵坐标等于方块数组除以2的商。据此,可进行坐标的转换。
//坐标转换
for (int i = 0; i < 4; i++)
{currentBlock[i].x = block[n][i] % 2;currentBlock[i].y = block[n][i] / 2;
}
3.方块旋转
方块旋转,简单来说,就是,在平面内,一个点绕着另一个点旋转90°。至于怎么求得旋转后的坐标,并不需要进行过多思考,只需懂得搜索引擎的使用,就可找到与之匹配的公式。在开发中,善于利用工具搭建自己的程序,也是一种重要的能力。
//顺时针旋转实现
for (int i = 0; i < 4; i++)
{Point tmp = currentBlock[i];currentBlock[i].x = -tmp.y + p.y + p.x;currentBlock[i].y = tmp.x - p.x + p.y;
}
4.消行处理
消行的原理为,从下至上,逐行检测,该行数组是否存在0,如该行数组都不为0,则该行数组被上一行数组替换。
void clearLine()
{int k = rowCount - 1;for (int i = rowCount - 1; i > 0; i--){int count = 0;for (int j = 0; j < columnCount; j++){if (table[i][j]){count++;}table[k][j] = table[i][j];}if (count < columnCount){k--;}
}
六、个性化定制
一个与众不同的背景,更能凸显这是个专属定制的俄罗斯方块。
//创建背景
Texture Bg;
Bg.loadFromFile("SC/Bg.jpg");
Sprite spriteBg(Bg);
方块的贴图,也是个性化的一方面。
//创建方块
Texture Fk;
Fk.loadFromFile("SC/Fk.jpg");
Sprite spriteFk(Fk);
一首悠悠动听的背景音乐,更能激发游戏兴致。
//导入背景音乐
Music music;
music.openFromFile("SC/Bg.ogg");
music.setLoop(true);//循环播放
music.play();
除此之外,还有许多个性化定制等待大家的发掘。
七、结语
希望博客们都能创造属于自己的俄罗斯方块!
如有错误之处,欢迎指正。