tl;dr
Check out my JS game engine: https://github.com/CSchnackenberg/TTjs.
TouchThing for JavaScript (TTjs)
In 2013 jbrosi and me were eager to explore JavaScript as platform for gaming. After writing a game engine in C++ for an unreleased game it was a special challenge to transfer that experience into the browser world. TTjs employs fundamental game development concepts and demonstrates how they can be applied to JavaScript.
It’s MIT licensed and open for anything fun :)
What’s in TTjs?
Building Blocks
Component | EFB |
---|---|
Entity Management | At it’s heart TTjs is an entity component engine. It allows you to instantiate game objects defined by an EntityDefinition . Once created they instantly do what one would expect them to do: they call update() . You can search for other components, destroy entities and search for them by name. |
Entity Definition (Property Management) | In TTjs you write text files to build the templates for your game objects. This is called EntityDefinition and it employs some powerful mechanics to orchestrate your objects and properties. You can derive objects, share properties and quickly modify vast parts of the game without doing one mouse click. It is perfectly compatible with merge and repository tools. Let me say it like this: it’s what I like most about it :) |
Resource/Asset Management | TTjs brings a set of build in resource manager and let you add new custom resource types. It helps you organize the loading of complex resources even with sub dependencies. E.G. you can write a json file which describes your sprite object and automatically load the required PNG files along with that. |
JavaScript Project Bootstrap | To create a new project it is enough to download the source into a folder or link it as a git-sub-module. Next you create a index.html and a main.js file, start a web server and you’re good to go. It brings everything with it. TTjs was build with the idea of just heaving a folder with a bunch of JS files: no build process, no transpiler and no on-top languages. The project supports class-loading via https://requirejs.org and the rest is just HTTP. |
2D Engine: FxLayer | For sprites, texts, shapes etc. TTjs offers a 2D engine named FxLayer . As you might have guessed: it uses layer to organize the scene. It features a set of predefined layer-types which you can manipulate. Depending on the case the layers are optimized and feature different sub level libraries. For example there is FxSpriteLayer offering support for all features of https://www.createjs.com/easeljs. For tilemap rendering there is FxFillayer optimized for that case. |
Flint Particle Engine | I took the liberty and brought back a library that I really enjoyed while working with https://kingart-games.com on a Flash game: Flint Particle Engine. I ported most of the objects to JavaScript and adjusted the renderer to work with easljs to be useable on FxSpriteLayer . |
Useful external libs | For math, collision detection and other useful stuff you can find the following libs: gl-matrix.net, Box2DWeb, lodash, poly2tri, Sylvester.js, text.js extension for require, PerformanceTimer. Using requirejs there is nothing stopping you adding other libraries you like. |
The power of EntityDefinitions
The engine was designed to work with map data created by an external editor. Like those you can create with Tiled. It is an important aspect because it lead to an entity configuration design that is not centered around an build-in-editor. With the lack of an editor we didn’t had the luxury of automating tasks with buttons, checkboxes or GUID linked resources. Instead we invented a config-file-approach that turned out to be awesome for many tasks: EntityDefinitions
.
In TTjs the definition format is based on JSON. Originally (for the C++ engine, see History) we had a better format for this and I hope to find the time to port it to JavaScript someday.
Now let me explain how the definition works and why I like the approach. Let’s start with an example:
{
"EntityDefinitionName": { // (A)
"components": [ // (B)
"ComponentClass1",
"ComponentClass2",
"ComponentClass3",
],
"properties": { // (C)
"someprop1": "somevalue1",
"someprop2": "somevalue2",
"someprop3": "somevalue3",
}
}
}
You simply create a json text file and put a list of EntityDefinitions
in there (see listing A). Then you define a list components and properties (see listing B and C). This simple example is already a full EntityDefinition
and once TTjs is running you can create game object instances from it. As many as required. Those instances or entities are the main game objects in the engine. Each entity object is composed of it’s own instances of the given component classes and each has access to the given properties.
EntityDefinition: Inheritance
This is nice. However, it becomes really nice when we add the parents mechanic and property-groups. To explain this I would like to take an example of a Bomb
. A normal bomb shall lay around, explode if hit and the player can pick it up and carry it around. Here is how that might look like:
{
Bomb: {
components: [
"Carryable",
"HitCollision",
"BombAI"
],
properties: {
pickUpLevel: "SILVER_GLOVES",
timeToExplode: "3sec",
hitCountToTrigger: 5
}
}
}
This bomb defines the necessary components that makes it a bomb (in this example we don’t care for Sprites etc.). The carry component defines that the player can carry it as an object and also reads the property pickUpLevel
to tell the player object that it can only be transported if he owns the item SILVER_GLOVES
. It also defines how many hits it requires before it’s being activated: hitCountToTrigger
. And how long it takes to explode, once activated. All in all a proper bomb.
So now we have a bomb. In TTjs we can now load our map and every object with the type Bomb
would behave like described here. Now, let’s go on and create a very special kind of bomb. A FragileBomb
that behaves basically the same but is less robust. With TTjs we can simply inherit:
{
FragileBomb: {
parent: "Bomb",
properties: {
timeToExplode: "1sec",
hitCountToTrigger: 1
}
}
}
Now we have a new EntityDefinition
which is a bomb with a little adjustment. In the map FragileBomb
can be used the same way we use Bomb
. They share the same components and the same properties but FragileBomb
overwrites two properties: timeToExplode
and hitCounterTrigger
. The change of those values is what a FragileBomb
separates from a normal Bomb
. The nice thing here is that every time we need to update the Bomb
(e.g. adding a component or add a properties) the FragileBomb
is updated automatically. This can be very powerful when dealing with many likewise objects but also quickly test things.
As a limitation inherited EntityDefinition cannot update or change the components. This is an intentional design decision. If something is so different that it requires additional or different components inherit is the wrong tool. In such a case it is better to simply copy & paste the components and go from there. EntityDefinition are defined and created during development time. They are not build to be modified during runtime.
EntityDefinition: property groups
If it is required to share certain properties throughout the EntityDefinitions
TTjs let you do that with property-groups. Let’s think of a Jump & Run game where we want to have physical properties for objects:
{
BasePhysics: {
properties: {
gravity: [0, -9.81],
jumpDirection: [0, 1]
}
},
Player: {
family: ["BasePhysics"], // (A)
components: [
Sprite,
JumpAndRunPhysics,
//...
],
properties: {
// ...
}
},
Enemy1: {
family: ["BasePhysics"], // (B)
components: [
Sprite,
JumpAndRunPhysics,
Enemy1AI
//...
],
properties: {
// ...
}
}
}
In the given example we define two EntityDefinitions
and they are both linked to the PropertyGroup
BasePhysics using the key word family
(see A, B). This means that they all share the properties defined in BasePhysics. If we now want to change the gravity for all objects that have the usual physics behavior we simply have to change one line and not many lines or worse: some source code line in JumpAndRunPhysics.
The EntityDefinition
allows to combine those features. Let’s create an Enemy2 that is the same as Enemy1 but with less gravity:
{
Enemy2: {
parent: "Enemy1"
properties: {
gravity: [0, -5.0] // (A)
}
}
}
For Enemy2 we inherit the components from Enemy1 and also the properties from BasePhysics but overwrite gravity property (see A).
Depending on the game you like to build those EntityDefinitions can be very handy. On one hand they bring structure and strict rules, but on the other hand they open up all kind of doors for hacking and quick testing. The text only approach works great with git and adding a new kind of object by simpl copy&paste a few lines of config can be incredible efficient.
Obviously there is way more to explain to those definitions. Properties need to be parsed and validated, resources need to be requested and there is also a thing called static objects
. However, that and more is stuff for other posts.
Originally jbrosi and me build an engine from scratch in C++. We called it TouchThing. The name related to the fact that we built an abstraction layer for operating systems with touch-input. A “thing to touch” if you will. We worked over a year on the engine and TTjs is heavily inspired by that engine.
The original engine is not open sourced and we never came to release the planned game. However, I build a nice little Zelda fan game and a Demo with it.