GUInity

is a 3D component-based game engine. It's a personal project started by myself, Guilherme Cunha.

Objectives

Name

I chose the name GUInity because it summarizes well what I'm trying to do:

Dependencies

Please note that even though I'm developing the engine from scratch, I'm also using a bunch of libraries to aid the development. For the purposes of this project, I've chosen to use, so far, the following libraries:

Also please note that due to possible licensing issues of the libraries and operational system specific binaries, I'm not providing any of them in this repository. The benefit is that the repository is fairly compact.

Goal

My goal is NOT to create a new and better Unity, but to understand how they were able to develop it. Unity has grown a lot over the last few years, they've been obviously doing something right. Therefore, I think it's a good foundation for a game engine.

Here's the Trello board where I keep features that I'd like to implement in the future.

Overall Look and Description

So I've been talking about component-based here and there but what does that mean? Developers that are familiar with Unity are already used to this concept but if that's not the case, here's the main idea.

The game objects, Actors, are merely containers that exist in the World. All the behaviour that an Actor must have is done by adding and removing Components to it, not through standard inheritance. For example, to create an Actor that would be rendered on the screen, it would need a MeshFilter, reference to a Mesh Assets and a Mesh Renderer components. The code would be:


// Create an Actor
shared_ptr spaceShipRoot = Factory::CreateActor("SpaceShip");
// Set the scale of the Actor
spaceShipRoot->transform->setScale(glm::vec3(1.2,1.2,1.2));

// Add a MeshFilter to the Actor        
shared_ptr meshFilter = spaceShipRoot->AddComponent();
// Set the MeshFilter Mesh  
meshFilter->setMesh(spaceShipMesh);
// Add a MeshRenderer to the Actor        
shared_ptr meshRenderer = spaceShipRoot->AddComponent();
// Set the Material for the MeshRenderer
meshRenderer->setMaterial(spaceShipMaterial);

The components that are available right now are:

The piece of code above is enough for creating an Actor and rendering it on the screen but you must've noticed that it references some variables that were not declared in it. The variables that are missing are references to Assets. Assets are game assets that are imported or generated by GUInity.

The files that are inside the "data" folder are imported at the start of the program and can be accessed through the AssetsDatabase. Examples of Assets that are imported automatically are:

Examples of Assets that needs to be created manually are :

The piece of code that completes the previous one would be this:


// Gets a reference to the Mesh spaceShip.fbx
shared_ptr<Mesh> spaceShipMesh =  AssetDatabase::getAsset<Mesh>("spaceShip.fbx");

// Gets a reference to the Texture spaceShipTexture.png
shared_ptr<Texture> spaceShipTexture = AssetDatabase::getAsset<Texture>("spaceShipTexture.png");

// Creates a Shader using a vertex shader and a fragment shader
shared_ptr<Shader> diffuseShader = AssetDatabase::createShader("LightShader", CommonData("vsLight.vs"), 
							CommonData("fsLight.fragmentshader"));
    
// Creates a Material using the LightShader created above
shared_ptr<Material> spaceShipMaterial = AssetDatabase::createMaterial("SpaceShipMaterial", diffuseShader);
// Sets the texture of the material
spaceShipMaterial->setParamTexture("_textureSampler", spaceShipTexture);

This is all very good but... how do I add custom behaviour to my Actors? I'm glad you asked!

Custom behaviour is added to the Actors in the form of ScriptComponents. They have certain functions that are called when the game is awakening, ticking or being shutdown.

One example of a custom behaviour is the following class PlayerScript


// PlayerScript.h
class PlayerScript : public ScriptComponent
{
public:

  // The velocity drag
  float dragFactor;
  // How fast I move?
  float moveSpeed;
  // How fast I turn?
  float rotateSpeed;
  // Current velocity
  glm::vec3 velocity;

  // Called to initialize the script
  virtual void awake() override;
  // Called every frame
  virtual void tick(float deltaSecods) override;
  // Apply drag to velocity
  void applyDrag(float deltaSeconds);
  // Called when this Actor collided with another Actor  
  virtual void onCollision(shared_ptr<Actor> actor) override;

};


// PlayerScript.cpp
void PlayerScript::awake()
{
  // Initialize variables
  dragFactor = 0.01f;
  moveSpeed = 0.1f;
  rotateSpeed = 5; 
}

void PlayerScript::tick(float deltaSeconds)
{
  // Get reference to actor that owns this script
  shared_ptr<Actor> actorLock = actor.lock();
  
  if (!actorLock)
    return;
    
  // Get reference to its transform  
  shared_ptr<Transform> transform = actorLock->transform;
    
  // If keyboard arrow up is being pressed  
  if (Input::getKey(GLFW_KEY_UP))
  {
    // Add to current velocity  
    velocity += transform->getUp() * moveSpeed; 
  }
  
  // If keyboard arrow up is being pressed  
  if (Input::getKey(GLFW_KEY_LEFT))
  {
    // Get current rotation and apply some rotation CCW
    glm::quat rot = transform->getRotation();
    glm::quat left = glm::angleAxis(deltaSeconds * rotateSpeed, transform->getForward());
        
    transform->setRotation(left * rot);
  }
  if (Input::getKey(GLFW_KEY_RIGHT))
  {
    // Get current rotation and apply some rotation CW
    glm::quat rot = transform->getRotation();
    glm::quat right = glm::angleAxis(deltaSeconds * rotateSpeed, -transform->getForward());
        
    transform->setRotation(right * rot);
  }
   
  // Translate the actor based on current velocity
  glm::vec3 position = transform->getPosition();

  position += velocity * deltaSeconds;

  transform->setPosition(position);
  
  // Apply drag to velocity
  applyDrag(deltaSeconds);
}

void PlayerScript::applyDrag(float deltaSeconds)
{
  // Add opposite force
  velocity -= velocity * dragFactor;
}

void PlayerScript::onCollision(shared_ptr<Actor> actor)
{
  // This actor collided with another one. Do something!    
}

This is just a basic introduction and there's much more to explore in GUInity.

The following class diagram is the most complete one I could make while still keeping it readable.

GUInity Overview
Image in new tab

GUInity game example

Below is a video showcasing a small Asteroids-like game developed using GUInity.

Considerations

Creating a game engine is not a simple task. Instead of trying to create the most optimized engine ever, I'm just "doing it", for now. Every now and then, when I feel like I completely grasped a concept, I go back and do my best to optimize it. Most of the times, I aim for readability and try to experiment with new features of C++11. I've been learning a lot from this project and intend to carry it on as well as I can.

I develop this project alongside my Master in Digital Media. This means I don't have as much time as I'd like to work on it. It also means that some periods this repository will have more updates than others.