diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a811de32..b6ce8a333 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Fixed +- Fixed issue where the `Camera` wasn't interpolated during fixed update, which is very noticeable when using camera locked strategies - Fixed issue where `IsometricMap` would debug draw collision geometry on non-solid tiles - Fixed issue where `CompositeCollider` offset was undefined if not set - Fixed Actor so it receives `predraw`/`postdraw` events per the advertised strongly typed events diff --git a/src/engine/Camera.ts b/src/engine/Camera.ts index c94d82d84..d8a9a1b1c 100644 --- a/src/engine/Camera.ts +++ b/src/engine/Camera.ts @@ -100,7 +100,7 @@ export enum Axis { */ export class LockCameraToActorStrategy implements CameraStrategy { constructor(public target: Actor) {} - public action = (target: Actor, _cam: Camera, _eng: Engine, _delta: number) => { + public action = (target: Actor, camera: Camera, engine: Engine, delta: number) => { const center = target.center; return center; }; @@ -314,6 +314,14 @@ export class Camera implements CanUpdate, CanInitialize { this._pos = watchAny(vec, () => (this._posChanged = true)); this._posChanged = true; } + /** + * Interpolated camera position if more draws are running than updates + * + * Enabled when `Engine.fixedUpdateFps` is enabled + */ + public interpolatedPos: Vector = this.pos.clone(); + + private _oldPos = this.pos.clone(); /** * Get or set the camera's velocity @@ -622,7 +630,7 @@ export class Camera implements CanUpdate, CanInitialize { // Ensure camera tx is correct // Run update twice to ensure properties are init'd - this.updateTransform(); + this.updateTransform(this.pos); // Run strategies for first frame this.runStrategies(engine, engine.clock.elapsed()); @@ -632,7 +640,8 @@ export class Camera implements CanUpdate, CanInitialize { // It's important to update the camera after strategies // This prevents jitter - this.updateTransform(); + this.updateTransform(this.pos); + this.pos.clone(this._oldPos); this.onInitialize(engine); this.events.emit('initialize', new InitializeEvent(engine, this)); @@ -693,6 +702,7 @@ export class Camera implements CanUpdate, CanInitialize { public update(engine: Engine, delta: number) { this._initialize(engine); this._preupdate(engine, delta); + this.pos.clone(this._oldPos); // Update placements based on linear algebra this.pos = this.pos.add(this.vel.scale(delta / 1000)); @@ -760,7 +770,7 @@ export class Camera implements CanUpdate, CanInitialize { // It's important to update the camera after strategies // This prevents jitter - this.updateTransform(); + this.updateTransform(this.pos); this._postupdate(engine, delta); } @@ -770,14 +780,28 @@ export class Camera implements CanUpdate, CanInitialize { * @param ctx Canvas context to apply transformations */ public draw(ctx: ExcaliburGraphicsContext): void { + // default to the current position + this.pos.clone(this.interpolatedPos); + + // interpolation if fixed update is on + // must happen on the draw, because more draws are potentially happening than updates + if (this._engine.fixedUpdateFps) { + const blend = this._engine.currentFrameLagMs / (1000 / this._engine.fixedUpdateFps); + const interpolatedPos = this.pos.scale(blend).add( + this._oldPos.scale(1.0 - blend) + ); + interpolatedPos.clone(this.interpolatedPos); + this.updateTransform(interpolatedPos); + } + ctx.multiply(this.transform); } - public updateTransform() { + public updateTransform(pos: Vector) { // center the camera const newCanvasWidth = this._screen.resolution.width / this.zoom; const newCanvasHeight = this._screen.resolution.height / this.zoom; - const cameraPos = vec(-this.x + newCanvasWidth / 2 + this._xShake, -this.y + newCanvasHeight / 2 + this._yShake); + const cameraPos = vec(-pos.x + newCanvasWidth / 2 + this._xShake, -pos.y + newCanvasHeight / 2 + this._yShake); // Calculate camera transform this.transform.reset(); diff --git a/src/engine/Collision/BodyComponent.ts b/src/engine/Collision/BodyComponent.ts index 03f470a91..6311bd3d3 100644 --- a/src/engine/Collision/BodyComponent.ts +++ b/src/engine/Collision/BodyComponent.ts @@ -35,7 +35,7 @@ export class BodyComponent extends Component<'ex.body'> implements Clonable = createId('body', BodyComponent._ID++); public events = new EventEmitter(); - private _oldTransform = new Transform(); + public oldTransform = new Transform(); /** * Indicates whether the old transform has been captured at least once for interpolation @@ -250,7 +250,7 @@ export class BodyComponent extends Component<'ex.body'> implements Clonable implements Clonable implements Clonable implements Clonable { public readonly types = ['ex.transform', 'ex.graphics'] as const; @@ -211,6 +212,7 @@ export class GraphicsSystem extends System { Camera._initialize(engine); Camera.rotation = Math.PI / 2; - Camera.updateTransform(); + Camera.updateTransform(Camera.pos); expect(Camera.transform.getRotation()).toBe(Math.PI / 2); });