Skip to content

Instantly share code, notes, and snippets.

@eonarheim
Last active May 12, 2020 14:03
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save eonarheim/3b243b78ce0711fc6c9ae63261521ad5 to your computer and use it in GitHub Desktop.
Save eonarheim/3b243b78ce0711fc6c9ae63261521ad5 to your computer and use it in GitHub Desktop.
New Graphics API documentation

Excalibur Graphics API

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.

This implementation is being tracked here

Folder: https://github.com/excaliburjs/Excalibur/tree/feature/drawing-graphics-redo/src/engine/Graphics

Excalibur Graphics Context

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

Built in Graphics

Loading images from files (png, jpeg, etc)

RawImage

ex.Graphics.RawImage is a new type in Excalibur that represents the "raw" 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 raw 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.RawImage("./img/myimg.png");

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

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

Or they can be loaded out of band Note: it is important to check that a RawImage 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.RawImage("./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
}

Actors, Entities, and the 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.

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

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);

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

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.RawImage and a projection into a final destination size.

const image = new ex.Graphics.RawImage('./img/myimage.png');
// keep in mind this wont work until the raw image is loaded
const sprite = new ex.Graphics.Sprite({
  rawImage: 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.RawImage('./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
    }
  ]
});

Frames can contain tags of information

var anim = new ex.Graphics.Animation({
  frames: [
    {
      graphic: playerGraphic,
      duration: 500,
      tags: {
        handPos: ex.vec(200, 200)
      }
    }
  ]
});

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 leftRunningAnimation = 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.SpriteShet({
  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.

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 only supports normal OS and web fonts at this time, sprite fonts are not yet supported. Please see the font type for more info

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,
    };
  })
});

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
});

Rect

Rect creates a rastered rectange graphic given a width and height

const rect = new ex.Graphics.Rect({
  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({
  drawHandler: (ctx: CanvasRenderingContext2D) => {
    ...
  }
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment