WARNING: WillowVox Engine v0.1.0 is a prototype build of the engine and is not intended to be used for serious projects. It is subject to many changes and should only be used for testing purposes or for fun.
What is WillowVox?
WillowVox Engine is a voxel world game engine.
This guide explains how to get started creating a project using WillowVox Engine
Prerequisites
You will need the following to create a project with WillowVox:
- A text editor or IDE (Recommended VSCode or Visual Studio)
- CMake
Step 1: Project Setup
Download the template app.
Extract the zip file. Open the folder in your favorite text editor. This guide will be using VSCode. You will see the following folder structure:

Setting up intellisense may be different depending on the IDE you're using. To set up VSCode, press Ctrl+Shift+P and run CMake: Configure
. This should create a build folder.
Open up the CMakeLists.txt
file in the root directory. You should see this in there:

Replace all instances of TemplateApp with the name of your application (Use find all and replace).
Open a command prompt in the root directory of the project and run the following commmands:
cd build
cmake --build . --config Debug
Navigate to the exe file in the build folder (should be build\bin\Debug
on Windows, but it depends on the compiler being used) and run the exe file. It should run at this point with a black screen.
Now that it runs, we can configure the window and application class. Open the src\main.cpp
file. This class is your application and derives from the base Application class. You can rename the namespace and class name to your project name
Note: The namespace and class names will also need to be changed inside the CreateApplication function at the end of the src\main.cpp
file as well as in the class constructor.
Take a look at the constructor for your application class.

Change the _applicationName
variable to the name of your application.
Optionally, uncomment and change the _defaultWindowWidth
and _defaultWindowHeight
variables to change the default size of the window.
The last thing you need to do set up is the name of the world. Open include\TemplateWorld.h
and change the namespace name to the name you used in the main file. Change the class name and the name of the file as well.

Change the #include <TemplateWorld.h>
line in src\main.cpp
to include the renamed file.
Then, on line 39 of src\main.cpp
, change TemplateWorld
in m_world = new TemplateWorld(_camera);
to match the name that you gave the class.
Build the project again. To do this, open up the build folder in your project and run the command cmake --build . --config Debug
. The application should run once again.
If you want to change the background color, add this line to the Start function in your application class:
main.cpp
...
void Start() override
{
_renderingAPI->SetCullFace(true);
_renderingAPI->SetDepthTest(true);
_window->SetBackgroundColor(0.6f, 0.8f, 1.0f, 1.0f);
_camera = new Camera(_window);
m_world = new ExampleWorld(_camera);
}
...
Step 2: World Generation
Now you have the project running, the next step is to add some basic terrain. Before we do this, though, we need to add a block to the application.
To add a block, we first need a texture. Currently, the game looks for the block textues as one big png file called block_map.png
located at the path assets\sprites\block_map.png
. Each block texture must be 16 pixels in both the height and the width. That file already exists and includes one gray block. We'll use that for this tutorial, but you can add your own textures here if you want.
To register a block, we msut call Blocks::RegisterBlock()
from the RegisterBlocks
function in the application class.
main.cpp
... #include <WillowVox/rendering/Camera.h> #include <WillowVox/resources/Blocks.h> #include <ExampleWorld.h> using namespace WillowVox; namespace ExampleProject { ... void RegisterBlocks() override { Blocks::RegisterBlock({ 0, 0, Block::SOLID, "Example Block" }); } ...
Here, we're creating a new Block object (hence the curly braces) and then passing that to the function. Let's break down each of the arguments in the Block constructor:
- texX (0): The x index of the texture in the block map image. Note that this isn't a pixel location but rather a texture location. For example, if you have 2 textures in the block map on the x axis, 0 would refer to the first texture and 1 would refer to the second texture.
- texY (0): The x index of the texture in the block map image. This is also a texture location like texX.
- blockType (Block::SOLID): The type of block. This influences how it is rendered. There are currently 5 types:
SOLID
,TRANSPARENT
,LEAVES
,BILLBOARD
,LIQUID
- blockName ("Example Block"): The name of the block. This can be useful for creating a UI.
There's another constructor for blocks that have a different texture for the top, bottom, and sides as well. You can check out the Block.h
file to find that.
WorldGen
Now that we have a block, we can make it generate. Go to the include\TemplateWorld.h
(or whatever you renamed it to). In there, you can see where we define the _worldGen
variable. Right now it's using the base WorldGen
class, which just generates an empty world. We're going to replace it with NoiseWorldGen
, which provides basic noise generation.
TemplateWorld.h
#pragma once #include <WillowVox/WillowVox.h> #include <WillowVox/math/NoiseSettings.h> #include <WillowVox/world/NoiseWorldGen.h> using namespace WillowVox; namespace ExampleProject { class ExampleWorld : public World { public: ExampleWorld(Camera* player) { m_mainCamera = player; _noiseSettings = new NoiseSettings2D(10.0f, 1.05f, 2, 0.5f, 3.0f, 0); _worldGen = new NoiseWorldGen(0, *_noiseSettings); m_chunkManager = new ChunkManager(*_worldGen); } ~ExampleWorld() { delete _worldGen; } private: NoiseSettings2D* _noiseSettings; WorldGen* _worldGen; }; }
Let's break down all the changes made.
We first include NoiseWorldGen.h
.
NoiseWorldGen
requires a NoiseSettings2D
object, so we create that as a private variable and then initialize it before initalizing _worldGen
. These are the arguments for NoiseSettings2D
:
- amplitude (10.0f): The amplitude of the noise. This effects how high/low the terrain will generate.
- frequency (1.05f): The frequency of the noise. This impacts the spacing between high and low points of the terrain.
- octaves (2): How many increments of noise to generate. The final value will be the sum of all the octaves.
- persistence (0.5f): The persistence of the noise. This is a multiplier that will be applied to amplitude at each octave.
- lacunarity (3.0f): The lacunarity of the noise. This is a multiplier that will be applied to frequency at each octave.
- heightOffset (0): The offset of the noise. This is simply added onto the noise and is useful to keep the ground level consistent.
After we create our NoiseSettings2D
, we can initialize _worldGen
. These are the arguments for NoiseWorldGen
:
- seed (0): The seed of the world. Giving it a random seed would generate a different world every time. Using the same seed will give you the same world.
- noiseSettings (*_noiseSettings): The noise settings for generating the world.
Upon running the game, you should see terrain generating.

NoiseWorldGen
is a very simple world generation class. You can create a child class of NoiseWorldGen
to customize it further. However, if you want a more robust form of terrain generation, I would highly recommend checking out TerrainGen.h
.
Step 3: Camera Controller
Now you can see terrain being generating in front of you, but it would be nice to be able to move around it. To do this, we'll implement a simple form of camera movement and learn how to use the engine's event system.
Movement
First, we'll add the ability to move around the world using WASD as well as Q and E for vertical movement. Add this to the Update
function inside your src\main.cpp
file:
main.cpp
... void Update() override { if (_window->KeyDown(Key::W)) _camera->position += _camera->Front() * 10.0f * m_deltaTime; } ...
We first start with checking whether or not the W key is being pressed. If it is, we move the camera forward. _camera->Front()
gives us the direction that the camera is facing, and we multiply that by a speed (10.0f) and m_deltaTime
to ensure that it is framerate independent.
Now that we have movement in the forward direction, we can easily add movement in the other directions. All of these movement directions are the same code but with different keys pressed and different directions.
main.cpp
... void Update() override { if (_window->KeyDown(Key::W)) _camera->position += _camera->Front() * 10.0f * m_deltaTime; if (_window->KeyDown(Key::S)) _camera->position -= _camera->Front() * 10.0f * m_deltaTime; if (_window->KeyDown(Key::D)) _camera->position += _camera->Right() * 10.0f * m_deltaTime; if (_window->KeyDown(Key::A)) _camera->position -= _camera->Right() * 10.0f * m_deltaTime; if (_window->KeyDown(Key::E)) _camera->position += _camera->Up() * 10.0f * m_deltaTime; if (_window->KeyDown(Key::Q)) _camera->position -= _camera->Up() * 10.0f * m_deltaTime; } ...
The player can now move in all directions.
Mouse Looking
Next, let's allow the player to turn the camera using the mouse.
To do this, we'll start by locking and hiding the mouse so that it doesn't move around the screen as the player is looking around. Add this to the Start
function in your src\main.cpp
file:
main.cpp
...
void Start() override
{
...
_window->SetMouseDisabled(true);
}
...
That will lock the mouse, but it would be nice to be able to toggle it at runtime. We can use a key press event to check for the escape key and then toggle the mouse when it is pressed. Add this to your main.cpp
's Start
function:
main.cpp
... void Start() override { ... _window->SetMouseDisabled(true); _window->KeyPressEventDispatcher.RegisterListener([this](KeyPressEvent& e) { if (e.m_key == Key::ESC) _window->ToggleMouseDisabled(); }); } ...
We register a listener to _window
's KeyPressEventDispatcher
and use a lambda function with the argument, KeyPressEvent& e
. This event gets called only when a key is first pressed, making it very useful for creating a toggle. We then check if that key that was pressed is the escape key and toggle the mouse if it is.
Now that we locked the mouse, we can implement mouse movement using the window's MouseMoveEventListener
variable. We first add a _firstFrame
private variable to our application class and initialize it to true.
main.cpp
...
class ExampleApp : public Application
{
public:
...
private:
bool _firstFrame = true;
Camera* _camera;
};
...
We use this variable to ensure that no camera direction movement occurs on the first frame of the application. Mouse movement on the first frame will cause the values of the direction variable to reach very high values, causing the camera movement to simply not work.
Now, we register a listener to the window's MouseMoveEventListener
:
main.cpp
... void Start() override { ... _window->MouseMoveEventDispatcher.RegisterListener([this](MouseMoveEvent& e) { if (_firstFrame) _firstFrame = false; else { _camera->direction.y += e.m_xOffset * 0.1f; _camera->direction.x -= e.m_yOffset * 0.1f; if (_camera->direction.x > 89.0f) _camera->direction.x = 89.0f; else if (_camera->direction.x < -89.0f) _camera->direction.x = -89.0f; } }); } ...
This code first skips the first frame of the application runtime and then changes the camera direction based on the x and y mouse offset (the amount that the mouse moved). This does not need to be multiplied by m_deltaTime
because the mouse offset is already framerate independent.
After changing the mouse direction, we lock the x axis (vertical movement) of the mouse so that when you look up or down, it locks instead of turning the camera upside down.
Now that we added that event listener function, you should now be able to move the camera around and turn using the mouse.
Step 4: Basic UI
The last thing I'm going to cover here is UI. The prototype version of the engine uses ImGui for the UI, so most what you can do with ImGui can be found on ImGui's GitHub page.
WillowVox Engine handles the initialization of ImGui as well as the start of frame and end of frame ImGui logic. The developer can simply create the UI windows.
The only thing we need to do before we start working with UI is set the client ImGui context in the RenderUI
function in our main.cpp
file:
main.cpp
...
void RenderUI() override
{
ImGui::SetCurrentContext(GetImGuiContext());
}
...
Without this line, our game would crash if we try to render any UI elements.
To use ImGui, you create an ImGui window using ImGui::Begin()
and then put any UI elements inside that window before closing it using ImGui::End()
. Here's a basic window:
main.cpp
... void RenderUI() override { ImGui::SetCurrentContext(GetImGuiContext()); ImGui::Begin("Example UI", nullptr, ImGuiWindowFlags_AlwaysAutoResize); ImGui::End(); } ...
This code will create a window with the title, "Example UI." The second argument will likely always be nullptr. The third argument is a flag for the window. This flag will make sure the window is big enough to fit the content inside of it. There are tons of flags that you can look through. If you want to do more advanced stuff with ImGui, you can look into their wiki linked above.
This gives us an empty window, so let's populate it. First, we'll add some text:
main.cpp
...
void RenderUI() override
{
ImGui::SetCurrentContext(GetImGuiContext());
ImGui::Begin("Example UI", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
ImGui::Text("This is a test to make sure the window is working.");
ImGui::End();
}
...
Text is straightforward. You just pass in the text into the ImGui::Text
function. Make sure that you put this code between the Begin and End functions. Another note to add here is that this function takes a c-style string, not a standard library string, so if you're working with std::string
, make sure you pass str.c_str()
(str being an std::string variable) into the function.
You can also pass variables into the text function like this:
main.cpp
...
void RenderUI() override
{
ImGui::SetCurrentContext(GetImGuiContext());
ImGui::Begin("Example UI", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
ImGui::Text("This is a test to make sure the window is working.");
ImGui::Text("Camera position: %f %f %f", _camera->position.x, _camera->position.y, _camera->position.z);
ImGui::End();
}
...
This works in a very similar way to printf
. Use %f
for floats, %d
for ints, and %s
for strings.
You can also use ImGui to change variables. Here's an example of a slider that changes the mouse sensitivity:
main.cpp
... class ExampleApp : public Application { public: ... void Start() override { ... _window->MouseMoveEventDispatcher.RegisterListener([this](MouseMoveEvent& e) { if (_firstFrame) _firstFrame = false; else { _camera->direction.y += e.m_xOffset * _mouseSensitivity; _camera->direction.x -= e.m_yOffset * _mouseSensitivity; if (_camera->direction.x > 89.0f) _camera->direction.x = 89.0f; else if (_camera->direction.x < -89.0f) _camera->direction.x = -89.0f; } }); } ... void RenderUI() override { ImGui::SetCurrentContext(GetImGuiContext()); ImGui::Begin("Example UI", nullptr, ImGuiWindowFlags_AlwaysAutoResize); ImGui::Text("This is a test to make sure the window is working."); ImGui::Text("Camera position: %f %f %f", _camera->position.x, _camera->position.y, _camera->position.z); ImGui::SliderFloat("Mouse Sensitivity", &_mouseSensitivity, 0.01f, 0.5f); ImGui::End(); } private: bool _firstFrame = true; float _mouseSensitivity = 0.1f; Camera* _camera; }; ...
Note that I added a _mouseSensitivity
variable and changed the camera look code to use _mouseSensitivity
instead of a hard-coded 10.0f.
To create the slider, I created the name for the slider, then passed in a pointer to the _mouseSensitivity
variable, and then set the min and max values.
Now, you can change the mouse sensitivity. There are other ways of changing variables, but I'm not going to cover them all here. You should now have something that looks like this:

ImGui is a very powerful tool and I would recommend looking more into how to use it for your projects. Please note, though, that I intend to replace ImGui with my own UI system for the full release of the engine.
Conclusion
This guide showed you how to get started using WillowVox Engine. This only scratches the surface of what you can do using WillowVox. There will be many additions and changes to WillowVox Engine leading up to the full release.