Skip to content

Working on the engine

For people changing RedGem itself. To make games with it, the rest of this manual is enough.

Demos

Demos: games/city (a crossroad with Ruby-driven traffic: lights, braking, spinning wheels; Kenney CC0 models), games/platformer (WASD/arrows, Space jumps: grab 5 coins, reach the flag) and games/driving (W/S throttle, A/D steer, Space hand brake, R reset: drive laps).

Showcase (games/showcase, not committed: it is built from downloaded assets). A street at dusk with a drivable car, traffic that keeps its distance and overtakes, parked cars, shop fronts and cafe tables, a crowd on the sidewalks, potholes and falling leaves, made to push the renderer. Blender only builds the scene; everything that happens in it is set up in games/showcase/scripts/main_scene.rb:

python3 -I tools/assets/fetch_showcase.py      # street: Poly Haven and ambientCG (CC0)
python3 -I tools/assets/fetch_ad_assets.py     # car and people: BlenderKit (see showcase/CREDITS.md)
SHOWCASE_RENDER=0 BLENDER_MEMORY_MAX=8500M tools/blender/run.sh build_showcase.py showcase
./build/src/redgem games/showcase           # then Play: WASD drives, Space brakes

The export needs about 7 GB of memory (close the editor first); without SHOWCASE_RENDER=0 it also renders showcase/renders/ad.png with Cycles, which needs an NVIDIA RTX card. games/city is rebuilt with tools/assets/fetch_kenney.sh and tools/blender/run.sh build_city.py games/city.

Tests

cd build && ctest --output-on-failure        # everything
ctest -L system                              # only the system tests

Tests are run by a program of their own, redgem-test, built beside redgem (see Libraries and programs). A system test is a Ruby file run against a game: redgem-test <game_dir> <file.rb> [--screenshots <dir>] [--headless] [--work <dir>] (--headless runs without a window, --work gives the run a folder of its own for its save file and notes; redgem-test --help lists every option).

The engine knows nothing of tests. Neither redgem nor redgem-player, which is what every built game ships as, has test code in it: what tests are written with (TestInput, SystemTest, RedGemTest, the runners and their command line) lives in src/testing and ruby/testing, a library that only redgem-test links. The engine gives it nothing but general options (EngineOptions: a fixed step per frame, an input fed by code, a save file, who shows the game) and the window a game is shown in tells whoever asks when a frame is drawn; the pictures a test takes (t.screenshot) are written by the test tool from there. Keep it that way: the app.ships_no_test_code test looks into the built redgem-player and redgem and fails when test code got in.

  • C++ unit + integration (doctest): math, extras parser, glTF loading from Blender fixtures, scene hierarchy, physics world, Ruby VM, and an engine nobody shows running the sandbox fixture game (and one shown by a presenter that records what it is handed).
  • Tests of the test tool (tests/testing/, doctest): its command line, its save file, what it adds to a game's Ruby, and whole runs of passing and failing tests.
  • Ruby tests run inside the engine's mruby: redgem-test --ruby tests/ruby/vector3_test.rb.
  • Editor system test (tests/editor_system/) opens the editor window on a copy of the platformer, picks, edits, plays, stops, undoes and saves.
  • System tests (tests/system/<game>/*_test.rb, run against games/<game>) open the real game window and drive it frame by frame with simulated input:
SystemTest.define do |t|
  t.hold(:w, frames: 60)
  t.wait_until(frames: 30) { Scene.find("Coin1").nil? }
  t.assert("score counts the coin") { GameManager.instance.coins == 1 }
  t.screenshot("collect_coin_after")        # saved to build/screenshots/<game>/
end

A template is tested as the game it makes: tests/system_templates/<template>/*_test.rb run against a game created from it by redgem new. Every template needs at least one.

Test fixtures are regenerated with tools/blender/run.sh build_test_fixtures.py tests/fixtures.

Layout

src/core      what everything shares: math, logging, the fixed timestep clock, files, the platform
              layer (the only place, with src/net/socket.cpp, that asks which system this is),
              embedding files in the binary, the game folder's layout (config.hpp)
src/scene     glTF loader (meshes, skeletons, animations), Blender extras parser, scene graph,
              animation playback, particles; the custom properties scenes carry (props.hpp)
src/physics   PhysicsWorld interface + Jolt implementation
src/input     keys, axes/actions, keyboard, mouse, gamepad and scripted input sources; a touch
              screen's on-screen controls (touch_input_source.hpp), which only the program for
              a touch screen puts in front of the others
src/net       sockets (the only place with OS socket calls), message framing and wire format, the
              host/guest session, discovery on the local network, the relay and the entry point
              of its program (relay_main.cpp)
src/audio     sound effects and music (raylib, or silence when there is no audio device)
src/storage   what a game keeps between runs (the save store)
src/render    the window (window.hpp: the only place that opens one, with its graphics card, frame
              pace and pointer), the renderer that draws in it and owns what is on the card (HDR,
              shadows, reflections, post-processing), shaders, environment, culling, mesh
              conversion, what differs per graphics API (gpu_profile.hpp), the font texts are
              drawn with
src/script    mruby VM, behaviour host, native bindings
src/app       what plays a game: the engine loop, which draws nothing and hands each frame
              (FrameView) to a presenter (presenter.hpp); the presenter that shows a game in a
              window of its own and the painter every drawing presenter shares; game.json, the
              game beside a built executable, where saves go, user settings, the entry points of
              the player (player_main.cpp; on Android, android_main.cpp)
src/editor    editor: scene document (undo, save, import), session (play mode), camera, picking,
              script tabs, ImGui panels and theme (src/editor/ui)
src/project   the tools around a game: new games from templates, builds (desktop and Android),
              recent games and the project hub, the command line, the entry point of redgem
              (main.cpp)
src/testing   what runs tests, apart from the engine: TestInput, the pictures a test takes of the
              game (TestScreen), the system test and Ruby test runners, the audio tests listen
              with, redgem-test and its command line
ruby/engine   Ruby prelude embedded in the runtime (Behaviour, Vector3, UI, Network...)
ruby/testing  the Ruby tests are written with (assertions, test cases, the system test DSL),
              embedded in redgem-test only
templates     the new-game templates: each folder is a whole game (scene, scripts, sounds) and a
              template.json about it, embedded in redgem as it is; a new game is a copy of one.
              A template is edited like any game: redgem templates/platformer
assets/editor fonts of the editor (Inter, JetBrains Mono; SIL Open Font License, see the license files)
games         demo games: scene.glb, scripts and the .blend each scene was built from
showcase      renders of the showcase scene and the credits of its assets
tools/blender scene builders (run with tools/blender/run.sh), walk_rig.py
tools/assets  downloaders of the free assets the demos use
tests         unit, integration, Ruby and system tests, with their fixtures
cmake         dependencies (pinned versions) and file embedding

The folders of src are layers, in the order above: each includes only itself and what is below it (core; then scene, net, input, audio, physics, storage; then render and script; then app; then editor, project; testing sees everything and nothing sees it). Each layer keeps its own numbers in a <layer>_config.hpp next to its code.

Libraries and programs

The folders are built into a few static libraries, one on top of the other, and each program links only the ones it is made of (src/CMakeLists.txt):

redgem_core     src/core                                           links nothing of the engine's
redgem_net      src/net                                            redgem_core, the JSON library
redgem_runtime  scene, physics, audio, input, storage, render,     redgem_net, raylib, Jolt, mruby,
                script, app, and the Ruby prelude                  cgltf, the JSON library
redgem_editor   src/editor                                         redgem_runtime, Dear ImGui
redgem_project  src/project (without main.cpp), the templates      redgem_editor
                and the licenses
redgem_testing  src/testing, ruby/testing                          redgem_runtime
redgem-player   src/app/player_main.cpp    redgem_runtime   what players receive: plays the game in
                                                            the game/ folder beside it, nothing else
redgem          src/project/main.cpp       redgem_project   the editor, the project hub, the command
                                                            line (new, build, --play)
redgem-relay    src/net/relay_main.cpp     redgem_net       the meeting point for games over the
                                                            internet: no window, for servers
redgem-test     src/testing/main.cpp       redgem_testing   runs a game's tests
libredgem.so    src/app/android_main.cpp   redgem_runtime   the player on Android, loaded by the app

A built game is a copy of the player, never of the program that builds it: redgem build and the editor's Build take redgem-player from beside redgem, or runtimes/<system>/ there for another system (tools/windows/build_runtime.sh, tools/android/build_runtime.sh build those). cmake --install build --component redgem --prefix <folder> puts the four programs in <folder>/bin and the players for other systems in <folder>/bin/runtimes, where the same lookups find them.

Keep what players receive small: nothing the player links may include editor/ or project/ (the include directions below say so, and linking proves it). The tests app.player_ships_no_tools and app.relay_ships_no_game look into the built programs (tests/ships_no_tools.sh) for texts only the tools, or only a game, have.

A program opens one window, through Window, and makes what draws in it afterwards (a Renderer, the editor's ui::Interface): the project hub, the editor and a played game each do. The engine never does: it is given a Presenter. WindowPresenter shows a game in its own window (redgem-player, redgem --play, redgem-test), the editor's Scene view is another (EditorApp::GameView), and without one nobody shows the game (NullPresenter: tests, servers). What a game can show is a field of FrameView, drawn in FramePainter: added there, it reaches every presenter. tests/include_directions.sh holds the table and fails the test suite on an include that goes up.

Not there yet

  • Blend trees and animation events. Characters copied with Duplicate in the editor keep the pose they were copied in.
  • Spot light cones, and camera clip planes from the scene (the engine uses fixed ones).
  • Reusing data when the same model is imported twice, and dropping the data of deleted imports from scene.glb (each import appends its meshes and textures).
  • Mirrored objects (negative scale) are drawn inside out.
  • More vehicle options from Blender properties (drive layout, engine torque); hot reload of scripts while playing.