Skip to content
On this page

Graphics Engine

TypeSpriteJS's 2D graphics engine is a scene graph. Instead of writing drawImage(...) or drawText(...) you setup graphic objects and compose them in a tree. The internal renderer translates those instructions to WebGL commands which makes it fast and flexible.

NOTE

This is not meant for building UIs. Check out GUI to get buttons on the screen.

Create a Graphics Engine

For starters it's best to use the @GraphicsEngine:typesprite:

ini
# myworld.edf

[!Core]
@GraphicsEngine:typesprite

[!MyPlayer->Core]
@PlayerSprite

Writing this will initilize a TypeSpriteJS GraphicsEngine object which we can use in all our objects in the given world.

Created as a static Entity you can access it via @link in your Component(s):

ts
import {GraphicsEngine, Component} from 'typesprite'

export class PlayerSprite extends Component {
    
    @link('GraphicsEngine:typesprite')
    private gfx:GraphicsEngine
    
    onInit() {
        this.gfx.gameLayer;   // < Here we attach all our graphic objects
    }
}
Custom GraphicsEngine

GraphicsEngine implements all the details of setting up a FatRenderer instance and offers a simple interface. However, there is nothing special about it and if you feel limited by it you can simply create a copy of GraphicsEngine.ts and adjust it to your needs.

Check out the implementation of GraphicsEngine.ts on GitHub.

Quick Example

To create something visible on the screen we need to do two things:

  1. Create a graphics object
  2. Attach it to a parent that is connected to the root-scene

Here an example to have simple, gray rectangle (quad) on the screen:

ts
import {Color, QuadElement, GraphicsEngine, Component, link} from 'typesprite'

export class PlayerSprite extends Component {
    
    @link('GraphicsEngine:typesprite')
    private gfx:GraphicsEngine
    
    onInit() {
        const quad = new QuadElement(100, 50) // (1)
        quad.setColor(Color.fromHash('#aaa'))
        quad.x = 10;
        quad.scale = 1.4;
        quad.rotation = 13;
        this.gfx.gameLayer.addChild(quad);    // (2)
    }
    
    // NOTE: Don't forget to call removeChild (see below)
}

Render elements, like QuadElement stay on the screen as long as they are directly (or indirectly) attached to the root scene (gfx.gameLayer).

RenderElement Classes

QuadElement is just an example. There are more RenderElement classes:

ClassDescription
RenderElementDraws nothing. Useful to organize your scene
QuadElementA rectangle with support for color gradients and alpha transparency
BitmapElementTextured element with color blending support.
SpriteElementA single frame of a Sprite Sheet
AnimationElementCan play frame animation from a Sprite Sheet.
NinePatchElementScaleable element based on the 9-patch-concept
ParticleRenderElementDisplays and controls a particle engine.
TextElementRenders Bitmap Font Texts.
TileLayerElementDraws a single layer of a tile matrix. It supports color blending and a viewport.
CustomShadedQuadElementRenders a textured quad using a FatMaterial with support for custom WebGL Fragment Shader
Custom elements with DirectRenderElement [Advanced]

The RenderElement-class is tightly coupled with the FatRenderer object, which is the core graphics object of TypeSpriteJS. You cannot create custom RenderElement implementations because the FatRenderer does not know how to render them.

However, you can subclass DirectRenderElement. This way you get access to the FatRenderer. This is useful if you like to draw many objects in a more custom way.

ts
export class CustomRenderElement extends DirectRenderElement {

    renderDirect(renderer: FatRenderer, parentMatrix: AffineMatrix) {
        // renderer.directDrawXXX(...)
    }
}

Base class: RenderElements

All 2D graphic objects inherit from RenderElement and they all share a wide range of common attributes and features.

RenderElement

Property/FunctionDesc
nameFor debugging
xx-position relative to it's parent
yy-position relative to it's parent
rotationRotation in degree
scaleXx-scale (default: 1)
scaleYy-scale (default: 1)
scaleUtility to read/write scaleX and scaleY at once
regXx-center-point of the element in pixel. Scaling, rotation will happen around this.
regYy-center-point of the element in pixel. Scaling, rotation will happen around this.
blendModenormal, additive, multiply and more. Controls how the colors are blended.
visibleIf false the object won't be drawn. This is also true for all children.
matrix [get]Returns AffineMatrix object that represents the combination of x, y, scale, rotation. If in manual matrix mode it'll return the custom matrix.
matrix [set]When set this enables the manual matrix mode for this object. This means you have to set all attributes using the matrix. x, y, scale and rotation have no effect in manual matrix mode.
useStandardMatrix()Resets the manual matrix mode of the object.
worldMatrix [get]Returns the current world matrix of the object.
addChild(...)Adds a child to this element
removeChild(...)Removes a child from this element
removeChildAt(...)Removes the child at the given index
getChildAt(...)Returns the child at the given index
numChildren [get]Returns the number of children

Rendering order

GraphicsEngine provides a gameLayer as a starting point. We can attach our render elements to it. The order is important: every element will be drawn ontop of the last one.

All RenderElement objects can have children. This way we can setup a scene-tree in a way that works for our game.

An example for a 2D Jump & Run could look like this:

bash
- gameLayer 
  - background
    - fillColor
    - parallax
  - level
    - ground
    - objects
      - enemy1
      - player
      - ...
  - forground
    - someDecals
    - godRays
ts
import {GraphicsEngine, Component, cmp} from 'typesprite'

export class LevelLayer extends Component {

    @cmp('GraphicsEngine:typesprite') 
    private gfx:GraphicsEngine
    
    public background:RenderElement;
    public level:RenderElement;
    public ground:RenderElement;
    public objects:RenderElement;
    public foreground:RenderElement;

    onInit() {
        this.background = new RenderElement();
        this.gfx.addChild(this.background);
        
        this.level = new RenderElement();
        this.gfx.addChild(this.level);

        this.ground = new RenderElement();
        this.level.addChild(this.ground);

        this.objects = new RenderElement();
        this.level.addChild(this.objects);
        
        this.foreground = new RenderElement();
        this.gfx.addChild(this.foreground);
    }
}
ini
[!Core]
@GraphicsEngine:typesprite
@LevelLayer

[!MyPlayer->Core]
@PlayerSprite
ts
import {QuadElement, GraphicsEngine, Component, link} from 'typesprite'

export class PlayerSprite extends Component {
    
    @link('LevelLayer')
    private layer:LevelLayer
    
    onInit() {
        const quad = new QuadElement(100, 50) 
        this.layer.objects.addChild(quad);
    }
}

Activation & Deactivation & Dispose

The 2D graphics engine does not ship a pre-built concept for a camera. All RenderElements that are attached to the root-scene will be rendered. The engine does not try to guess what is visible and what not.

If you have large worlds with many objects off the screen you should activate/deactivate them by implementing an EntityActivator. Entities that are not important to the current game scene should be disabled.

Make sure your game components honor activation and deactivation. You can either add/remove them or switch their visibility:

ts
export class PlayerSprite extends Component {
    
    @link('LevelLayer')
    private layer:LevelLayer
    private quad:QuadElement;
    
    onInit() {
        this.quad = new QuadElement(100, 50)
    }

    onActivate() {
        this.layer.objects.addChild(this.quad);
    }

    onDeactivate() {
        this.layer.objects.removeChild(this.quad);
    }
}
ts
export class PlayerSprite extends Component {
    
    @link('LevelLayer')
    private layer:LevelLayer
    private quad:QuadElement;

    onInit() {
        this.quad = new QuadElement(100, 50)
        this.layer.objects.addChild(this.quad);
    }

    onActivate() {
        this.quad.visible = true;
    }

    onDeactivate() {
        this.quad.visible = false;   
    }
    
    onDispose() {
        this.layer.objects.removeChild(this.quad);
    }
}