用代码写俄罗斯方块(CC)
儿时经典的掌机游戏,你还记得吗?
花式消行技巧,荣耀伙伴的最高纪录,仿佛又回到了纯真的战斗时代。
致敬俄罗斯方块之父"阿列克谢·帕基特诺夫",一起实现这个传奇经典游戏吧!
当时他耗费了6天时间,今天的你需要几天呢?
在我们的B站中有详细的讲解视频,以及相关的素材,只需90分钟就能实现这个经典!https://www.bilibili.com/video/BV1uK4y187zT?from=search&seid=5164603845757173126
先看一下游戏效果:
搭建环境
首先,我们来搭建开发环境:
1. VC 2010或以上版本,推荐使用VS2015以上版本。
2. 下载SFML库,可以到SFML官网下载,或者联系作者。
SFML是一个非常方便的工具库,我下载的是SFML2.5.1 32位版本(32位可以在32位和64位平台运行)
下载后解压到电脑的任意位置,比如:C:\SFML\SFML-2.5.1_32bit
使用VC 或者VS创建一个空项目,然后配置头文件以及库文件。
配置使用的库文件:
sfml-window-d.lib
sfml-system-d.lib
sfml-graphics-d.lib
sfml-audio-d.lib
准备游戏素材
准备好游戏的素材(图片,背景音乐,音效音乐, 字体文件)。
背景图片如下,也可以更换成自己喜欢的其他图片。
游戏区域四周的方框图片,这个图片可以让游戏界面更好看哦,也可以省略不要。
俄罗斯方块的纹理图片如下。在代码中,我们会按照小方块的大小进行切割,得到多个不同颜色的小方块。
游戏逻辑结构
搭建游戏框架
先写好游戏框架:
#include <SFML/Graphics.hpp>
#include <SFML/Audio.hpp>
#include <time.h>
using namespace sf;
void keyEvent(RenderWindow * window) {
Event e;
// pollEvent 从事件队列中取出一个事件
while (window->pollEvent(e))
{
if (e.type == Event::Closed)
window->close();
if (e.type == Event::KeyPressed) {
switch (e.key.code) {
case Keyboard::Up:
break;
case Keyboard::Left:
break;
case Keyboard::Right:
break;
default:
break;
}
}
}
}
int main()
{
srand(time(0));
// 创建窗口
RenderWindow window(
VideoMode(320, 416), //窗口大小
"Rock"); //标题
// 创建表示图片的精灵
Texture t2, t3;
t2.loadFromFile("images/bg2.jpg");
t3.loadFromFile("images/frame.png");
Sprite spriteBg(t2);
Sprite spriteFrame(t3);
while (window.isOpen())
{
keyEvent(&window);
window.clear(Color::White);
window.draw(spriteBg);
window.draw(spriteFrame);
window.display();
}
return 0;
}
运行后,效果如下:
俄罗斯方块的设计
俄罗斯方块的实现,有很多实现方式,最简单的方式是使用多个二位数组,每个二位数组来表示一种方块。不过有更高效的实现方式,使用一个二维数组来表示多种俄罗斯方块。
const int ROW_COUNT = 20;
const int COL_COUNT = 10;
int blocks[7][4] = {
1,3,5,7, // I
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, // 田
};
所以对于"I"字型的方块,{1,3,5,7} 就使用4个坐标来表示,(1,0),(1,1),(1,2),(1,3)
struct Point {
int x, y;
} curBlock[4], BakBlock[4];
......
for (int i = 0; i < 4; i )
{
curBlock[i].x = blocks[n][i] % 2;
curBlock[i].y = blocks[n][i] / 2;
}
旋转效果,最简单的方式是,为每一种方向使用一个二位数组。我们使用最灵活的方式,对俄罗斯方块进行旋转处理。
直接使用通用的数学公式,就可以直接得到旋转后的坐标位置。
直接使用数学公式,平面中,一个点(x,y)绕任意点(dx,dy)顺时针旋转a度后的坐标
xx= (x - dx)*cos(-a) - (y - dy)*sin(-a) dx ;
yy= (x - dx)*sin(-a) (y - dy)*cos(-a) dy ;
平面中,一个点(x,y)绕任意点(dx,dy)逆时针旋转a度后的坐标
xx= (x - dx)*cos(a) - (y - dy)*sin(a) dx ;
yy= (x - dx)*sin(a) (y - dy)*cos(a) dy ;
我们需要的是逆时针旋转90度代入公式,得:
Point p = curBlock[1]; //center of rotation
for (int i = 0; i < 4; i )
{
struct Point tmp = curBlock[i];
curBlock[i].x = p.x - tmp.y p.y;
curBlock[i].y = p.y tmp.x - p.x;
}
每次循环开始的时候,就累加时间,如果时间超过延时间隔,就调用自定义的降落函数drop()
......
while (window.isOpen())
{
float time = clock.getElapsedTime().asSeconds();
clock.restart(); //计时器重启及时
timer = time;
keyEvent(&window);
if (timer > delay) {
drop(); //下降一个位置
timer = 0;
}
.....
}
void drawBlocks(RenderWindow *window, Sprite *spriteBlock) {
// 绘制已降落完毕的方块
for (int i = 0; i < ROW_COUNT; i )
for (int j = 0; j < COL_COUNT; j )
{
if (table[i][j] == 0) continue;
spriteBlock->setTextureRect(IntRect(table[i][j] * 18, 0, 18, 18));
spriteBlock->setPosition(j * 18, i * 18);
spriteBlock->move(28, 31); //offset
window->draw(*spriteBlock);
}
// 绘制当前方块
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); //offset
window->draw(*spriteBlock);
}
}
从底部向上逐行扫描。
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];
}
// 如果没有满行,就继续扫描上一行
// 如果已经满行,k不变,在这个满行内,存放下一次的扫描结果
if (count < COL_COUNT) k--;
else {
score = 10;
sou.play();
}
}
char tmp[16];
sprintf_s(tmp, "%d", score);
textScore.setString(tmp);
}
越是优化代码,越是演练,越是思考,就越能发现C/C 的优势所在。
边看视频边写代码,遇到问题,私信小编。
详细教程点击下方“了解更多”
,免责声明:本文仅代表文章作者的个人观点,与本站无关。其原创性、真实性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容文字的真实性、完整性和原创性本站不作任何保证或承诺,请读者仅作参考,并自行核实相关内容。文章投诉邮箱:anhduc.ph@yahoo.com