Skip to content

Sloth-King/Fisheye-Engine

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

12 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Fisheye Engine

fisheye logo

Fisheye is a 3D game engine written and usable in C++. The intent was to create an easy to use, performant, and customizable platform for game developement.

This is a university project made by two people in ~two months.

No generative AI was used in the making of this project.

fisheye-vid.mp4

Features

  • Scene graph, gameobjects and components

    A game is composed of tree-like scenes; hierarchies of gameobjects, using an Entity-Component approach (as used by the Unity engine). Components define the behaviors of gameobjects. The user can create new components easily.

  • Deffered rendering

    The rendering happens in two passes. First, we draw all the rendering informations (normals, albedos, PBR data, ...) in world-space to 2D buffers, then we use the data to generate the final texture. This saves time on lighting calculation, allows us to have a very big number of lights in the scene with only a small performance cost.

  • Physics engine

    Our physics engine runs on its own loop and is partly inspired on a Valve talk on the Source engine, in order to compute and solve collisions efficiently.

    The physics engine is still experimental. Things may not work as expected (particularily rotation-wise :s )

    For now, the physics update loop runs on the same thread as the render update loop. This will change in the future.

    fisheye physics
  • Rendering and physics servers

    For efficient processing of batch operations, we took inspiration from ECS architecture and the Godot game engine. Rendering code and physics-related code is ran by servers akin to ECS systems, which work fluidly around the scene hierarchy and result in more efficient processing.

  • Additional demo candy

    A fully functional voxel and chunking system, with simple world generation based on noise functions, has been implemented for the 'voxel submarine' demo. The demo and its components are also present

How To Use

We'll create a very simple game to show how one may use the engine.

Initial setup

Let's start by creating a Game in our main function, setting some of its parameters, initialize openGL and start the update loops.

#include "engine/includes/core.h" // imports core engine features
#include "engine/includes/components.h" // imports base engine components
void main (){
  Game game;

  game.settings.windowWidth = 1280;
  game.settings.windowHeight = 720;
  
  game.init(); // initialize graphical backend

  setupGameScene(game); // function detailed in the next part

  game.start(); // start update and physics loops
  game.waitGameStopped(); // wait for the quit signal before terminating

}

Build and launch using the provided Cmake. This gives us a fully black window.

Cube and light

Let us add a cube and a light to our scene. We fill up the setupScene function called in our main.

void setupScene(Game& game){
    Scene scene; // Create a scene to contain our gameobjects
    GameObject world; // root of our scene

    // load an obj file
    Mesh cube = ResourceLoader::load_mesh_obj("../game/resources/meshes/supercube.obj");

    // We create a PBR material object. A handle is the (possibly shared) owner of a resource
    // As long as one handle exists, the resource is not freed up (akin to a shared_ptr)
    cube.material = Handle<MaterialPBR>(Texture("../game/resources/textures/logo.jpg"));

    // Add a 'Transform' compoment to the world
    // addComponent creates the component on the gameobject and returns a pointer to it.
    // The pointer is guaranteed to be valid for the lifetime of the gameobject.
    C_Transform* t = world->addComponent<C_Transform>(); 
    t->setPosition(glm::vec3(0, -0.2, 0)); // using glm as a placeholder, as we'll be creating our own algebra module.
    t->setScale(glm::vec3(0.1, 0.1, 0.1));

    // add a mesh component (directly on the world for now)
    world->addComponent<C_Mesh>() -> mesh = cube; 

    // light
    GameObject light;
    auto* lightComponent = light->addComponent<C_Light>();
    lightComponent->light.color = glm::vec3(1.0, 1.0, 1.0);
    light->addComponent<C_Transform>()->setPosition(glm::vec3(10.0, 10.0, 5.0));

    world->addChild(std::move(light)); // We give the light to the world (amen)
    lightComponent->light.intensity = 100.0; // but the discarded object is still valid to use as a non-owning pointer to the gameobject

    scene.setRoot(std::move(world)); // we give the world to the scene
    game.setScene(std::move(scene)); // and the scene to the game
}

We get the following scene:

one cube in the void

From there, we could add more components to further customize the behavior of our gameobjects.

Implementing new behaviors

In order to customize the behavior of the gameobjects in our world, we can create new components. We do this by inheriting from the Component class.

This gives us five virtual functions that we may override to our liking :

  • onEnterScene() & onExitScene(), called when the owning gameobject enters or exits a scene
  • onUpdate($\delta_t$)), onPhysicsUpdate($\delta_t$) et onLateUpdate($\delta_t$) are called at different stages of the frame's computation.

By inheriting other components (and creating new virtual functions to override), we can easily and organically expand the capabilities of our gameobjects.

For example, the C_RigidBody Component offers an onContact(Contact) function, which allows inheriting components to execute code on a physics collision.

Let's create a player controller component. This also demonstrates the user inputs system:

class C_PlayerController: public Component {
public:
    float movement_speed = 2.0;
    void jump();

    void inputCallback(const InputEvent & e){
        if (e == "move_up" && e.pressed() && isOnGround){
            jump(); 
    }
    C_PlayerController(){
      // user input callback registration
      // The lambda is necessary as we can't cast a method to a generic function signature
      Input::addInputListener(
          [& /*capture ambient scope*/](const InputEvent & e){
              inputCallback(e);
          }
      );

      // Defined here for the sake of the example. It would be better to define them once and for all at the game start.
      Input::addKeybind( "move_right",
        KeySpec(BINDTYPE_KEYBOARD, GLFW_KEY_D)
      );
      Input::addKeybind("move_left",
        KeySpec(BINDTYPE_KEYBOARD, GLFW_KEY_A) 
      );
      Input::addKeybind("move_up",
        KeySpec(BINDTYPE_MOUSE, GLFW_MOUSE_BUTTON_1) 
      );
  }

  virtual void _onUpdate(float delta) override{

      delta *= movement_speed;

      // check if we do have a transform
      if (!getOwner()->hasComponent<C_Transform>()) return;

      auto* transform = getOwner()->getComponent<C_Transform>();

      if (Input::isInputPressed("move_right")){
          transform->move(glm::vec3(1, 0, 0) * delta);
      }
      if (Input::isInputPressed("move_left")){
          transform->move(glm::vec3(-1, 0, 0) * delta);
      }
  }
};

Then, we just need to add this component to a gameobject in order to move it around:

For now, those 9 compoments are available in the engine :

  • C_Transform
  • C_RigidBody
  • C_Collider
  • C_Mesh
  • C_Camera
  • C_Light
  • C_PlayerController*
  • C_MapManager*
  • C_voxelMesh*

*created for the 'voxel submarine' demo

In the future, we'd like to create a diverse and extensive library of authored components.

Thanks and enjoy !

About

A from scratch game engine made in C++

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors