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 game = std::make_unique<Game>();

auto main(int argc, char *argv[]) -> int
{
    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 game = std::make_unique<Game>();

auto main(int argc, char *argv[]) -> int
{
    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_ScaleModeBest);

        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 game = std::make_unique<Game>();

auto main(int argc, char *argv[]) -> int
{
    SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "2");

    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 game = std::make_unique<Game>();

auto main(int argc, char *argv[]) -> int
{
    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 game = std::make_unique<Game>();

auto main(int argc, char *argv[]) -> int
{
    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(1);
        }

        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 % 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},
                {255, 255, 255, 255}, textureWidth, textureHeight);
        }
    }
};

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

auto main(int argc, char *argv[]) -> int
{
    game->SetScreenSize(1000, 600);

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

    return game->Run();
}