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

An attempt to create open source abstract game engine for browser

About • Vision • Status • Features • Integrations • Quickstart • FAQ • Examples • Architecture • Support • License


About

This project strives to be an open-source framework, providing ready-to-use tool set for making web applications, related to 2D/3D graphics and physics simulation (mostly games), quickly and easily. There are plenty of cool libraries for in-browser games, such as three.js for rendering using WebGL, ammo.js for WebAssembly physics and many more: gg-web-engine is not going to compete with any of them and re-invent the bicycle, but rather work on top of such libraries: the developer, using gg-web-engine, will have full control on those libraries, besides the built-in gg-web-engine functionality. The core of the engine DO NOT rely on any specific library and the end product can be quickly switched from one stack of physics/rendering libraries to anothers or even have self-implemented own solutions by implementing simple bindings to gg-web-engine core.

Vision

  • The project is not going to provide low-level solutions for rendering, physics, sounds etc. but have the integration with other libraries for that
  • The project is going to work with both 2D and 3D worlds
  • The project provides ready-to-use common functionality: canvas, game world, rendering loop, physics ticks, key/mouse controls and many more down the road
  • The project provides common entities: rigid primitive, cameras, raycast vehicle and many more down the road
  • The project provides own way of serialization objects: has a built-in blender exporter which will export geometry and rigid body properties to files, seamlessly supported by the engine (3D world only for now)
  • The project does not restrict developer from working with integrated libraries directly
  • The project is written on TypeScript
  • The project is module-based ES6 code
  • The project intensively uses rxjs

Status

Experimental release. I'll be happy to see any feature requests and bug reports in the Issues and Pull Requests are more than welcome. This project is initialized with parts of my own attempt to implement replica of old NFS game The Need For Speed Web (far from final version), by the way it was using cannon.js and then migrated to ammo.js with minimum changes to the architecture, which in fact inspired me to start this project: there was a lot to learn and realize that game development for browsers deserves more than available. Right now the engine is still focused on that NFS project and all the functionality is written specifically for it, so one can notice that there is currently lack of functionality which is not related to racing games.

Features

  • Automatically working physics/rendering ticks
  • Automatic physics body/visual mesh position/rotation binding
  • Controllers interface, allowing to add some functions as part of tick
  • rigid bodies
  • trigger zones
  • physics debugger view
  • UI developer console
  • free-fly camera controller (3D world only)
  • raycast car entity (3D world only) and dedicated keyboard controller for it
  • map graph, allowing to load only nearest part of map, using graph of map areas (3D world only)

Integrations

Note: at this early step, the project does not give much flexibility in that regard, will be changed in future

Quickstart

Installation:

  1. npm install --save @gg-web-engine/core
  2. install and setup integration modules, example below uses @gg-web-engine/three and @gg-web-engine/ammo integrations

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:

FAQ

Viewport looks not centered / bad rendering resolution on mobile / retina display

Try to add to your html : <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1, maximum-scale=1">

Examples

Usage in frameworks

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 animationFrameScheduler from rxjs, which 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, e.g. "sub-world", containing everything related to rendering. This is a component, which has to be implemented by integration library
  • physics world, e.g. "sub-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 in world) by pressing `. Your game can provide custom console commands using world.registerConsoleCommand function

Support

You can support project by:

License

Apache License