「C/C 」源码教程——手写经典俄罗斯方块

「C/C 」源码教程——手写经典俄罗斯方块

首页休闲益智俄罗斯数学方块更新时间:2024-05-28
小白手写游戏经典-俄罗斯方块

儿时经典的掌机游戏,你还记得吗?

花式消行技巧,荣耀伙伴的最高纪录,仿佛又回到了纯真的战斗时代。

致敬俄罗斯方块之父"阿列克谢·帕基特诺夫",一起实现这个传奇经典游戏吧!

当时他耗费了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 的优势所在。

边看视频边写代码,遇到问题,私信小编。

详细教程点击下方“了解更多

查看全文
大家还看了
也许喜欢
更多游戏

Copyright © 2024 妖气游戏网 www.17u1u.com All Rights Reserved