A modular, open-source game engine for the web. Build stunning 2D/3D games and simulations with ease.
About โข Key Features โข Vision โข Current Status โข Integrations โข Quickstart โข Examples โข Documentation โข Architecture โข FAQ โข Support โข License
GG-Web-Engine is an open-source framework designed to accelerate the development of web-based 2D/3D applications, including games and simulations. Instead of reinventing the wheel, it integrates seamlessly with powerful libraries like Three.js for rendering and Ammo.js for physics, giving developers complete control over these tools.
Built with flexibility in mind, the engine is designed to work with various tech stacks and allows swapping libraries or creating custom solutions with minimal effort.
- Multi-library Integration: Supports rendering and physics libraries like Three.js, Ammo.js, Pixi.js, Matter.js, and more.
- 2D & 3D Support: Build both 2D and 3D worlds effortlessly.
- Ready-to-use Tools: Includes core functionalities like rendering loops, physics ticks, input handling, and more.
- Serialization & Export: Built-in Blender exporter for 3D geometry and physics properties.
- Entity System: Modular design with reusable entities for rendering, physics, cameras, vehicles, and more.
- Extensible Architecture: Easily integrate or replace components with custom implementations.
- Modern Tech Stack: Written in TypeScript with RxJS for reactive programming.
- Provide seamless integrations for existing rendering, physics, and sound libraries.
- Deliver a robust foundation for both 2D and 3D game worlds.
- Empower developers with tools like developer console, cameras, and debug utilities.
- Maintain modularity to ensure maximum flexibility for developers.
- Stay library-agnostic, enabling developers to switch or customize libraries with ease.
This engine began as part of a project to recreate an old NFS game The Need For Speed Web. Its modular architecture was inspired by the challenges of switching from Cannon.js to Ammo.js and, eventually, to custom reverse-engineered physics engine with minimal changes.
While the current focus is on racing game features and 3D worlds, future updates will expand the engine's versatility. Contributions, feature requests, and bug reports are warmly welcomed!
- Physics/Rendering Synchronization: Automates position/rotation updates.
- Customizable Controllers: Add functionality with reusable tick-based controllers.
- Entities: Predefined entities like rigid bodies, triggers, raycast vehicles, and more.
- Developer Console: Built-in UI console for debugging and tweaking settings.
- Map Graph Loading: Load map areas dynamically based on proximity (3D worlds).
- Free-Fly Camera: Explore 3D worlds effortlessly.
Note: Current integrations are in early stages and will become more flexible in future releases.
- @gg-web-engine/three - 3D rendering (Three.js)
- @gg-web-engine/ammo - 3D physics (Ammo.js)
- @gg-web-engine/rapier3d - 3D physics (Rapier.js)
- @gg-web-engine/pixi - 2D rendering (Pixi.js)
- @gg-web-engine/matter - 2D physics (Matter.js)
- @gg-web-engine/rapier2d - 2D physics (Rapier.js)
- Install the core package:
npm install --save @gg-web-engine/core
- Install integration modules. For example, to use Three.js and Ammo.js:
npm install --save @gg-web-engine/three @gg-web-engine/ammo
- add somewhere in dom tree:
<canvas id="gg"></canvas>
- remove default margin from page via CSS:
body { margin: 0; }
- write bootstrap script, example:
import { Gg3dWorld, Pnt3, Qtrn } from '@gg-web-engine/core';
import { ThreeSceneComponent } from '@gg-web-engine/three';
import { AmmoWorldComponent } from '@gg-web-engine/ammo';
// create world
const world = new Gg3dWorld(new ThreeSceneComponent(), new AmmoWorldComponent());
await world.init();
// create viewport and renderer
const renderer = world.addRenderer(
world.visualScene.factory.createPerspectiveCamera(),
document.getElementById('gg')! as HTMLCanvasElement
);
renderer.position = { x: 12, y: 12, z: 12 };
renderer.rotation = Qtrn.lookAt(renderer.camera.position, Pnt3.O);
// create floor (static rigid body)
world.addPrimitiveRigidBody({
shape: { shape: 'BOX', dimensions: { x: 7, y: 7, z: 1 } },
body: { dynamic: false },
});
// spawn cubes with mass 1kg twice a second
const spawnTimer = world.createClock(true);
spawnTimer.tickRateLimit = 2;
spawnTimer.tick$.subscribe(() => {
// generate cube
let item = world.addPrimitiveRigidBody({
shape: { shape: 'BOX', dimensions: { x: 1, y: 1, z: 1 } },
body: { mass: 1 },
});
// set position to cube
item.position = { x: Math.random() * 5 - 2.5, y: Math.random() * 5 - 2.5, z: 10 };
// delete cube from world after 30 seconds
setTimeout(() => { world.removeEntity(item, true); }, 30000);
});
// start simulation
world.start();
And run it:
-
Angular: Example Project
-
React: Example Project
Explore the complete technical documentation: GitHub Pages.
The most important thing in the engine is GgWorld. It encapsulates everything you need in your game runtime: ticker clock, visual scene, physics simulation world etc. One browser tab can run one or many GG worlds. In order to add something to the world, caller code needs to add Entities. Entity is anything which works in the world: tick-based controller, rigid body, renderer etc. World and entity are self-sufficient in gg core package, so they do not depend on selected integration library. Entity can use from 0 to many World Components - those are fully dependent on integration library, so libraries in general case only implement components.
Full technical documentation available at GitHub Pages
Clock is an entity, responsible for tracking time and firing ticks. It measures time and on each tick emits two numbers:
elapsedTime
and delta
, where delta
always equals to difference between current elapsed time, and elapsed time,
fired on the previous tick. All clock instances have hierarchy: pausing clock will automatically pause all of its child
clocks, which is nice to use for in-game timers: all timers will be paused when world clock is paused. There are two
built-in implementations of clock:
Singleton, starts emitting ticks as soon as accessed. For scheduling ticks, it uses requestAnimationFrame
API.
The elapsed time for each tick is a timestamp, e.g. total amount of milliseconds, passed from
01.01.1970 00:00:00.000 UTC. The instance of this clock is always the root clock in clocks hierarchy
The class for all remaining clocks: it measures time elapsed when was started. Has the ability to be paused/resumed, and elapsed time will not be affected by pause: it will proceed from the same state it was paused. Every world has its own instance of PausableClock, where parent clock is GgGlobalClock
flowchart LR
GgGlobalClock.instance --> w1[world1 clock]
GgGlobalClock.instance --> w2[world2 clock]
w1 --> l1[Level clock]
l1 --> l2[Some timer on level]
World is a container of all entities of your game, manages the entire flow. Though it is possible to have multiple worlds in one page, in most cases you only need one. World consists of:
- clock
- visual scene, containing everything related to rendering. This is a component, which has to be implemented by integration library
- physics world, containing everything related to physics simulation. This is a component, which has to be implemented by integration library
- list of all spawned world entities, sorted by tick order, and API for spawning/removing them
- logic to propagate clock ticks to every spawned active entity
- keyboard input
There are two built-in variants of world implementation: Gg2dWorld and Gg3dWorld
Example of hierarchy of entities of simple scene, which uses three.js + ammo.js:
flowchart TB
w{"[CORE]\n3D World"} --> cn0["[CORE]\nsome controller"]
w --> rb0["[CORE]\nrigid body entity"]
w --> rb1["[CORE]\n3d model"]
w --> rb2["[CORE]\ntrigger entity"]
rb0 --> c0("[THREE]\nmesh component")
rb0 --> c1("[AMMO]\nphysics body component")
rb1 --> c2("[THREE]\nmesh component")
rb2 --> c3("[AMMO]\nphysics body component")
Anything, which has to be implemented in integration library:
- IVisualScene2dComponent / IVisualScene3dComponent a wrapper around visual scene or display object container
- IDisplayObject2dComponent / IDisplayObject3dComponent a wrapper around mesh or sprite
- IRenderer2dComponent / IRenderer3dComponent a wrapper around renderer
- IPhysicsWorld2dComponent / IPhysicsWorld3dComponent a wrapper around physics world
- IRigidBody2dComponent / IRigidBody3dComponent a wrapper around rigid body
- ITrigger2dComponent / ITrigger3dComponent a wrapper around physics object, which only detects intersections with other physics objects
- ICameraComponent a wrapper around camera (3D world)
- IRaycastVehicleComponent raycast vehicle (3D world)
For instance, @gg-web-engine/three
implements 4 components: IVisualScene3d
, IDisplayObject3d
, IRenderer3d
, ICamera
.
Basically, everything that listens ticks and can be added/removed from world. Built-in entities:
- Entity2d / Entity3d encapsulates display object (sprite or mesh respectively) and rigid body. Synchronizes position/rotation each tick
- Trigger2dEntity / Trigger3dEntity has only physics body, but instead of participating in collisions, emits events when some another positionable entity entered/left its area
- InlineTickController simple controller, which can be created and added to world using one line of code
- Renderer controller, which renders the scene and controls canvas size (if canvas provided). Makes canvas appearing fullscreen by default
- AnimationMixer controller, which mixes animations: use-case is if you have some animation function, and you need a smooth transition to another animation function
- Entity2dPositioningAnimator / Entity3dPositioningAnimator controllers extending AnimationMixer, which apply position/rotation to positionable entity
- Camera3dAnimator dedicated AnimationMixer for perspective camera: translates camera, target, up, fov etc.
- FreeCameraController a controller, allows to control camera with WASD + mouse
- CarKeyboardHandlingController a controller allowing to control car with keyboard
- MapGraph3dEntity an entity, which loads parts of big map and disposes loaded map chunks, which are far away
- RaycastVehicle3dEntity a general entity with raycast vehicle. Encapsulates positioning binding for chassis and wheels meshes, provides simplified interface for applying engine or brake forces
- GgCarEntity a more sophisticated 4-wheel car which simulates engine with torque table, gear box etc.
Input is a class, responsible for handling external actions, such as mouse move, key presses, gamepad interactions etc. When implementing multiplayer, it probably will be the best place to handle incoming data for reflecting it on the world state. Input does not depend on ticks and is not a part of the world, it should be created and used by some controller entity, added to the world. All inputs extend abstract class Input<TStartParams, TStartParams>. Engine provides those inputs out-of-box:
This input handles key presses, allows to setup key bindings: provides Observable, which emits true on key down and false on key up. When binding many keys to the same functionality, will emit true when any of bound keys pressed, and false only when all bound keys released. Every world has its own instance of keyboard controller
This input handles mouse movements and provides an Observable, which emits how much mouse position changed after last event. Supports pointer lock functionality
A shortcut for implementing direction key bindings: WASD, arrows, or both at once. Provides observable with direction
flowchart LR
world --> e1[Player Character]
world --> e2[Player Controller]
world --> e0[...other world entities]
e2 --Creates input, listens to events--> DirectionKeyboardInput
e2 --Changes character state on tick--> e1
There is simple factory, allowing to easily create rigid bodies. See 2D and 3D factories
Currently, there is only one loader available, and only for 3D world. It uses own format of serializing blender scene: .glb+.meta files, where glb is a binary GLTF file, containing mesh+materials, and meta is a json file, containing evverything from blend file, not included in glb, such as empty objects; rigid bodies; splines. Right now it is on very early stage. The script to make glb+meta from blender file is here: build_blender_scene.py
Engine provides a simple console, which can be used at runtime (if enabled) by pressing `. Your game can
provide custom console commands using GgStatic.instance.registerConsoleCommand
function.
Command | Arguments | Description |
---|---|---|
commands |
- | Print all available commands. List includes global commands and commands, specific to currently selected world. Run "world" to check which world is currently selected and "world {world_name}" to select desired world |
help |
string |
Print doc string of provided command |
worlds |
- | Print all currently available worlds |
world |
string? |
Get name of selected world or select world by name. Use "worlds" to get list of currently available worlds |
stats_panel |
0|1? |
Turn on/off stats panel, skip argument to toggle value |
debug_panel |
0|1? |
Turn on/off debug panel, skip argument to toggle value |
bind_key |
string, ...string |
Bind a keyboard key by code to console command. Check key codes here. Use "unbind_key" command to unbind it |
unbind_key |
string |
Unbind a keyboard key from console command |
Command | Arguments | Description |
---|---|---|
timescale |
float? |
Get current time scale of selected world clock or set it. Default value is 1.0 (no time scale applied) |
fps_limit |
int? |
Get current tick rate limit of selected world clock or set it. 0 means no limit applied |
renderers |
- | Print all renderers in selected world |
debug_view |
0|1?, string? |
Turn on/off physics debug view, skip first argument to toggle value. Second argument expects renderer name, if not provided first renderer will be picked. Use "renderers" to get list of renderers in the world |
performance |
int?, avg|peak? |
Measure how much time was spent per entity in world. Arguments are samples amount (20 by default) and "peak" or "avg" choice, both arguments are optional. "avg" report sorts entities by average time consumed, "peak" records highest value for each entity |
Command | Arguments | Description |
---|---|---|
gravity |
?float, ?float |
Get or set 2D world gravity vector. 1 argument sets vector {x: 0, y: value}, 2 arguments sets the whole vector. Default value is "9.82" or "0 9.82" |
Command | Arguments | Description |
---|---|---|
gravity |
?float, ?float, ?float |
Get or set 3D world gravity vector. 1 argument sets vector {x: 0, y: 0, z: -value}, 3 arguments set the whole vector. Default value is "9.82" or "0 0 -9.82" |
Add the following meta tag to your <head>
:
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1, maximum-scale=1">
Feel free to dive into the code, contribute, or just have fun creating with GG-Web-Engine. Together, letโs shape the future of browser-based game development! ๐ฎ
You can support project by:
- giving any feedback, bug report, feature request to Issues
- fork & submit a Pull Request