Custom Renderer Plugins
Excalibur knows how to draw many types graphics to the screen by default comes with those pre-installed into the ExcaliburGraphicsContext. However, you may have a unique requirement to provide custom WebGL commands into excalibur, this can be done with a custom renderer plugin.
warning
This is an advanced API it is recommended you use built in graphics unless you are comfortable with building WebGL geometry and shaders.
Registering a renderer plugin
Registering a renderer with the graphics context will allow you to call it's draw method during your game.
typescript
const game = new ex.Engine({...});game.start().then(() => {// registergame.graphicsContext.register(new MyCustomRenderer());});// call from a graphics callback or eventconst actor = new ex.Actor({...});actor.graphics.onPostDraw = (graphicsContext) => {graphicsContext.draw<MyCustomRenderer>('myrenderer', ...);}
typescript
const game = new ex.Engine({...});game.start().then(() => {// registergame.graphicsContext.register(new MyCustomRenderer());});// call from a graphics callback or eventconst actor = new ex.Actor({...});actor.graphics.onPostDraw = (graphicsContext) => {graphicsContext.draw<MyCustomRenderer>('myrenderer', ...);}
Writing a custom render plugin
In order to build a custom renderer extend the RendererPlugin interface
typescript
export class MyCustomRenderer extends ex.RendererPlugin {/*** Unique type name for this renderer plugin*/readonly type: string = 'myrenderer';/*** Render priority tie breaker when drawings are at the same z index* @warning Not yet used by excalibur*/priority: number = 0;/*** Initialize your renderer** @param gl* @param context*/initialize(gl: WebGLRenderingContext, context: ExcaliburGraphicsContextWebGL): void {// initialize and compile shader}/*** Issue a draw command to draw something to the screen* @param args*/draw(some: ex.Vector, args: ex.Color): void {// update internal state with draw command with the args}/*** @returns if there are any pending draws in the renderer*/hasPendingDraws(): boolean {// if there are any un-flushed drawingsreturn false;}/*** Flush any pending graphics draws to the screen*/flush(): void {// flush all pending draws to the screen}}
typescript
export class MyCustomRenderer extends ex.RendererPlugin {/*** Unique type name for this renderer plugin*/readonly type: string = 'myrenderer';/*** Render priority tie breaker when drawings are at the same z index* @warning Not yet used by excalibur*/priority: number = 0;/*** Initialize your renderer** @param gl* @param context*/initialize(gl: WebGLRenderingContext, context: ExcaliburGraphicsContextWebGL): void {// initialize and compile shader}/*** Issue a draw command to draw something to the screen* @param args*/draw(some: ex.Vector, args: ex.Color): void {// update internal state with draw command with the args}/*** @returns if there are any pending draws in the renderer*/hasPendingDraws(): boolean {// if there are any un-flushed drawingsreturn false;}/*** Flush any pending graphics draws to the screen*/flush(): void {// flush all pending draws to the screen}}
For example this is the PointRenderer implementation
typescript
export class PointRenderer implements RendererPlugin {public readonly type = 'ex.point';public priority: number = 0;private _shader: Shader;private _maxPoints: number = 10922;private _buffer: VertexBuffer;private _layout: VertexLayout;private _gl: WebGLRenderingContext;private _context: ExcaliburGraphicsContextWebGL;private _pointCount: number = 0;private _vertexIndex: number = 0;initialize(gl: WebGLRenderingContext, context: ExcaliburGraphicsContextWebGL): void {this._gl = gl;this._context = context;this._shader = new Shader({vertexSource: pointVertexSource,fragmentSource: pointFragmentSource});this._shader.compile();this._shader.use();this._shader.setUniformMatrix('u_matrix', this._context.ortho);this._buffer = new VertexBuffer({size: 7 * this._maxPoints,type: 'dynamic'});this._layout = new VertexLayout({shader: this._shader,vertexBuffer: this._buffer,attributes: [['a_position', 2],['a_color', 4],['a_size', 1]]});}draw(point: Vector, color: Color, size: number): void {// Force a render if the batch is fullif (this._isFull()) {this.flush();}this._pointCount++;const transform = this._context.getTransform();const opacity = this._context.opacity;const finalPoint = transform.multv(point);const vertexBuffer = this._buffer.bufferData;vertexBuffer[this._vertexIndex++] = finalPoint.x;vertexBuffer[this._vertexIndex++] = finalPoint.y;vertexBuffer[this._vertexIndex++] = color.r / 255;vertexBuffer[this._vertexIndex++] = color.g / 255;vertexBuffer[this._vertexIndex++] = color.b / 255;vertexBuffer[this._vertexIndex++] = color.a * opacity;vertexBuffer[this._vertexIndex++] = size * Math.max(transform.getScaleX(), transform.getScaleY());}private _isFull() {if (this._pointCount >= this._maxPoints) {return true;}return false;}hasPendingDraws(): boolean {return this._pointCount !== 0;}flush(): void {// nothing to draw early exitif (this._pointCount === 0) {return;}const gl = this._gl;this._shader.use();this._layout.use(true);this._shader.setUniformMatrix('u_matrix', this._context.ortho);gl.drawArrays(gl.POINTS, 0, this._pointCount);GraphicsDiagnostics.DrawnImagesCount += this._pointCount;GraphicsDiagnostics.DrawCallCount++;this._pointCount = 0;this._vertexIndex = 0;}}
typescript
export class PointRenderer implements RendererPlugin {public readonly type = 'ex.point';public priority: number = 0;private _shader: Shader;private _maxPoints: number = 10922;private _buffer: VertexBuffer;private _layout: VertexLayout;private _gl: WebGLRenderingContext;private _context: ExcaliburGraphicsContextWebGL;private _pointCount: number = 0;private _vertexIndex: number = 0;initialize(gl: WebGLRenderingContext, context: ExcaliburGraphicsContextWebGL): void {this._gl = gl;this._context = context;this._shader = new Shader({vertexSource: pointVertexSource,fragmentSource: pointFragmentSource});this._shader.compile();this._shader.use();this._shader.setUniformMatrix('u_matrix', this._context.ortho);this._buffer = new VertexBuffer({size: 7 * this._maxPoints,type: 'dynamic'});this._layout = new VertexLayout({shader: this._shader,vertexBuffer: this._buffer,attributes: [['a_position', 2],['a_color', 4],['a_size', 1]]});}draw(point: Vector, color: Color, size: number): void {// Force a render if the batch is fullif (this._isFull()) {this.flush();}this._pointCount++;const transform = this._context.getTransform();const opacity = this._context.opacity;const finalPoint = transform.multv(point);const vertexBuffer = this._buffer.bufferData;vertexBuffer[this._vertexIndex++] = finalPoint.x;vertexBuffer[this._vertexIndex++] = finalPoint.y;vertexBuffer[this._vertexIndex++] = color.r / 255;vertexBuffer[this._vertexIndex++] = color.g / 255;vertexBuffer[this._vertexIndex++] = color.b / 255;vertexBuffer[this._vertexIndex++] = color.a * opacity;vertexBuffer[this._vertexIndex++] = size * Math.max(transform.getScaleX(), transform.getScaleY());}private _isFull() {if (this._pointCount >= this._maxPoints) {return true;}return false;}hasPendingDraws(): boolean {return this._pointCount !== 0;}flush(): void {// nothing to draw early exitif (this._pointCount === 0) {return;}const gl = this._gl;this._shader.use();this._layout.use(true);this._shader.setUniformMatrix('u_matrix', this._context.ortho);gl.drawArrays(gl.POINTS, 0, this._pointCount);GraphicsDiagnostics.DrawnImagesCount += this._pointCount;GraphicsDiagnostics.DrawCallCount++;this._pointCount = 0;this._vertexIndex = 0;}}