Small examples for game development in Qt, OpenGL, Box2D, Bullet Physics, and OpenAL
-
Detecting a mobile browser with Qt WebAssembly (topic)
It is important to determine whether a WebAssembly game is running on a mobile device or a personal computer in order to show or hide the joystick.
#ifdef Q_OS_WASM #include <emscripten.h> #endif #include "widget.h" #ifdef Q_OS_WASM EM_JS(bool, isMobile, (), { return /iPhone|iPad|iPod|Android/i.test(navigator.userAgent); }) #endif Widget::Widget(QWidget *parent) : QWidget(parent) { bool isMobile = false; #ifdef Q_OS_WASM isMobile = isMobile(); #endif #if defined Q_OS_ANDROID || defined Q_OS_IOS isMobile = true; #endif qDebug() << "isMobile:" << isMobile; }
Don't forget to add
wasm:
here:QT += core gui widgets CONFIG += c++17 wasm: INCLUDEPATH += "C:\emsdk\upstream\emscripten\cache\sysroot\include" SOURCES += \ main.cpp \ widget.cpp HEADERS += \ widget.h
-
Get data with Qt client from deployed Node.js server using WebSockets (topic)
My following Qt 6.6.3 example works on Android, Desktop, and Web (with Qt WebAssembly). It prints data received from the server. The server contains the Box2D-WASM library. It sends the gravity value in JSON format when a client is connected. It is useful example to make multiplayer games with physics on the server side. I have deployed the example on free Glitch hosting: https://glitch.com/edit/#!/merciful-regal-soursop from the GitHub repository: send-gravity-from-server-to-client-box2d-wasm-js The client contains only one
main.cpp
file. It outputs the following information to the console:connected "{\"action\":\"scGravity\",\"data\":\"{\\\"x\\\":0,\\\"y\\\":-3}\"}"
You should download OpenSSL to run the following example on Android. Open the following window in Qt Creator (
Edit
>Preferences...
>Devices
>Android
):Add the following path to your pro-file:
QT += core gui websockets widgets android: include(C:/Qt/Tools/OpenSSL-1.1.1j/Win_x64/bin/openssl.pri) CONFIG += c++17 SOURCES += \ main.cpp
Read how to add OpenSSL to your CMake project if you use CMake instead of QMake in the Qt documentaion: Adding OpenSSL Support for Android
Build the following example for Android, Desktop, and WebAssembly (I have tested it):
main.cpp
#include <QtNetwork/QNetworkRequest> #include <QtWebSockets/QWebSocket> #include <QtWidgets/QApplication> #include <QtWidgets/QWidget> class Widget : public QWidget { Q_OBJECT public: Widget() { setWindowTitle("Show gravity from server with Box2D-WASM"); resize(420, 200); connect(&m_webSocket, &QWebSocket::connected, this, &Widget::onConnected); connect(&m_webSocket, &QWebSocket::textMessageReceived, this, &Widget::onMessageReceived); connect(&m_webSocket, &QWebSocket::errorOccurred, this, &Widget::onErrorOccurred); QUrl url("wss://merciful-regal-soursop.glitch.me"); QNetworkRequest request; request.setUrl(url); request.setRawHeader(QByteArray("User-Agent"), QByteArray("Mozilla/5.0 " "(Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) " "Chrome/124.0.0.0 Safari/537.36")); m_webSocket.open(request); } private slots: void onConnected() { qDebug() << "connected"; } void onMessageReceived(const QString &message) { qDebug() << message; } void onErrorOccurred(QAbstractSocket::SocketError error) { qDebug() << "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"; qDebug() << "Error:" << error; qDebug() << "Device supports OpenSSL:" << QSslSocket::supportsSsl(); qDebug() << "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"; } private: QWebSocket m_webSocket; }; #include "main.moc" int main(int argc, char *argv[]) { QApplication app(argc, argv); Widget w; w.show(); return app.exec(); }
-
How to set up PyOpenAL
PyOpenAL (released Dec 17, 2019): https://pypi.org/project/PyOpenAL/
pip install PyOpenAL
You can use original OpenAL API but PyOpenAL has a helpful wrapper functions. If your audio file plays with problems try to open it in the Audacity and export it with
Encoding: Signed 16-bit PCM
: -
Play sound by button click with PyOpenAL
pip install PyOpenAL PySide6
import sys from openal import oalOpen from PySide6.QtWidgets import QApplication, QPushButton, QWidget class Widget(QWidget): def __init__(self): super().__init__() self.setWindowTitle("PyOpenAL, PySide6") self.resize(300, 150) self.btn = QPushButton("Play Sound", self) self.btn.clicked.connect(self.onPlay) self.source = oalOpen("assets/bounce.wav") def onPlay(self): self.source.play() if __name__ == "__main__": app = QApplication(sys.argv) w = Widget() w.show() sys.exit(app.exec())
These are the Audacity settings when exporting an audio file. By default for PyOpenAL it must be "Encoding: Signed 16-bit PCM":
-
Music volume slider
pip install PyOpenAL PySide6
The free audio file from itch is used in this example.
import sys from openal import oalOpen, oalQuit from PySide6.QtCore import Qt from PySide6.QtWidgets import (QApplication, QCheckBox, QHBoxLayout, QPushButton, QSlider, QVBoxLayout, QWidget) class Widget(QWidget): def __init__(self): super().__init__() self.setWindowTitle("PyOpenAL, PySide6") self.resize(300, 150) # Music check box playMusic = QCheckBox("Play music") # playMusic.toggle() # print(playMusic.isChecked()) playMusic.stateChanged.connect(self.onPlayMusic) # Sound button playSoundButton = QPushButton("Play sound") playSoundButton.clicked.connect(self.onPlaySound) mousicVolume = 0.1 # Volume slider volumeSlider = QSlider(Qt.Orientation.Horizontal) volumeSlider.setValue(int(mousicVolume * 100)) volumeSlider.valueChanged[int].connect(self.onChangeVolume) self.mousicSource = oalOpen("assets/infant_tour.wav") self.soundSource = oalOpen("assets/bounce.wav") self.mousicSource.set_gain(mousicVolume) self.mousicSource.set_looping(True) vbox = QVBoxLayout() vbox.addWidget(volumeSlider) vbox.addWidget(playMusic) vbox.addWidget(playSoundButton) vbox.addStretch(1) hbox = QHBoxLayout() hbox.addLayout(vbox) hbox.addStretch(1) self.setLayout(hbox) def onPlayMusic(self, state): if state == Qt.CheckState.Checked.value: self.mousicSource.play() else: self.mousicSource.pause() def onPlaySound(self): self.soundSource.play() def onChangeVolume(self, value): self.mousicSource.set_gain(value / 100) def closeEvent(self, event): oalQuit() if __name__ == "__main__": app = QApplication(sys.argv) w = Widget() w.show() sys.exit(app.exec())
-
Custom OpenGL button for WebAssembly and Android
You cannot make the standard Qt button for WebAssembly because of this bug: https://bugreports.qt.io/browse/QTBUG-120651 (you can use the patch from the "Gerrit Reviews") But OpenGL allows to make custom GUI elements.
WebAssembly demo on free Netlify hosting (QOpenGLWindow)
QOpenGLWindow
works on Android 7.1.2 in the mobile browser without problems. You can find a lot of GUI game assets on itch: https://itch.io/game-assets/tag-gui I use the following GUI game asset in this demo: https://wenrexa.itch.io/holoui I have used Free Texture Packer to pack to button textures to one texture + json-file. Text was added in GIMP.The same demo for SDL3:
Click buttons with color ID using glReadPixels
This example uses the following GUI asset: https://wenrexa.itch.io/holoui. You can find a lot of GUI assets on itch: https://itch.io/game-assets/tag-gui I packed the GUI textures using Free Texture Packer to one texture + JSON. RapidJSON is used to parse the JSON file.
Tools: SDL3, Emscripten, C++, CMake, OpenGL ES 2.0, GLM, stb_image, Free Texture Packer, and RapidJSON
-
Activating the discrete GeForce of Radeon video card and OpenGL 3.3 Core profile on laptops
You can make it with the following code. The first one is for GeForce. The second one is for Radeon. If you can GeForce the second like with be ignored.
#ifdef _WIN32 #include <windows.h> extern "C" __declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001; extern "C" __declspec(dllexport) DWORD AmdPowerXpressRequestHighPerformance = 0x00000001; #endif
main.cpp
#ifdef _WIN32 #include <windows.h> extern "C" __declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001; extern "C" __declspec(dllexport) DWORD AmdPowerXpressRequestHighPerformance = 0x00000001; #endif #include <QtGui/QOpenGLContext> #include <QtGui/QOpenGLFunctions> #include <QtGui/QSurfaceFormat> #include <QtWidgets/QApplication> #include <QtOpenGL/QOpenGLWindow> class OpenGLWindow : public QOpenGLWindow, private QOpenGLFunctions { void initializeGL() override { initializeOpenGLFunctions(); qDebug() << "OpenGL version:" << (const char*) glGetString(GL_VERSION); qDebug() << "GLSL version: " << (const char*) glGetString(GL_SHADING_LANGUAGE_VERSION); qDebug() << "Vendor: " << (const char*) glGetString(GL_VENDOR); } }; int main(int argc, char *argv[]) { QApplication::setAttribute(Qt::ApplicationAttribute::AA_UseDesktopOpenGL); QApplication app(argc, argv); QSurfaceFormat fmt; fmt.setDepthBufferSize(24); // Request OpenGL 3.3 core or OpenGL ES 3.0. if (QOpenGLContext::openGLModuleType() == QOpenGLContext::LibGL) { qDebug("Requesting 3.3 core context"); fmt.setVersion(3, 3); fmt.setProfile(QSurfaceFormat::CoreProfile); } else { qDebug("Requesting 3.0 context"); fmt.setVersion(3, 0); } OpenGLWindow w; w.setFormat(fmt); w.show(); return app.exec(); }
Output:
Requesting 3.3 core context OpenGL version: 3.3.0 NVIDIA 391.35 GLSL version: 3.30 NVIDIA via Cg compiler Vendor: NVIDIA Corporation
By default the laptops with two video cards use the integrated video card:
main.cpp
#include <QtGui/QOpenGLContext> #include <QtGui/QOpenGLFunctions> #include <QtGui/QSurfaceFormat> #include <QtWidgets/QApplication> #include <QtOpenGL/QOpenGLWindow> class OpenGLWindow : public QOpenGLWindow, private QOpenGLFunctions { void initializeGL() override { initializeOpenGLFunctions(); qDebug() << "OpenGL version:" << (const char*) glGetString(GL_VERSION); qDebug() << "GLSL version: " << (const char*) glGetString(GL_SHADING_LANGUAGE_VERSION); qDebug() << "Vendor: " << (const char*) glGetString(GL_VENDOR); } }; int main(int argc, char *argv[]) { QApplication::setAttribute(Qt::ApplicationAttribute::AA_UseDesktopOpenGL); QApplication app(argc, argv); QSurfaceFormat fmt; fmt.setDepthBufferSize(24); // Request OpenGL 3.3 core or OpenGL ES 3.0. if (QOpenGLContext::openGLModuleType() == QOpenGLContext::LibGL) { qDebug("Requesting 3.3 core context"); fmt.setVersion(3, 3); fmt.setProfile(QSurfaceFormat::CoreProfile); } else { qDebug("Requesting 3.0 context"); fmt.setVersion(3, 0); } OpenGLWindow w; w.setFormat(fmt); w.show(); return app.exec(); }
Output:
Requesting 3.3 core context OpenGL version: 3.1.0 - Build 9.17.10.4459 GLSL version: 1.40 - Intel Build 9.17.10.4459 Vendor: Intel
-
-
Rotate a 3D vector around X, Y, Z axes
In my example, three vectors are rotated 10 degrees around the X, Y and Z axes:
- Rotate the vector (0.f, 0.f, 1.f) 10 degrees around the X axis. Output: (0, -0.173648, 0.984808)
- Rotate the vector (1.f, 0.f, 0.f) 10 degrees around the Y axis. Output: (0.984808, 0, -0.173648)
- Rotate the vector (0.f, 1.f, 0.f) 10 degrees around the Z axis. Output: (-0.173648, 0.984808, 0)
#include <QtGlobal> #include <QtMath> #include <QtGui/QOpenGLFunctions> #include <QtGui/QVector3D> #include <QtOpenGL/QOpenGLWindow> #include <QtWidgets/QApplication> class OpenGLWindow : public QOpenGLWindow, private QOpenGLFunctions { virtual void initializeGL() override { initializeOpenGLFunctions(); // Rotate a 3D vector around the x-axis QVector3D vector1(0.f, 0.f, 1.f); QVector3D origin1(0.f, 0.f, 0.f); float r1 = qDegreesToRadians(10.f); QVector3D out1 = rotateX(vector1, origin1, r1); qDebug() << out1; // Rotate a 3D vector around the y-axis QVector3D vector2(1.f, 0.f, 0.f); QVector3D origin2(0.f, 0.f, 0.f); float r2 = qDegreesToRadians(10.f); QVector3D out2 = rotateY(vector2, origin2, r2); qDebug() << out2; // Rotate a 3D vector around the z-axis QVector3D vector3(0.f, 1.f, 0.f); QVector3D origin3(0.f, 0.f, 0.f); float r3 = qDegreesToRadians(10.f); QVector3D out3 = rotateZ(vector3, origin3, r3); qDebug() << out3; } QVector3D rotateX(const QVector3D &vector, const QVector3D &origin, float rad) { // Translate point to the origin QVector3D pos; pos.setX(vector.x() - origin.x()); pos.setY(vector.y() - origin.y()); pos.setZ(vector.z() - origin.z()); // Perform rotation QVector3D rot; rot.setX(pos.x()); rot.setY(pos.y() * qCos(rad) - pos.z() * qSin(rad)); rot.setZ(pos.y() * qSin(rad) + pos.z() * qCos(rad)); // Translate to correct position QVector3D out; out.setX(rot.x() + origin.x()); out.setY(rot.y() + origin.y()); out.setZ(rot.z() + origin.z()); return out; } QVector3D rotateY(const QVector3D &vector, const QVector3D &origin, float rad) { // Translate point to the origin QVector3D pos; pos.setX(vector.x() - origin.x()); pos.setY(vector.y() - origin.y()); pos.setZ(vector.z() - origin.z()); // Perform rotation QVector3D rot; rot.setX(pos.z() * qSin(rad) + pos.x() * qCos(rad)); rot.setY(pos.y()); rot.setZ(pos.z() * qCos(rad) - pos.x() * qSin(rad)); // Translate to correct position QVector3D out; out.setX(rot.x() + origin.x()); out.setY(rot.y() + origin.y()); out.setZ(rot.z() + origin.z()); return out; } QVector3D rotateZ(const QVector3D &vector, const QVector3D &origin, float rad) { // Translate point to the origin QVector3D pos; pos.setX(vector.x() - origin.x()); pos.setY(vector.y() - origin.y()); pos.setZ(vector.z() - origin.z()); // Perform rotation QVector3D rot; rot.setX(pos.x() * qCos(rad) - pos.y() * qSin(rad)); rot.setY(pos.x() * qSin(rad) + pos.y() * qCos(rad)); rot.setZ(pos.z()); // Translate to correct position QVector3D out; out.setX(rot.x() + origin.x()); out.setY(rot.y() + origin.y()); out.setZ(rot.z() + origin.z()); return out; } }; int main(int argc, char *argv[]) { QApplication::setAttribute(Qt::ApplicationAttribute::AA_UseDesktopOpenGL); QApplication app(argc, argv); OpenGLWindow w; w.show(); return app.exec(); }
pro
QT += core gui openglwidgets win32: LIBS += -lopengl32 CONFIG += c++17 SOURCES += \ main.cpp
Source: https://glmatrix.net/docs/vec3.js.html#line564
Using GLM:
// Input vector auto v = glm::vec3(0.f, 0.f, 1.f); std::cout << v.x << ", " << v.y << ", " << v.z << std::endl; // Rotation auto rotationMatrix = glm::rotate(glm::mat4(1.f), glm::radians(10.f), glm::vec3{1.f, 0.f, 0.f}); auto newVector = rotationMatrix * glm::vec4(v, 0.f); std::cout << newVector.x << ", " << newVector.y << ", " << newVector.z << std::endl; // Output: 0, -0.173648, 0.984808
Stack Oveflow: https://stackoverflow.com/a/78633772/4159530
-
This post is deleted!
-
I have found two solutions how I can make the first person movement in the browser. Qt doesn't allow it because it is impossible to call
emscripten_request_pointerlock()
from Qt application: https://bugreports.qt.io/browse/QTBUG-126513The first solution is to use pure WebGL and Ammo.js: Click to run a demo in the browser
The second solution is to use SDL3, Emscripten, and Bullet Physics: Click to run a demo in the browser
The demo allows to move with first person and third person view:
Baked shadows and textures using SDL3, Emscripten, OpenGL ES 2.0, Bullet Physics, and C++
-
-
Critical problems for WebAssembly
- Background color of parent window is changed to black: https://bugreports.qt.io/browse/QTBUG-120651
- emscripten_request_pointerlock() returns EMSCRIPTEN_RESULT_UNKNOWN_TARGET: https://bugreports.qt.io/browse/QTBUG-126513
- Dealing with keyboard layout for input on Qt WebAssembly: https://forum.qt.io/post/790688