Would you ever build a 3D Endless Runner Mobile Game with Qt? and if no why?
-
@johngod looks amazing.Do you have any tutorial on QtQuick3D?
-
@Ronel_qtmaster @Ronel_qtmaster I dont, I have learn it using the docs and demos.
The assets that I have used make all the difference in visual of the game.
Here is my take on 3D game development, one has to know 3d game development, them the tools you use are a bonus.
For example I have used OpenGL before, but it is very low level, the API is not user friendly, you have to do a lot of coding for basic stuff, like loading textures. To me learning OpenGL was a very good school, but in the end it was a bit overkill to what I wanted to achieve.
Why did I choose Quick3D? Compared with Q3D, I liked quick3D API better, with more documentation and examples, and at the time I believed that a lot more resources would be put in quick3D development from the Qt company than in the Q3D. Why I do not use Godot, Unity, or other? Because I am not a game developer, I am a hobbyist developer in my free time, and I really like Qt, so I just went with my gut feeling and jumped in the quick3D wagon and I did the right thing. For sure there could be better tools or tools that I would like better if I know them, but time is limited and I can’t learn every stuff out there. I can reuse the knowledge that I am gaining with quick3D, because I developed other 3D apps than games, not sure I could use unity, godot, for other stuff than games.
What I like in Quick3D? It has a nice API, supports loading textures easily, loading animated assets from blender, Maya, and others, you have to use balsam utility, but once you get used to it, it works fine. Skyboxes are a beauty, they just work easily, I could never manage to put OpenGL skybox working correctly. The way you compose a scene using Nodes in the Viwe3D are more powerful and versatile than I thought at first. It has built in cameras, but in this it falls short, it lacks a proper camera for FPS games. I had to develop my own FPS cameras for my game. If you guys from Qt company are listen, adding a good FPS camera should not be that hard, also the Qt rockstars devs always surprise for how good API’s they make. There are several types of lights, they just work out of the box. The recent addition of physics is very good, I still haven explore these, but I think it will bring games to the next level. Quick3D performance seems to be good, I get 120 frames with a gaming monitor.
At the end of the day, you can build a great game and still be a failure. It is a like a lottery, were the tools you use are least important, being the assets and the marking huge deals. Then some guy comes and builds a flappy bird with crappy graphs, and it gets huge success :) -
@johngod said in Would you ever build a 3D Endless Runner Mobile Game with Qt? and if no why?:
you have to do a lot of coding for basic stuff, like loading textures
Loading textures is a bad example because it doesn't require much code:
m_texture.create(); m_texture.setData(QImage(texturePath).mirrored()); m_texture.setMinMagFilters(QOpenGLTexture::Filter::Linear, QOpenGLTexture::Filter::Linear); m_texture.setWrapMode(QOpenGLTexture::WrapMode::ClampToEdge);
But if you need to load materials using a specular map, a normal map, or you need to implement glass, water, fire, smoke and so on - yes, in this case you will have to learn a lot and write a lot of code. I haven't tried to implement this in OpenGL. I especially believe that it is very difficult to implement PBR materials using OpenGL, as was implemented using Qt3D: PBR Materials QML Example These materials were also implemented in Qt Quick 3D: https://doc.qt.io/qt-6/quick3d-pbr.html But some people need PBR, and some don’t.
I spend a lot of time implementing BMFont (text rendering), skeletal animation, and sprite animation with pure WebGL (I haven't rewritten it in Qt OpenGL yet), but once you've implemented this stuff once, you can copy it from project to project.
I want to rewrite the following example in Qt OpenGL ES from OpenGL1/SDL2/Python. I created a C++ version of this example to create an EXE file because PyInstaller creates a very large EXE file. Perhaps this will be useful for those who want to use OpenGL, since text is very important for games. I can help to rewrite it to Qt, OpenGL ES 2.0
- Source in Python, PySDL2: font-opengl1-sdl2-py.zip - 63.5 KB
- EXE in C++, SDL2: font-opengl1-sdl2-cpp-win64bit-exe.zip - 1.41 MB
Here is 244 lines of code in one file:
import ctypes import sys from dataclasses import dataclass import numpy as np from OpenGL.GL import * from PIL import Image from sdl2 import * maxFPS = 60.0 window: SDL_Window = None def fatalError(message): print(message) if window: SDL_DestroyWindow(window) SDL_Quit() exit(-1) @dataclass class CharData: id: int = 0 x: int = 0 y: int = 0 w: int = 0 h: int = 0 xOffset: int = 0 yOffset: int = 0 xAdvance: int = 0 class Font: def __init__(self, fontContentPath, texture): self.charMap = {} self.charIndices = {} self.texture = texture self.parse(fontContentPath) self.vertPositions = [] for i in range(len(self.charMap)): # vertex 0 self.vertPositions.append(0.0) self.vertPositions.append(0.0) self.vertPositions.append(0.0) # vertex 1 self.vertPositions.append(0.0) self.vertPositions.append(1.0) self.vertPositions.append(0.0) # vertex 2 self.vertPositions.append(1.0) self.vertPositions.append(0.0) self.vertPositions.append(0.0) # vertex 3 self.vertPositions.append(1.0) self.vertPositions.append(1.0) self.vertPositions.append(0.0) drawIndex = 0 self.texCoords = [] for i in self.charMap: cd = self.charMap[i] self.charIndices[i] = drawIndex drawIndex += 4 x = cd.x / 512.0 y = cd.y / 512.0 w = cd.w / 512.0 h = cd.h / 512.0 # vertex 0 self.texCoords.append(x) self.texCoords.append(y) # vertex 1 self.texCoords.append(x) self.texCoords.append(y + h) # vertex 2 self.texCoords.append(x + w) self.texCoords.append(y) # vertex 3 self.texCoords.append(x + w) self.texCoords.append(y + h) def parse(self, filePath): f = open(filePath, "r") # Skip three lines for i in range(3): f.readline() # Get the count count = int(f.readline().split("=")[1]) # Get char info for i in range(count): charData = CharData() tokens = f.readline().split() charData.id = int(tokens[1].split("=")[1]) charData.x = int(tokens[2].split("=")[1]) charData.y = int(tokens[3].split("=")[1]) charData.w = int(tokens[4].split("=")[1]) charData.h = int(tokens[5].split("=")[1]) charData.xOffset = int(tokens[6].split("=")[1]) charData.yOffset = int(tokens[7].split("=")[1]) charData.xAdvance = int(tokens[8].split("=")[1]) self.charMap[chr(charData.id)] = charData f.close() class Text: def __init__(self, font: Font, x: float, y: float, scale: float, text: str): self.font = font self.text = text self.x = x self.y = y self.scale = scale def draw(self): glBindTexture(GL_TEXTURE_2D, self.font.texture) glVertexPointer(3, GL_FLOAT, 0, self.font.vertPositions) glTexCoordPointer(2, GL_FLOAT, 0, self.font.texCoords) cursorX = 0 for i in range(len(self.text)): cd: CharData = self.font.charMap[self.text[i]] glMatrixMode(GL_MODELVIEW) glLoadIdentity() glTranslatef(self.x + cursorX + cd.xOffset * self.scale, self.y + cd.yOffset * self.scale, 0) glScalef(cd.w * self.scale, cd.h * self.scale, 1) glDrawArrays(GL_TRIANGLE_STRIP, self.font.charIndices[chr(cd.id)], 4) cursorX += cd.xAdvance * self.scale def setText(self, text: str): self.text = text def createTexture(path): image = Image.open(path) data = image.convert("RGBA").tobytes() glEnable(GL_TEXTURE_2D) texture = glGenTextures(1) glBindTexture(GL_TEXTURE_2D, texture) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST) glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image.width, image.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data) glBindTexture(GL_TEXTURE_2D, 0) return texture def main(): if SDL_Init(SDL_INIT_VIDEO) < 0: fatalError(SDL_GetError()) winW = 350 winH = 350 window = SDL_CreateWindow( b"OpenGL1, SDL2, Python", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, winW, winH, SDL_WINDOW_OPENGL) if not window: fatalError(SDL_GetError()) context = SDL_GL_CreateContext(window) if not context: fatalError("Failed to create the SDL_GL context") glEnable(GL_BLEND) glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1) glClearColor(0.2, 0.2, 0.2, 1.0) glMatrixMode(GL_PROJECTION) glLoadIdentity() glOrtho(0, winW, winH, 0, -100, 100) glEnableClientState(GL_VERTEX_ARRAY) glEnableClientState(GL_TEXTURE_COORD_ARRAY) vertPositions = np.array([ -0.5, -0.5, 0, -0.5, 0.5, 0, 0.5, -0.5, 0, 0.5, 0.5, 0 ], dtype=np.float32) texCoords = np.array([ 0, 0, 0, 1, 1, 0, 1, 1 ], dtype=np.float32) marioFontTexture = createTexture("assets/fonts/super-plumber-brothers-y00v.png") marioFont = Font("assets/fonts/super-plumber-brothers-y00v.fnt", marioFontTexture) title = Text(marioFont, 40, 50, 0.5, "Hello, Super Mario!") comicFontTexture = createTexture("assets/fonts/comic71.png") comicFont = Font("assets/fonts/comic71.fnt", comicFontTexture) secondsText = Text(comicFont, 40, 200, 0.5, "seconds = 0") marioIconTexture = createTexture("assets/sprites/mario-icon.png") lastTime = SDL_GetTicks() seconds = 0.0 event = SDL_Event() running = True while running: while SDL_PollEvent(ctypes.byref(event)) != 0: if event.type == SDL_QUIT: running = False startTicks = SDL_GetTicks() glClear(GL_COLOR_BUFFER_BIT) title.draw() currentTime = SDL_GetTicks() deltaTime = (currentTime - lastTime) / 1000.0 lastTime = currentTime seconds += deltaTime secondsText.setText("seconds = " + str(seconds)) secondsText.draw() glBindTexture(GL_TEXTURE_2D, marioIconTexture) glVertexPointer(3, GL_FLOAT, 0, vertPositions) glTexCoordPointer(2, GL_FLOAT, 0, texCoords) glMatrixMode(GL_MODELVIEW) glLoadIdentity() glTranslatef(175, 150, 0) glScalef(100, 100, 1) glDrawArrays(GL_TRIANGLE_STRIP, 0, 4) SDL_GL_SwapWindow(window) # Limit the FPS to the max FPS frameTicks = SDL_GetTicks() - startTicks if 1000.0 / maxFPS > frameTicks: SDL_Delay(int(1000.0 / maxFPS - frameTicks)) SDL_GL_DeleteContext(context) SDL_DestroyWindow(window) SDL_Quit() return 0 if __name__ == "__main__": sys.exit(main())
-
@Michele-Rossi said in Would you ever build a 3D Endless Runner Mobile Game with Qt? and if no why?:
Would you be so kind to share your thought on which kind of "tooling for the job" Qt would be missing today? Would you be able to point concrete examples?
I did:
You don't have a tool on hand to wire shaders, build materials, modify a render graph, etc.
... but I have the creeping suspicion that we are talking about different things. I was specifically talking about Qt3D, which is a module that was developed by KDAB. Whereas I imagine you're soliciting opinions on Qt Quick3D (and by extension Qt DS), which were developed by the TQtC.
As mentioned I have no opinion on the latter, as I have not used them, and probably I never will. -
For a game ultimately what you need from the surrounding platform/toolkit/environment is just the "runtime, platform infrastructure", i.e. the ways to access the platform utilities for rendering, audio, data and to receive input.
In a well designed game engine the engine part is generic and the platform specific stuff is abstracted away so that the engine doesn't depend on any specific implementation. This design makes it more portable and re-usable across devices and OSes.
So if/when you have engine designed this way using Qt for it would basically just be useful for providing the window glue, the context setup, input handling and data access implementations that the engine then uses.
This works fine and can be done without any problems. But the problem is that the Qt framework is just huge.
For a deployed game the API surface is rather small and it's beneficial to reduce the package size. (Ok AAA type of titles are huge of course but talking about small mobile games etc) so ideally its better to use the native platform services (such as the native android apis) to create the necessary "host app context" and implement the game runtime abstractions natively without using Qt.
-
@SamiV123 said in Would you ever build a 3D Endless Runner Mobile Game with Qt? and if no why?:
But the problem is that the Qt framework is just huge.
Qt taking up too much space on your hard drive? Or is the EXE/APK too big? The game's resources will still be consumed many times more than Qt. Other Android development tools are also quite expensive. For example, MSVC takes 15-20 GB (it includes VS - 2 GB, Microsoft Windows SDK - 10 GB, .NET - the rest). It is possible to develop 3D games for Android using Unreal Engine, but this engine takes up more than 30 GB. Qt (with Android and WebAssembly) requires 3.92 GB. EXE requires 27 MB, APK - 16 MB, WASM - 20 MB. It's not too big. I tried using SDL2 for Android two weeks ago, but I can't draw a triangle with OpenGL, it shows a black screen: https://github.com/libsdl-org/SDL/issues/9045 I haven't tried SFML and OpenGL for 3D on Android because it doesn't support WebAssembly. Perhaps the author of the topic should pay attention to libGDX for developing 3D games for Android, because this framework has a built-in Bullet Physics 3D engine. Personally, what I didn’t like about libGDX is that the developers of this framework have not officially implemented Bullet Physics support for assembly in HTML5/WebGL. It seems to me that it will be difficult to write your own engine in Java and OpenGL for Android so that you can compile for Android, Desktop and HTML5/WebGL.
What I like most about Qt is that it has everything I need for Android and WebAssembly:
- Built-in library for parsing JSON and XML. I don't have to create these Android and WebAssembly libraries to load sprites using Free Texture Packer and load slot levels using Tiled
- Built-in library for transmitting JSON data via WebSockets. I don't need Android and WebAssembly support to work with WebSockets in multiplayer games.
- built-in representation of OpenGL functions. I don't need to create GLAD or GLEW to initialize function pointers.
- Built-in texture loading. I don't need to collect SOIL
- Built-in library for working with linear algebra. I don't need to create a GLM to work with vectors and matrices.
- I easily assembled OpenGL-Soft for Android to work with 2D-3D sound. For WebAssembly I use the Web Audio API for 2D-3D audio. Here is my step-by-step instruction how to build OpenAL-Soft for Android: https://forum.qt.io/post/769489
- I easily added Box2D and Bullet Physics as sources. In Qt Creator you can add a source link to a project. This method works on Android and WebAssembly.
- One code base for Android, Desktop and WebAssembly. For example, in SFML on Android you need to replace the Java API from C++ code.
In Qt, I use fast C++ rather than slower C#, Java, JavaScript, Python, etc. I easily create Android, Desktop, and WebAssembly applications. I don't have to spend time putting together the necessary library components.
-
sure but why add an additional +8mb per each Qt library when it's not needed?
There are also many small games that have minimal assets.
Indirectly many of your bullet points for using Qt while valid and reasonable reasons for using Qt can also be replaced with single header libraries.
stb_image for simple image loading.
nlohmaan/json for JSON
GLM is a header only library, not much to do in order to use
GLAD and GLEW are junk that is not needed. all you need is "get proc address" and it works correctly and universally.Using Qt means nothing whether you're using C++ or python or whatever. You can write native C++ code without Qt and you can write Qt apps using Python or whatever.
-
@SamiV123 said in Would you ever build a 3D Endless Runner Mobile Game with Qt? and if no why?:
sure but why add an additional +8mb per each Qt library when it's not needed?
8 MB is the price of time saved. For me, the most important thing is the development of network games with clients for Android, Desktop and WebAssembly with a server on Node.js with physics on free hosting render.com. I spent a lot of time trying to connect
websocketpp
to render.com, but there were problems that I couldn't solve. I triedsocket.io-client-cpp
, but it didn't work either: https://github.com/socketio/socket.io-client-cpp/issues/384. I do all this as a hobby, learning C++/Python programming, physics with physics engines, client-server programming in simple games, basic linear algebra for computer graphics in OpenGL.Qt is the perfect time saving solution I have found for learning C++, Python and OpenGL for developing Android, Desktop and HTML5/WebGL clients on web sockets. Saving hard disk space is achieved using PySide and WebAssembly, because you only need to install PySide, PyOpenGL, PIL, and so on once. PySide and Siblime Text 4 take up very little space. The PySide application can be easily rewritten in Qt C++ to create APK, EXE and WebAssembly. The PySide application carries the weight of the project's source codes. WebAssembly requires no hard drive space. Today people have hard drives with terabyte capacity. For them, an extra 30 MB is nothing. Now games weigh 100, 150 or more GB. Nowadays, many people use PyInstaller, Cordova and Electron to create executable applications that weigh 150-200 MB, while Qt executable applications weigh only about 30 MB. You can use Unreal Engine to create applications with 3D graphics, but in this case the executable file will be very large. I don't know how much. But it seems to me that compiled applications on Unreal Engine weigh at least 300 MB.
-
@johngod Thank you for your input! What you achieved it's impressive considering there are no real examples out there. So to achieve a 3D Endless runner what do you think would be missing to Design Studio and Quick 3D to make the creation of your game faster? Just out of the box examples, or other features like level creation, etc...?
-
@Michele-Rossi Truth be told I have never used DesignStudio, I have to check that.
For a 3D runner, you will need a collision detection, I think you have that now in quick3d physics, I started my game before that, I had to do my own collision detection algorithms in javascript.
You will need a FPS Camera, you can steal my from my game code :) or build it your self, math quarternions are your friends for this. A good FPS Camera is missing in quick3D.
You will need some good assets, that makes a huge difference, I have used free assets from https://quaternius.com/ (I also paid a contribution to this guy, I think it is nice give back), and especifically in my game I have bought a cheap license of a software that does the skyboxs textures images of the universe. You will need a sky box (not mandatory, but your game graphics will improve a lot), quick 3D has that out of the box.
Loading the animated obj file characters and converting them to quick 3D with balsam utility was a trial and error experience, I had to fine tune some balsam options to get it right, an example with this would have been nice to have (Btw, if Qt developers are reading this, the qt resource editor could be improved by allowing to load several files from folders and subfolders at the same time, the earlier versions of balsam created a huge amount of files from one character obj). I also had to do some trial and error learning with quick3D TimeLine to fine tune the build in character animations.
A panel dialogue is missing, I had to build my own game panel with dialogues and images, not that difficult but it would have saved me some time.
There is WasdController for the keyboard to controll the game, but I was not aware of that until recently (I am always amazed of how much stuff Qt has that I dont know about:) so I had rebuild the wheel doing my own keyboard controller :)
The levels changing I have manage with states, but I guess it could be easier to do it with stackview or something like that.
For the levels creation, I am reusing components, but I am still thinking of ways to improve this.
If you want your game to be in mobile (and I think it should), you will need a Touch wheel pad controller, I had to build my own. It was not that trivial, because besides the touch wheel pad I wanted to use other screen buttons, had to use MultiPointTouchArea, and fiddle around quite a bit to get that right.
Also for the infinite game runner loop you will need some random stuff to be generated, but that goes with the levels creation.
There could many other stuff I am missing. Let me know If I can further help you, good luck to your game. -
@Michele-Rossi Hi. I see that this thread is extensive, but I would also like to express my opinion. When it comes to Qt and 3D, I can recommend you one chapter of the book Mastering Qt. Chapter 6 describes how to create a snake 3D game using QtQuick components and uses shaders and models. I think this is a good basis for writing something better later. Fortunately, there is a repository for this book and you can download and test the code. Link to the example: https://github.com/PacktPublishing/Mastering-Qt-5/tree/master/Chapter_06
-
This post is deleted!