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:
- OpenGL 3.3 for Desktop: https://rextester.com/MMC15477
- OpenGL ES 2.0 for Desktop, Android, and iOS: https://rextester.com/YHXB28198
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();
}