The thin c++ game engine.
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 gamestart()
is called when the game session is startedpump()
is called on each framescene()
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 callingscene_viewer()
- initialize the render graph by calling
begin()
on the scene - (optionally) add a
Node
to the render graph by callingnode()
- add a
Cube
to the render graph by callingshape()
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 callingmodels()
on the applicationGfxSystem
- fetch the
Model
by callingfile()
on the asset store - draw the
Model
by callingitem()
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 formouse_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 callingadd_scene()
on theGame
object - in the
scene()
handler function, add onePainter
for each type of entity you want to draw, passing the function (paint_monster()
in the example) - call
next_frame()
on theGameScene
, 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;
};