贪吃蛇实现
对于游戏而言,
我们需要一个QGraphicsScene,作为游戏发生的舞台;
一个QGraphicsView,作为观察游戏舞台的组件;
以及若干元素,用于表示游戏对象,比如蛇、食物以及障碍物等。
绘制地图
需求:
创建 400* 400 的20*20灰色格子的黑色边界的地图
成品代码:
#ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> class QGraphicsScene; class QGraphicsView; class GameController; class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(QWidget *parent = 0); ~MainWindow(); //private slots: // void adjustViewSize(); private: void initScene(); void initSceneBackground(); QGraphicsScene *scene; QGraphicsView *view; GameController *game; }; #endif // MAINWINDOW_H
#include <QGraphicsView> #include <QTimer> #include <qaction.h> #include <qmenubar.h> #include <qapplication.h> #include <qmessagebox.h> #include "mainwindow.h" //#include "constant.h" const int TILE_SIZE = 20; MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), scene(new QGraphicsScene(this)), view(new QGraphicsView(scene, this))/*, game(new GameController(*scene, this))*/ { setCentralWidget(view); resize(600, 600); initScene(); initSceneBackground(); QTimer::singleShot(0, this, SLOT(adjustViewSize())); } void MainWindow::initScene() { scene->setSceneRect(-100, -100, 200, 200); } void MainWindow::initSceneBackground() { QPixmap bg(TILE_SIZE, TILE_SIZE); QPainter p(&bg); p.setBrush(QBrush(Qt::gray)); p.drawRect(0, 0, TILE_SIZE, TILE_SIZE); view->setBackgroundBrush(QBrush(bg)); } MainWindow::~MainWindow() { }
思路:
1.初始化地图大小 600600
2.初始矩形的逻辑坐标为(-100,-100)为原点
长宽逻辑长为200200
3.初始化背景为QBrush为灰色,
QPixmap作为背景画刷,铺满整个视图,(默认是重复平铺),大小10*10
注意:
static void QTimer::singleShot(int msec, QObject * receiver, const char * member);
在 msec 毫秒之后,调用 receiver 的 member 槽函数。在我们的代码中,第一个参数传递的是 0,也就是 0ms 之后,调用this->adjustViewSize()
效果图:
设计蛇和食物
设计食物
需求
绘制
一个红色的小圆饼,大小要比地图中的一个方格要小
实现
思路:
代码:
// food.h // #ifndef FOOD_H #define FOOD_H #include <QGraphicsItem> class Food : public QGraphicsItem { public: Food(qreal x, qreal y); QRectF boundingRect() const override; void paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) override; QPainterPath shape() const override; }; #endif // FOOD_H // food.cpp // #include <QPainter> #include "constants.h" #include "food.h" static const qreal FOOD_RADIUS = 3.0; Food::Food(qreal x, qreal y) { setPos(x, y); setData(GD_Type, GO_Food);//该图形元素添加额外的数据信息 } QRectF Food::boundingRect() const //返回一个用于包裹住图形元素的矩形 { return QRectF(-TILE_SIZE, -TILE_SIZE, TILE_SIZE * 2, TILE_SIZE * 2 ); } void Food::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) //使用QPainter将图形元素绘制出来。 { painter->save(); painter->setRenderHint(QPainter::Antialiasing); painter->fillPath(shape(), Qt::red); painter->restore(); } QPainterPath Food::shape() const //矢量轮廓线,绘制路径 { QPainterPath p; p.addEllipse(QPointF(TILE_SIZE / 2, TILE_SIZE / 2), FOOD_RADIUS, FOOD_RADIUS); //draw circle return p; }
#ifndef GAMECONTROLLER_H #define GAMECONTROLLER_H #include <QObject> #include <QGraphicsScene> #include <QTimer> #include <QAction> class Snake; class Food; class GameController : public QObject { Q_OBJECT public: GameController(); GameController(QGraphicsScene *scene, QObject *parent); virtual ~GameController() {} public slots: void pause(); void resume(); private: QAction * resumeAction; QTimer timer; QGraphicsScene *scene; Snake *snake; }; #endif // GAMECONTROLLER_H //gamecontroller.cpp #include <QEvent> #include <QGraphicsScene> #include <QKeyEvent> #include <QMessageBox> #include <QAction> #include "gamecontroller.h" #include "food.h" //#include "snake.h" GameController::GameController(QGraphicsScene *scene, QObject *parent) : QObject(parent), scene(scene) /*,snake(new Snake(this))*/ { timer.start(1000/33); Food *a1 = new Food(0, -50); scene->addItem(a1); //scene->addItem(snake); scene->installEventFilter(this); resume(); } void GameController::pause() { disconnect(&timer, SIGNAL(timeout()), scene, SLOT(advance())); } void GameController::resume() { connect(&timer, SIGNAL(timeout()), scene, SLOT(advance())); }
GameController的工作是,初始化场景中的游戏对象,开始游戏循环。每一个游戏都需要有一个游戏循环,类型于事件循环。想象一个每秒滴答 30 次的表。每次响起滴答声,游戏对象才有机会执行相应的动作:移动、检查碰撞、攻击或者其它一些游戏相关的活动。为方便起见,我们将这一次滴答成为一帧,那么,每秒 30 次滴答,就是每秒 30 帧。游戏循环通常使用定时器实现,因为应用程序不仅仅是一个游戏循环,还需要响应其它事件,比如游戏者的鼠标键盘操作。正因为如此,我们不能简单地使用无限的 for 循环作为游戏循环。
在 Graphics View Framework 中,每一帧都应该调用一个称为advance()的函数。QGraphicsScene::advance()会调用场景中每一个元素自己的advance()函数。所以,如果图形元素需要做什么事,必须重写QGraphicsItem的advance(),然后在游戏循环中调用这个函数。
设计蛇
需求
1.蛇具有复杂得多的形状。因为蛇的形状随着游戏者的控制而不同,因此,我们必须找出一个能够恰好包含蛇头和所有身体块的矩形。这也是 boundingRect() 函数所要解决的问题。
2.蛇会长大(比如吃了食物之后)。因此,我们需要在蛇对象中增加一个用于代表蛇身体长度的growing变量:当growing为正数时,蛇的身体增加一格;当growing为负数时,蛇的身体减少一格。
3.advance()函数用于编码移动部分,这个函数会在一秒内调用 30 次(这是我们在GameController的定时器中决定的)。
实现
思路:
1.绘制一个黄色方框 添加到scene里面
2.控制小蛇
绘制蛇
代码:
#ifndef SNAKE_H #define SNAKE_H #include <QGraphicsItem> #include "gamecontroller.h" class Snake :public QGraphicsItem { public: Snake(); Snake(GameController *controller); QRectF boundingRect() const override; QPainterPath shape() const override; void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override; private: QPointF head; QList<QPointF> tail; }; //snake.cpp #include <QPainter> #include <constants.h> #include "snake.h" static const qreal SNAKE_SIZE = TILE_SIZE; Snake::Snake(GameController *controller) : head(0, 0)/*, growing(7), speed(5), moveDirection(NoMove), controller(controller)*/ { } QRectF Snake::boundingRect() const { qreal minX = head.x(); qreal minY = head.y(); qreal maxX = head.x(); qreal maxY = head.y(); foreach (QPointF p, tail) { maxX = p.x() > maxX ? p.x() : maxX; maxY = p.y() > maxY ? p.y() : maxY; minX = p.x() < minX ? p.x() : minX; minY = p.y() < minY ? p.y() : minY; } QPointF tl = mapFromScene(QPointF(minX, minY)); QPointF br = mapFromScene(QPointF(maxX, maxY)); QRectF bound = QRectF(tl.x(), // x tl.y(), // y br.x() - tl.x() + SNAKE_SIZE, // width br.y() - tl.y() + SNAKE_SIZE //height ); return bound; } QPainterPath Snake::shape() const { QPainterPath path; path.setFillRule(Qt::WindingFill); path.addRect(QRectF(0, 0, SNAKE_SIZE, SNAKE_SIZE)); foreach (QPointF p, tail) { QPointF itemp = mapFromScene(p); path.addRect(QRectF(itemp.x(), itemp.y(), SNAKE_SIZE, SNAKE_SIZE)); } return path; } void Snake::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) { painter->save(); painter->fillPath(shape(), Qt::yellow); painter->restore(); } #endif // SNAKE_H //gamecontroller.cpp #include "gamecontroller.h" #include "food.h" #include "snake.h" GameController::GameController(QGraphicsScene *scene, QObject *parent) : QObject(parent), scene(scene), snake(new Snake(this)) { timer.start(1000/33); Food *a1 = new Food(0, -50); scene->addItem(a1); scene->addItem(snake); scene->installEventFilter(this); resume(); }
控制蛇
详解 QT Event 以及 Event Filter 事件处理
代码实现:
#ifndef GAMECONTROLLER_H #define GAMECONTROLLER_H #include <QObject> #include <QGraphicsScene> #include <QTimer> #include <QAction> class Snake; class Food; class GameController : public QObject { Q_OBJECT public: GameController(); GameController(QGraphicsScene &scene, QObject *parent); virtual ~GameController() {} public slots: void pause(); void resume(); protected: bool eventFilter(QObject *object, QEvent *event); private: void handleKeyPressed(QKeyEvent *event); void addNewFood(); void setResume(); private: QAction * resumeAction; QTimer timer; QGraphicsScene &scene; Snake *snake; bool isPause; }; #endif // GAMECONTROLLER_H //gamecontroller.cpp #include <QEvent> #include <QGraphicsScene> #include <QKeyEvent> #include <QMessageBox> #include <QAction> #include "gamecontroller.h" #include "food.h" #include "snake.h" #include "mainwindow.h" GameController::GameController(QGraphicsScene &scene, QObject *parent) : QObject(parent), scene(scene), snake(new Snake(*this)) { timer.start(1000/33); Food *a1 = new Food(0, -50); scene.addItem(a1); scene.addItem(snake); scene.installEventFilter(this); resume(); } void GameController::() { disconnect(&timer, SIGNAL(timeout()), &scene, SLOT(advance())); } void GameController::resume() { connect(&timer, SIGNAL(timeout()), &scene, SLOT(advance())); } bool GameController::eventFilter(QObject *object, QEvent *event) { if (event->type() == QEvent::KeyPress) { handleKeyPressed((QKeyEvent *)event); return true; } else { return QObject::eventFilter(object, event); } } void GameController::handleKeyPressed(QKeyEvent *event) { if (!isPause) switch (event->key()) { case Qt::Key_Left: snake->setMoveDirection(Snake::MoveLeft); break; case Qt::Key_Right: snake->setMoveDirection(Snake::MoveRight); break; case Qt::Key_Up: snake->setMoveDirection(Snake::MoveUp); break; case Qt::Key_Down: snake->setMoveDirection(Snake::MoveDown); break; case Qt::Key_Space: (); break; } else resume(); }