Graphics

Excalibur has a new graphics system that has been developed to hide the underyling implementation. This allows optimizations like webgl batch rendering and shader programs to increase the speed and power of our drawings. This new system does a great deal to reduce the complexity of the drawing stack for both developers using Excalibur and the maintainers.

Experimental WebGL support

The new webgl support implements an automatic 2d batch renderer that dramatically improves draw performance.

To use this experiment turn on the flag before engine construction.

ex.Flags.enable('use-webgl');
const game = new ex.Engine(...);

Excalibur Graphics Context

This is an abstraction (ex.Graphics.ExcaliburGraphicsContext) over the underlying drawing mechanism and only knows how to draw ex.Graphic objects.

ImageSource

Loading images from files (png, jpeg, etc)

ex.Graphics.ImageSource is a new type in Excalibur that represents the source bitmap and contains the logic for loading that image from a file. You can thing of this as the file representation in Excalibur for the image.

These images can be presented to the loader at the start of an Excalibur game

const game = new ex.Engine({ width: 800, height: 600 })

const image = new ex.Graphics.ImageSource('./img/myimg.png')

const loader = new ex.Loader()
loader.addResource(image)

game.start(loader).then(() => {
  // resources like ImageSource loaded before game started
})

Or they can be loaded out of band Note: it is important to check that a ImageSource has been loaded before using it at runtime to avoid errors and visual bugs if your game is loading images this way.

const image = new ex.Graphics.ImageSource('./img/myimg.png')

image.load().then(() => {
  // image loaded
  // good for use in sprites inside this function
})

if (image.isLoaded()) {
  // image.data is good for use in sprites
}

GraphicsComponent

There is a new component, ex.GraphicsComponent to work with these graphics with ex.Actor's and other ex.Entity's

The ex.GraphicsComponent alows users to manage graphics with Actors and Entities in an easy way

Adding/Showing graphics

The graphics component allows developers to save named graphics to avoid passing around graphic object references if desired. These can be used to show specific graphics.

actor.graphics.add('jump', jumpAnimation)
actor.graphics.show('jump') // display the graphic
// equivalent to
actor.graphics.show(jumpAnimation) // display the graphic
actor.graphics.hide() // hide the graphic

If no name is specified when added to the graphics component it is considered the 'default' graphic and is shown automatically.

// graphic considered 'default' and displayed automatically
actor.graphics.add(jumpAnimation)

Layers

The components adds a why multiple graphics on top or behind each other for a certain actor or entity.

Layers can be ordered numerically, larger negative layers behind, and positive layers in front.

actor.graphics.createLayer({ name: 'background', order: -1 })
actor.graphics.createLayer({ name: 'foreground', order: 1 })

actor.graphics.getLayer('background').show(myBackground)
actor.graphics.getLayer('foreground').show(myForeground)

// no longer display the background
actor.graphics.getLayer('background').hide()

There is always a layer named 'default' at order: 0

actor.graphics.show(myAnimation)
// is equivalent to
actor.graphics.getLayer('default').show(myAnimation)

Component Specific Overrides

  • visible: boolean

    • Shows or hides the all the graphics for this component
  • opacity: number

    • Applies an opacity to all the graphics shown for this component
  • offset: Vector

    • Offset in pixels to shift the graphics for this component
  • anchor: Vector

    • Anchor to apply to all drawings in this component if set, if null the drawing's anchor is respected.

Graphics

Excalibur ex.Graphics break into 2 main categories, ex.Graphic.Sprite based and ex.Graphic.Raster based. That is to say Raster and non-Raster graphics.

ex.Graphics have a bunch of useful feature that work for ALL types of graphics

  • clone(): Graphic

    • Returns a new deep copy of this graphic
  • width: number

    • Gets or sets the width of the graphic
  • height: number

    • Gets or sets the height of the graphic
  • flipHorizontal: boolean

    • Gets or sets the flipHorizontal, which will flip the graphic horizontally (across the y axis)
  • flipVertical: boolean

    • Gets or sets the flipVertical, which will flip the graphic vertically (across the x axis)
  • rotation: number

    • Gets or sets the rotation of the graphic (in radians)
  • opacity: number

    • Gets or sets the opacity of the graphic, 0 is transparent, 1 is solid (opaque).
  • scale: Vector

    • Gets or sets the scale of the graphic, this effects the width and height of the graphics
  • origin: Vector

    • Gets or sets the origin of the graphic, if not set the center of the graphic is the origin

Sprite

A sprite is a view into a ex.Graphics.ImageSource and a projection into a final destination size.

const image = new ex.Graphics.ImageSource('./img/myimage.png')
// keep in mind this wont work until the raw image is loaded
const sprite = new ex.Graphics.Sprite({
  ImageSource: image,
  sourceView: {
    // Take a small slice of the source image starting at pixel (10, 10) with dimension 20 pixels x 20 pixels
    x: 10,
    y: 10,
    width: 20,
    height: 20,
  },
  destSize: {
    // Optionally specify a different projected size, otherwise use the source
    width: 100,
    height: 100,
  },
})

Many times a sprite is the exact same view and size as the source raw image so there is a quick static helper to do this

const image = new ex.Graphics.ImageSource('./img/myimage.png')
// keep in mind this wont work until the raw image is loaded
const sprite = ex.Graphics.Sprite.from(image)

Animation

Animations are a series of graphics that take a specific duration in milliseconds. Each of these units is called a "Frame". There are a few playing strategies as well to consider

export enum AnimationStrategy {
  /**
   * Animation ends without displaying anything
   */
  End = 'end',
  /**
   * Animation loops to the first frame after the last frame
   */
  Loop = 'loop',
  /**
   * Animation plays to the last frame, then backwards to the first frame, then repeats
   */
  PingPong = 'pingpong',

  /**
   * Animation ends stopping on the last frame
   */
  Freeze = 'freeze',
}

Animation frames can be created by hand in the following example

const animation = new ex.Graphics.Animation({
  frames: [
    {
      graphic: newSprite,
      duration: 500,
    },
    {
      graphic: circle,
      duration: 1000,
    },
    {
      graphic: rect,
      duration: 1500,
    },
    {
      graphic: triangle,
      duration: 2000,
    },
  ],
})

Animations can be constructed quickly from ex.Graphics.SpriteSheets

// Use the specified sprite sheet, take frames 1 - 10 (including 10), with a frame duration of 50 millseconds
const left = ex.Graphics.Animation.fromSpriteSheet(
  spriteSheetRun,
  ex.Util.range(1, 10),
  50
)

Animations also emit events per frame, per loop, and per end (if it completes).

anim.on('loop', (a) => {
  console.log('loop')
})
anim.on('frame', (f) => {
  console.log('frame')
})
anim.on('ended', (a) => {
  console.log('ended')
})

SpriteSheet

SpriteSheet is really an ordered collection of sprites from the same base image.

const spriteSheet = new ex.Graphics.SpriteSheet({
  image: imageRun,
  sprites: [...]
})

If you spritesheet is a neat grid there is a static builder for you to slice up that raw image.

const spriteSheetRun = ex.Graphics.SpriteSheet.fromGrid({
  image: imageRun,
  grid: {
    rows: 1,
    columns: 21,
    spriteHeight: 96,
    spriteWidth: 96,
  },
})

GraphicsGroup

A graphics group is an new graphic that draws a graphics in some relation to one another. This can be useful when you want to compose graphics together into a single graphic. Graphics groups do support all types of graphics including animations

const group = new ex.Graphics.GraphicsGroup({
  members: [
    {
      graphic: newSprite,
      pos: ex.vec(0, 0),
    },
    {
      graphic: newSprite,
      pos: ex.vec(50, 0),
    },
    {
      graphic: newSprite,
      pos: ex.vec(0, 50),
    },
    {
      graphic: text,
      pos: ex.vec(100, 20),
    },
    {
      graphic: circle,
      pos: ex.vec(50, 50),
    },
    {
      graphic: anim,
      pos: ex.vec(200, 200),
    },
    {
      graphic: triangle,
      pos: ex.vec(0, 200),
    },
  ],
})

Raster

Rasters are a type of ex.Graphic built by constructing a bitmap (using CanvasRenderingContext2D) in memory which is then sent to the drawing context. This allows the use of features available to developers in the 2D canvas to produce Graphics for Excalibur. It's important to share or limit the number of Raster graphics due to their memory footprint in the browser.

Rasters are only rendered when they need to be, if no properties change on them, they are not recalculated. Rasters can be forced to be re-draw by calling .flagDirty().

See these useful raster implemenations

A custom raster can be built implementing the execute function

export class MyRaster extends ex.Graphics.Raster {
  constructor() {
    super()
  }

  execute(ctx: CanvasRenderingContext2D): void {
    // my custom raster
    ctx.fillStyle = 'blue'
    ctx.fillRect(0, 0, 20, 20)
  }
}

ex.Raster properties and methods

  • abstract execute(ctx: CanvasRenderingContext2D): void

    • This is the bitmap generation implementation, this will construct the desired bitmap on the CanvasRenderingContext2D.
  • rasterize(): void

    • This causes the underlying bitmap to be generated calling the execute() implementation in the process

Text

Text is a raster graphic that can be used to draw text to the screen, all of the normal things that can be done with rasters and graphics can be done with text.

Text supports both normal OS/web fonts and Sprite fonts.

Example

var text = new ex.Graphics.Text({
  text: 'This is raster text ❤️',
  font: new ex.Graphics.Font({ size: 30 }),
})

Font

This specifies the font characteristics for a text raster.

Possible font options

export interface FontOptions {
  size?: number
  unit?: FontUnit
  family?: string
  style?: FontStyle
  bold?: boolean
  textAlign?: TextAlign
  baseAlign?: BaseAlign
  direction?: Direction
  shadow?: {
    blur?: number
    offset?: Vector
    color?: Color
  }
}
var text = new ex.Graphics.Text({
  text: 'This is raster text ❤️',
  font: new ex.Graphics.Font({
    size: 30,
    unit: FontUnit.Px,
    family: 'sans-serif',
    style: FontStyle.Normal,
    bold: false,
    textAlign: TextAlign.Left,
    baseAlign: BaseAlign.Alphabetic,
    direction: Direction.LeftToRight,
    shadow: {
      blur: 2,
      offset: ex.Vec(2, 2),
      color: ex.Color.Black,
    };
  })
});

SpriteFont

Sometimes you want to use text of your own creation from a spritesheet,

const spriteFontSheet = ex.Graphics.SpriteSheet.fromGrid({
  image: spriteFontImage,
  grid: {
    rows: 3,
    columns: 16,
    spriteWidth: 16,
    spriteHeight: 16
  }
});

const spriteFont = new ex.Graphics.SpriteFont({
    alphabet: '0123456789abcdefghijklmnopqrstuvwxyz,!\'&."?- ',
    caseInsensitive: true,
    spriteSheet: spriteFontSheet
  });
});

const text = new ex.Graphics.Text({
  text: 'This is sprite font text!!',
  font: spriteFont
});

Polygon

Polygon creates a rastered polygon graphic given a set of points

const triangle = new ex.Graphics.Polygon({
  points: [ex.vec(10 * 5, 0), ex.vec(0, 20 * 5), ex.vec(20 * 5, 20 * 5)],
  color: ex.Color.Yellow,
})

Circle

Circle creates a rastered circle graphic given a raidus

const circle = new ex.Graphics.Circle({
  radius: 10,
  color: ex.Color.Red,
})

Rectangle

Rectangle creates a rastered rectange graphic given a width and height

const rect = new ex.Graphics.Rectangle({
  width: 100,
  height: 100,
  color: ex.Color.Green,
})

Canvas

The canvas is a special type of raster graphic that acts like a shim between direct CanvasRenderingContext2D drawing and the ExcaliburGraphicsContext

This type of raster is re-rendered every time the graphic is drawn, thus it should be used sparingly due to this inefficiency.

const canvas = new Canvas({
  draw: (ctx: CanvasRenderingContext2D) => {
    ...
  }
});