VERSION 2 CURRENTLY UNDER CONSTRUCTION!! :D
My first attempt at writing a from-scratch OpenGL game engine in C++, humbly called the "Bengine." The Qt GUI Framework is used extensively to provide OpenGL Widgets and make up the GUI for the Bengine Editor (learn more about the Bengine Editor here). The entire engine was programmed using Test-Driven Development strategies, utilizing the Google Test Framework to swiftly implement unit tests (learn more about the Bengine Tests here).
A demonstration of the latest capabilities of the engine and the editor:
- The Core Engine
- Dynamic DLL
- Math Library
- GameObject API
- Resource Management
- Rendering Engine
- Shaders and Uniform Variables
- The Editor
- Qt Framework
- Data-Driven Design
- The Tests
- Google Test
- Believing in Test-Driven Development
After transitioning from a Windows to a Macbook, I aborted this C++ version of my game engine for a revamped Java version (see https://github.com/BGR360/JavaGameEngine3D). However, I then discovered that that was a silly decision and have stopped working on the Java version. I have not put any work into this engine for a while and will probably restart from scratch if I want to continue working on it.
Why do I do this?
When I tell people about this project, I often hear them say, "You know, you could always just download Unity or UDK and use that to make games... Why you gotta go and write your own?" So really, why the hell am I doing this?
I'm not making a game engine so that I can make games. Sure, once it's done, I'll be able to do that if I want, but that's not what it's about. It's about the learning experience and the sense of accomplishment that comes with being able to write something from scratch. Sure, it'll never ever be the most efficient or groundbreaking or innovative game engine, but it'll be mine. I think that's pretty damn cool.
I owe many thanks to Jamie King and Benny Bobaganoosh for their great tutorials and learning resources on C++, OpenGL, and Game Engine programming. These guys do great work and you should check them out.
The Core Engine
The Core engine functionality was written entirely by me in standard C++11.
See Also: Core Engine Source Code
The Engine is implemented as a DLL, which at times led to some annoying problems with DLL-boundaries and memory allocation, but was ultimately meant to make the engine more robust and easy to use.
Access to OpenGL function calls was provided by GLEW (OpenGL Extension Wrangler). A rendering context and surface upon which to render was provided by Qt's
QOpenGLWidget class. Aside from this, all rendering code in my engine is pure OpenGL function calls, no high-level garbage.
See Also: Include folder
Homemade Math Library
One of the most important things required for a 3D game engine is a solid Vector and Matrix math library so that Matrix-Vector transformations in 3D space can be made easily. All of the common Vector and Matrix operations were coded by hand by me. I'm not going to say it was fun.
See Also: Math Library Source Code
The overall design of the Bengine external API is the GameObject. The GameObject API that I wrote is very similar to the structure of the Unity game engine.
Composition over Inheritance
Inheritance and polymorphism are great, but they certainly have their limitations, especially in the game world. For instance, you might define a class of objects that is
Movable and one that is
Static, and another class of objects that is
Renderable. The problem is that a
GameObject (for instance, a
NonPlayerCharacter) may be
Renderable. This leads to a diamond-shaped inheritance tree, which is a problem.
GameObject / \ / \ Renderable Movable \ / \ / NonPlayerCharacter
The solution is Composition. Instead of using multiple inheritance, a
GameObject is comprised of multiple
GameComponents which define its behavior. This leads to a much safer sort of inheritance tree:
GameComponent GameObject / \ | / \ +------------|-----------+ PhysicalComponent RenderableComponent | NonPlayerCharacter | / \ +------------------------+ / \ | - MovableComponent | StaticComponent MovableComponent | - RenderableComponent | +------------------------+
In the Bengine, every
GameObject has a list of
GameComponents which can be dynamically assigned. The GameComponents define the GameObject's behavior at runtime by specifying a custom
update() function. For example:
// Create GameObject and add Components GameObject go; go.addComponent(new MeshRenderer); go.addComponent(new PhysicsComponent); // Get Component and modify properties MeshRenderer* renderingComp = go.getComponent<MeshRenderer>(); renderingComp.setMesh(aMesh); renderingComp.setMaterial(aMaterial);
See Also: GameObject API
For any game to function well, there needs to be a centralized way to manage the assets (or Resources) in a scene. This is done through the
ResourceManager class. In hindsight, the design of
ResourceManager is pretty poor and hardly object-oriented, but it worked.
ResourceManager stores all resource-like object (Images, Textures, Materials, Meshes, Shaders) in maps so that each resource is mapped to a name (a string). To obtain a reference to a resource in-game, call one of the
get() functions with the name of the resource.
// Load resources ResourceManager res; res.loadMesh("res/meshes/monkey.obj", "monkey"); res.loadShader("res/shaders/basic_shader.vs", "basic_shader"); res.loadTexture("res/textures/bricks.jpg", "bricks", 0); // Create a material res.createMaterial("bricks", res.getShader("basic_shader"), res.getTexture("bricks")); // Create a Monkey GameObject GameObject monkey; monkey.addComponent( new MeshRenderer( res.getMesh("monkey"), res.getMaterial("bricks") ) );
See Also: Resources Source Code
Designing the Rendering Engine was one of the most difficult parts of the whole project. I really struggled with finding my own way to make a robust object-oriented system. What I ended up with definitely worked but was far from elegant (at least in my eyes).
RenderingEngine class is responsible for rendering the
Scene has its own
ResourceManager and root
RenderingEngine traverses through the
GameObject tree and calls
render() on any
GameObjects which have
The strange thing is that
RenderingEngine inherits from Qt's
QOpenGLWidget. This is what throws off much of the system's object-orientedness. I had to do this because of how Qt works with OpenGL. When using Qt, any calls to OpenGL functions must occur within the
GameObject which can be rendered has a
RenderingComponent. Currently, the only known
RenderingComponent in the Bengine is the
MeshRenderer stores a pointer to a
Mesh and a
Material, which is all that it needs to properly render a mesh (because a Material has a pointer to a
Mesh is defined by a set of vertices, normals, and UV coordinates.
Meshes are also indexed to help save space. Currently, only
.obj files can be loaded to create
Meshes. You can view the new-and-improved obj parsing algorithm here.
Shaders and Uniform Variables
The compilation and loading of GLSL Shaders in the Bengine is done through the
Shader (.h .cpp) and
ShaderProgram (.h .cpp) classes. Just like in OpenGL, a
ShaderProgram is comprised of multiple
Shaders at different stages (the Vertex Shader, Fragment Shader, Geometry Shader, etc.).
The system I created for shader uniform variables, I think, was a good one. The whole
UniformVariable class (.h .inl .cpp) was templated so that different datatypes could specify how they are
bind()ed to the shader program.
One really cool thing is that Uniform Variables are automatically detected while parsing the GLSL source code. Seriously, check it out!
In the Bengine, a
Material is defined by a
Texture and a
ShaderProgram which is used to render it. The creation of
Materials is done by the
ResourceManager (see here).
See Also: Rendering Source Code
See Also: Editor Source Code
See Also: Qt Home Page
See Also: Example Game and Scene Files
See Also: Google Test Framework
Believing in Test-Driven Development
See Also: All of the Testing Code