使用c++和QT图形框架进行实现(QT 5.8)
采用QT提供的图形开发库QGraphicsView QGraphicsScene QGraphicsItem.
项目完整的代码:https://github.com/qiny1012/Tetris
建立一个子类MyItem继承QGraphicsItem。构造一个矩形元素,可以给这个元素贴图设置颜色美化程序。
建立一个类MyGroup用来设置方块组,有o型,L型,J型等,包含一个MyItem的链表。
建立一个类MyScene继承QGraphicsScene,来实现方块的显示和运动。
类的核心参数
- MyItem,
MyItem(int w,int h);//构造一个宽为w,高为h的矩形方块
- MyGroup:
int pos_x;
int pos_y;//设置方块组的位置
QList
int Type_rotation;
int Type_style;//设置当前的状态。方块组的方块有7中类型,每一种类型有4中旋转的状态。
MyGroup();
MyGroup(int pos_x,int pos_y,int type);//构造函数,前两个是方块组放到位置,第三个参数是方块的类型,默认的旋转类型是0.
int isColliding();//碰撞检测
void zhuangLeft();//顺时针旋转90
void zhuangRigth();//逆时针旋转90
void setPos(int x,int y);//设置方块组中各个元素的坐标
void moveBy(int x,int y);//移动方块组中的全部元素。
- MyScene
QGraphicsLineItem * lineItem1;
QGraphicsLineItem * lineItem2;
QGraphicsLineItem * lineItem3;//用于构造界面的线元素,也用于碰撞检测
QTimer *timer;//定时器
MyGroup * curGroup;//当前可以移动的方块组
MyGroup * nextGroup;//下一个方块组,设置在游戏的右上角
QGraphicsItem * type_ret[20][10];//界面上方块的数组,把地图看成一个10*20的数组,通过这个元素,确定是否可以销毁一行数据
int score;//得分,并没有使用
int testEnd;//检测游戏是否结束的一个变量
explicit MyScene(QObject *parent = 0);使用信号和槽函数就需要这样写,explicit表示参数不可以强制类型转化
void initScene();//初始化场景
int IsColliding(); //碰撞检测
void addGroup(MyGroup * group);//添加一个方块组到场景中
void creatNewGroup(); //产生一个方块组并且放在场景中
void setRet(); //方块放置完成记录方块。
int testRet(int row); //如果有放置之后要检测这一行是不是已经数据完成了,如果成功放回1,否则0
int destroyFromRet(int row); //把row行的数据删除,然后再把数据全部向下移动
int moveDownRet(int row);//移动小于row的数据,并且修改他们的位置
void strat();//开始游戏
void stop();//暂停游戏
void starNewGame();//开始一个新的比赛,清除所有的元素,并且重新生成curGroup和nextGroup。
void keyPressEvent(QKeyEvent * keyEvent);//键盘事件
程序执行的过程:
- 建立一个QGraphicsView的对象,添加到Ui中,在建立一个myscene的对象,设置他的大小。
- 设置要给按钮,可以调用myscene的start()函数。
- Myscene内部的定时器,没过0.5秒进行移动一次curGroup
- 如果curGroup发生了碰撞,就撤下移动,并判断是否有放满的行。如果有就删除该行,并且移动该行以上的数据。
- 如果失败会提示,并且重新开始
难点:
- 坐标,myItem移动之后,无法获取他的坐标.pos()总是返回0,0.想要使用myitem的pos必须设置。
- 旋转,如果使用库中的QGraphicsItemGroup,有现成的旋转函数,但是使用的过程之后,无法正常的进行碰撞检测,最后没有使用这个类。旋转函数是自己写的,它是通过移动方块到指定的位置,实现旋转的感觉。
- 游戏结束。目前如果程序一直按space键,还是会出现bug。
源码(如果没有看懂可以直接去参考源码,写的很稀碎)
1.MyItem.h和MyItem.cpp
#includeclass MyItem : public QGraphicsRectItem { public: MyItem(); MyItem(int x); MyItem(int w,int h); MyItem(int x,int y,int w,int h); }; #include "myitem.h" //使用这个函数构造一个item的类 MyItem::MyItem(int w, int h) { this->setRect(0,0,18,18); } //没有使用到 MyItem::MyItem(int x, int y, int w, int h) { this->setRect(x,y,w,h); }
2. MyGroup.h和MyGroup.cpp
#include "myitem.h" #include#include #include #include #include class MyGroup { public : int pos_x; int pos_y; //组的位置 public: MyGroup(); MyGroup(int pos_x,int pos_y,int type); int isColliding(); void zhuangLeft(); void zhuangRigth(); void clear(); QList list; int Type_rotation; //设置当前的状态。 int Type_style; void setPos(int x,int y); void moveBy(int x,int y); }; #include "mygroup.h" int type_pos_x [7][4][4] = { //第一种情况 { {0,20,0,20}, {0,20,0,20}, {0,20,0,20}, {0,20,0,20}, }, //第二个图形 { {0,0,0,20}, {20,0,-20,-20}, {20,20,20,0}, {0,20,40,40}, }, //第三个图形 { {20,20,20,0}, {20,0,-20,-20}, {0,0,0,20}, {0,20,40,40}, }, //第四个图形 { {0,0,20,20}, {20,0,0,-20}, {0,0,20,20}, {20,0,0,-20}, }, //第5个图形 { {20,20,0,0}, {20,0,0,-20}, {20,20,0,0}, {20,0,0,-20}, }, //第6个图形 { {0,0,0,0}, {-40,-20,0,20}, {0,0,0,0}, {-40,-20,0,20}, }, //第7个图形 { {0,-20,0,20}, {20,0,0,0}, {0,20,0,-20}, {-20,0,0,0}, }, }; int type_pos_y [7][4][4] = { //第一种情况 { {0,0,20,20}, {0,0,20,20}, {0,0,20,20}, {0,0,20,20}, }, //第二个图形 { {0,20,40,40}, {0,0,0,20}, {20,0,-20,-20}, {20,20,20,0}, }, //第三个图形 { {0,20,40,40}, {20,20,20,0}, {20,0,-20,-20}, {0,0,0,20}, }, //第四个图形 { {0,20,20,40}, {0,0,20,20}, {0,20,20,40}, {0,0,20,20}, }, //第5个图形 { {0,20,20,40}, {20,20,0,0}, {0,20,20,40}, {20,20,0,0}, }, //第6个图形 { {0,20,40,60}, {0,0,0,0}, {0,20,40,60}, {0,0,0,0}, }, //第7个图形 { {0,20,20,20}, {0,-20,0,20}, {20,0,0,0}, {0,20,0,-20}, }, }; int type_x[7][4] ={ {0,0,20,20} ,{0,0,0,20} ,{20,20,20,0} ,{0,0,0,0} ,{0,0,20,20} ,{20,20,0,0} ,{20,0,20,40} }; int type_y[7][4] = { {0,20,0,20} ,{0,20,40,40} ,{0,20,40,40} ,{0,20,40,60} ,{0,20,20,40} ,{0,20,20,40} ,{0,20,20,20} }; MyGroup::MyGroup() { } MyGroup::MyGroup(int pos_x, int pos_y, int type) { Type_rotation = 0; Type_style = type; this->setPos(pos_x,pos_y); MyItem * item1 = new MyItem(18,18); MyItem * item2 = new MyItem(18,18); MyItem * item3 = new MyItem(18,18); MyItem * item4 = new MyItem(18,18); item1->setPos(pos_x+type_pos_x[type][Type_rotation][0],pos_y+type_pos_y[type][Type_rotation][0]); item2->setPos(pos_x+type_pos_x[type][Type_rotation][1],pos_y+type_pos_y[type][Type_rotation][1]); item3->setPos(pos_x+type_pos_x[type][Type_rotation][2],pos_y+type_pos_y[type][Type_rotation][2]); item4->setPos(pos_x+type_pos_x[type][Type_rotation][3],pos_y+type_pos_y[type][Type_rotation][3]); list.append(item1); list.append(item2); list.append(item3); list.append(item4); } int MyGroup::isColliding() { QGraphicsItem *item; foreach(item, list) { if(item->collidingItems(Qt::ContainsItemBoundingRect).count()>0)//collidingItems返回与当前item碰撞的子item列表 return 1;//代表至少有一个item发生了碰撞 } return 0; } void MyGroup::zhuangLeft() { Type_rotation++; if(Type_rotation == 4) { Type_rotation = 0; } qDebug() << type_pos_x[Type_style][Type_rotation][0] << type_pos_x[Type_style][Type_rotation][0] ; list.at(0)->setPos(pos_x + type_pos_x[Type_style][Type_rotation][0],pos_y + type_pos_y[Type_style][Type_rotation][0]); list.at(1)->setPos(pos_x + type_pos_x[Type_style][Type_rotation][1],pos_y + type_pos_y[Type_style][Type_rotation][1]); list.at(2)->setPos(pos_x + type_pos_x[Type_style][Type_rotation][2],pos_y + type_pos_y[Type_style][Type_rotation][2]); list.at(3)->setPos(pos_x + type_pos_x[Type_style][Type_rotation][3],pos_y + type_pos_y[Type_style][Type_rotation][3]); } void MyGroup::zhuangRigth() { Type_rotation--; if(Type_rotation == -1) { Type_rotation = 3; } qDebug() << type_pos_x[Type_style][Type_rotation][0] << type_pos_x[Type_style][Type_rotation][0] ; list.at(0)->setPos(pos_x + type_pos_x[Type_style][Type_rotation][0],pos_y + type_pos_y[Type_style][Type_rotation][0]); list.at(1)->setPos(pos_x + type_pos_x[Type_style][Type_rotation][1],pos_y + type_pos_y[Type_style][Type_rotation][1]); list.at(2)->setPos(pos_x + type_pos_x[Type_style][Type_rotation][2],pos_y + type_pos_y[Type_style][Type_rotation][2]); list.at(3)->setPos(pos_x + type_pos_x[Type_style][Type_rotation][3],pos_y + type_pos_y[Type_style][Type_rotation][3]); } void MyGroup::clear() { list.clear(); } void MyGroup::setPos(int x, int y) { pos_x = x ; pos_y = y ; } void MyGroup::moveBy(int x, int y) { pos_x = pos_x + x; pos_y = pos_y + y; list.at(0)->moveBy(x,y); list.at(1)->moveBy(x,y); list.at(2)->moveBy(x,y); list.at(3)->moveBy(x,y); }
3.MyScene.h和MyScene.cpp
#include#include "myitem.h" #include #include #include #include #include #include #include #include #include "mygroup.h" #include class MyScene : public QGraphicsScene { Q_OBJECT public: QGraphicsLineItem * lineItem1; QGraphicsLineItem * lineItem2; QGraphicsLineItem * lineItem3; QTimer *timer; MyGroup * curGroup; MyGroup * nextGroup; QGraphicsItem * type_ret[20][10]; int score; int testEnd; public: explicit MyScene(QObject *parent = 0); void initScene(); int IsColliding(); //return 1 yes return 0 no void keyPressEvent(QKeyEvent * keyEvent); void addGroup(MyGroup * group); void creatNewGroup(); //产生一个方块组并且放在场景中 void setRet(); //方块放置完成记录方块。 int testRet(int row); //如果有放置之后要检测这一行是不是已经数据完成了,如果成功放回1,否则0 int destroyFromRet(int row); //把row行的数据删除,然后再把数据全部向下移动 int moveDownRet(int row); void strat(); void stop(); void starNewGame(); public slots: int moveDownTest(); //return 1 is over; }; #include "myscene.h" MyScene::MyScene(QObject *parent) : QGraphicsScene(parent) { initScene(); } //初始化 void MyScene::initScene() { lineItem1 = new QGraphicsLineItem; lineItem1->setLine(198,-10,198,400); this->addItem(lineItem1); lineItem2 = new QGraphicsLineItem; lineItem2->setLine(401,-10,401,400); this->addItem(lineItem2); lineItem3 = new QGraphicsLineItem; lineItem3->setLine(201,400,399,400); this->addItem(lineItem3); this->setSceneRect(0,0,580,410); qsrand(QTime(0,0,0).secsTo(QTime::currentTime())); int type = qrand(); type = type % 7; this->curGroup = new MyGroup(280,0,type); this->addGroup(curGroup); type = qrand(); type = type % 7; this->nextGroup = new MyGroup(450,50,type); this->addGroup(nextGroup); timer = new QTimer(this); connect(timer, SIGNAL(timeout()), this, SLOT(moveDownTest())); //设置状态,当前没有一个方块在界面中 for(int i = 0; i< 20 ;i ++) { for(int j = 0 ;j < 10 ;j ++) { type_ret[i][j] = NULL; } } testEnd = 0; } //碰撞检测 int MyScene::IsColliding() { QList list = this->collidingItems(lineItem1); if(list.size() != 0) { qDebug() << "line 1 "<<endl; return 1; } list = this->collidingItems(lineItem2); if(list.size() != 0) { //this->group->moveUp(); qDebug() << "line 2 "<<endl; return 1; } list = this->collidingItems(lineItem3); if(list.size() != 0) { qDebug() << "line 3 "<<endl; //this->group->moveUp(); return 1; } if(this->curGroup->isColliding()) { return 1; }; return 0; } //键盘事件 void MyScene::keyPressEvent(QKeyEvent *keyEvent) { if(!timer->isActive()) { return ; } if(keyEvent->key() == Qt::Key_Left || keyEvent->key() == Qt::Key_A) { curGroup->moveBy(-20,0); if(this->IsColliding()) { curGroup->moveBy(20,0); return ; } } else if(keyEvent->key() == Qt::Key_Right || keyEvent->key() == Qt::Key_D) { curGroup->moveBy(20,0); if(this->IsColliding()) { curGroup->moveBy(-20,0); return ; } } else if(keyEvent->key() == Qt::Key_Down || keyEvent->key() == Qt::Key_S) { curGroup->moveBy(0,20); if(this->IsColliding()) { curGroup->moveBy(0,-20); return ; } } else if(keyEvent->key() == Qt::Key_Q) { curGroup->zhuangLeft(); if(this->IsColliding()) { curGroup->zhuangRigth(); } } if(keyEvent->key() == Qt::Key_Space) { this->testEnd ++ ; while(!this->IsColliding()) { curGroup->moveBy(0,20); } curGroup->moveBy(0,-20); } if(keyEvent->key() == Qt::Key_P) { if(timer->isActive()) { timer->stop(); } else { timer->start(500); } } } //把一组图形放到scene中 void MyScene::addGroup(MyGroup *group) { this->addItem(group->list.at(0)); this->addItem(group->list.at(1)); this->addItem(group->list.at(2)); this->addItem(group->list.at(3)); } void MyScene::creatNewGroup() { qsrand(QTime(0,0,0).secsTo(QTime::currentTime())); int type = qrand(); type = type % 7; curGroup = new MyGroup(280,0,nextGroup->Type_style); this->removeItem(nextGroup->list.at(0)); this->removeItem(nextGroup->list.at(1)); this->removeItem(nextGroup->list.at(2)); this->removeItem(nextGroup->list.at(3)); this->nextGroup = new MyGroup(450,50,type); this->addGroup(nextGroup); this->addGroup(curGroup); testEnd = 0; } void MyScene::setRet() { //此处为了解决下面移动问题采用一种笨方法 int row[4]; int count = 0; QGraphicsItem * item ; //qDebug() << curGroup->pos_x << curGroup->pos_y; foreach (item, curGroup->list) { int i = (item->pos().x() - 200) / 20; int j = item->pos().y() / 20; qDebug() << i << j ; type_ret[j][i] = item; row[count] = j; count ++ ; } //从开始遍历,仅仅遍历row中存在的,也就是刚才存放过的 //如果消除的顺序不是cong'shang倒下就会出现下面的数据无法处理 for(int i = 0 ; i < 20;i ++ ) { if(testRet(i)) { destroyFromRet(i); } } } int MyScene::testRet(int row) { for(int i = 0 ; i < 10 ; i++) { if(type_ret[row][i] == NULL) return 0; } return 1; } int MyScene::destroyFromRet(int row) { int pos_x = 210; int pos_y = row * 20 + 10; //删除一行的元素 for(int i = 0 ; i < 10 ; i++) { this->removeItem(type_ret[row][i]); type_ret[row][i] = NULL ; } moveDownRet(row); return 0; } int MyScene::moveDownRet(int row) { for(int i = row - 1 ;i >-1 ; i --) { for(int j = 0 ; j < 10 ;j ++) { if(type_ret[i][j]) { type_ret[i][j]->moveBy(0,20); type_ret[i+1][j] = type_ret[i][j]; type_ret[i][j] = NULL; } } } return 0; } void MyScene::strat() { timer->start(500); } void MyScene::stop() { timer->stop(); } void MyScene::starNewGame() { timer->stop(); QGraphicsItem * item ; foreach(item,curGroup->list) { this->removeItem(item); } foreach(item,nextGroup->list) { this->removeItem(item); } for(int i = 0 ; i < 20; i ++) { for(int j = 0 ; j < 10 ; j++) { if(type_ret[i][j]) { this->removeItem(type_ret[i][j]); type_ret[i][j] = NULL; } } } qsrand(QTime(0,0,0).secsTo(QTime::currentTime())); int type = qrand(); type = type % 7; this->curGroup = new MyGroup(280,0,type); this->addGroup(curGroup); qDebug() << "clear" <<endl; type = qrand(); type = type % 7; this->nextGroup = new MyGroup(450,50,type); this->addGroup(nextGroup); } //下落的函数 int MyScene::moveDownTest() { if(curGroup == NULL) { return 0; } curGroup->moveBy(0,20); if(this->IsColliding()) { if(testEnd == 0) { //比赛结束 //QMessageBox::warning(this, tr("game over"),tr("GAME OVER\n" "start new game"),QMessageBox::Ok); qDebug() << "game over"; this->starNewGame(); return 0; } curGroup->moveBy(0,-20); setRet(); curGroup->clear(); creatNewGroup(); return 0; } testEnd ++; return 1; }
4.mainwindow.cpp
#include "mainwindow.h" #include "ui_mainwindow.h" #include "myscene.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); this->setGeometry(0,30,800,420); ui->graphicsView->setGeometry(0,0,610,420); scene = new MyScene (); ui->graphicsView->setScene(scene); } MainWindow::~MainWindow() { delete ui; } void MainWindow::on_pushButton_clicked() { scene->strat(); } void MainWindow::on_pushButton_2_clicked() { scene->stop(); }
需要在ui中添加两个按钮,和要给GraphicsView
有什么问题可以问我。可以从github上下载完整项目