Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

Example of Snake game from NoobTuts tutorial rewritten in Qt C++ OpenGL



  • Example of Snake game from NoobTuts tutorial (Python Snake Game) rewritten in Qt C++ OpenGL

    Demo for Windows: Snake2DNoobTuts_OpenGLES20_Qt5Cpp (11 MB)

    There are two versions of sources:

    Snake2DNoobTuts_OpenGLES20_Qt5Cpp.gif

    main.cpp (OpenGL 3.3)

    // Add this line to .pro:
    // win32: LIBS += -lopengl32
    
    #ifdef _WIN32
    #include <windows.h>
    extern "C" __declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001;
    extern "C" __declspec(dllexport) DWORD AmdPowerXpressRequestHighPerformance = 0x00000001;
    #endif
    
    #include <QtWidgets/QApplication>
    #include <QtWidgets/QWidget>
    #include <QtWidgets/QOpenGLWidget>
    #include <QtWidgets/QVBoxLayout>
    #include <QtWidgets/QHBoxLayout>
    #include <QtWidgets/QLabel>
    #include <QtGui/QOpenGLShaderProgram>
    #include <QtGui/QOpenGLBuffer>
    #include <QtGui/QMatrix4x4>
    #include <QtGui/QKeyEvent>
    #include <QtCore/QList>
    #include <QtCore/QMutableListIterator>
    #include <QtCore/QTimer>
    #include <QtCore/QRandomGenerator>
    
    class OpenGLWidget : public QOpenGLWidget {
        Q_OBJECT
    public:
        OpenGLWidget(QWidget *parent = nullptr) : QOpenGLWidget(parent) {
            setFocusPolicy(Qt::StrongFocus);
        }
    signals:
        void updateScore(QString score);
        void updateLives(QString lives);
    private slots:
        void onUpdate() {
            // Move snake
            // Insert new position in the beginning of the snake list
            m_snake.insert(0, m_snake[0] + m_snakeDir);
            m_snake.removeLast();
            // Collision with itself
            int hx = m_snake[0].x();
            int hy = m_snake[0].y();
            for (int i = 0; i < m_snake.length(); i++) {
                if (i == 0)
                    continue;
                if (hx == m_snake[i].x() && hy == m_snake[i].y()) {
                    m_food.clear();
                    m_snake.clear();
                    m_snake.append(m_startPos);
                    m_snakeDir = m_startDir;
                    emit updateLives("Lives: " + QString::number(--m_lives));
                    update();
                    return;
                }
            }
            // Spawn food
            // Spawn food with 5% chance
            int r = QRandomGenerator::global()->bounded(0, 20);
            if (r == 0) {
                int x = QRandomGenerator::global()->bounded(0, m_fieldWidth);
                int y = QRandomGenerator::global()->bounded(0, m_fieldHeight);
                m_food.append(QVector2D(x, y));
            }
            // Let the snake eat the food
            // Get the snake's head x and y position
            QMutableListIterator<QVector2D> i(m_food);
            while (i.hasNext()) {
                QVector2D f = i.next();
                if (hx == f.x() && hy == f.y()) { // Is the head where the food is?
                    m_snake.append(QVector2D(f.x(), f.y())); // Make the snake longer
                    i.remove(); // Remove the food
                    m_score += 10;
                    emit updateScore("Score: " + QString::number(m_score));
                }
            }
            // Collisions with borders
            if (hx < 0 || m_fieldWidth <= hx ||
                hy < 0 || m_fieldHeight <= hy)
            {
                m_lives--;
                m_food.clear();
                m_snake.clear();
                m_snake.append(m_startPos);
                m_snakeDir = m_startDir;
                if (m_lives == 0) {
                    m_lives = 3;
                    m_score = 0;
                    emit updateScore("Score: " + QString::number(m_score));
                }
                emit updateLives("Lives: " + QString::number(m_lives));
            }
            update();
        }
    private:
        QOpenGLShaderProgram m_program;
        QOpenGLBuffer m_vertPosBuffer;
        float m_fieldWidth = 20.f; // Internal resolution
        float m_fieldHeight = 20.f; // Internal resolution
        QMatrix4x4 m_projMatrix;
        QMatrix4x4 m_modelMatrix;
        QList<QVector2D> m_snake; // Snake list of (x, y) positions
        QList<QVector2D> m_food;
        QVector2D m_startPos = QVector2D(5.f, 10.f);
        QVector2D m_startDir = QVector2D(1, 0);
        QVector2D m_snakeDir = m_startDir; // Snake movement direction
        QTimer m_timer;
        int m_score = 0;
        int m_lives = 3;
    
        void initializeGL() override {
    //        qDebug() << QString("w = %1, h = %2").arg(width()).arg(height());
            glClearColor(0.f, 0.f, 0.f, 1.f);
            glEnable(GL_DEPTH_TEST);
            const char *vertShaderSrc =
                    "#version 330 core\n"
                    "in vec3 aPosition;"
                    "uniform mat4 uMvpMatrix;"
                    "void main()"
                    "{"
                    "    gl_Position = uMvpMatrix * vec4(aPosition, 1.0);"
                    "}";
            const char *fragShaderSrc =
                    "#version 330 core\n"
                    "uniform vec4 uColor;"
                    "out vec4 fragColor;"
                    "void main()"
                    "{"
                    "    fragColor = uColor;"
                    "}";
            m_program.addShaderFromSourceCode(QOpenGLShader::Vertex, vertShaderSrc);
            m_program.addShaderFromSourceCode(QOpenGLShader::Fragment, fragShaderSrc);
            m_program.link();
            m_program.bind();
            float vertPositions[] = {
                0.f, 0.f, 0.f,
                1.f, 0.f, 0.f,
                0.f, 1.f, 0.f,
                1.f, 1.f, 0.f
            };
            m_vertPosBuffer.create();
            m_vertPosBuffer.bind();
            m_vertPosBuffer.allocate(vertPositions, sizeof(vertPositions));
            m_program.bindAttributeLocation("aPosition", 0);
            m_program.setAttributeBuffer(0, GL_FLOAT, 0, 3);
            m_program.enableAttributeArray(0);
            m_projMatrix.ortho(0.f, m_fieldWidth, 0.f, m_fieldHeight, -100.f, 100.f);
            m_snake.append(m_startPos);
            emit updateScore("Score: " + QString::number(m_score));
            emit updateLives("Lives: " + QString::number(m_lives));
            connect(&m_timer, &QTimer::timeout, this, &OpenGLWidget::onUpdate);
            m_timer.start(200);
        }
        void paintGL() override {
            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
            drawFood();
            drawSnake();
        }
        void resizeGL(int w, int h) override {
            glViewport(0, 0, w, h);
        }
        void keyPressEvent(QKeyEvent *e) override {
            if (e->key() == Qt::Key_W || e->key() == Qt::Key_Up)
                if (m_snakeDir != QVector2D(0, -1))
                    m_snakeDir = QVector2D(0, 1);
            if (e->key() == Qt::Key_S || e->key() == Qt::Key_Down)
                if (m_snakeDir != QVector2D(0, 1))
                    m_snakeDir = QVector2D(0, -1);
            if (e->key() == Qt::Key_A || e->key() == Qt::Key_Left)
                if (m_snakeDir != QVector2D(1, 0))
                    m_snakeDir = QVector2D(-1, 0);
            if (e->key() == Qt::Key_D || e->key() == Qt::Key_Right)
                if (m_snakeDir != QVector2D(-1, 0))
                    m_snakeDir = QVector2D(1, 0);
        }
        void drawRect(float x, float y, float width, float height, QColor color) {
            m_modelMatrix.setToIdentity();
            m_modelMatrix.translate(QVector3D(x, y, 0.f));
            m_modelMatrix.scale(width, height, 1.f);
            m_program.bind();
            m_program.setUniformValue("uMvpMatrix", m_projMatrix * m_modelMatrix);
            m_program.setUniformValue("uColor", color);
            glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
        }
        void drawSnake() {
            foreach (const QVector2D &cell, m_snake) {
                drawRect(cell.x(), cell.y(), 1, 1, QColor(255, 255, 255, 255));
            }
        }
        void drawFood() {
            foreach (const QVector2D &f, m_food) {
                drawRect(f.x(), f.y(), 1, 1, QColor(0, 0, 255, 255));
            }
        }
    };
    
    class Window : public QWidget {
    public:
        Window(QWidget *parent = nullptr) : QWidget(parent) {
            setWindowTitle("C++ OpenGL");
            setFixedSize(239, 268);
            QFont font = QFont("Areal", 14);
            m_labelScore.setFont(font);
            m_labelScore.setText("");
            m_labelScore.setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
            m_labelLives.setFont(font);
            m_labelLives.setText("");
            m_labelLives.setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
            QHBoxLayout *hboxOutput = new QHBoxLayout();
            hboxOutput->addWidget(&m_labelScore);
            hboxOutput->addWidget(&m_labelLives);
            QHBoxLayout *hbox = new QHBoxLayout();
            hbox->addWidget(&m_openGLWidget);
            QVBoxLayout *vbox = new QVBoxLayout(this);
            vbox->addLayout(hboxOutput);
            vbox->addLayout(hbox);
            connect(&m_openGLWidget, &OpenGLWidget::updateScore,
                    [this](const QString &s){ m_labelScore.setText(s); });
            connect(&m_openGLWidget, &OpenGLWidget::updateLives,
                    [this](const QString &s){ m_labelLives.setText(s); });
        }
    private:
        OpenGLWidget m_openGLWidget;
        QLabel m_labelScore;
        QLabel m_labelLives;
    };
    
    #include "main.moc"
    
    int main(int argc, char *argv[]) {
        QApplication a(argc, argv);
        Window w;
        w.show();
        return a.exec();
    }
    

    main.cpp (OpenGL ES 2.0)

    // Add this line to .pro:
    // win32: LIBS += -lopengl32
    
    #ifdef _WIN32
    #include <windows.h>
    extern "C" __declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001;
    extern "C" __declspec(dllexport) DWORD AmdPowerXpressRequestHighPerformance = 0x00000001;
    #endif
    
    #include <QtWidgets/QApplication>
    #include <QtWidgets/QWidget>
    #include <QtWidgets/QOpenGLWidget>
    #include <QtWidgets/QVBoxLayout>
    #include <QtWidgets/QHBoxLayout>
    #include <QtWidgets/QLabel>
    #include <QtGui/QOpenGLShaderProgram>
    #include <QtGui/QOpenGLBuffer>
    #include <QtGui/QMatrix4x4>
    #include <QtGui/QKeyEvent>
    #include <QtCore/QList>
    #include <QtCore/QMutableListIterator>
    #include <QtCore/QTimer>
    #include <QtCore/QRandomGenerator>
    
    class OpenGLWidget : public QOpenGLWidget {
        Q_OBJECT
    public:
        OpenGLWidget(QWidget *parent = nullptr) : QOpenGLWidget(parent) {
            setFocusPolicy(Qt::StrongFocus);
        }
    signals:
        void updateScore(QString score);
        void updateLives(QString lives);
    private slots:
        void onUpdate() {
            // Move snake
            // Insert new position in the beginning of the snake list
            m_snake.insert(0, m_snake[0] + m_snakeDir);
            m_snake.removeLast();
            // Collision with itself
            int hx = m_snake[0].x();
            int hy = m_snake[0].y();
            for (int i = 0; i < m_snake.length(); i++) {
                if (i == 0)
                    continue;
                if (hx == m_snake[i].x() && hy == m_snake[i].y()) {
                    m_food.clear();
                    m_snake.clear();
                    m_snake.append(m_startPos);
                    m_snakeDir = m_startDir;
                    emit updateLives("Lives: " + QString::number(--m_lives));
                    update();
                    return;
                }
            }
            // Spawn food
            // Spawn food with 5% chance
            int r = QRandomGenerator::global()->bounded(0, 20);
            if (r == 0) {
                int x = QRandomGenerator::global()->bounded(0, m_fieldWidth);
                int y = QRandomGenerator::global()->bounded(0, m_fieldHeight);
                m_food.append(QVector2D(x, y));
            }
            // Let the snake eat the food
            // Get the snake's head x and y position
            QMutableListIterator<QVector2D> i(m_food);
            while (i.hasNext()) {
                QVector2D f = i.next();
                if (hx == f.x() && hy == f.y()) { // Is the head where the food is?
                    m_snake.append(QVector2D(f.x(), f.y())); // Make the snake longer
                    i.remove(); // Remove the food
                    m_score += 10;
                    emit updateScore("Score: " + QString::number(m_score));
                }
            }
            // Collisions with borders
            if (hx < 0 || m_fieldWidth <= hx ||
                hy < 0 || m_fieldHeight <= hy)
            {
                m_lives--;
                m_food.clear();
                m_snake.clear();
                m_snake.append(m_startPos);
                m_snakeDir = m_startDir;
                if (m_lives == 0) {
                    m_lives = 3;
                    m_score = 0;
                    emit updateScore("Score: " + QString::number(m_score));
                }
                emit updateLives("Lives: " + QString::number(m_lives));
            }
            update();
        }
    private:
        QOpenGLShaderProgram m_program;
        QOpenGLBuffer m_vertPosBuffer;
        float m_fieldWidth = 20.f; // Internal resolution
        float m_fieldHeight = 20.f; // Internal resolution
        QMatrix4x4 m_projMatrix;
        QMatrix4x4 m_modelMatrix;
        QList<QVector2D> m_snake; // Snake list of (x, y) positions
        QList<QVector2D> m_food;
        QVector2D m_startPos = QVector2D(5.f, 10.f);
        QVector2D m_startDir = QVector2D(1, 0);
        QVector2D m_snakeDir = m_startDir; // Snake movement direction
        QTimer m_timer;
        int m_score = 0;
        int m_lives = 3;
    
        void initializeGL() override {
    //        qDebug() << QString("w = %1, h = %2").arg(width()).arg(height());
            glClearColor(0.f, 0.f, 0.f, 1.f);
            glEnable(GL_DEPTH_TEST);
            const char *vertShaderSrc =
                    "attribute vec3 aPosition;"
                    "uniform mat4 uMvpMatrix;"
                    "void main()"
                    "{"
                    "    gl_Position = uMvpMatrix * vec4(aPosition, 1.0);"
                    "}";
            const char *fragShaderSrc =
                    "uniform vec4 uColor;"
                    "void main()"
                    "{"
                    "    gl_FragColor = uColor;"
                    "}";
            m_program.addShaderFromSourceCode(QOpenGLShader::Vertex, vertShaderSrc);
            m_program.addShaderFromSourceCode(QOpenGLShader::Fragment, fragShaderSrc);
            m_program.link();
            m_program.bind();
            float vertPositions[] = {
                0.f, 0.f, 0.f,
                1.f, 0.f, 0.f,
                0.f, 1.f, 0.f,
                1.f, 1.f, 0.f
            };
            m_vertPosBuffer.create();
            m_vertPosBuffer.bind();
            m_vertPosBuffer.allocate(vertPositions, sizeof(vertPositions));
            m_program.bindAttributeLocation("aPosition", 0);
            m_program.setAttributeBuffer(0, GL_FLOAT, 0, 3);
            m_program.enableAttributeArray(0);
            m_projMatrix.ortho(0.f, m_fieldWidth, 0.f, m_fieldHeight, -100.f, 100.f);
            m_snake.append(m_startPos);
            emit updateScore("Score: " + QString::number(m_score));
            emit updateLives("Lives: " + QString::number(m_lives));
            connect(&m_timer, &QTimer::timeout, this, &OpenGLWidget::onUpdate);
            m_timer.start(200);
        }
        void paintGL() override {
            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
            drawFood();
            drawSnake();
        }
        void resizeGL(int w, int h) override {
            glViewport(0, 0, w, h);
        }
        void keyPressEvent(QKeyEvent *e) override {
            if (e->key() == Qt::Key_W || e->key() == Qt::Key_Up)
                if (m_snakeDir != QVector2D(0, -1))
                    m_snakeDir = QVector2D(0, 1);
            if (e->key() == Qt::Key_S || e->key() == Qt::Key_Down)
                if (m_snakeDir != QVector2D(0, 1))
                    m_snakeDir = QVector2D(0, -1);
            if (e->key() == Qt::Key_A || e->key() == Qt::Key_Left)
                if (m_snakeDir != QVector2D(1, 0))
                    m_snakeDir = QVector2D(-1, 0);
            if (e->key() == Qt::Key_D || e->key() == Qt::Key_Right)
                if (m_snakeDir != QVector2D(-1, 0))
                    m_snakeDir = QVector2D(1, 0);
        }
        void drawRect(float x, float y, float width, float height, QColor color) {
            m_modelMatrix.setToIdentity();
            m_modelMatrix.translate(QVector3D(x, y, 0.f));
            m_modelMatrix.scale(width, height, 1.f);
            m_program.bind();
            m_program.setUniformValue("uMvpMatrix", m_projMatrix * m_modelMatrix);
            m_program.setUniformValue("uColor", color);
            glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
        }
        void drawSnake() {
            foreach (const QVector2D &cell, m_snake) {
                drawRect(cell.x(), cell.y(), 1, 1, QColor(255, 255, 255, 255));
            }
        }
        void drawFood() {
            foreach (const QVector2D &f, m_food) {
                drawRect(f.x(), f.y(), 1, 1, QColor(0, 0, 255, 255));
            }
        }
    };
    
    class Window : public QWidget {
    public:
        Window(QWidget *parent = nullptr) : QWidget(parent) {
            setWindowTitle("C++ OpenGL");
            setFixedSize(239, 268);
            QFont font = QFont("Areal", 14);
            m_labelScore.setFont(font);
            m_labelScore.setText("");
            m_labelScore.setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
            m_labelLives.setFont(font);
            m_labelLives.setText("");
            m_labelLives.setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
            QHBoxLayout *hboxOutput = new QHBoxLayout();
            hboxOutput->addWidget(&m_labelScore);
            hboxOutput->addWidget(&m_labelLives);
            QHBoxLayout *hbox = new QHBoxLayout();
            hbox->addWidget(&m_openGLWidget);
            QVBoxLayout *vbox = new QVBoxLayout(this);
            vbox->addLayout(hboxOutput);
            vbox->addLayout(hbox);
            connect(&m_openGLWidget, &OpenGLWidget::updateScore,
                    [this](const QString &s){ m_labelScore.setText(s); });
            connect(&m_openGLWidget, &OpenGLWidget::updateLives,
                    [this](const QString &s){ m_labelLives.setText(s); });
        }
    private:
        OpenGLWidget m_openGLWidget;
        QLabel m_labelScore;
        QLabel m_labelLives;
    };
    
    #include "main.moc"
    
    int main(int argc, char *argv[]) {
        QApplication a(argc, argv);
        Window w;
        w.show();
        return a.exec();
    }
    

Log in to reply