Skip to content

An attempt to create open source game engine for browser

License

Notifications You must be signed in to change notification settings

AndyGura/gg-web-engine

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

๐Ÿš€ GG-Web-Engine

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


๐ŸŽจ About

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.

๐ŸŒŸ Key Features

  • 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.

๐Ÿ”ญ Vision

  • 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.

๐Ÿšง Current Status

Experimental Release

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!

โœจ Features at a Glance

  • 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.

๐Ÿ”Œ Integrations

Note: Current integrations are in early stages and will become more flexible in future releases.

โšก Quickstart

Installation

  1. Install the core package:
npm install --save @gg-web-engine/core
  1. Install integration modules. For example, to use Three.js and Ammo.js:
npm install --save @gg-web-engine/three @gg-web-engine/ammo

Usage:

  1. add somewhere in dom tree: <canvas id="gg"></canvas>
  2. remove default margin from page via CSS: body { margin: 0; }
  3. 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:

๐Ÿ› ๏ธ Examples

Framework Usage

๐Ÿ“š Documentation

Explore the complete technical documentation: GitHub Pages.

๐ŸŒŒ Architecture

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

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

Example of clocks hierarchy

flowchart LR
  GgGlobalClock.instance --> w1[world1 clock]
  GgGlobalClock.instance --> w2[world2 clock]
  w1 --> l1[Level clock]
  l1 --> l2[Some timer on level]
Loading

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")
Loading

Anything, which has to be implemented in integration library:

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

Example of input usage

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
Loading

Factory

There is simple factory, allowing to easily create rigid bodies. See 2D and 3D factories

Loader

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

Console

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.

Default global console commands

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

Default world-specific console commands

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

Default 2D world-specific console commands

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"

Default 3D world-specific console commands

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"

โ“ FAQ

Why is the viewport not centered or blurry on mobile/retina displays?

Add the following meta tag to your <head>:

<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1, maximum-scale=1">

๐Ÿค Support

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:

๐Ÿ“œ License

Apache License