俄罗斯方块代码阅读
总结的知识点
知识点一 SFML的固定窗口
在阅读别人源码的时候发现放大后会拉伸,效果不好
自己修改为固定窗口大小
sf::RenderWindow window(sf::VideoMode(320, 480), "the Game", sf::Style::Close);//固定窗口
//window(VideoMode, title, [style, graphics settings]),创建一个窗口,style为枚举,在等于close时是固定大小,具有标题栏、最小化按钮,关闭按钮。
知识点二 SFML的坐标系
采用了左上角为坐标原点,像素为单位的坐标系。
知识点三 各种图形的表示
采用了直观的表示,在后面通过计算确定坐标
//对应两行四列(0~7)的生成7种图形,+(VS用※才能对齐)作为旋转的中心点!d
int figures[7][4] =
{
1,3,5,7, //□■ □□ □□ □□ □□ □□ □□
2,4,5,7, //□+ ■□ □■ □■ ■+ □■ ■+
3,5,4,6, //□■ +■ ■+ ■+ □■ □+ ■■
3,5,4,7, //□■ □■ ■□ □■ □■ ■■ □□
2,3,5,7,
3,5,7,6,
2,3,4,5,
};
知识点四 在UGUI坐标系的旋转
(x,y)绕原点顺时针旋转α°,有:
本例向右旋转90°,有:
以选中的中心点为原点,相对坐标旋转后加上中心点坐标即为原坐标系的旋转后的坐标点。
全部代码
#include <SFML/Graphics.hpp>
#include <time.h>
using namespace sf;
const int M = 20;
const int N = 10;
//整个游戏的领域
int field[M][N] = { 0 };
//存图形的4个点
struct Point
{
int x, y;
} a[4], b[4];
//对应两行四列(0~7)的生成7种图形,※作为旋转的中心点
int figures[7][4] =
{
1,3,5,7, //□■ □□ □□ □□ □□ □□ □□
2,4,5,7, //□※ ■□ □■ □■ ■※ □■ ■※
3,5,4,6, //□■ ※■ ■※ ■※ □■ □※ ■■
3,5,4,7, //□■ □■ ■□ □■ □■ ■■ □□
2,3,5,7,
3,5,7,6,
2,3,4,5,
};
bool check()
{
for (int i = 0; i < 4; i++)
if (a[i].x < 0 || a[i].x >= N || a[i].y >= M) return 0;
else if (field[a[i].y][a[i].x]) return 0;
return 1;
};
int main()
{
srand(time(0));
sf::RenderWindow window(sf::VideoMode(320, 480), "the Game", sf::Style::Close);//固定窗口
Texture t1, t2, t3;
t1.loadFromFile("images/tiles.png");
t2.loadFromFile("images/background.png");
t3.loadFromFile("images/frame.png");
Sprite s(t1), background(t2), frame(t3);
//初始位置,是否旋转,颜色
int dx = 0; bool rotate = 0; int colorNum = 1;
float timer = 0, delay = 0.3;
Clock clock;
while (window.isOpen())
{
float time = clock.getElapsedTime().asSeconds();
clock.restart();
timer += time;
Event e;
while (window.pollEvent(e))
{
if (e.type == Event::Closed)
window.close();
if (e.type == Event::KeyPressed)
//↑键,将rotate标志置为1;
if (e.key.code == Keyboard::Up) rotate = true;
//←键,向左平移一个单位(18像素)
else if (e.key.code == Keyboard::Left) dx = -1;
//→键,向→平移一个单位
else if (e.key.code == Keyboard::Right) dx = 1;
}
//↓键,
if (Keyboard::isKeyPressed(Keyboard::Down)) delay = 0.05;
<- Move -> ///
for (int i = 0; i < 4; i++) { b[i] = a[i]; a[i].x += dx; }
if (!check()) for (int i = 0; i < 4; i++) a[i] = b[i];
//Rotate//
if (rotate)
{
Point p = a[1]; //center of rotation
for (int i = 0; i < 4; i++)
{
int x = a[i].y - p.y;
int y = a[i].x - p.x;
a[i].x = p.x - x;
a[i].y = p.y + y;
}
if (!check()) for (int i = 0; i < 4; i++) a[i] = b[i];
}
///Tick//
if (timer > delay)
{
for (int i = 0; i < 4; i++) { b[i] = a[i]; a[i].y += 1; }
if (!check())
{
for (int i = 0; i < 4; i++) field[b[i].y][b[i].x] = colorNum;
colorNum = 1 + rand() % 7;
int n = rand() % 7;
//计算图形的点位,ui坐标系
for (int i = 0; i < 4; i++)
{
a[i].x = figures[n][i] % 2;
a[i].y = figures[n][i] / 2;
}
}
timer = 0;
}
///check lines//
int k = M - 1;
for (int i = M - 1; i > 0; i--)
{
int count = 0;
for (int j = 0; j < N; j++)
{
if (field[i][j]) count++;
field[k][j] = field[i][j];
}
if (count < N) k--;
}
dx = 0; rotate = 0; delay = 0.3;
/draw//
window.clear(Color::White);
window.draw(background);
for (int i = 0; i < M; i++)
for (int j = 0; j < N; j++)
{
if (field[i][j] == 0) continue;
s.setTextureRect(IntRect(field[i][j] * 18, 0, 18, 18));
s.setPosition(j * 18, i * 18);
s.move(28, 31); //offset
window.draw(s);
}
for (int i = 0; i < 4; i++)
{
s.setTextureRect(IntRect(colorNum * 18, 0, 18, 18));
s.setPosition(a[i].x * 18, a[i].y * 18);
s.move(28, 31); //offset
window.draw(s);
}
window.draw(frame);
window.display();
}
return 0;
}
运行截图
待改进的地方
- 没有封装成类,所有的逻辑都在主函数中完成
- 没有死亡检测,仅仅是转换和下降判断是否合法
- 没有计分机制和下个图形预测
- 直接在左上角4行2列绘制图形,在足够高度时会直接改变已经存在的图形,也就是上面说的没有死亡检测和生成检测
阅读体会
更加深刻的了解了编译练接运行的步骤,分别测试了静态和动态DLL,记录了一个特别坑的点——自己用的VS在调试时的生成的exe运行环境是项目目录而不是exe存放目录,需要更改调试运行环境
能够体会到一个程序的完整编写过程,把自己前几章学的大概都用了一下
明白了学习的三角变化和矩阵运算的一个实际运用方向,而不是仅仅调用Rotate函数
定个小目标
用更c++风格改写这个小demo,~~ 加上第二章学习的AssetManager类实现资源的自动管理(好像整个程序过程都用了3个Texture,不能释放) ~~
争取加上死亡判定,大概思路,在最上面单独设立一个生成区,省去生成合法判定,在最上一行检测是否存在物体,有即死亡