toy is a thin and modular c++ game engine that also runs on the web.

toy is under heavy development, not yet stable, fully documented or production ready

Coding games first

The first concern of toy is to bring the thinnest and simplest stack of technology for making games directly from C++, instead of focusing on a monolithic editor, or insulating the user from the core systems.

Versatile

Through direct control over expressive rendering and user interface systems, toy is perfect to build games with atypical constraints: complex user interface / rendering schemes, procedural generation..

Fast paced

Designed for quick iterations and experimentation, entirely scriptable, toy provides a fully programmable 3d renderer, with direct access to shaders, materials and render paths.

Technological stack

As a collection of game programming building blocks, toy aims to foster an ecosystem of simple reusable components for building your own game technology.

Thin reusable c++ components

user interface

  • immediate-mode widget declarations
  • fully automatic layout
  • css-like skinning
  • image-based skinning
  • style sheets
  • varied input widgets
  • docking system

renderer

  • immediate-mode rendering graph declaration
  • simple meshes and models
  • shaders, programs and materials
  • skeletal animations and skinning
  • pluggable filters, render passes and render pipelines
  • OBJ and glTF model importers

zero-cost scripting

  • automatic binding of c++ game code
  • textual languages: lua
  • upcoming languages: Wren, C#
  • nodal visual scripting language
  • editors for both text and visual scripts

modular zero-cost tools

  • automatic serialization of c++ game classes
  • generic object inspector
  • generic graph outliner
  • text-based script editor
  • node-based visual script editor
  • prefab editor
  • particles editor

sound

  • immediate-mode sounds declaration in scene graph
  • 3d sound and listener spatialization
  • ogg static and stream sound support

PBR pipeline

  • renderer extension
  • physically based material model
  • cascaded shadow maps
  • reflection probes (WIP)
  • HDR pipeline, glow, tonemapping

physics

  • bullet physics integration
  • rigid body simulation
  • raycasts, convex sweeps scene queries

navmesh

  • recast tiled navmesh generation
  • detour pathfinding queries

Examples

Highlights

simple and lightweight

simplicity is the core aim and philosophy behind toy. the codebase is about one-tenth the size of competing engines, and toy is so light, the whole editor runs in your browser !

game code first

toy is first and foremost meant to build games in native c++ code, in direct contact with the core systems. this allows for much greater control than typical scripting in-engine.

modular

each functionality is enclosed in a small, simple, easy to understand code building block. most of these blocks lie in the underlying mud library.

extensible

as a collection of modules, toy is a perfect fit to build your own game technology, keeping full control over the components you use, the application design and the control flow.

versatile

toy is designed from the start with complex games in mind, such as strategy or role playing games, by giving full control over its powerful user interface and rendering systems.

thin zero-cost tools

reflection automatically extends your game core code for seamless scripting, editing, inspection of your game objects, types and procedures in the built-in tools/editor.

educative

toy aims to provide simplest technical solutions to typical game programming problems, to be easily studied and understood, hoping to be a driver of education on game development topics.

fast iteration

coupling seamless bindings of both built-in systems and game code to various scripting languages, hot-reload of native code, and immediate UI and rendering, toy provides fast iteration speeds.

Snippets

Creating the App

That's it for the minimal toy game : we have a running app, with a black screen :) The GameModule interface provides four main hooks for writing your game logic:

  • init() is called when initializing the game
  • start() is called when the game session is started
  • pump() is called on each frame
  • scene() is called everytime a scene is created

Note: GameModule and GameShell are merely helper class: you aren't in any way constrained to use them. If you want complete control over your application flow, you can borrow the setup logic from the Shell.cpp file, and setup a working application in a couple dozen lines of code.

class MyGame : public GameModule
{
public:
    virtual void init(GameShell& app, Game& game) {}
    
    virtual void start(GameShell& app, Game& game) {}
    
    virtual void pump(GameShell& app, Game& game) {}
    
    virtual void scene(GameShell& app, GameScene& scene) {}
};

int main()
{
    GameShell app(TOY_RESOURCE_PATH, argc, argv);

    GameModule module;
    app.run_game(module);
}

Rendering a Cube

Rendering objects is simply a matter of creating a Scene, a Viewer, and submitting some geometry to the Scene.

  • initialize the UI graph by calling begin() on the ui root
  • add a Viewer to the UI graph by calling scene_viewer()
  • initialize the render graph by calling begin() on the scene
  • (optionally) add a Node to the render graph by calling node()
  • add a Cube to the render graph by calling shape()

Note: The scene_viewer() function declares a special kind of Viewer that holds its own Scene inside. If you created a Scene separately, just call the viewer() function instead.

class MyGame : public GameModule
{
public:
    // ...
    
    virtual void pump(GameShell& app, Game& game)
    {
        Widget& ui = app.m_ui.begin();
        Viewer& viewer = ui::scene_viewer(ui);
        
        Gnode& scene = viewer.m_scene.begin();
        
        Gnode& node = gfx::node(scene, {}, vec3(0.f, 0.f, 0.f));
        gfx::shape(node, Cube(1.f), Symbol(Colour::Pink));
    }
};

Rendering a Model

Rendering a 3d Model is almost as simple as rendering a shape. Instead of calling shape(), you need to fetch the Model before calling item().

  • get the Model asset store by calling models() on the application GfxSystem
  • fetch the Model by calling file() on the asset store
  • draw the Model by calling item()

Note: You might want to get the Model once and store it somewhere.

class MyGame : public GameModule
{
public:
    virtual void pump(GameShell& app, Game& game)
    {
        // ... (initialize the viewer)
        
        Model& model = app.m_gfx_system.models().file("my_model.obj");
        
        Gnode& node = gfx::node(scene, {}, vec3(0.f, 0.f, 0.f));
        gfx::item(node, model);
    }
};

Reacting to Input

Querying events is done on the Widget object returned by the widget declaration, using the following functions (in this case on the Viewer):

  • key_event() by passing the key code and the type of input event to check for
  • mouse_event() by passing the mouse button and the type of input event to check for

Note: To fully grasp event dispatching you need to get familiar with how toy deals with the concepts of modal receivers, and focused receivers.

class MyGame : public GameModule
{
public:
    virtual void pump(GameShell& app, Game& game)
    {
        static vec3 position = Zero3;
        
        Widget& ui = app.m_ui.begin();
        Viewer& viewer = ui::scene_viewer(ui);
        
        if(viewer.key_event(KC_A, EventType::Pressed))
            position += X3;
        if(viewer.key_event(KC_D, EventType::Pressed))
            position -= X3;
    }
};

Defining an Entity type

An Entity in toy is a spatial object with children. It's convenient to define game objects types composed around an Entity as well as other components:

  • an Entity component has a position, a rotation, and a list of children
  • a Movable component has a linear and angular velocity
class Monster : public Complex
{
public:
	Monster(Id id, Entity& parent, const vec3& position)
        : m_entity(id, parent, position)
        , m_movable(m_entity)
    {}

	Entity m_entity;
	Movable m_movable;
};

Drawing all Entities

An optional way to draw Entities is to create a GameScene, and to add a Painter to it. Each frame, for each Painter you attached to it, the GameScene goes over all Entities of the given type, and send them to the given draw function.

  • create a GameScene by calling add_scene() on the Game object
  • in the scene() handler function, add one Painter for each type of entity you want to draw, passing the function (paint_monster() in the example)
  • call next_frame() on the GameScene, each frame
void paint_monster(Gnode& parent, Monster& monster)
{
	gfx::shape(parent, Cube(1.f), Symbol(Colour::Pink));
}

class MyGame : public GameModule
{
public:
    virtual void pump(GameShell& app, Game& game)
    {
        static GameScene& scene = game.add_scene();
        Viewer& viewer = ui::viewer(app.m_ui.begin(), scene);
        scene.next_frame();
    }
    
    virtual void scene(GameShell& app, GameScene& scene)
    {
        static OmniVision vision = { *scene.m_game.m_world };
        scene.entity_painter("Monsters", vision.m_store, paint_monster);
    }
};

Defining a Physics Entity type

To give Rigid Body dynamics to an Entity you can add a Solid component to it, with the following parameters:

  • the Entity itself
  • the geometric Shape of the body
  • whether it is a static body
  • the mass of the body
class Crate : public Complex
{
public:
	Crate(Id id, Entity& parent, const vec3& position)
        : m_entity(id, *this, parent, position)
        , m_movable(m_entity)
        , m_solid(m_entity, *this, Cube(1.f), false, 1.f))
    {}

	Entity m_entity;
	Movable m_movable;
    Solid m_solid;
};