Skip to main content

Migration

Migrating from v0.32.x to v1.0.0​

v1.0.0 is a major version and carries every breaking change that was deferred during the v0.x line. Most of them are renames in service of a consistent API, the rest are behavior fixes. The full list is in the CHANGELOG, this page summarizes them and shows the codemod that does the bulk of the work.

Run the codemod first​

The ex upgrade command in create-excalibur chains every migration from v0.29.3 onward and rewrites your source with formatting-preserving edits. It classifies call sites against the Excalibur types you currently have installed, so run it before you bump the dependency:

sh
# preview the full plan, writes nothing
npx create-excalibur@latest upgrade --dry-run
# apply the rewrites and bump package.json
npx create-excalibur@latest upgrade
# then install the new version
npm install
sh
# preview the full plan, writes nothing
npx create-excalibur@latest upgrade --dry-run
# apply the rewrites and bump package.json
npx create-excalibur@latest upgrade
# then install the new version
npm install

Options: --to next targets the prerelease line, --migrate-only rewrites code but leaves package.json alone, --allow-dirty skips the clean-git-tree requirement (a clean tree is your undo button, so prefer committing first). With the CLI installed globally the same command is ex upgrade (alias ex up).

Every migration is one of three kinds:

  • Automated: rewritten for you, no review needed.
  • Manual: a // ex-upgrade(<id>): … breadcrumb comment is inserted at each site with a link and a recipe, because the right fix depends on what your code meant.
  • Notification: no code change, a heads up about a behavior change printed in the plan.

After running it, search your project for ex-upgrade( and work through the breadcrumbs, then build and type-check. If a migration is missing for something in your project, open an issue on create-excalibur.

Naming (automated)​

Synchronous boolean state is always an isX/hasX getter, side-effecting calls are verb methods, and async state is a promise property.

  • Zero-argument isX()/hasX() methods became getters, drop the parentheses: entity.isKilled, engine.isRunning, engine.isDisposed, clock.isRunning, scene.isCurrentScene, sound.isPlaying/isPaused/isStopped, resource.isLoaded (every Loadable), matrix.isIdentity, transform.isMirrored, boundingBox.hasZeroDimensions, camera.hasChanged, animation.isComplete, timer.isComplete, transition.isComplete, gamepad.isConnected, and more.
  • Methods that do real work stay verbs: PolygonCollider.isConvex → checkConvex(), SparseHashGrid.hasChanged → refreshAndCheckChanged(), every input toggleEnabled(enabled) → setEnabled(enabled), Engine.toggleInputEnabled → setInputEnabled.
  • Option keys: visible → isVisible on ActorOptions, TriggerOptions, GraphicsComponentOptions; solid → isSolid on tiles; graph edge directed → isDirected; Slide({ easingFunction }) → Slide({ easing }); Timer({ fcn }) → Timer({ action }).
  • Renames: Engine.loadingComplete → isLoaded, Engine.ready (boolean) → isReady, Engine.isReady() (promise) → await engine.ready, Animation.done → isComplete, Gamepads.enabled → pollingEnabled, PointerEvent.active → isActive, Transition.started → hasStarted, isOffScreen → isOffscreen, isHiDpi → isHiDPI, Future.isCompleted → isComplete, Color.equal → equals, Screen.isFullScreen/goFullScreen/exitFullScreen → isFullscreen/enterFullscreen/exitFullscreen, Direction (text) → TextDirection, EnterViewPortEvent/ExitViewPortEvent → EnterViewportEvent/ExitViewportEvent.
  • Pausing: Actor.paused (a PauseComponent) is now the read-only pauseComponent; use actor.isPaused and actor.canPause. PauseComponentInterface → PauseComponentOptions.
  • System lifecycle hooks match the rest of the engine: initialize/update/preupdate/postupdate → onInitialize/onUpdate/onPreUpdate/onPostUpdate.

Time units (automated, check sound code by hand)​

Everything is milliseconds and nothing carries an Ms suffix: Engine.currentFrameElapsedMs → currentFrameElapsed, FrameStats.elapsedMs → elapsed, GifFrame.delayMs → delay, ClockOptions.defaultUpdateMs → defaultUpdate, Animation.durationPerFrameMs → durationPerFrame.

Sound, SoundTrack, and PlayOptions position/duration, seek(), getPlaybackPosition(), getTotalPlaybackDuration(), and scheduledStartTime are now milliseconds (previously seconds). The codemod cannot know whether a literal was seconds, so audit these:

typescript
// Before: seconds
sound.play({ duration: 0.5 });
sound.seek(2);
// After: milliseconds
sound.play({ duration: 500 });
sound.seek(2000);
typescript
// Before: seconds
sound.play({ duration: 0.5 });
sound.seek(2);
// After: milliseconds
sound.play({ duration: 500 });
sound.seek(2000);

Sound (mostly automated, play/resume is manual)​

  • The audio abstraction is gone, Web Audio is the only implementation. ex.WebAudioInstance → ex.SoundTrack; ex.Audio, ex.AudioImplementation, ex.ExResponse, ex.ExResponseType are removed (pass the response type string to new ex.Resource(path, 'arraybuffer')).

  • Sound.play() always starts a new track and never changes the sound's configuration. Previously play() resumed paused tracks and play(volume) permanently set Sound.volume.

    typescript
    // Before
    sound.play(0.5); // set volume forever AND resumed if paused
    // After
    sound.play({ volume: 0.5 }); // one-off volume for this track, multiplies sound.volume
    sound.resume(); // continue a paused or seeked track
    sound.volume = 0.5; // change the configuration
    typescript
    // Before
    sound.play(0.5); // set volume forever AND resumed if paused
    // After
    sound.play({ volume: 0.5 }); // one-off volume for this track, multiplies sound.volume
    sound.resume(); // continue a paused or seeked track
    sound.volume = 0.5; // change the configuration
  • SoundManager is a real mixer. A sound routes through a single channel (SoundConfig.channel, not channels), sounds is an array of named configs, addChannel/removeChannel/defaultVolume are removed, SoundManager.volume is the master volume, and play() resolves to a boolean (false when dropped by a concurrency cap).

    typescript
    // Before
    new ex.SoundManager({
    channels: ['fx', 'music'],
    sounds: { jump: { sound: jump, volume: 0.4, channels: ['fx'] } }
    });
    // After
    new ex.SoundManager({
    channels: ['fx', 'music'],
    sounds: [{ name: 'jump', sound: jump, volume: 0.4, channel: 'fx' }]
    });
    typescript
    // Before
    new ex.SoundManager({
    channels: ['fx', 'music'],
    sounds: { jump: { sound: jump, volume: 0.4, channels: ['fx'] } }
    });
    // After
    new ex.SoundManager({
    channels: ['fx', 'music'],
    sounds: [{ name: 'jump', sound: jump, volume: 0.4, channel: 'fx' }]
    });
  • Muting is gain-based. mute() no longer pauses and unmute() no longer starts playback, a muted sound keeps advancing and is mid-clip when unmuted. mute()/unmute()/toggle() with no argument act on the master output.

Screen space (manual, only clipping display modes)​

Screen space is rooted at the top-left of the safe content area everywhere: CoordPlane.Screen entities, pointer events, Screen.worldToScreenCoordinates, and Screen.contentArea all agree. Previously worldToScreenCoordinates returned raw canvas coordinates and contentArea.left/top carried the canvas inset in the FitScreenAndFill, FitContainerAndFill, FitScreenAndZoom, and FitContainerAndZoom modes. Fixed, FitScreen, FillScreen, FitContainer, and FillContainer are unaffected.

typescript
// Before: canvas-rooted, for example to position an HTML overlay
const canvasPos = engine.screen.worldToScreenCoordinates(actor.pos);
// After: add the inset back if you really want canvas coordinates
const canvasPos = engine.screen.worldToScreenCoordinates(actor.pos).add(engine.screen.contentAreaOffset);
typescript
// Before: canvas-rooted, for example to position an HTML overlay
const canvasPos = engine.screen.worldToScreenCoordinates(actor.pos);
// After: add the inset back if you really want canvas coordinates
const canvasPos = engine.screen.worldToScreenCoordinates(actor.pos).add(engine.screen.contentAreaOffset);

Materials and post processors (manual)​

  • Material.update() passes a MaterialContext instead of the composite shader:

    typescript
    // Before
    material.update((shader) => shader.trySetUniformFloatVector('iMouse', pos));
    // After
    material.update((context) => (context.uniforms.iMouse = pos));
    // or skip update() entirely
    material.uniforms.iMouse = pos;
    typescript
    // Before
    material.update((shader) => shader.trySetUniformFloatVector('iMouse', pos));
    // After
    material.update((context) => (context.uniforms.iMouse = pos));
    // or skip update() entirely
    material.uniforms.iMouse = pos;
  • MaterialOptions is a union type (fragmentSource only, passes only, or both), interfaces that extends MaterialOptions must become type intersections.

  • PostProcessor.process(source, destination) is required; the single-pass getShader()/getLayout() path, ex.ScreenShader, and the v_texcoord varying are removed. Use ex.ShaderPipelinePostProcessor with passes, or ex.ShaderPass, and v_uv.

Behavior changes​

  • rotateBy({ angleRadiansOffset, duration }) with no rotationType rotates by exactly the signed offset. Offsets larger than π go the long way, and 2 * Math.PI is a full revolution (previously a no-op). Pass an explicit rotationType for the old shortest-path behavior.
  • TileMap defaults to compositeStrategy: 'separate'. The codemod writes the old 'together' default explicitly onto existing tilemaps so nothing changes until you opt in.
  • Scene deactivation flushes pending entity and component removals synchronously.
  • Realistic physics bodies sleep by default (since v0.32).
  • Font and text render more accurately with smaller textures, so text looks slightly different.
  • Engine.plugins/addPlugin, Scene.pause/resume, Actor.pauseComponent/isPaused/canPause, and the lighting engine option are new engine members. Subclass fields with those names now override engine behavior.

Removed (automated where a replacement exists)​

  • ex.Shape → ex.Colliders (Colliders.Box/Polygon/Circle/Edge/Capsule)
  • ColliderComponent.set() → use()
  • ActorArgs/ParticleEmitterArgs → ActorOptions/ParticleEmitterOptions, ex.Pool → ex.ArenaPool
  • EasingFunctions.* and the 4-argument easing form → the (t: number) => number easings; EaseTo/EaseBy and actions.easeTo/easeBy → MoveTo/MoveBy with easing
  • body.sleeping → isSleeping, body.setSleeping(x) → body.isSleeping = x, body.center → globalPos
  • entity.active → isActive, graphics.visible → isVisible
  • actor.getGlobalPos()/getGlobalRotation()/getGlobalScale() → globalPos/globalRotation/globalScale
  • BoundingBox.draw → debug, Vector.size → magnitude, engine.isFullScreen → isFullscreen
  • world.queryTags(...) → world.query({ tags: { all: [...] } }), TagsComponent
  • Gamepad.isButtonPressed → isButtonHeld
  • Actor.draggable: handle pointerdragstart/pointerdragmove/pointerdragend yourself
  • Flags.useLegacyImageRenderer() and the legacy image renderer
  • ex.EventTypes, the lowercase ex.Events.* aliases, AddedComponent/RemovedComponent
  • @obsolete decorator, Util.addItemToArray, CapturePointerConfig, Particle.DefaultConfig, the GifParser/Stream/LegacyWebAudioSource exports, rAF/cAF/AudioContext polyfills
  • The UMD bundle: Excalibur is ESM-only, require('excalibur') no longer works

Build​

  • The engine tsconfig uses module: "esnext" with moduleResolution: "bundler"; consumers importing source directly need bundler-compatible tooling.

Migrating from v0.28.x to v0.29.0​

  • ex.Entity.tags is now a javascript Set instead of an Array this will affect methods that inspected tags as an array before.

  • ex.SpriteSheet.getSprite(...) will now throw on invalid sprite coordinates, this is likely always an error and a warning is inappropriate. This also has the side benefit that you will always get a definite type out of the method.

  • Changed the Font default base align to Top this is more in line with user expectations. This does change the default rendering to the top left corner of the font instead of the bottom left.

  • ex.Physics static is marked as deprecated, configuring these setting will move to the ex.Engine({...}) constructor

    typescript
    const engine = new ex.Engine({
    ...
    physics: {
    solver: ex.SolverStrategy.Realistic,
    gravity: ex.vec(0, 20),
    arcade: {
    contactSolveBias: ex.ContactSolveBias.VerticalFirst
    },
    }
    })
    typescript
    const engine = new ex.Engine({
    ...
    physics: {
    solver: ex.SolverStrategy.Realistic,
    gravity: ex.vec(0, 20),
    arcade: {
    contactSolveBias: ex.ContactSolveBias.VerticalFirst
    },
    }
    })
  • ex.Engine.goToScene's second argument now takes GoToOptions instead of just scene activation data

    typescript
    game.goToScene('myscene', {
    /**
    * Optionally supply scene activation data passed to Scene.onActivate
    */
    sceneActivationData?: TActivationData,
    /**
    * Optionally supply destination scene "in" transition, this will override any previously defined transition
    */
    destinationIn?: Transition,
    /**
    * Optionally supply source scene "out" transition, this will override any previously defined transition
    */
    sourceOut?: Transition,
    /**
    * Optionally supply a different loader for the destination scene, this will override any previously defined loader
    */
    loader?: DefaultLoader
    });
    typescript
    game.goToScene('myscene', {
    /**
    * Optionally supply scene activation data passed to Scene.onActivate
    */
    sceneActivationData?: TActivationData,
    /**
    * Optionally supply destination scene "in" transition, this will override any previously defined transition
    */
    destinationIn?: Transition,
    /**
    * Optionally supply source scene "out" transition, this will override any previously defined transition
    */
    sourceOut?: Transition,
    /**
    * Optionally supply a different loader for the destination scene, this will override any previously defined loader
    */
    loader?: DefaultLoader
    });
  • ECS components and system are now simplified to not require a string type name, now they just use the runtime type name instead.

    • On your components remove the type argument class MyComponent extends Component<'mycomponent'> to class MyComponent extends Component
    • On your systems remove the type arguments class MySystem extends System<MyComponent> to class MySystem extends System
    • Systems no longer get magically passed the entities in System.update() you now must create an explicit query. Queries automatically get updated
      typescript
      export class MyComponent extends Component {
      data = 'foo';
      }
      class MySystem extends System {
      systemType = SystemType.Update;
      query: Query<typeof MyComponent>;
      constructor(world: World) {
      super();
      this.query = world.query([MyComponent]);
      }
      update(elapsed: number): void {
      for (let entity of this.query.entities) {
      const myComponent = entity.get(MyComponent);
      console.log(myComponent.data);
      }
      }
      }
      typescript
      export class MyComponent extends Component {
      data = 'foo';
      }
      class MySystem extends System {
      systemType = SystemType.Update;
      query: Query<typeof MyComponent>;
      constructor(world: World) {
      super();
      this.query = world.query([MyComponent]);
      }
      update(elapsed: number): void {
      for (let entity of this.query.entities) {
      const myComponent = entity.get(MyComponent);
      console.log(myComponent.data);
      }
      }
      }
  • Graphics components no longer support layering built in, they now ONLY hold 1 graphic at a time. With this actor.graphics.show() is now deprecated graphics.use() replaces the functionality. This change simplifies usage of graphics for folks building games and removes complexity caused by supporting layering and multiple graphics in Excalibur.

    • To use layering use
      1. A GraphicsGroup, you can specify a list of graphics in painter order (first is draw first, then so on)
        typescript
        const actor = new ex.Actor({...});
        const graphicsGroup = new ex.GraphicsGroup({
        members: [
        new ex.Rectangle({width: 100, height: 100, color: ex.Color.Red}),
        { offset: ex.vec(100, 100), graphic: new ex.Circle({radius: 100, color: ex.Color.Blue})}
        ]
        });
        actor.graphics.use(graphicsGroup);
        typescript
        const actor = new ex.Actor({...});
        const graphicsGroup = new ex.GraphicsGroup({
        members: [
        new ex.Rectangle({width: 100, height: 100, color: ex.Color.Red}),
        { offset: ex.vec(100, 100), graphic: new ex.Circle({radius: 100, color: ex.Color.Blue})}
        ]
        });
        actor.graphics.use(graphicsGroup);
      2. Child actors if you need more control over graphic movement, transform, or behavior
        typescript
        const actor = new ex.Actor({...});
        const child = new ex.Actor({
        pos: ex.vec(100, 100),
        rotation: Math.PI / 4,
        scale: ex.vec(3, 3)
        });
        actor.addChild(child);
        actor.graphics.use(new ex.Rectangle({width: 100, height: 100, color: ex.Color.Red}));
        child.graphics.use(new ex.Rectangle({width: 10, height: 10, color: ex.Color.Blue}));
        typescript
        const actor = new ex.Actor({...});
        const child = new ex.Actor({
        pos: ex.vec(100, 100),
        rotation: Math.PI / 4,
        scale: ex.vec(3, 3)
        });
        actor.addChild(child);
        actor.graphics.use(new ex.Rectangle({width: 100, height: 100, color: ex.Color.Red}));
        child.graphics.use(new ex.Rectangle({width: 10, height: 10, color: ex.Color.Blue}));

Migrating from v0.26.0 to v0.27.0​

  • ex.Engine.snapToPixel now defaults to false, it was unexpected to have pixel snapping on by default it has now been switched. You may need to explicitly switch it depending on your desired effect.

  • The ex.Physics.useRealisticPhysics() physics solver has been updated to fix a bug in bounciness to be more physically accurate, this does change how physics behaves. Setting ex.Body.bounciness = 0 will simulate the old behavior.

  • ex.TransformComponent.getGlobalMatrix() has been removed, use this instead

    typescript
    const actor = new ex.Actor({...});
    const transform = actor.get(TransformComponent);
    const matrix = transform.get().matrix;
    typescript
    const actor = new ex.Actor({...});
    const transform = actor.get(TransformComponent);
    const matrix = transform.get().matrix;
  • ex.TransformComponent.posChanged$ has been removed, it incurs a steep performance cost.

  • ex.EventDispatcher meta events 'subscribe' and 'unsubscribe' were unused and undocumented and have been removed.

  • ex.TileMap tiles are now drawn from the lower left by default to match with ex.IsometricMap and Tiled, but can be configured with renderFromTopOfGraphic to restore the previous behavior.

    typescript
    const tilemap = new ex.TileMap({
    ...
    renderFromTopOfGraphic: true
    })
    typescript
    const tilemap = new ex.TileMap({
    ...
    renderFromTopOfGraphic: true
    })
  • Scene onActivate and onDeactivate methods have been changed to receive a single context parameter, an object containing the previousScene, nextScene, and optional data passed in from goToScene(). Update any onActivate or onDeactivate handlers

    typescript
    onActivate(ctx: SceneActivationContext<TActivationData>) {
    ...
    }
    typescript
    onActivate(ctx: SceneActivationContext<TActivationData>) {
    ...
    }

Migrating from v0.25.3 to v0.26.0​

The old drawing API had been removed from excalibur, this should not affect you unless you were using the ex.Flags.useLegacyDrawing(). If so you must switch to the new graphics api.

ex.TileMap has several breaking changes around naming, but brings it consistent with Tiled terminology and the new ex.IsometricMap. Additionally the new names are easier to follow.

  • Constructor has been changed to the following
    typescript
    new ex.TileMap({
    pos: ex.vec(100, 100),
    tileWidth: 64,
    tileHeight: 48,
    rows: 20,
    columns: 20,
    })
    typescript
    new ex.TileMap({
    pos: ex.vec(100, 100),
    tileWidth: 64,
    tileHeight: 48,
    rows: 20,
    columns: 20,
    })
  • ex.Cell has been renamed to ex.Tile
    • ex.Tile now uses addGraphic(...), removeGraphic(...), clearGraphics() and getGraphics() instead of having an accessible ex.Tile.graphics array.
  • ex.TileMap.data has been renamed to ex.TileMap.tiles
  • ex.TileMap.getCell(..) has been renamed to ex.TileMap.getTile(...)
  • ex.TileMap.getCellByIndex(...) has been renamed to ex.TileMap.getTileByIndex(...)
  • ex.TileMap.getCellByPoint(...) has been renamed to ex.TileMap.getTileByPoint(...)

The following types have been removed:

  • ex.SortedList old sorted list is removed, use the built in browser Array.sort
  • ex.Collection old collection type is removed, use the built in browser Array
  • ex.Util import site, exported code promoted ex.*
  • ex.DisplayMode.Position is removed, use CSS to position the canvas
  • ex.Trait interface, traits are not longer supported
  • ex.Promises old promise implementation is removed in favor of browser promises

Notable method & property removals

  • ex.Actor
    • .getZIndex() and .setZIndex() removed use .z
  • ex.Scene
    • .screenElements removed in favor of .entities
    • .addScreenElement(...) removed use .add(...)
    • .addTileMap(...) removed use .add(...)
    • .removeTileMap(...) removed use .remove(...)
  • ex.Timer
    • .unpause() removed use .resume()
  • ex.Camera
    • .rx removed use .angularVelocity
  • ex.BodyComponent
    • .sx removed use .scaleFactor
    • .rx removed use .angularVelocity
  • ex.ActionsComponent
    • .asPromise() removed use .toPromise()
  • ex.ActionContext
    • .asPromise() removed use .toPromise()
  • ex.Color
    • Misspellings corrected

Migrating from v0.25.1 to v0.25.2​

  • EventEmitter no longer tampers with this on the event callbacks which may be a breaking change for some people. Change callbacks's to be an arrow function to capture this
typescript
class MyActor extends ex.Actor {
constructor() {
super()
// Change from this
this.on('precollision', this.onPreCollision)
// To this
this.on('precollision', (evt) => this.onPreCollision(evt))
}
}
typescript
class MyActor extends ex.Actor {
constructor() {
super()
// Change from this
this.on('precollision', this.onPreCollision)
// To this
this.on('precollision', (evt) => this.onPreCollision(evt))
}
}

Migrating from v0.24.5 to v0.25.0​

Graphics​

  • Replace ex.Texture with ex.ImageSource

  • Replace ex.Texture.asSprite() with ex.ImageSource.toSprite()

typescript
const image = new ex.ImageSource('./img/myimage.png')
// keep in mind this wont work until the image source is loaded
const sprite = image.toSprite()
typescript
const image = new ex.ImageSource('./img/myimage.png')
// keep in mind this wont work until the image source is loaded
const sprite = image.toSprite()
  • Update ex.Sprite constructor arguments
typescript
const image = new ex.ImageSource('./path/to/image.png')
const sprite = new ex.Sprite({
image: 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,
},
})
typescript
const image = new ex.ImageSource('./path/to/image.png')
const sprite = new ex.Sprite({
image: 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,
},
})
  • Update new ex.SpriteSheet() constructor with ex.SpriteSheet.fromImageSource()
typescript
const runImage = new ex.ImageSource(runImageSrc)
const runSheet = ex.SpriteSheet.fromImageSource({
image: runImage,
grid: {
rows: 1,
columns: 21,
spriteWidth: 96,
spriteHeight: 96,
},
})
typescript
const runImage = new ex.ImageSource(runImageSrc)
const runSheet = ex.SpriteSheet.fromImageSource({
image: runImage,
grid: {
rows: 1,
columns: 21,
spriteWidth: 96,
spriteHeight: 96,
},
})
  • Replace Legacy Drawing Usage

    • ex.Actor.addDrawing() with ex.Actor.graphics.add()
    • ex.Actor.setDrawing() with ex.Actor.graphics.use() or ex.Actor.graphics.show()
  • Replace Legacy ex.Actor.onPostDraw and ex.Actor.onPreDraw with actor.graphics or ex.Canvas

typescript
const canvas = new ex.Canvas({
cache: true,
draw: (ctx: CanvasRenderingContext2D) => {
// custom drawing with CanvasRenderingContext2D
},
})
actor.use(canvas)
actor.graphics.onPreDraw = (exctx: ExcaliburGraphicsContext) => {
// custom drawing with ExcaliburGraphicsContext
}
actor.graphics.onPostDraw = (exctx: ExcaliburGraphicsContext) => {
// custom drawing with ExcaliburGraphicsContext
}
typescript
const canvas = new ex.Canvas({
cache: true,
draw: (ctx: CanvasRenderingContext2D) => {
// custom drawing with CanvasRenderingContext2D
},
})
actor.use(canvas)
actor.graphics.onPreDraw = (exctx: ExcaliburGraphicsContext) => {
// custom drawing with ExcaliburGraphicsContext
}
actor.graphics.onPostDraw = (exctx: ExcaliburGraphicsContext) => {
// custom drawing with ExcaliburGraphicsContext
}

Actors​

  • Actor only supports the option bag constructor new ex.Actor({...})
  • Label only supports the option bag constructor new ex.Label({...})
  • Replace ex.Actor.rx with ex.Actor.angularVelocity

Camera​

  • ex.Camera.z has been renamed to property ex.Camera.zoom which is the zoom factor
  • ex.Camera.zoom(...) has been renamed to function ex.Camera.zoomOverTime()

Engine​

  • DisplayMode's have changed (#1733) & (#1928):

    • DisplayMode.FitContainer fits the screen to the available width/height in the canvas parent element, while maintaining aspect ratio and resolution
    • DisplayMode.FillContainer update the resolution and viewport dynamically to fill the available space in the canvas parent element, DOES NOT preserve aspectRatio
    • DisplayMode.FitScreen fits the screen to the available browser window space, while maintaining aspect ratio and resolution
    • DisplayMode.FillScreen now does what DisplayMode.FullScreen used to do, the resolution and viewport dynamically adjust to fill the available space in the window, DOES NOT preserve aspectRatio (#1733)
    • DisplayMode.FullScreen is now removed, use Screen.goFullScreen().

Physics​

  • Rename ex.Edge to ex.EdgeCollider and ex.ConvexPolygon to ex.PolygonCollider

Timer​

  • Timers must be added to a scene and Timer.start() called for them to start
  • Timer.unpause() has be deprecated in favor of Timer.resume() (#1864)

Actions​

  • Rewrite ex.Actor.actions.repeat() and ex.Actor.actions.repeatForever() to use the ctx to define the set of repeating actions
typescript
const actor = new ex.Actor()
actor.actions
// Move up in a zig-zag by repeating 5 times
.repeat((ctx) => {
ctx.moveBy(10, 0, 10)
ctx.moveBy(0, 10, 10)
}, 5)
.callMethod(() => {
console.log('Done repeating!')
})
typescript
const actor = new ex.Actor()
actor.actions
// Move up in a zig-zag by repeating 5 times
.repeat((ctx) => {
ctx.moveBy(10, 0, 10)
ctx.moveBy(0, 10, 10)
}, 5)
.callMethod(() => {
console.log('Done repeating!')
})

TileMap​