Handcrank Engine is an easy-to-use game engine based on simple principles that are quick to understand and build with.

Current version: pre-alpha

Getting Started

Creating and rendering objects in Handcrank was made to be as easy as possible, with the most simple example taking only a few lines to achieve.

#include "HandcrankEngine/HandcrankEngine.hpp"
#include "HandcrankEngine/RectRenderObject.hpp"

using namespace HandcrankEngine;

auto main(int argc, char *argv[]) -> int
{
#ifdef __EMSCRIPTEN__
    SDL_SetHint(SDL_HINT_EMSCRIPTEN_CANVAS_SELECTOR, CANVAS_SELECTOR);
#endif

    auto game = std::make_unique<Game>();

    game->SetScreenSize(1000, 600);

    auto rect = std::make_shared<RectRenderObject>();

    rect->SetAnchor(RectAnchor::HCENTER | RectAnchor::VCENTER);

    rect->SetRect(game->GetWidth() / 2, game->GetHeight() / 2, 250, 250);
    rect->SetFillColor(255, 0, 0, 255);

    game->AddChildObject(rect);

    return game->Run();
}

Simple Event Loop

#include <iostream>

#include "HandcrankEngine/HandcrankEngine.hpp"

using namespace HandcrankEngine;

class LoopDebugger : public RenderObject
{
  public:
    void Update(const double deltaTime) override
    {
        std::cout << "Update " << std::to_string(deltaTime) << std::endl;
    }
    void FixedUpdate(const double fixedDeltaTime) override
    {
        std::cout << "Fixed Update " << std::to_string(fixedDeltaTime)
                  << std::endl;
    }
};

auto main(int argc, char *argv[]) -> int
{
#ifdef __EMSCRIPTEN__
    SDL_SetHint(SDL_HINT_EMSCRIPTEN_CANVAS_SELECTOR, CANVAS_SELECTOR);
#endif

    auto game = std::make_unique<Game>();

    game->SetScreenSize(1000, 600);

    game->AddChildObject(std::move(std::make_unique<LoopDebugger>()));

    return game->Run();
}

Sprite Animations

Sprite animations can be generated via automatic frame calculation based on image width and frame count, or manually like in the example below. Either way, sprite animations were made to be simple to work with and customize.

#include "alienGreen.h"

#include "HandcrankEngine/HandcrankEngine.hpp"
#include "HandcrankEngine/SpriteRenderObject.hpp"

using namespace HandcrankEngine;

class GreenAlien : public SpriteRenderObject
{
  public:
    void Start() override
    {
        LoadTexture(game->GetRenderer(), images_alienGreen_png,
                    images_alienGreen_png_len);

        SDL_SetTextureScaleMode(texture, SDL_ScaleMode::SDL_SCALEMODE_PIXELART);

        SetAnchor(RectAnchor::HCENTER | RectAnchor::VCENTER);

        SetPosition(game->GetWidth() / 2, game->GetHeight() / 2);

        SetFrameSpeed(0.25);

        AddFrame({69, 193, 68, 93});
        AddFrame({0, 0, 70, 96});

        Play();
    }
};

auto main(int argc, char *argv[]) -> int
{
#ifdef __EMSCRIPTEN__
    SDL_SetHint(SDL_HINT_EMSCRIPTEN_CANVAS_SELECTOR, CANVAS_SELECTOR);
#endif

    auto game = std::make_unique<Game>();

    game->SetScreenSize(1000, 600);

    game->AddChildObject(std::move(std::make_unique<GreenAlien>()));

    return game->Run();
}

SVG

#include "Ghostscript_Tiger.h"

#include "HandcrankEngine/HandcrankEngine.hpp"
#include "HandcrankEngine/ImageRenderObject.hpp"

using namespace HandcrankEngine;

auto main(int argc, char *argv[]) -> int
{
#ifdef __EMSCRIPTEN__
    SDL_SetHint(SDL_HINT_EMSCRIPTEN_CANVAS_SELECTOR, CANVAS_SELECTOR);
#endif

    auto game = std::make_unique<Game>();

    game->SetScreenSize(1000, 600);

    auto image = std::make_shared<ImageRenderObject>();

    image->LoadTexture(game->GetRenderer(), images_Ghostscript_Tiger_svg,
                       images_Ghostscript_Tiger_svg_len);

    image->SetAnchor(RectAnchor::HCENTER | RectAnchor::VCENTER);

    image->SetPosition(game->GetWidth() / 2, game->GetHeight() / 2);

    game->AddChildObject(image);

    return game->Run();
}

Animations

#include "HandcrankEngine/Animator.hpp"
#include "HandcrankEngine/HandcrankEngine.hpp"
#include "HandcrankEngine/RectRenderObject.hpp"

using namespace HandcrankEngine;

auto main(int argc, char *argv[]) -> int
{
#ifdef __EMSCRIPTEN__
    SDL_SetHint(SDL_HINT_EMSCRIPTEN_CANVAS_SELECTOR, CANVAS_SELECTOR);
#endif

    auto game = std::make_unique<Game>();

    game->SetScreenSize(1000, 600);

    auto cube = std::make_shared<RectRenderObject>(
        game->GetWidth() / 2, game->GetHeight() / 2, 250, 250);

    cube->SetAnchor(RectAnchor::HCENTER | RectAnchor::VCENTER);

    cube->SetFillColor(MAX_R, 0, 0, 0);

    game->AddChildObject(cube);

    auto animator = std::make_shared<Animator>(Animator::Mode::SEQUENCE, true);

    float internalAlpha = 0;

    // Fade in
    animator->AddAnimation(std::make_shared<Animation>(
        [&](const double deltaTime, const double elapsedTime)
        {
            auto color = cube->GetFillColor();

            internalAlpha += 500 * deltaTime;

            auto alpha = std::clamp(internalAlpha, 0.0F, (float)MAX_ALPHA);

            cube->SetFillColor(color.r, color.g, color.b, alpha);

            return alpha == (float)MAX_R ? 0 : 1;
        }));

    // Pause
    animator->AddAnimation(std::make_shared<Animation>(
        [&](const double deltaTime, const double elapsedTime)
        { return elapsedTime > 0.1 ? 0 : 1; }));

    // Fade out
    animator->AddAnimation(std::make_shared<Animation>(
        [&](const double deltaTime, const double elapsedTime)
        {
            auto color = cube->GetFillColor();

            internalAlpha -= 500 * deltaTime;

            auto alpha = std::clamp(internalAlpha, 0.0F, (float)MAX_ALPHA);

            cube->SetFillColor(color.r, color.g, color.b, alpha);

            return alpha == 0 ? 0 : 1;
        }));

    // Pause
    animator->AddAnimation(std::make_shared<Animation>(
        [&](const double deltaTime, const double elapsedTime)
        { return elapsedTime > 0.1 ? 0 : 1; }));

    game->AddChildObject(animator);

    return game->Run();
}

Tiled Plugin

Tiled support is made avalible via a plugin that can be downloaded from https://github.com/HandcrankEngine/third-party-plugins

Assets Used: https://kenney.nl/assets/tiny-battle

#include <vector>

#include "HandcrankEngine/Animator.hpp"
#include "HandcrankEngine/HandcrankEngine.hpp"
#include "HandcrankEngine/VertexRenderObject.hpp"

#include "Tiled/Tiled.hpp"

#include "tilemap.h"
#include "tilemap_json.h"

using namespace HandcrankEngine;

class TileMap : public VertexRenderObject
{
  private:
    const float scale = 2.5;
    const int tileWidth = 16;
    const int tileHeight = 16;

    std::vector<TiledLayer> tiledLayers;

  public:
    using VertexRenderObject::VertexRenderObject;

    void Start() override
    {
        LoadTransparentTexture(game->GetRenderer(), images_tilemap_png,
                               images_tilemap_png_len, {0, 0, 0, 0});

        std::string jsonString(reinterpret_cast<char *>(images_tilemap_json),
                               images_tilemap_json_len);

        tiledLayers = LoadTiledLayersFromString(jsonString);

        auto animator =
            std::make_shared<Animator>(Animator::Mode::SEQUENCE, true);

        for (auto i = 0; i < tiledLayers.size(); i += 1)
        {
            animator->AddAnimation(std::make_shared<Animation>(
                [i, this](double deltaTime, double elapsedTime)
                {
                    AddTileSet(tiledLayers[i], tileWidth, tileHeight);

                    return 0;
                }));

            animator->AddDelay(1000);
        }

        AddChildObject(animator);
    }

    void AddTileSet(const TiledLayer &tiledLayer, const int tileWidth,
                    const int tileHeight)
    {
        auto tilesPerCol = textureWidth / tileWidth;

        for (const auto &tile : tiledLayer.tiles)
        {
            auto tileIndex = tile.tileID - 1;
            auto srcX = (tileIndex % (int)tilesPerCol) * tileWidth;
            auto srcY = (tileIndex / tilesPerCol) * tileHeight;

            auto screenX = (float)tile.x * tileWidth * scale;
            auto screenY = (float)tile.y * tileHeight * scale;

            GenerateTextureQuad(
                vertices, indices,
                {screenX, screenY, (float)tileWidth * scale,
                 (float)tileHeight * scale},
                {(float)srcX, (float)srcY, (float)tileWidth, (float)tileHeight},
                {1, 1, 1, 1}, textureWidth, textureHeight);
        }
    }
};

auto main(int argc, char *argv[]) -> int
{
#ifdef __EMSCRIPTEN__
    SDL_SetHint(SDL_HINT_EMSCRIPTEN_CANVAS_SELECTOR, CANVAS_SELECTOR);
#endif

    auto game = std::make_unique<Game>();

    game->SetScreenSize(1000, 600);

    game->AddChildObject(std::move(std::make_unique<TileMap>()));

    return game->Run();
}