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
:
# 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):
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:
- Create a graphics object
- Attach it to a parent that is connected to the root-scene
Here an example to have simple, gray rectangle (quad) on the screen:
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:
Class | Description |
---|---|
RenderElement | Draws nothing. Useful to organize your scene |
QuadElement | A rectangle with support for color gradients and alpha transparency |
BitmapElement | Textured element with color blending support. |
SpriteElement | A single frame of a Sprite Sheet |
AnimationElement | Can play frame animation from a Sprite Sheet. |
NinePatchElement | Scaleable element based on the 9-patch-concept |
ParticleRenderElement | Displays and controls a particle engine. |
TextElement | Renders Bitmap Font Texts. |
TileLayerElement | Draws a single layer of a tile matrix. It supports color blending and a viewport. |
CustomShadedQuadElement | Renders 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.
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/Function | Desc |
---|---|
name | For debugging |
x | x-position relative to it's parent |
y | y-position relative to it's parent |
rotation | Rotation in degree |
scaleX | x-scale (default: 1) |
scaleY | y-scale (default: 1) |
scale | Utility to read/write scaleX and scaleY at once |
regX | x-center-point of the element in pixel. Scaling, rotation will happen around this. |
regY | y-center-point of the element in pixel. Scaling, rotation will happen around this. |
blendMode | normal , additive , multiply and more. Controls how the colors are blended. |
visible | If 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:
- gameLayer
- background
- fillColor
- parallax
- level
- ground
- objects
- enemy1
- player
- ...
- forground
- someDecals
- godRays
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);
}
}
[!Core]
@GraphicsEngine:typesprite
@LevelLayer
[!MyPlayer->Core]
@PlayerSprite
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:
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);
}
}
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);
}
}