Building a Tiny Game Engine in Python: The HappyQQ Game Maker Walk-through
Last updated: 11 August 2025
Why read this article?
If you want to …
-
type “draw a red rectangle” in plain Chinese and see it on screen -
embed high-speed SDL graphics inside a PySide6 window -
let Lua scripts call your own drawing routines -
ship the whole thing as a stand-alone EXE for Windows
… then the HappyQQ Game Maker (hqqgm) skeleton is a gentle place to start.
It is not a commercial-grade engine; it is a concise, transparent example that you can extend, cut, or re-brand.
1. What exactly is hqqgm?
In one line:
hqqgm = PySide6 main window + SDL3 render area + Lua logic + Chinese-to-Python translator + PyInstaller packager.
Module | Purpose | File |
---|---|---|
Qt application | starts the main window, menus, resize logic | app.py |
SDL embed | turns a Qt widget into an SDL render target | sdl_embed.py |
Lua bridge | exposes Python functions to Lua scripts | lua_bridge.py |
mini interpreter | converts Chinese commands to Python calls | mini_interpreter.py |
assets folder | textures, fonts, sounds | assets/ |
2. Five-minute quick start
These steps are copied straight from the project README; they have been tested on Windows 10 and 11 with Python 3.11.
2.1 Install the toolkit
# Windows PowerShell
py -3.11 -m venv .venv
. .venv\Scripts\Activate.ps1
python -m pip install -U pip
pip install -e .
The -e .
switch installs the package in editable mode, so later code edits do not need a reinstall.
2.2 Run the default demo
python -m hqqgm
A Qt window appears with an SDL surface inside.
The title bar shows the running FPS.
Press Esc to quit.
2.3 Try the Chinese-script demo
python -m hqqgm --demo-interpreter
The console prints each Chinese line and immediately executes it.
Typical output:
清屏蓝色
draw_rect 100 100 200 200 红色
2.4 Compile a Chinese script to Python
Create example.cn.txt
:
清屏蓝色
画矩形 50 50 100 100 红色
Then run:
python -m hqqgm --compile .\example.cn.txt --out .\example.gen.py
example.gen.py
contains a single function:
def run(api):
api.clear(color="blue")
api.draw_rect(50, 50, 100, 100, color="red")
You can now:
-
run python example.gen.py
directly (a tiny loader is added automatically) -
bundle example.gen.py
with your final EXE so non-programmers can change only the Chinese script and re-compile.
3. Deep dive into the folder layout
src/
└── hqqgm/
├── __init__.py
├── __main__.py # CLI entry point, handles --compile, --demo-interpreter
├── app.py # Qt side: main window, menu bar, resize events
├── sdl_embed.py # SDL side: window handle, renderer, frame pacing
├── lua_bridge.py # Lua side: Runtime, API registration, traceback
├── mini_interpreter.py # tokenizer → AST → Python code
└── assets/ # optional; png, ttf, wav
Tip
Keep paths relative:
Path(__file__).parent / "assets" / "myfont.ttf"
This makes both local runs and PyInstaller builds find the files without extra configuration.
4. How SDL lives inside Qt
4.1 Core idea
Qt owns the window and the event loop.
SDL only renders inside a child widget.
Steps (short version):
-
Create a QWidget
subclass (SdlWidget
). -
Ask Qt for its native handle ( winId()
on Windows,effectiveWinId()
on X11). -
Feed that handle to SDL_CreateWindowFrom
. -
Create an SDL renderer and draw whenever Qt says “repaint”.
4.2 Minimal code excerpt
# sdl_embed.py
from PySide6.QtCore import QTimer
from PySide6.QtWidgets import QWidget
import sdl3 as sdl
class SdlWidget(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setAttribute(Qt.WA_OpaquePaintEvent)
self.timer = QTimer(self)
self.timer.timeout.connect(self.render)
def showEvent(self, event):
self.sdl_window = sdl.SDL_CreateWindowFrom(int(self.winId()))
self.renderer = sdl.SDL_CreateRenderer(self.sdl_window, None, 0)
self.timer.start(16) # ~60 FPS
def render(self):
sdl.SDL_SetRenderDrawColor(self.renderer, 0, 0, 0, 255)
sdl.SDL_RenderClear(self.renderer)
# call lua or translator here
sdl.SDL_RenderPresent(self.renderer)
Note
On macOS you need the NSView handle, not NSWindow.
The demo repository currently shows only the Windows path.
5. Lua ↔ Python communication
5.1 Install lupa
pip install lupa
5.2 Expose Python functions
# lua_bridge.py
from lupa import LuaRuntime
lua = LuaRuntime(unpack_returned_tuples=True)
def draw_rect(x, y, w, h, color):
# forward to SDL
...
lua.globals()['draw_rect'] = draw_rect
5.3 Sample Lua script
-- game.lua
draw_rect(50, 50, 100, 100, "red")
Run it:
with open("game.lua") as f:
lua.execute(f.read())
6. The Chinese-to-Python interpreter
6.1 Goal in plain words
Let non-coders write:
循环 100 次
清屏黑色
画圆 随机位置 半径=5 颜色=随机
延迟 16
结束
6.2 Translation pipeline
-
Tokenise by whitespace and punctuation. -
Recognise “verb + arguments”. -
Map verbs to Python method calls.
6.3 Live examples
Chinese command | Generated Python |
---|---|
清屏蓝色 | api.clear(color="blue") |
画矩形 10 20 30 40 红色 | api.draw_rect(10,20,30,40,color="red") |
7. Creating a stand-alone EXE
7.1 PyInstaller one-liner
pip install pyinstaller
pyinstaller -F -w -n HappyQQGameMaker src/hqqgm/__main__.py
Flags explained:
Flag | Meaning |
---|---|
-F |
single file |
-w |
no console window |
-n |
final EXE name |
7.2 Common pitfalls
Symptom | Fix |
---|---|
SDL DLL not found | add --add-binary "path_to_sdl3.dll;." |
Qt platform plugin missing | use --onedir first, then trim unneeded files |
Hard-coded paths | detect sys._MEIPASS at runtime |
8. Extending the skeleton
Below are realistic next steps that stay within the scope of the original code.
8.1 Richer Chinese syntax
Add variables and if-statements:
如果 分数 > 100 则
打印 "You win!"
否则
打印 "Keep trying!"
结束
8.2 Hot-reload
Watch the script file for changes and re-compile on the fly.
8.3 Scene tree
Wrap every drawable in a Node class, similar to Godot’s approach.
8.4 Audio support
Link SDL_mixer in the same way SDL renderer is linked.
8.5 Visual editor
Drag-and-drop nodes, then generate the Chinese script automatically.
9. FAQ: Ten questions people always ask
Question | Short answer |
---|---|
Does it run on Linux or macOS? | Yes, but the window-handle code needs small tweaks. |
Can I use Python 3.9? | The author tested 3.11; 3.10 works; 3.9 is untested. |
Can I load PNG images? | Yes—link SDL_image and add api.draw_texture(...) . |
Does Chinese scripting support if/else? | Not yet; the parser is intentionally minimal. |
Can Lua trigger Qt signals? | Yes—expose a Python wrapper that emits the signal. |
Can I embed a web view? | Replace the SDL widget with QWebEngineView ; same idea. |
What frame rate can I expect? | 60 FPS with thousands of rectangles on a mid-range laptop. |
May I use it in a commercial product? | MIT license—go ahead, just keep the copyright notice. |
Is there a debugger? | Use VSCode; break points in Python work, Lua tracebacks are printed. |
Any plans for Android? | Possible with Qt for Android and SDL’s Android port, but extra work. |
10. Recap
HappyQQ Game Maker shows you four concrete things:
-
SDL and Qt can share the same window; the handle is the bridge. -
Lua and Python talk through lupa in a handful of lines. -
A Chinese-to-Python translator is just a lexer and a dictionary. -
PyInstaller can wrap everything into one EXE—the real trick is managing file paths.
Clone the skeleton, delete what you do not need, and grow the rest.
Have fun building—and playing—your own tiny game engine.
End of article