Nikola is a cross-platform framework for window creation, input handling, audio playback, and rendering using OpenGL 4.5+, designed for your game development and prototyping needs. Inspired by classic game engines like Doom, Quake, and the Source Engine, it is lightweight yet robust and flexible. A game engine creation tool, if you will.
- A fully documented, single-header implementation for each library module.
- Cross-platform window creation with OpenGL 4.5+.
- Gamepad, keyboard, and mouse input support.
- A flexible and configurable indirect renderer, using modern OpenGL (4.5+).
- A simple resource manager with a custom resource format (NBR) that supports hot-reloading of resources.
- Support for multiple image formats including JPEG, PNG, BMP, TGA, and more.
- 3D model loading with support for OBJ, FBX, Collada, and GLTF formats.
- Integrated ImGui support, featuring an abstracted
editorlayer for editing engine-specific types through a GUI. - A versitile lighting system using the PBR shading model with built-in HDR support.
- A fully-fledged audio system with both 2D audio and 3D spatialized audio, supporting multiple formats such as MP3, WAV, and OGG.
- A simple but powerful physics system, using the Jolt Physics Engine.
- A robust and easy-to-use game UI system, using HTML and CSS to decalre and style widgets.
- Simple 3D skeletal animation system, supporting the GLTF format (plus GLB, of course).
Here are some games that were made with Nikola:
Before proceeding with any build or compilation step, Nikola needs to be cloned from the Git repo.
git clone https://github.com/FrodoAlaska/Nikola.gitAnd now that Nikola is cloned, we can start the build process.
There are two main ways to build Nikola. The first is to use the traditional CMake commands below:
mkdir build
cd build
cmake .. And then to build Nikola you can use:
cmake --build .Now the second way to build Nikola is to use the build scripts found in the scripts directory. There are a few build scripts but there are only two important ones: build-nikola.sh for Linux and build-nikola.ps1 for Windows. Each script takes in a few flags to make both the development and build process easier.
--clean = Have a new fresh build
--debug = Build for the debug configuration
--rel = Build for the release configuration
--jobs [threads] = Threads to use when building
--run-testbed = Run the testbed examples
--reload-res = Reload the resources cache
--help = Display this help message
The --run-testbed and --reload-res flags call other scripts in the same directory. Hence, the multiple shell scripts. But, generally, the build-nikola.* script is the main and most important build script.
The --debug and --rel must be used independently. Moreover, one of the flags have to be used to know which configuration to build for. If none of them are used, the script will default to a Debug build. Depending on which flag is passed to the build script, a build-debug and/or build-release will be created in the main directory. Any build artifacts generated by --run-testbed or --reload-res will be dumped into one of the previously mentioned directories.
The --reload-res flag will call the reload-resources.* script to convert any resources to the .nbr engine format for resources. However, the reload-resources.* in particular is very specific to the current development environment. You can use your own paths and specific resources in the script or use the NBR tool directly.
Here's a simple example of the core library working in action. The example below will open a basic window and initialze a graphics context.
#include <nikola/nikola.h>
int main() {
// Initialze the library
if(!nikola::init()) {
return -1;
}
// Openinig the window
nikola::i32 win_flags = nikola::WINDOW_FLAGS_FOCUS_ON_CREATE | nikola::WINDOW_FLAGS_GFX_HARDWARE;
nikola::Window* window = nikola::window_open("Hello, Nikola", 1366, 768, win_flags);
if(!window) {
return -1;
}
// Creating a graphics context
nikola::GfxContextDesc gfx_desc = {
.window = window,
.states = nikola::GFX_STATE_DEPTH | nikola::GFX_STATE_STENCIL,
};
nikola::GfxContext* gfx = nikola::gfx_context_init(gfx_desc);
if(!gfx) {
return -1;
}
// Main loop
while(nikola::window_is_open(window)) {
// Will stop the application when F1 is pressed
if(nikola::input_key_pressed(nikola::KEY_F1)) {
break;
}
// Clear the screen to black
nikola::gfx_context_clear(gfx, 0.0f, 0.0f, 0.0f, 1.0f);
// Swap the internal window buffer
nikola::gfx_context_present(gfx);
// Poll the window events
nikola::window_poll_events(window);
}
// De-initialze
nikola::gfx_context_shutdown(gfx);
nikola::window_close(window);
nikola::shutdown();
}