Greetings to everyone. In this article I would like to continue discussing about Goknar Engine by the means of input system and renderer. Then I will show how I made a very simple UI with ImGui. Let’s start with the InputManager.

Input Manager

InputManager class is fully biult on delegates. To mention what is a delegate for those who are not familiar with delegates: A delegate is simply a function pointer where you can bind functions and call them when the conditions are met.

I used standard function library because it makes private and/or non-static functions bindable to the delegates.

There is five different delegatable input types which are mouse for mouse clicks, keyboard for keyboard entries, cursor for the cursor position, scroll for the scroll rolls, and char for the character inputs to be used in textbox like components.

I hold these delegates in unordered maps since it is a hashing container type, and has constant time complexity accordingly.

#ifndef __INPUTMANAGER_H__
#define __INPUTMANAGER_H__

#include "pch.h"

#include "Core.h"

struct GLFWwindow;

enum class GOKNAR_API INPUT_TYPE : uint8_t
{
    G_KEYBOARD = 0,
    G_CHAR,
    G_MOUSE,
    G_CURSOR,
    G_SCROLL
};

enum class GOKNAR_API INPUT_ACTION : uint8_t
{
    G_PRESS = 0,
    G_RELEASE,
    G_REPEAT
};

typedef std::function KeyboardDelegate;
typedef std::vector  < KeyboardDelegate > KeyboardDelegateVector;

typedef std::function KeyboardListener;
typedef std::vector  < KeyboardListener > KeyboardListenerVector;

typedef std::function MouseDelegate;
typedef std::vector  < MouseDelegate > MouseDelegateVector;

typedef std::function CursorDelegate;
typedef std::vector  < CursorDelegate > CursorDelegateVector;

typedef std::function ScrollDelegate;
typedef std::vector  < ScrollDelegate > ScrollDelegateVector;

typedef std::function CharDelegate;
typedef std::vector  < CharDelegate > CharDelegateVector;


class GOKNAR_API InputManager
{
public:
    InputManager();
    ~InputManager();

    void Init();

    static inline void KeyboardCallback(GLFWwindow *window, int key, int scanCode, int action, int mod);
    static inline void CursorPositionCallback(GLFWwindow *window, double xPosition, double yPosition);
    static inline void MouseButtonCallback(GLFWwindow *window, int button, int action, int mods);
    static inline void ScrollCallback(GLFWwindow *window, double xOffset, double yOffset);
    static inline void CharCallback(GLFWwindow *window, unsigned int codePoint);

    void AddKeyboardInputDelegate(int keyCode, INPUT_ACTION inputAction, const KeyboardDelegate &binderFunction)
    {
        switch (inputAction)
        {
            case INPUT_ACTION::G_PRESS: 
                pressedKeyDelegates_[keyCode].push_back(binderFunction);
                break;
            case INPUT_ACTION::G_RELEASE:
                releasedKeyDelegates_[keyCode].push_back(binderFunction);
                break;
            case INPUT_ACTION::G_REPEAT:
                repeatedKeyDelegates_[keyCode].push_back(binderFunction);
                break;
            default: ;
        }
    }

    void AddKeyboardListener(const KeyboardListener &keyboardListener)
    {
        keyboardListeners_.push_back(keyboardListener);
    }

    void AddMouseInputDelegate(int keyCode, INPUT_ACTION inputAction, const KeyboardDelegate &binderFunction)
    {
        switch (inputAction)
        {
        case INPUT_ACTION::G_PRESS:
            pressedMouseDelegates_[keyCode].push_back(binderFunction);
            break;
        case INPUT_ACTION::G_RELEASE:
            releasedMouseDelegates_[keyCode].push_back(binderFunction);
            break;
        case INPUT_ACTION::G_REPEAT:
            repeatedMouseDelegates_[keyCode].push_back(binderFunction);
            break;
        default:;
        }
    }

    void AddCursorDelegate(const CursorDelegate &cursorDelegate)
    {
        cursorDelegates_.push_back(cursorDelegate);
    }

    void AddScrollDelegate(const ScrollDelegate &cursorDelegate)
    {
        scrollDelegates_.push_back(cursorDelegate);
    }

    void AddCharDelegate(const CharDelegate &charDelegate)
    {
        charDelegates_.push_back(charDelegate);
    }

private:
    // Keyboard Delegates
    std::unordered_map < int, KeyboardDelegateVector > pressedKeyDelegates_;
    std::unordered_map < int, KeyboardDelegateVector > releasedKeyDelegates_;
    std::unordered_map < int, KeyboardDelegateVector > repeatedKeyDelegates_;

    KeyboardListenerVector keyboardListeners_;

    // Mouse Delegates
    std::unordered_map < int, MouseDelegateVector > pressedMouseDelegates_;
    std::unordered_map < int, MouseDelegateVector > releasedMouseDelegates_;
    std::unordered_map < int, MouseDelegateVector > repeatedMouseDelegates_;

    // Cursor Delegates
    CursorDelegateVector cursorDelegates_;

    // Scroll Delegates
    ScrollDelegateVector scrollDelegates_;

    // Char Delegates
    CharDelegateVector charDelegates_;
};

#endif
#include "pch.h"

#include "InputManager.h"
#include "WindowManager.h"
#include "Engine.h"

#include "GLFW/glfw3.h"
#include "Log.h"

InputManager::InputManager()
{
}

InputManager::~InputManager() = default;

void InputManager::Init()
{
    GLFWwindow *window = engine->GetWindowManager()->GetWindow();

    glfwSetKeyCallback(window, InputManager::KeyboardCallback);
    glfwSetCursorPosCallback(window, InputManager::CursorPositionCallback);
    glfwSetMouseButtonCallback(window, InputManager::MouseButtonCallback);
    glfwSetScrollCallback(window, InputManager::ScrollCallback);
    glfwSetCharCallback(window, InputManager::CharCallback);
}

void InputManager::KeyboardCallback(GLFWwindow *window, int key, int scanCode, int action, int mod)
{
    for(const KeyboardListener GetInputManager()->keyboardListeners_)
    {
        keyboardListener(key, scanCode, action, mod);
    }

    switch (action)
    {
        case GLFW_PRESS:
        {
            for (const KeyboardDelegate pressedKeyDelegate : engine->GetInputManager()->pressedKeyDelegates_[key])
            {
                pressedKeyDelegate();
            }

            break;
        }
        case GLFW_RELEASE:
        {
            for (const KeyboardDelegate releasedKeyDelegate : engine->GetInputManager()->releasedKeyDelegates_[key])
            {
                releasedKeyDelegate();
            }

            break;
        }
        case GLFW_REPEAT:
        {
            for (const KeyboardDelegate repeatedKeyDelegate : engine->GetInputManager()->repeatedKeyDelegates_[key])
            {
                repeatedKeyDelegate();
            }

            break;
        }
        default:
            break;
    }
}

void InputManager::CursorPositionCallback(GLFWwindow* window, double xPosition, double yPosition)
{
    for (const CursorDelegate cursorDelegate : engine->GetInputManager()->cursorDelegates_)
    {
        cursorDelegate(xPosition, yPosition);
    }
}

void InputManager::MouseButtonCallback(GLFWwindow* window, int button, int action, int mods)
{
    switch (action)
    {
    case GLFW_PRESS:
    {
        for (const MouseDelegate pressedMouseDelegate : engine->GetInputManager()->pressedMouseDelegates_[button])
        {
            pressedMouseDelegate();
        }

        break;
    }
    case GLFW_RELEASE:
    {
        for (const MouseDelegate releasedMouseDelegate : engine->GetInputManager()->releasedMouseDelegates_[button])
        {
            releasedMouseDelegate();
        }

        break;
    }
    case GLFW_REPEAT:
    {
        for (const MouseDelegate repeatedMouseDelegate : engine->GetInputManager()->repeatedMouseDelegates_[button])
        {
            repeatedMouseDelegate();
        }

        break;
    }
    default:
        break;
    }
}

void InputManager::ScrollCallback(GLFWwindow* window, double xOffset, double yOffset)
{
    for (const ScrollDelegate scrollDelegate : engine->GetInputManager()->scrollDelegates_)
    {
        scrollDelegate(xOffset, yOffset);
    }
}

void InputManager::CharCallback(GLFWwindow * window, unsigned int codePoint)
{
    for (const CharDelegate charDelegate : engine->GetInputManager()->charDelegates_)
    {
        charDelegate(codePoint);
    }
}

Renderer

Renderer class is the class that renders every renderable object in the screen. It sets buffer data, organizes and initializes OpenGL, and then renders the screen at each frame.

Actually the Renderer class is not completed yet, but it is needed to draw even a simple shape on the screen.

/*
*    Game Engine Project
*    Emre Baris Coskun
*    2018
*/

#ifndef __RENDERER_H__
#define __RENDERER_H__

#include "Core.h"
#include "GraphicsManager.h"

#include 

class GOKNAR_API RenderingObject;

class GOKNAR_API Renderer
{
public:
    Renderer();
    ~Renderer();

    void SetBufferData();
    void Init();

    void Render();

    void AddObjectToRenderer(RenderingObject *object);

private:
    std::vector objectsToBeRendered_;

    GraphicsManager *graphicsManager_;

    gmUInt vertexBufferId_;
    gmUInt indexBufferId_;
};

#endif
/*
*    Game Engine Project
*    Emre Baris Coskun
*    2018
*/
#include "pch.h"

#include "Renderer.h"
#include "Engine.h"
#include "Scene.h"
#include "glad/glad.h"

Renderer::Renderer(): vertexBufferId_(0), indexBufferId_(0)
{
    graphicsManager_ = engine->GetGraphicsManager();
}

Renderer::~Renderer() = default;

void Renderer::SetBufferData()
{
}

void Renderer::Init()
{
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_CULL_FACE);
    glDepthFunc(GL_LEQUAL);
}

void Renderer::Render()
{
    glClearColor(GLfloat(Scene::mainScene->bgColor.r), GLfloat(Scene::mainScene->bgColor.g), GLfloat(Scene::mainScene->bgColor.b), 1.f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    // TODO Render scene
}

void Renderer::AddObjectToRenderer(RenderingObject *object)
{
    //objectsToBeRendered_.push_back(object);
}

A Simple UI with ImGui

I used ImGui for the editor user interface of the engine. The reason I used ImGui is that ImGui is open-source, easy to use, and cross platform.

Firstly I submoduled ImGui repository on GitHub. Then I copied ImGuiImplOpenGL3 class to my solution to make my job easier. And finally I creat ImGuiEditor class, and add some functions to it.

#pragma once

#include "../Editor.h"
#include "imgui.h"
#include "Goknar/Math.h"

struct GLFWwindow;

namespace MAIN_MENU_ACTIVEABLE_ITEMS
{
    inline const char* VIEW_BASIC_ASSETS_ACTIVE = "+ Basic Assets";
    inline const char* VIEW_BASIC_ASSETS_PASSIVE = "- Basic Assets";
    inline const char* VIEW_LOG_ACTIVE = "+ Log";
    inline const char* VIEW_LOG_PASSIVE = "- Log";
};

struct ImGuiLog
{
    ImGuiTextBuffer     Buf;
    ImGuiTextFilter     Filter;
    ImVector       LineOffsets;        // Index to lines offset. We maintain this with AddLog() calls, allowing us to have a random access on lines
    bool                AutoScroll;
    bool                ScrollToBottom;

    ImGuiLog()
    {
        AutoScroll = true;
        ScrollToBottom = false;
        Clear();
    }

    void  Clear()
    {
        Buf.clear();
        LineOffsets.clear();
        LineOffsets.push_back(0);
    }

    void AddLog(const char* fmt, ...) IM_FMTARGS(2)
    {
        int old_size = Buf.size();
        va_list args;
        va_start(args, fmt);
        Buf.appendfv(fmt, args);
        va_end(args);
        for (int new_size = Buf.size(); old_size < new_size; old_size++)
            if (Buf[old_size] == '\n')
                LineOffsets.push_back(old_size + 1);
        if (AutoScroll)
            ScrollToBottom = true;
    }

    void Draw(const char* title, bool* p_open = NULL)
    {
        if (!ImGui::Begin(title, p_open))
        {
            ImGui::End();
            return;
        }

        if (ImGui::BeginPopup("Options"))
        {
            if (ImGui::Checkbox("Auto-scroll", &AutoScroll))
                if (AutoScroll)
                    ScrollToBottom = true;
            ImGui::EndPopup();
        }

        if (ImGui::Button("Options"))
            ImGui::OpenPopup("Options");
        ImGui::SameLine();
        bool clear = ImGui::Button("Clear");
        ImGui::SameLine();
        bool copy = ImGui::Button("Copy");
        ImGui::SameLine();
        Filter.Draw("Filter", -100.0f);

        ImGui::Separator();
        ImGui::BeginChild("scrolling", ImVec2(0, 0), false, ImGuiWindowFlags_HorizontalScrollbar);

        if (clear)
            Clear();
        if (copy)
            ImGui::LogToClipboard();

        ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0));
        const char* buf = Buf.begin();
        const char* buf_end = Buf.end();
        if (Filter.IsActive())
        {
            for (int line_no = 0; line_no < LineOffsets.Size; line_no++)
            {
                const char* line_start = buf + LineOffsets[line_no];
                const char* line_end = (line_no + 1 < LineOffsets.Size) ? (buf + LineOffsets[line_no + 1] - 1) : buf_end;
                if (Filter.PassFilter(line_start, line_end))
                    ImGui::TextUnformatted(line_start, line_end);
            }
        }
        else
        {
            ImGuiListClipper clipper;
            clipper.Begin(LineOffsets.Size);
            while (clipper.Step())
            {
                for (int line_no = clipper.DisplayStart; line_no < clipper.DisplayEnd; line_no++)
                {
                    const char* line_start = buf + LineOffsets[line_no];
                    const char* line_end = (line_no + 1 < LineOffsets.Size) ? (buf + LineOffsets[line_no + 1] - 1) : buf_end;
                    ImGui::TextUnformatted(line_start, line_end);
                }
            }
            clipper.End();
        }
        ImGui::PopStyleVar();

        if (ScrollToBottom)
            ImGui::SetScrollHereY(1.0f);
        ScrollToBottom = false;
        ImGui::EndChild();
        ImGui::End();
    }
};


class GOKNAR_API ImGuiEditor : public Editor
{
public:
    ImGuiEditor();
    ~ImGuiEditor();

    void Init() override;
    void Tick(float deltaTime) override;

    void Log(const char *logMessage);
private:
    // Input events
    void OnKeyboardEvent(int key, int scanCode, int action, int mod);

    void OnCursorMove(double xPosition, double yPosition);
    void OnScroll(double xOffset, double yOffset);
    void OnLeftClickPressed();
    void OnLeftClickRelease();
    void OnCharPressed(unsigned int codePoint);

    void ShowAbout();
    void ShowBasicAssetsBrowser();
    void ShowLog();
    void ShowMainMenu();

    ImGuiLog imguiLog_;

    Vector2i windowSize_;

    bool showAbout_;
    bool showBasicAssetsBrowser_;
    bool showLog_;
};
#include "pch.h"

#include "ImGuiEditor.h"

#include "imgui.h"
#include "GLFW/glfw3.h"
#include "glad/glad.h"
#include "ImGuiOpenGL.h"

#include "Goknar/Engine.h"
#include "Goknar/WindowManager.h"
#include "Goknar/InputManager.h"
#include "Goknar/Log.h"

ImGuiEditor::ImGuiEditor()
{
    showAbout_ = false;
    showBasicAssetsBrowser_ = true;
    showLog_ = false;
}

ImGuiEditor::~ImGuiEditor()
{

}

void ImGuiEditor::Init()
{
    engine->GetInputManager()->AddKeyboardListener(std::bind(ImGuiEditor::OnKeyboardEvent, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4));

    engine->GetInputManager()->AddCursorDelegate(std::bind(ImGuiEditor::OnCursorMove, this, std::placeholders::_1, std::placeholders::_2));
    engine->GetInputManager()->AddScrollDelegate(std::bind(ImGuiEditor::OnScroll, this, std::placeholders::_1, std::placeholders::_2));

    engine->GetInputManager()->AddMouseInputDelegate(GLFW_MOUSE_BUTTON_1, INPUT_ACTION::G_PRESS, std::bind(ImGuiEditor::OnLeftClickPressed, this));
    engine->GetInputManager()->AddMouseInputDelegate(GLFW_MOUSE_BUTTON_1, INPUT_ACTION::G_RELEASE, std::bind(ImGuiEditor::OnLeftClickRelease, this));

    engine->GetInputManager()->AddCharDelegate(std::bind(ImGuiEditor::OnCharPressed, this, std::placeholders::_1));

    windowManager_ = engine->GetWindowManager();
    const Vector2i windowSize = windowManager_->GetWindowSize();

    ImGui::CreateContext();
    ImGui::StyleColorsDark();

    ImGuiIO io = ImGui::GetIO();
    io.DisplaySize = ImVec2((float)windowSize.x, (float)windowSize.y);
    io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors;
    io.BackendFlags |= ImGuiBackendFlags_HasSetMousePos;
    io.KeyRepeatRate = 0.1f;

    // TODO: Implement and set Goknar Keymaps
    io.KeyMap[ImGuiKey_Tab] = GLFW_KEY_TAB;
    io.KeyMap[ImGuiKey_LeftArrow] = GLFW_KEY_LEFT;
    io.KeyMap[ImGuiKey_RightArrow] = GLFW_KEY_RIGHT;
    io.KeyMap[ImGuiKey_UpArrow] = GLFW_KEY_UP;
    io.KeyMap[ImGuiKey_DownArrow] = GLFW_KEY_DOWN;
    io.KeyMap[ImGuiKey_PageUp] = GLFW_KEY_PAGE_UP;
    io.KeyMap[ImGuiKey_PageDown] = GLFW_KEY_PAGE_DOWN;
    io.KeyMap[ImGuiKey_Home] = GLFW_KEY_HOME;
    io.KeyMap[ImGuiKey_End] = GLFW_KEY_END;
    io.KeyMap[ImGuiKey_Insert] = GLFW_KEY_INSERT;
    io.KeyMap[ImGuiKey_Delete] = GLFW_KEY_DELETE;
    io.KeyMap[ImGuiKey_Backspace] = GLFW_KEY_BACKSPACE;
    io.KeyMap[ImGuiKey_Space] = GLFW_KEY_SPACE;
    io.KeyMap[ImGuiKey_Enter] = GLFW_KEY_ENTER;
    io.KeyMap[ImGuiKey_Escape] = GLFW_KEY_ESCAPE;
    io.KeyMap[ImGuiKey_A] = GLFW_KEY_A;
    io.KeyMap[ImGuiKey_C] = GLFW_KEY_C;
    io.KeyMap[ImGuiKey_V] = GLFW_KEY_V;
    io.KeyMap[ImGuiKey_X] = GLFW_KEY_X;
    io.KeyMap[ImGuiKey_Y] = GLFW_KEY_Y;
    io.KeyMap[ImGuiKey_Z] = GLFW_KEY_Z;

    ImGui_Init("#version 410 core");

    GOKNAR_CORE_INFO("ImGui Editor Is Initiated.");
}

void ImGuiEditor::Tick(float deltaTime)
{
    windowSize_ = windowManager_->GetWindowSize();

    ImGuiIO io = ImGui::GetIO();
    io.DisplaySize = ImVec2((float)windowSize_.x, (float)windowSize_.y);

    ImGui_NewFrame();
    ImGui::NewFrame();

    //static bool showDemo = true;
    //ImGui::ShowDemoWindow(showDemo);

    ShowMainMenu();

    if(showAbout_)
    {
        ShowAbout();
    }

    if(showBasicAssetsBrowser_)
    {
        ShowBasicAssetsBrowser();
    }

    if(showLog_)
    {
        ShowLog();
    }

    ImGui::Render();
    ImGui_RenderDrawData(ImGui::GetDrawData());
}

void ImGuiEditor::Log(const char* logMessage)
{
    imguiLog_.AddLog(logMessage);
}

void ImGuiEditor::OnKeyboardEvent(int key, int scanCode, int action, int mod)
{
    ImGuiIO io = ImGui::GetIO();

    switch (action)
    {
    case GLFW_PRESS:
    {
        io.KeysDown[key] = true;

        io.KeyCtrl = io.KeysDown[GLFW_KEY_LEFT_CONTROL] || io.KeysDown[GLFW_KEY_RIGHT_CONTROL];
        io.KeyShift = io.KeysDown[GLFW_KEY_LEFT_SHIFT] || io.KeysDown[GLFW_KEY_RIGHT_SHIFT];
        io.KeyAlt = io.KeysDown[GLFW_KEY_LEFT_ALT] || io.KeysDown[GLFW_KEY_RIGHT_ALT];
        io.KeySuper = io.KeysDown[GLFW_KEY_LEFT_SUPER] || io.KeysDown[GLFW_KEY_RIGHT_SUPER];
        break;
    }
    case GLFW_RELEASE:
    {
        io.KeysDown[key] = false;
        break;
    }
    default:
        break;
    }
}

void ImGuiEditor::OnCursorMove(double xPosition, double yPosition)
{
    ImGuiIO io = ImGui::GetIO();
    io.MousePos.x = (float)xPosition;
    io.MousePos.y = (float)yPosition;
}

void ImGuiEditor::OnScroll(double xOffset, double yOffset)
{
    ImGuiIO io = ImGui::GetIO();
    io.MouseWheelH += (float)xOffset;
    io.MouseWheel += (float)yOffset;
}

void ImGuiEditor::OnLeftClickPressed()
{
    ImGuiIO io = ImGui::GetIO();
    io.MouseDown[GLFW_MOUSE_BUTTON_1] = true;
}

void ImGuiEditor::OnLeftClickRelease()
{
    ImGuiIO io = ImGui::GetIO();
    io.MouseDown[GLFW_MOUSE_BUTTON_1] = false;
}

void ImGuiEditor::OnCharPressed(unsigned int codePoint)
{
    ImGuiIO io = ImGui::GetIO();
    io.AddInputCharacter(codePoint);
}

void ImGuiEditor::ShowMainMenu()
{
    if (ImGui::BeginMainMenuBar())
    {
        if (ImGui::BeginMenu("File"))
        {
            if (ImGui::MenuItem("Save"))
            {

            }
            if (ImGui::MenuItem("Save as"))
            {

            }
            if (ImGui::MenuItem("Exit"))
            {
                engine->Exit();
            }
            ImGui::EndMenu();
        }

        if (ImGui::BeginMenu("Edit"))
        {
            ImGui::EndMenu();
        }

        if (ImGui::BeginMenu("View"))
        {
            if (ImGui::MenuItem(showLog_ ? MAIN_MENU_ACTIVEABLE_ITEMS::VIEW_LOG_ACTIVE : MAIN_MENU_ACTIVEABLE_ITEMS::VIEW_LOG_PASSIVE))
            {
                showLog_ = !showLog_;
            }
            if (ImGui::MenuItem(showBasicAssetsBrowser_ ? MAIN_MENU_ACTIVEABLE_ITEMS::VIEW_BASIC_ASSETS_ACTIVE : MAIN_MENU_ACTIVEABLE_ITEMS::VIEW_BASIC_ASSETS_PASSIVE))
            {
                showBasicAssetsBrowser_ = !showBasicAssetsBrowser_;
            }
            ImGui::EndMenu();
        }

        if (ImGui::BeginMenu("Help"))
        {
            if (ImGui::MenuItem("About"))
            {
                showAbout_ = !showAbout_;
            }
            ImGui::EndMenu();
        }

        ImGui::EndMainMenuBar();
    }
}

void ImGuiEditor::ShowAbout()
{
    ImGui::Begin("About", showAbout_, ImGuiWindowFlags_NoCollapse);
    ImGui::Text("Emre Baris Coskun \nemrebariscoskun@gmail.com");
    ImGui::End();
}

void ImGuiEditor::ShowBasicAssetsBrowser()
{
    ImGui::Begin("Basic Assets", showBasicAssetsBrowser_, ImGuiWindowFlags_NoCollapse);

    if(ImGui::Button("Add a Simple Cube"))
    {
        imguiLog_.AddLog("A simple cube is added to the scene.\n");
    }
    ImGui::End();
}

void ImGuiEditor::ShowLog()
{
    imguiLog_.Draw("Goknar Log", showLog_);
}

Result

via Gfycat

GitHub repository: github.com/emrebarisc/GoknarEngine