Turbo-Hybrid Game Engine

thumbnail
  • C++
  • SDL2
  • bgfx

Details

The Turbo Hybrid Game Engine is a 3D game framework that includes a game object component system built using a structure of arrays, JSON data serialization, and bgfx 3D rendering.

Overview

This project was completed over 15 weeks as apart of a class on game engine development during the Fall of 2022. Creating an SDL window, game objects, components, and basic player movement were core to the course design. JSON parsing and 3D rendering were optional components. As a part of the final project I teamed up with Steven Annunziato to help with Implementing a 3D renderer.

We made the design decision to ensure our solution allowed for custom shaders on a per object basis. We settled on bgfx for its wide use in games including Minecraft. It also abstracted the backend service decreasing the time and boilerplate for setting up OpenGL. With solid documentation we dove head first implementing a MVP matrix for each game object that had a cube component renderer.

Technical Details

Below is a blog post going into all the details about our rendering implementation.

Personal Contributions:

  • Implemented emscripten and windows build systems using SDL and C++.
  • Utilized JSON file format for reading game object and component data.
  • Worked on integrating bgfx 3D rendering library into the engine and built out a cube renderer component

Overview

In a world of game engines and package managers, it is easy to become detached from the core processes that make up modern software. For game developers, this comes in the form of software like Unity, Unreal Engine, Gamemaker, and Godot. Especially in 3D software, the process of rendering is taken for granted. Even the idea of creating an application window can be a concept that is hard to grasp. This was true for me before starting to build this engine. I have been using PCs since Windows XP yet I had no intuition about how to create a window.

The Turbo Hybrid Game engine was created to teach myself how to build an application from scratch. The goals for this engine:

  • Build a game engine application from scratch
  • Compile to multiple platforms
  • Data loaded from JSON
  • Render 3D graphics

GameObject Data

All the game objects are stored inside json with a component structure. The list of game objects is an array and each component is marked is a 4 letter string. Each 4 letter string can be converted into a enum representing a component type. Here is a simple file where the components are TRAN(Transform), CUBE(Cube renderer), and PLRC(Player Controller).

{ "GameObjects" : [ { "TRAN" : [0, 0,5 ], "CUBE" : { "Color": [1, 1, 1, 1] }, "PLRC" : { "Speed" : [0.1] } }, { "TRAN" : [4, 0, 0], "CUBE" : { "Color": [1, 0, 1, 1] } } ] }

3D Graphics

The main chunk of this project was the 3D Graphics rendering. The project was completed as a part of the Champlain College Game Programming curriculum. But when it came to how we implemented Serialization and what we did for our final project approaches differed. I worked with StevenAnnunziato to develop our 3D graphics solution. We decided to implement bgfx as an intermediate graphics library. This allowed us to focus on the concepts of 3D rendering rather than getting stuck on lower ideas. This also allowed me to focus on integrating it into the engine while Steve was able to integrate the shaders.

Creating a render function

The first thing I want to look at is the render function. Inside my main loop, I am creating two matrices that I am going to pass into bgfx to define my 3D space. These matrices are the view matrix and projection matrix. The View matrix defines my camera while the projection matrix determines the type of camera. the two most common types are perspective and orthographic. Here I am using perspective. This means I am defining a perspective camera.

GLM is a math library that we needed to use on windows because the built-in math library inside of bx was not suitable for use on windows. GLM is an open source header-only math library. GLM is based on the OpenGL specification which makes it perfect for our use.

/* Set up view projection matrix */ // Look at the first gameObject const glm::vec3 at = gameObjects[0]-> GetTransform()-> GetLocation(). Vec3(); // location of the eye/camera const glm::vec3 eye = { 0.0f, 0.0f, -5.0f }; // reference for up vector const glm::vec3 up = { 0.0f, 1.0f, 0.0f }; // view matrix is created glm::mat4x4 view = glm::lookAt(eye, at, up); // create projection matrix using a perspective projection glm::mat4x4 proj = glm::perspective (80.0f, //FOV float(WIDTH) / float(HEIGHT), //Aspect Ratio 0.1f, //Near Clipping plane 100.0f); //Far Clipping plane // set view and projection matrix bgfx::setViewTransform(0, &view, &proj); /* Render Cube components */ TurboHybrid::ComponentSystem::GetComponentSystem()-> renderCubes(engine->frame); /* Render next frame */ bgfx::frame();

Rendering Cube render components

The Turbo hybrid engine uses a component bases structure of arrays. This means that all the components are stored on the stack right next to each other. By looping through an array of CubeRenderer components I compute specific actions. Each CubeRenderer component references the same vertex and index buffers that define the cube. They also are using the same shader program.

void TurboHybrid::CubeRenderer::render(const float& deltatime) { assert(m_vbh.idx != 0 || m_ibh.idx != 0 || m_program.idx != 0); //set up render state for object uint64_t state = 0 | (BGFX_STATE_WRITE_R) | (BGFX_STATE_WRITE_G) | (BGFX_STATE_WRITE_B) | (BGFX_STATE_WRITE_A) | BGFX_STATE_WRITE_Z | BGFX_STATE_DEPTH_TEST_LESS | BGFX_STATE_CULL_CW | BGFX_STATE_MSAA | UINT64_C(0) ; // init with no translation glm::mat4x4 model = glm::mat4(1.0f); // Set Position to the transform component position Vector3 pos3 = gameObject->GetTransform()->GetLocation(); glm::vec3 pos = glm::vec3(pos3.x, pos3.y, pos3.z); model = glm::translate(model, pos); // Set rotation float rotationDirection = 100.0f; if (gameObject->GetPlayerController() != nullptr) { rotationDirection *= -1; // If player controlled invert rotation } model = glm::rotate( model, //Matrix deltatime / rotationDirection, //Rotation amount glm::vec3(1.0f, 1.0f, 0.0f)); //axis of rotation bgfx::setTransform(&model); // Set Color uniform to pass information to the shader float color[4] = { m_color.r, m_color.g, m_color.b, m_color.a }; bgfx::setUniform(m_uniform, color); // Set Vertex and Index Buffers bgfx::setVertexBuffer(0, m_vbh); bgfx::setIndexBuffer(m_ibh); // Set render states. bgfx::setState(state); //Each object has unique state //Submit program for rendering bgfx::submit(0, m_program); }

This is what renders each cube component separately. Adding things like mesh information and setting different shaders would just be a matter of changing out the buffers and the program. Adding additional components for things like collision would be the first step to making a more versatile game engine.

Conclusion

I learned a lot from developing this engine. It was really satisfying to see all the steps of the process from window creation to rendering full 3D images. Bgfx was a great option for developing my own 3D components while maintaining control of the graphics pipeline while interfacing with your graphics backend of choice. It also supports various backends and platforms which makes it a great option for games. This project is only scratching the surface but I am really glad to of had the experience.

If your interested in the codebase it is on my GitHub and if you have any questions feel to shoot me a message on linkedin