From df71c3b2f9c1ab3dfe5de5d17fbb8e4daf45be51 Mon Sep 17 00:00:00 2001 From: Erik Onarheim Date: Fri, 26 Jan 2024 15:36:28 -0600 Subject: [PATCH] fix: Issue with pointer events on screen elements (#2902) This PR fixes an issue discovered on the discord around pointer events and ScreenElements - Fixed issue where pointer events did not work properly when using [[ScreenElement]]s - Fixed issue where debug draw was not accurate when using *AndFill suffixed [[DisplayMode]]s --- CHANGELOG.md | 3 +- sandbox/tests/pointer/pointer.ts | 28 +++++++++++++++++- src/engine/Camera.ts | 2 +- src/engine/Debug/DebugSystem.ts | 6 ++++ src/engine/Graphics/GraphicsComponent.ts | 27 ++++++++++++++++-- src/engine/Screen.ts | 11 +++++++- src/spec/ActorSpec.ts | 6 ++-- src/spec/PointerInputSpec.ts | 36 ++++++++++++++++++++++++ 8 files changed, 110 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f7397e3e..316898c3f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,7 +20,8 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Fixed -- +- Fixed issue where pointer events did not work properly when using [[ScreenElement]]s +- Fixed issue where debug draw was not accurate when using *AndFill suffixed [[DisplayMode]]s ### Updates diff --git a/sandbox/tests/pointer/pointer.ts b/sandbox/tests/pointer/pointer.ts index 411a264be..34f45b153 100644 --- a/sandbox/tests/pointer/pointer.ts +++ b/sandbox/tests/pointer/pointer.ts @@ -8,6 +8,24 @@ class Player extends ex.Actor { class Game2 extends ex.Engine { initialize() { + const screenElement = new ex.ScreenElement({ + x: 100, + y: 100, + height: 50, + width: 100, + color: ex.Color.Red + }); + screenElement.pointer.useColliderShape = true; + screenElement.pointer.useGraphicsBounds = true; + screenElement.on('pointerdown', () => { + console.log('screen element down') + }); + screenElement.on('pointerup', () => { + console.log('screen element up') + }); + this.add(screenElement); + + const player1 = new Player(ex.Color.Green); this.add(player1); player1.z = 10; @@ -44,6 +62,14 @@ class Game2 extends ex.Engine { this.start(loader); } } -var game2 = new Game2({width: 600, height: 400}); +var game2 = new Game2({ + width: 600, + height: 400, + antialiasing: false, + displayMode: ex.DisplayMode.FitScreenAndFill +}); +game2.debug.collider.showBounds = true; +game2.debug.graphics.showBounds = true; +game2.toggleDebug(); game2.initialize(); \ No newline at end of file diff --git a/src/engine/Camera.ts b/src/engine/Camera.ts index 14f50757f..2524f8c6a 100644 --- a/src/engine/Camera.ts +++ b/src/engine/Camera.ts @@ -691,7 +691,7 @@ export class Camera implements CanUpdate, CanInitialize { } public updateViewport() { - // recalc viewport + // recalculate viewport this._viewport = new BoundingBox( this.x - this._halfWidth, this.y - this._halfHeight, diff --git a/src/engine/Debug/DebugSystem.ts b/src/engine/Debug/DebugSystem.ts index fcc139321..7a11331de 100644 --- a/src/engine/Debug/DebugSystem.ts +++ b/src/engine/Debug/DebugSystem.ts @@ -91,6 +91,9 @@ export class DebugSystem extends System { this._pushCameraTransform(tx); this._graphicsContext.save(); + if (tx.coordPlane === CoordPlane.Screen) { + this._graphicsContext.translate(this._engine.screen.contentArea.left, this._engine.screen.contentArea.top); + } this._graphicsContext.z = txSettings.debugZIndex; this._applyTransform(entity); @@ -185,6 +188,9 @@ export class DebugSystem extends System { // World space this._graphicsContext.save(); + if (tx.coordPlane === CoordPlane.Screen) { + this._graphicsContext.translate(this._engine.screen.contentArea.left, this._engine.screen.contentArea.top); + } this._graphicsContext.z = txSettings.debugZIndex; motion = entity.get(MotionComponent); if (motion) { diff --git a/src/engine/Graphics/GraphicsComponent.ts b/src/engine/Graphics/GraphicsComponent.ts index 137422bbd..51c6822f8 100644 --- a/src/engine/Graphics/GraphicsComponent.ts +++ b/src/engine/Graphics/GraphicsComponent.ts @@ -6,6 +6,8 @@ import { Logger } from '../Util/Log'; import { BoundingBox } from '../Collision/Index'; import { Component } from '../EntityComponentSystem/Component'; import { Material } from './Context/material'; +import { Logger } from '../Util/Log'; +import { WatchVector } from '../Math/watch-vector'; /** * Type guard for checking if a Graphic HasTick (used for graphics that change over time like animations) @@ -307,15 +309,36 @@ export class GraphicsComponent extends Component<'ex.graphics'> { */ public opacity: number = 1; + + private _offset: Vector = Vector.Zero; + /** * Offset to apply to graphics by default */ - public offset: Vector = Vector.Zero; + public get offset(): Vector { + return new WatchVector(this._offset, () => { + this.recalculateBounds(); + }); + } + public set offset(value: Vector) { + this._offset = value; + this.recalculateBounds(); + } + + private _anchor: Vector = Vector.Half; /** * Anchor to apply to graphics by default */ - public anchor: Vector = Vector.Half; + public get anchor(): Vector { + return new WatchVector(this._anchor, () => { + this.recalculateBounds(); + }); + } + public set anchor(value: Vector) { + this._anchor = value; + this.recalculateBounds(); + } /** * Flip all graphics horizontally along the y-axis diff --git a/src/engine/Screen.ts b/src/engine/Screen.ts index 371347da3..5386f2361 100644 --- a/src/engine/Screen.ts +++ b/src/engine/Screen.ts @@ -544,7 +544,8 @@ export class Screen { * Excalibur screen space. * * Excalibur screen space starts at the top left (0, 0) corner of the viewport, and extends to the - * bottom right corner (resolutionX, resolutionY) + * bottom right corner (resolutionX, resolutionY). When using *AndFill suffixed display modes screen space + * (0, 0) is the top left of the safe content area bounding box not the viewport. * @param point */ public pageToScreenCoordinates(point: Vector): Vector { @@ -575,6 +576,10 @@ export class Screen { newX = (newX / this.viewport.width) * this.resolution.width; newY = (newY / this.viewport.height) * this.resolution.height; + // offset by content area + newX = newX - this.contentArea.left; + newY = newY - this.contentArea.top; + return new Vector(newX, newY); } @@ -590,6 +595,10 @@ export class Screen { let newX = point.x; let newY = point.y; + // offset by content area + newX = newX + this.contentArea.left; + newY = newY + this.contentArea.top; + newX = (newX / this.resolution.width) * this.viewport.width; newY = (newY / this.resolution.height) * this.viewport.height; diff --git a/src/spec/ActorSpec.ts b/src/spec/ActorSpec.ts index a31dee9f8..7199b1328 100644 --- a/src/spec/ActorSpec.ts +++ b/src/spec/ActorSpec.ts @@ -146,11 +146,11 @@ describe('A game actor', () => { it('should have constructor anchor set on graphics component', () => { const actor = new ex.Actor({anchor: ex.vec(.7, .7)}); - expect(actor.anchor).toEqual(ex.vec(.7, .7)); - expect(actor.graphics.anchor).toEqual(ex.vec(.7, .7)); + expect(actor.anchor).toBeVector(ex.vec(.7, .7)); + expect(actor.graphics.anchor).toBeVector(ex.vec(.7, .7)); actor.anchor = ex.vec(0, 0); - expect(actor.graphics.anchor).toEqual(ex.vec(0, 0)); + expect(actor.graphics.anchor).toBeVector(ex.vec(0, 0)); }); it('will inherit the scene from the parent entity after being added', () => { diff --git a/src/spec/PointerInputSpec.ts b/src/spec/PointerInputSpec.ts index ab9108572..6027e89e0 100644 --- a/src/spec/PointerInputSpec.ts +++ b/src/spec/PointerInputSpec.ts @@ -168,6 +168,42 @@ describe('A pointer', () => { expect(actualOrder).toEqual(['actor4', 'actor3', 'actor2', 'actor1']); }); + it('should dispatch point events on screen elements', () => { + const pointerDownSpy = jasmine.createSpy('pointerdown'); + const screenElement = new ex.ScreenElement({ + x: 50, + y: 50, + width: 100, + height: 100, + color: ex.Color.Red + }); + screenElement.on('pointerdown', pointerDownSpy); + + engine.add(screenElement); + + executeMouseEvent('pointerdown', document, null, 50, 50); + executeMouseEvent('pointerdown', document, null, 50, 150); + executeMouseEvent('pointerdown', document, null, 150, 50); + executeMouseEvent('pointerdown', document, null, 150, 150); + executeMouseEvent('pointerdown', document, null, 100, 100); + + engine.currentScene.update(engine, 0); + + expect(pointerDownSpy).toHaveBeenCalledTimes(5); + + pointerDownSpy.calls.reset(); + + executeMouseEvent('pointerdown', document, null, 49, 49); + executeMouseEvent('pointerdown', document, null, 49, 151); + executeMouseEvent('pointerdown', document, null, 151, 49); + executeMouseEvent('pointerdown', document, null, 151, 151); + + engine.currentScene.update(engine, 0); + + expect(pointerDownSpy).toHaveBeenCalledTimes(0); + + }); + it('can have pointer event canceled', () => { const actualOrder = [];