A new base for making games and interactive stuff. Early release — usable, but evolving.
“...maybe the real treasure was all the engines we made along the way!” -- Bob Nystrom
For now, we have:
- Data-driven Entity-Component-System architecture, with an RTTI system
- Integrated editor (WIP)
- Lua scripting
- SDL_Renderer for basic 2D (uses appropriate GPU-accelerated render solution depending on platform)
- Graph-based audio playback and processing engine
- 2D Physics and collision handling
- Particle system
- Resource management
- Some extras: tilemaps, textured text, etc...
- A Lupi-compatible runtime system
The engine supports the following platforms:
- Linux (native and Flatpak builds)
- Emscripten (WASM)
- Windows (amd64)
- Android (aarch64)
- Nintendo Wii
- preliminary version using this SDL3 port, atop devkitPPC and libogc2.
macOS should be relatively easy to add, once I actually have the necessary hardware and environment. An attempt at a hackintosh VM did not work out. At this point, I'd rather have a native macOS build machine when time and money allow.
It should be possible to build for other platforms and architectures, but this it is neither tested nor supported.
Like many custom engines of today, newbase uses a data-driven ECS (Entity-Component-System) solution. This is based on the entt library to provide the optimized data structures.
This approach aims to split content, state, and logic as much as possible. The basic idea is to represent the game world as a set of identifiers (entities) that may have arbitrary sets of data associated with them. Every piece of data is a component. This data is ideally "pure" (a POD datatype in C++ parlance), without associated logic. The logic is provided by systems, operating over the entities and associated components.
The component data is stored together with other data of the same type, aiming to maximize data locality when systems operate on components in bulk. Updating transforms is a classic example of such usage.
There are many articles and primers around the web written about ECSs, if that is of interest. The entt wiki is a good start. This implementaion is a "pure" ECS, not requiring entities to have any specific data associated with it, besides their identifiers.
Functionality is grouped in systems, that can be linked to the final executable as per its configuration. Systems work cooperatively to process the entities' component data and provide functionality used by the applications.
Here is a high-level description of the currently-implemented systems:
This is a simple video rendering system based on SDL3's Render system. It can draw textured sprites and geometry, with color modulation, in 2D. Its relative simplicity, from relying on SDL3's renderers, means it can work atop all of SDL3's render backends: Vulkan, OpenGL [ES], DX, Metal, software, all of them. This ensures that whatever platform is targeted, we can rely on SDL for basic 2D rendering support, with proper hardware acceleration in all major platforms.
ImGui is supported for debug and tooling (as it should be when using any render system of the engine).
The audio system is custom-made for the engine. It is based on an acyclic directed processing graph, that processes data in a pull fashion, generating samples as requested. Feedback is not directly supported by the graph, but can be used internally by the processing nodes when required (e.g. echo and reverb).
Audio sources can use in-memory buffers, or be streamed. In both cases they can be looped, and arbitrary loop points are supported.
There is a simple api (with Lua bindings) for playing sound effects and background music, just by specifying the resources to use. Simpler games can use it to play sound the easiest possible way. When using this API, the audio graph is managed automatically.
More complex games can use the audio graph directly, to chain effects and do more advanced processing. This requires usage of the C++ API.
See the audio system documentation for more detailed info.
The base input system is a basic layer atop SDL's keyboard and joypad. It maps possible controller and keyboard inputs to defined player actions.
Mouse input and multiple players are not yet supported, but is planned for the future.
This system provides Lua scripting support for the engine, written from the bottom-up using our RTTI system.
The initial idea was to allow for different scripting engines to be implementable, and this is still doable if a project requires it. But Lua has shown itself to be capable and performant enough. So it remains the one and only scripting interface supported by the engine.
This system implements 2D rigid-body physics, via the industry-standard Box2D library.
(TODO add more details)
The "lupi" system is a compatibility layer for games written in Lua against the Lupi console API, a 480x270, 256-color indexed retro console. Each cart runs its own Lua source, unmodified, in an isolated lua_State, driven by a single update(frame) callback per simulation step.
Real carts ship no source assets. Lupi's own build pipeline normally slices sprite sheets, builds a color palette, and compiles Tiled maps ahead of time. "lupi" reimplements that pipeline itself at cart-boot instead, so a cart's original source tree runs directly with no separate compiler or devkit involved. See doc/system_lupi.md for the full picture.
These are core systems of the engine, that implement standard data flows that other systems rely upon. The engine can run without them, but otherwise expected functionality may not work correctly.
newbase aims to be as portable as possible, and not reinvent the wheel. Therefore, we leverage the following:
- SDL3 - Main platform abstraction library, with many facilities for games
- entt - Data structures and utilities for ECS systems
- glm - 3D math library
- rapidyaml - A fast and complete YAML library
- lua - The embeddable scripting language and runtime
- (it may be possible to use LuaJIT for even faster speeds, but this is not yet tested)
- ImGui - A lean C++ GUI library with flexible platform support
- stb - Single-header C++ libraries for specific uses (OGG decoding, TTF fonts)
- tracy - A frame profiler for CPU and interactive graphics workloads
- box2d - The 2D physics engine for games
These dependencies were carefully chosen for a good mix of power, flexibility, and portability. All of them are linked statically into the resulting binaries, optionally with LTO.
TODO: steamapi, other stuff... Most of those are just skeleton systems.
In addition to the above libraries, which should all be vendored-in, we also use the following tools during build time:
- CMake
- Python 3
- Jinja2
- PyYAML
All of the Python dependencies are listed under scripts/requirements.txt, for easy virtual environment setup.
CMake is the main build tool for this project. Python 3 is also a build-time dependency, as described above.
The engine is set up to be statically linked with the final executable, and has toggles for enabling LTO, aiming for best possible performance. This is one of the reasons only MIT, BSD and zlib-licensed dependencies are used.
This project is mostly built and tested on Linux. It boils down to the following:
- Install the SDL3 build dependencies.
- Ensure CMake and Python 3.10+ are installed.
- Prepare a Python virtual environment with the dependencies and enable it:
python -m venv venv source venv/bin/activate pip install --upgrade pip pip install -r scripts/requirements.txt - Then, configure and build the project using CMake:
cmake -B ./build -S . -DCMAKE_BUILD_TYPE=RelWithDebInfo -DNEWBASE_LTO=ON cmake --build ./build
The Android build mostly follows from the SDL Android build process. The project is set up to use the Android NDK, and CMake to build the native code. This was the main reference used in setting up the build.
For an example of an Android project using the engine (the demo), see the project files under demo/android-project.
The project is occasionally built natively on Windows with Visual Studio. After checking out submodules, it should be built like any other CMake project on Windows that targets the MSVC toolchain.
Additionally, the project is built on the CI/CD pipeline using the MXE cross-compilation toolchain. See the workflow file for details.
For the CMake configuration process, it is important to have Python 3.10+ available, and a virtual environment setup with the packages from scripts/requirements.txt installed.
This project also targets the web via Emscripten. The process is similar to the Linux build, but requires the Emscripten SDK to be installed and activated.
- Install the Emscripten SDK and activate it:
git clone ... cd emsdk ./emsdk install latest ./emsdk activate latest source ./emsdk_env.sh - Prepare a Python virtual environment with the dependencies and enable it:
python -m venv venv source venv/bin/activate pip install --upgrade pip pip install -r scripts/requirements.txt - Then, configure and build the project using CMake:
cmake -B ./build -S . -DCMAKE_BUILD_TYPE=RelWithDebInfo -DNEWBASE_LTO=ON -DCMAKE_TOOLCHAIN_FILE=$EMSCRIPTEN/cmake/Modules/Platform/Emscripten.cmake cmake --build ./build
Integrating the web build into a page or web app, and distributing, is outside of the scope of this document. Look into the Emscripten documentation for more details.
NEWBASE_LTO: enable link-time optimization. Defaults to OFF.NEWBASE_SDL_STATIC: whether to build and use SDL statically. Defaults to ON.NEWBASE_TRACING: enable Tracy profiler integration. Defaults to OFF.NEWBASE_FDO_FILES: generate and install the XDG .desktop files, required for Flatpak builds. Defaults to OFF.NEWBASE_NATIVE_RES_PREFIX: specify the relative path where resources will be installed and searched for. Defaults to "nb".
You can set these options when running cmake by passing -DOPTION=VALUE.
This project is licensed under the BSD 3-Clause license. See LICENSE for details.
For questions, suggestions, or bug reports, just contact me :)