Happy New Year Excalibur!
We've put together a fireworks demo to commemorate the occasion!
Tap, click, or keypress to add more fireworks:
In 2024 a lot happened for Excalibur! We had 3 big releases v0.28.x, v0.29.x, and v0.30.x! Join the newsletter to make sure you don't miss a thing!
2 new core conributers: Matt Jennings & Justin Young
Thriving discord with lots of cool games being built ๐
RECORD number of outside code contributions to Excalibur! Everyone that's commented, made an issue, discussion, or commited to a repo!
We are at 1.9k stars on github and growing, give us a star!
AND We have GitHub Sponsors & Patreons! Many thanks!
- 2 x Anonymous
- Ribsom
- PlanetCraft
- AdamE
- Latanya
- Feremabraz
- MRJP-Consulting
Highlightsโ
Development Build & EcmaScript Modulesโ
We now ship a excalibur.development.js, which provides additional debug information about issues that you would probably not want to ship to a production game. This provides a better development experience for our devs helping them find bugs faster.
Additionally we ship both an UMD and ESM bundle in Excalibur for modern bunders, the ESM build drastically reduces bundle sizes for folks.
Quick Startโ
A big boost to productivity is the new Excalibur CLI for quickly scaffolding games in your favorite bundler technology or even vanilla JavaScript! This tool pulls all our open source template repos from github.
sh
npx create-excalibur@latest
sh
npx create-excalibur@latest
This tool was built by contributor Manu Hernandez, big thanks to him for donating his time and building this great tool!
Accomplishments & Cool Stuffโ
You might notice a pattern here but Justin (aka Mookie) has been an MVP this year
Goal Oriented Action Planning by Justin Young
Wave Function Collapse by Justin Young
Dev.to Web Game Challenge Winner Justin Young
Demo Reel Games people are making in Excalibur
Performanceโ
We've really turned the screws on performance in excalibur with v0.30.x being roughly 2-3 times faster than v0.29.x, both in the physics and the graphics! A lot of this was achieved through new data structures, and removing allocations from the hot loop using arena style object pools.
Check out the Excalibur Bunnymark for raw draw performance!
In the physics realm we switched to a new spatial data structure "SparseHashGrid" that seems to yield better performance than our "DynamicAABBTree" previously, especially for large numbers of colliders. Autsider666 really helped dig into this collision performance endeavor, check out his Idle Survivors. We can support many 100s of collisions at once!
Debuggingโ
We have new debug drawing API you can use anywhere in your game to debug.
typescript
ex.Debug.drawRay(ray: Ray, options?: { distance?: number, color?: Color })ex.Debug.drawBounds(boundingBox: BoundingBox, options?: { color?: Color })ex.Debug.drawCircle(center: Vector, radius: number, options?: ...)ex.Debug.drawPolygon(points: Vector[], options?: { color?: Color })ex.Debug.drawText(text: string, pos: Vector)ex.Debug.drawLine(start: Vector, end: Vector, options?: LineGraphicsOptions)ex.Debug.drawLines(points: Vector[], options?: LineGraphicsOptions)ex.Debug.drawPoint(point: Vector, options?: PointGraphicsOptions)
typescript
ex.Debug.drawRay(ray: Ray, options?: { distance?: number, color?: Color })ex.Debug.drawBounds(boundingBox: BoundingBox, options?: { color?: Color })ex.Debug.drawCircle(center: Vector, radius: number, options?: ...)ex.Debug.drawPolygon(points: Vector[], options?: { color?: Color })ex.Debug.drawText(text: string, pos: Vector)ex.Debug.drawLine(start: Vector, end: Vector, options?: LineGraphicsOptions)ex.Debug.drawLines(points: Vector[], options?: LineGraphicsOptions)ex.Debug.drawPoint(point: Vector, options?: PointGraphicsOptions)
typescript
onPreUpdate(engine: ex.Engine, elapsedMs: number): void {this.vel = ex.Vector.Zero;this.graphics.use('down-idle');if (engine.input.keyboard.isHeld(ex.Keys.ArrowRight)) { ... }if (engine.input.keyboard.isHeld(ex.Keys.ArrowLeft)) { ... }if (engine.input.keyboard.isHeld(ex.Keys.ArrowUp)) { ... }if (engine.input.keyboard.isHeld(ex.Keys.ArrowDown)) { ... }ex.Debug.drawRay(new ex.Ray(this.pos, this.vel),{ distance: 100, color: ex.Color.Red });}
typescript
onPreUpdate(engine: ex.Engine, elapsedMs: number): void {this.vel = ex.Vector.Zero;this.graphics.use('down-idle');if (engine.input.keyboard.isHeld(ex.Keys.ArrowRight)) { ... }if (engine.input.keyboard.isHeld(ex.Keys.ArrowLeft)) { ... }if (engine.input.keyboard.isHeld(ex.Keys.ArrowUp)) { ... }if (engine.input.keyboard.isHeld(ex.Keys.ArrowDown)) { ... }ex.Debug.drawRay(new ex.Ray(this.pos, this.vel),{ distance: 100, color: ex.Color.Red });}
We now have a chrome & firefox extension to help debug your games in the browser. (This is a great place to contribute to open source! We have a LONG wish list for the plugin)
Big thanks to Adam Due Hansen for adding recent fixes that allow debug settings to be saved across browser refresh!
ECS (Entity Component System)โ
Simplified Custom System implementations
- Systems are passed an ECS world as a default constructor
- Systems can use any number of queries
typescript
import * as ex from 'excalibur';export class MySystem extends ex.System {static priority = ex.SystemPriority.Average;public readonly systemType = ex.SystemType.Draw;query: Query<typeof ex.TransformComponent | typeof ex.GraphicsComponent>;constructor(public world: ex.World) {super();this.query = this.world.query([ex.TransformComponent, ex.GraphicsComponent]);}public update(elapsed: number): void {// TODO implement system using query(s)}}
typescript
import * as ex from 'excalibur';export class MySystem extends ex.System {static priority = ex.SystemPriority.Average;public readonly systemType = ex.SystemType.Draw;query: Query<typeof ex.TransformComponent | typeof ex.GraphicsComponent>;constructor(public world: ex.World) {super();this.query = this.world.query([ex.TransformComponent, ex.GraphicsComponent]);}public update(elapsed: number): void {// TODO implement system using query(s)}}
New Tag Queries
- New simplified way to query entities
ex.World.query([MyComponentA, MyComponentB])
- New way to query for tags on entities
ex.World.queryTags(['A', 'B'])
Coroutinesโ
These are a powerful tool for doing computation over time, and one of the best examples of that is animation. Coroutines are create for complex behavior and animations over time. They read very linearly for doing complex sequences over time compared to another approach where you might set flags and track timing in a class.
You can do lots of cool things in the body of coroutines in excalibur:
typescript
ex.coroutine(function*() {...yield 100; // wait 100msyield; // wait to next frameyield Promsie.resolve(); // wait for promiseyield* ex.coroutine(function* () { ..}); // wait for nested coroutine});
typescript
ex.coroutine(function*() {...yield 100; // wait 100msyield; // wait to next frameyield Promsie.resolve(); // wait for promiseyield* ex.coroutine(function* () { ..}); // wait for nested coroutine});
A more concrete example is in Jelly Jumper, where we use coroutines to handle the complex squash/stretch of player's graphic
typescript
// apply a stretch animation when jumpingif (this.animation.is('jump') && this.oldVel.y >= 0 && this.vel.y < 0) {ex.coroutine(this.scene!.engine,function* (this: Player): ReturnType<ex.CoroutineGenerator> {const duration = 70const scaleTo = 1 + 1 * this.FX_SQUISH_AMOUNTconst easing = ex.EasingFunctions.EaseOutCubicconst force = this.vel.ylet elapsed = 0// stretch player graphic while jumpingwhile (this.vel.y < force * 0.25) {elapsed += yield 1if (elapsed < duration) {this.squishGraphic(easing(Math.min(elapsed, duration), 1, scaleTo, duration))}}elapsed = 0// un-stretch player graphic while jumpingwhile (!this.touching.bottom.size) {elapsed += yield 1if (elapsed < duration) {this.squishGraphic(easing(Math.min(elapsed, duration), scaleTo, 1, duration * 2))}}this.squishGraphic(1)}.bind(this))}
typescript
// apply a stretch animation when jumpingif (this.animation.is('jump') && this.oldVel.y >= 0 && this.vel.y < 0) {ex.coroutine(this.scene!.engine,function* (this: Player): ReturnType<ex.CoroutineGenerator> {const duration = 70const scaleTo = 1 + 1 * this.FX_SQUISH_AMOUNTconst easing = ex.EasingFunctions.EaseOutCubicconst force = this.vel.ylet elapsed = 0// stretch player graphic while jumpingwhile (this.vel.y < force * 0.25) {elapsed += yield 1if (elapsed < duration) {this.squishGraphic(easing(Math.min(elapsed, duration), 1, scaleTo, duration))}}elapsed = 0// un-stretch player graphic while jumpingwhile (!this.touching.bottom.size) {elapsed += yield 1if (elapsed < duration) {this.squishGraphic(easing(Math.min(elapsed, duration), scaleTo, 1, duration * 2))}}this.squishGraphic(1)}.bind(this))}
Graphicsโ
Pixel Artโ
First class pixel art art support with custom shaders/settings for the nicest looking pixel art you've ever seen. This removes common shimmering/banding artifacts that are visible when using pixel art with nearest neighbor filtering.
typescript
const engine = new ex.Engine({pixelArt: true,...});
typescript
const engine = new ex.Engine({pixelArt: true,...});
Check out pixelArt: true
! Smooth as butter AA but still preserving the pixel art!
Compared with before when using antialiasing: false
, you may need to go fullscreen but obvious banding and fat pixels are visible. This is a common visual issue when working with pixel art
The magic is doing subtle subpixel antialiasing along the pixel seams to avoid artifacts by adjusting the UVs and letting bilinear filtering do the hard work for us.
c
// Inigo Quilez pixel art filter// https://jorenjoestar.github.io/post/pixel_art_filtering/vec2 uv_iq(in vec2 uv, in vec2 texture_size) {vec2 pixel = uv * texture_size;vec2 seam=floor(pixel+.5);vec2 dudv=fwidth(pixel);pixel=seam+clamp((pixel-seam)/dudv,-.5,.5);return pixel/texture_size;}
c
// Inigo Quilez pixel art filter// https://jorenjoestar.github.io/post/pixel_art_filtering/vec2 uv_iq(in vec2 uv, in vec2 texture_size) {vec2 pixel = uv * texture_size;vec2 seam=floor(pixel+.5);vec2 dudv=fwidth(pixel);pixel=seam+clamp((pixel-seam)/dudv,-.5,.5);return pixel/texture_size;}
SVGs as Graphicsโ
Did you know you can use SVG's now as graphics in excalibur?! You can!
You can inline an SVG string if you're really strong in SVG markup, or you want to generate SVG's on the fly!
typescript
const svgImage = ex.ImageSource.fromSvgString(svg`<svg version="1.1"id="svg2"xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"sodipodi:docname="resize-full.svg" inkscape:version="0.48.4 r9939"xmlns="http://www.w3.org/2000/svg"width="800px" height="800px"viewBox="0 0 1200 1200" enable-background="new 0 0 1200 1200" xml:space="preserve"><path id="path18934" fill="#000000ff" inkscape:connector-curvature="0" d="M670.312,0l177.246,177.295L606.348,418.506l175.146,175.146l241.211-241.211L1200,529.688V0H670.312z M418.506,606.348L177.295,847.559L0,670.312V1200h529.688l-177.246-177.295l241.211-241.211L418.506,606.348z"/></svg>`);
typescript
const svgImage = ex.ImageSource.fromSvgString(svg`<svg version="1.1"id="svg2"xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"sodipodi:docname="resize-full.svg" inkscape:version="0.48.4 r9939"xmlns="http://www.w3.org/2000/svg"width="800px" height="800px"viewBox="0 0 1200 1200" enable-background="new 0 0 1200 1200" xml:space="preserve"><path id="path18934" fill="#000000ff" inkscape:connector-curvature="0" d="M670.312,0l177.246,177.295L606.348,418.506l175.146,175.146l241.211-241.211L1200,529.688V0H670.312z M418.506,606.348L177.295,847.559L0,670.312V1200h529.688l-177.246-177.295l241.211-241.211L418.506,606.348z"/></svg>`);
Additionally you can load svgs from files
typescript
const svgExternal = new ex.ImageSource('path/to/my.svg');
typescript
const svgExternal = new ex.ImageSource('path/to/my.svg');
Nine Slice Spritesโ
Excalibur now supports Nine Slice Sprites! Thanks Justin!
Check out the demo app to play around with this feature!
Useful for creating bits of UI, or for tiling areas with a boarder!
typescript
const nineSlice = new ex.NineSlice({width: 300,height: 100,source: inputImageSource,sourceConfig: {width: 64,height: 64,topMargin: 5,leftMargin: 7,bottomMargin: 5,rightMargin: 7},destinationConfig: {drawCenter: true,horizontalStretch: ex.NineSliceStretch.Stretch,verticalStretch: ex.NineSliceStretch.Stretch}});actor.graphics.add(nineSlice);
typescript
const nineSlice = new ex.NineSlice({width: 300,height: 100,source: inputImageSource,sourceConfig: {width: 64,height: 64,topMargin: 5,leftMargin: 7,bottomMargin: 5,rightMargin: 7},destinationConfig: {drawCenter: true,horizontalStretch: ex.NineSliceStretch.Stretch,verticalStretch: ex.NineSliceStretch.Stretch}});actor.graphics.add(nineSlice);
Tiling Sprites & Animationsโ
We now have convenient and performant tiling support for sprites and animations! You can specify you wrapping mode in x/y. If one of the dimensions exceeds the intrinsic width/height of the original image it can be Clamped (which stretches the last pixel), Repeat (which repeats the pixels from left to right), or RepeatMirror (which repeats the pixels from the last edge like a mirror).
typescript
const tiledGroundSprite = new ex.TiledSprite({image: groundImage,width: game.screen.width,height: 200,wrapping: {x: ex.ImageWrapping.Repeat,y: ex.ImageWrapping.Clamp}});
typescript
const tiledGroundSprite = new ex.TiledSprite({image: groundImage,width: game.screen.width,height: 200,wrapping: {x: ex.ImageWrapping.Repeat,y: ex.ImageWrapping.Clamp}});
This can also be done with animations!
typescript
const tilingAnimation = new ex.TiledAnimation({animation: cardAnimation,sourceView: {x: 20, y: 20},width: 200,height: 200,wrapping: ex.ImageWrapping.Repeat});
typescript
const tilingAnimation = new ex.TiledAnimation({animation: cardAnimation,sourceView: {x: 20, y: 20},width: 200,height: 200,wrapping: ex.ImageWrapping.Repeat});
Bezier Curvesโ
typescript
player.actions.rotateTo({angleRadians: angle, duration: 1000, rotationType});player.actions.moveTo({pos: ex.vec(100, 100), duration: 1000});player.actions.scaleTo({scale: ex.vec(2, 2), duration: 1000});player.actions.repeatForever(ctx => {ctx.curveTo({controlPoints: [cp1, cp2, dest],duration: 5000,mode: 'uniform'});ctx.curveTo({controlPoints: [cp2, cp1, start1],duration: 5000,mode: 'uniform'});});
typescript
player.actions.rotateTo({angleRadians: angle, duration: 1000, rotationType});player.actions.moveTo({pos: ex.vec(100, 100), duration: 1000});player.actions.scaleTo({scale: ex.vec(2, 2), duration: 1000});player.actions.repeatForever(ctx => {ctx.curveTo({controlPoints: [cp1, cp2, dest],duration: 5000,mode: 'uniform'});ctx.curveTo({controlPoints: [cp2, cp1, start1],duration: 5000,mode: 'uniform'});});
HTML UI with Pixel Conversionโ
Convert from Excalibur "world" pixles to CSS pixels, this is very useful when you want your game HTML to scale with the excalibur canvas and match up.
css
.ui-container {pointer-events: none;position: absolute;transform-origin: 0 0;transform: scale(calc(var(--ex-pixel-ratio)),calc(var(--ex-pixel-ratio)));}
css
.ui-container {pointer-events: none;position: absolute;transform-origin: 0 0;transform: scale(calc(var(--ex-pixel-ratio)),calc(var(--ex-pixel-ratio)));}
Here are some example repos of this being employed with HTML UI
GPU Particlesโ
You already saw the fireworks! With GPU particles you can run hundreds of thousands of particles no sweat using instanced rendering with transform feedback under the hood. This means that the entire particle simulation runs on the GPU for maximum speed.
typescript
export class Firework extends Actor {random: Random;trail: GpuParticleEmitter;explosion: GpuParticleEmitter;explosion2: GpuParticleEmitter;life: number;body: BodyComponent;originalPos: Vector;inProgress: boolean = false;constructor(pos: Vector, life: number, random: Random) {super({ name: "Firework" })this.random = random;this.originalPos = pos.clone();this.pos = pos;this.acc = vec(0, 800);this.body = new BodyComponent();this.life = life;this.trail = new GpuParticleEmitter({isEmitting: false,emitRate: 70,particle: {life: 1000,endColor: Color.White,beginColor: Color.White,minSpeed: 10,maxSpeed: 30,startSize: 3,endSize: 0,fade: true,acc: vec(0, 50),}});this.explosion = new GpuParticleEmitter({isEmitting: false,particle: {life: 2000,fade: true,startSize: 5,endSize: 2,minSpeed: 10,maxSpeed: 200,acc: vec(0, 100),beginColor: this.randomColor(),endColor: this.randomColor()}});this.explosion2 = new GpuParticleEmitter({isEmitting: false,particle: {life: 1000,fade: true,startSize: 5,endSize: 2,minSpeed: 10,maxSpeed: 200,acc: vec(0, 100),beginColor: this.randomColor(),endColor: this.randomColor()}});this.addChild(this.trail);this.addChild(this.explosion);this.addChild(this.explosion2);}private _colors = [Color.fromHex("#ff0000"),Color.fromHex("#0078ff"),Color.fromHex("#ffffff"),Color.fromHex("#d059c5"),Color.fromHex("#dff241"),Color.fromHex("#05ff1c"),Color.fromHex("#ffdf00"),Color.fromHex("#3e00f9"),Color.fromHex("#ff5fc0"),Color.fromHex("#ff3f3f"),Color.fromHex("#f66706"),]private randomColor(): Color {return this.random.pickOne(this._colors);}launch() {if (this.inProgress) return;this.inProgress = true;this.pos = this.originalPos;coroutine(this.scene!.engine, (function*(this: Firework) {this.vel = vec(this.random.floating(-200, 200), this.random.floating(-800, -1000));this.trail.isEmitting = true;let hasExploded = false;let life = this.life;while (life > 0) {const elapsed = yield;life -= elapsed;if (this.vel.y >= 0 && !hasExploded) {hasExploded = true;this.trail.isEmitting = false;this.explosion.emitParticles(500);this.explosion2.emitParticles(500);}}this.trail.clearParticles();this.explosion.clearParticles();this.explosion2.clearParticles();this.inProgress = false;} as CoroutineGenerator).bind(this))}}
typescript
export class Firework extends Actor {random: Random;trail: GpuParticleEmitter;explosion: GpuParticleEmitter;explosion2: GpuParticleEmitter;life: number;body: BodyComponent;originalPos: Vector;inProgress: boolean = false;constructor(pos: Vector, life: number, random: Random) {super({ name: "Firework" })this.random = random;this.originalPos = pos.clone();this.pos = pos;this.acc = vec(0, 800);this.body = new BodyComponent();this.life = life;this.trail = new GpuParticleEmitter({isEmitting: false,emitRate: 70,particle: {life: 1000,endColor: Color.White,beginColor: Color.White,minSpeed: 10,maxSpeed: 30,startSize: 3,endSize: 0,fade: true,acc: vec(0, 50),}});this.explosion = new GpuParticleEmitter({isEmitting: false,particle: {life: 2000,fade: true,startSize: 5,endSize: 2,minSpeed: 10,maxSpeed: 200,acc: vec(0, 100),beginColor: this.randomColor(),endColor: this.randomColor()}});this.explosion2 = new GpuParticleEmitter({isEmitting: false,particle: {life: 1000,fade: true,startSize: 5,endSize: 2,minSpeed: 10,maxSpeed: 200,acc: vec(0, 100),beginColor: this.randomColor(),endColor: this.randomColor()}});this.addChild(this.trail);this.addChild(this.explosion);this.addChild(this.explosion2);}private _colors = [Color.fromHex("#ff0000"),Color.fromHex("#0078ff"),Color.fromHex("#ffffff"),Color.fromHex("#d059c5"),Color.fromHex("#dff241"),Color.fromHex("#05ff1c"),Color.fromHex("#ffdf00"),Color.fromHex("#3e00f9"),Color.fromHex("#ff5fc0"),Color.fromHex("#ff3f3f"),Color.fromHex("#f66706"),]private randomColor(): Color {return this.random.pickOne(this._colors);}launch() {if (this.inProgress) return;this.inProgress = true;this.pos = this.originalPos;coroutine(this.scene!.engine, (function*(this: Firework) {this.vel = vec(this.random.floating(-200, 200), this.random.floating(-800, -1000));this.trail.isEmitting = true;let hasExploded = false;let life = this.life;while (life > 0) {const elapsed = yield;life -= elapsed;if (this.vel.y >= 0 && !hasExploded) {hasExploded = true;this.trail.isEmitting = false;this.explosion.emitParticles(500);this.explosion2.emitParticles(500);}}this.trail.clearParticles();this.explosion.clearParticles();this.explosion2.clearParticles();this.inProgress = false;} as CoroutineGenerator).bind(this))}}
Materialsโ
Use custom shader code to do neat effects in excalibur, like doing Star Marbles.
You can use the screen texture to do cool things like reflections in the water.
Scene Ergonomicsโ
You can now define scenes up front and get strong typing in addition to addScene(...)
typescript
const game = new ex.Engine({scenes: {scene1: {scene: scene1,transitions: {out: new ex.FadeInOut({duration: 1000, direction: 'out', color: ex.Color.Black}),in: new ex.FadeInOut({duration: 1000, direction: 'in'})}},scene2: {scene: scene2,loader: ex.DefaultLoader, // Constructor only option!transitions: {out: new ex.FadeInOut({duration: 1000, direction: 'out'}),in: new ex.FadeInOut({duration: 1000, direction: 'in', color: ex.Color.Black })}},scene3: ex.Scene // Constructor only option!}})// Specify the boot loader & first scene transition from loadergame.start('scene1',{inTransition: new ex.FadeInOut({duration: 500, direction: 'in', color: ex.Color.ExcaliburBlue})loader: boot,});
typescript
const game = new ex.Engine({scenes: {scene1: {scene: scene1,transitions: {out: new ex.FadeInOut({duration: 1000, direction: 'out', color: ex.Color.Black}),in: new ex.FadeInOut({duration: 1000, direction: 'in'})}},scene2: {scene: scene2,loader: ex.DefaultLoader, // Constructor only option!transitions: {out: new ex.FadeInOut({duration: 1000, direction: 'out'}),in: new ex.FadeInOut({duration: 1000, direction: 'in', color: ex.Color.Black })}},scene3: ex.Scene // Constructor only option!}})// Specify the boot loader & first scene transition from loadergame.start('scene1',{inTransition: new ex.FadeInOut({duration: 500, direction: 'in', color: ex.Color.ExcaliburBlue})loader: boot,});
Tile Map Pluginsโ
Tiled plugin was completely rewritten and is SO MUCH better, we support nearly every feature of Tiled now!
New LDtk Plugin check out the sample game using the plugin here
New Spritefusion Plugin check out the sample game using the plugin here
Aseprite Pluginโ
Native .aseprite
support iterate on your assets whild you build your game.
typescript
import { AsepriteResource } from "@excaliburjs/plugin-aseprite";const game = new Engine({width: 600,height: 400,displayMode: DisplayMode.FitScreen});// Nativeconst asepriteSpriteSheet = new AsepriteResource('./beetle.aseprite');// Or JSON export// const asepriteSpriteSheet = new AsepriteResource('./beetle.json');const loader = new Loader([asepriteSpriteSheet]);game.start(loader).then(() => {const anim = asepriteSpriteSheet.getAnimation('Loop');const actor = new Actor({pos: vec(100, 100)});actor.graphics.use(anim);game.currentScene.add(actor);});
typescript
import { AsepriteResource } from "@excaliburjs/plugin-aseprite";const game = new Engine({width: 600,height: 400,displayMode: DisplayMode.FitScreen});// Nativeconst asepriteSpriteSheet = new AsepriteResource('./beetle.aseprite');// Or JSON export// const asepriteSpriteSheet = new AsepriteResource('./beetle.json');const loader = new Loader([asepriteSpriteSheet]);game.start(loader).then(() => {const anim = asepriteSpriteSheet.getAnimation('Loop');const actor = new Actor({pos: vec(100, 100)});actor.graphics.use(anim);game.currentScene.add(actor);});
If you're curious about how this was built, we live streamed the process!
Path finding Pluginโ
Justin put together an official path finding plugin that supports both Djikstra & A*!
You can see the plugin in action in this sample, click to move the little guy around.
JSFXR Pluginโ
The JSFXR plugin is super useful for creating extra small sounds programmatically instead of downloading large wav/mp3 files!
shell
npm i @excaliburjs/plugin-jsfxr
shell
npm i @excaliburjs/plugin-jsfxr
This allows you to sepcify the config for the audio, which could be changed on the fly as well.
typescript
import { SoundConfig } from "@excaliburjs/plugin-jsfxr";export const sounds: { [key: string]: SoundConfig } = {};// Generate configs https://excaliburjs.com/sample-jsfxr/sounds["pickup"] = {oldParams: true,wave_type: 1,p_env_attack: 0,p_env_sustain: 0.02376922019231107,p_env_punch: 0.552088780864157,p_env_decay: 0.44573175628456596,p_base_freq: 0.6823818961421457,p_freq_limit: 0,p_freq_ramp: 0,p_freq_dramp: 0,p_vib_strength: 0,p_vib_speed: 0,p_arp_mod: 0,p_arp_speed: 0,p_duty: 0,p_duty_ramp: 0,p_repeat_speed: 0,p_pha_offset: 0,p_pha_ramp: 0,p_lpf_freq: 1,p_lpf_ramp: 0,p_lpf_resonance: 0,p_hpf_freq: 0,p_hpf_ramp: 0,sound_vol: 0.25,sample_rate: 44100,sample_size: 16,};let sndPlugin = new JsfxrResource();sndPlugin.init(); //initializes the JSFXR libraryfor (const sound in sounds) {sndPlugin.loadSoundConfig(sound, sounds[sound]);}// playsndPlugin.playSound("pickup");
typescript
import { SoundConfig } from "@excaliburjs/plugin-jsfxr";export const sounds: { [key: string]: SoundConfig } = {};// Generate configs https://excaliburjs.com/sample-jsfxr/sounds["pickup"] = {oldParams: true,wave_type: 1,p_env_attack: 0,p_env_sustain: 0.02376922019231107,p_env_punch: 0.552088780864157,p_env_decay: 0.44573175628456596,p_base_freq: 0.6823818961421457,p_freq_limit: 0,p_freq_ramp: 0,p_freq_dramp: 0,p_vib_strength: 0,p_vib_speed: 0,p_arp_mod: 0,p_arp_speed: 0,p_duty: 0,p_duty_ramp: 0,p_repeat_speed: 0,p_pha_offset: 0,p_pha_ramp: 0,p_lpf_freq: 1,p_lpf_ramp: 0,p_lpf_resonance: 0,p_hpf_freq: 0,p_hpf_ramp: 0,sound_vol: 0.25,sample_rate: 44100,sample_size: 16,};let sndPlugin = new JsfxrResource();sndPlugin.init(); //initializes the JSFXR libraryfor (const sound in sounds) {sndPlugin.loadSoundConfig(sound, sounds[sound]);}// playsndPlugin.playSound("pickup");
Example of how to integrate this into your game
New Tutorialโ
We have a new tutorial! It's a new flappy bird clone called "Excalibird".
New High Fidelity Samplesโ
- Jelly Jumper by Matt Jennings and the source code
- Sum Monsters and the source code
- Tiny Tactics and the source code
- Excalibird and the source code
Caliburn Gamesโ
The core contributors around Excalibur have started a business! We are modeling ourselves off of w4games, providing: educational content, support, project development, and console porting services. Excalibur.js and Excalibur Studio will always be open source.
Our plan for 2025 is to release 2 commercial games on various platforms AND offer Nintendo Switch Console publishing!!!
Visit caliburn.games today and start working with us.
Excaliburjs.TVโ
We are planning on releasing a number of free and paid courses through Caliburn Games.
Sign up for more details when they become available https://excaliburjs.tv
Near Futureโ
Sound Managerโ
We are planning on adding a "Sound Manager" to organize sounds into different "tracks" that can be mixed and muted independently. For example sound effects and background music.
Folks have implemented this in almost every game made, so we plan on adding a first party implementation to make this easy.
Collision and Physicsโ
We are planning a number of highly requested improvements to physics and collisions.
- Continuous Collision Improvements
- Swept AABB TOI
- ShapeCasting
- Surface Velocity on bodies (think conveyor belts)
- Arcade Friction
Lightingโ
The future versions of Excalibur will have lighting features to really enhance your games. Big thanks to Justin for digging in and figuring out our approach here!
The plan is to add spot lights and point lights, under the hood this uses a technique called raymarching.
Excalibur Studioโ
We are working on an open source visual editor for Excalibur, so that folks can drag and drop, design, code, configure games, and export to various platforms!
We'll be opening up the source code as soon as we have something that works for a vertical slice of game dev.
v1.0โ
The majority of APIs are now stable and not much will change between now and the v1.0 announcement. Right now we are working on adding a couple more features, light refactoring, and cleaning up some edge case bugs.
We expect to be making a v1.0 announcement soon!