From 3c389de21e7d6d6ba9686ca6df2ee9aa2ca4a59f Mon Sep 17 00:00:00 2001 From: Mathieu Hermann Date: Wed, 1 Jan 2025 19:26:42 +0100 Subject: [PATCH 1/9] refactor(touch-manager): rework completely the touch manager to avoid missing offclick --- src/app/game/actors/skier.ts | 121 ++++++++++++++++------------ src/app/game/scenes/race.ts | 2 +- src/app/game/utils/touch-manager.ts | 41 ++++++++-- 3 files changed, 106 insertions(+), 58 deletions(-) diff --git a/src/app/game/actors/skier.ts b/src/app/game/actors/skier.ts index ad54cee..3bf82d9 100644 --- a/src/app/game/actors/skier.ts +++ b/src/app/game/actors/skier.ts @@ -8,6 +8,18 @@ import type { Game } from '../game'; import { SkierActions } from '../models/skier-actions.enum'; import { SkierGraphics } from '../utils/skier-graphics'; +class SkierIntentions { + public leftCarvingIntention: number; + public rightCarvingIntention: number; + public hasBrakingIntention: boolean; + + constructor(leftCarvingIntention?: number, rightCarvingIntention?: number, hasBrakingIntention?: boolean) { + this.leftCarvingIntention = leftCarvingIntention ?? 0; + this.rightCarvingIntention = rightCarvingIntention ?? 0; + this.hasBrakingIntention = hasBrakingIntention ?? false; + } +} + const LEFT_ANGLE_OFFSET = (3 / 4) * Math.PI; const RIGHT_ANGLE_OFFSET = (1 / 4) * Math.PI; const RADIAN_PI = 2 * Math.PI; @@ -20,6 +32,8 @@ export class Skier extends Actor { public racing = false; public finish = false; + private skierIntentions = new SkierIntentions(); + private leftParticlesEmitter!: GpuParticleEmitter; private rightParticlesEmitter!: GpuParticleEmitter; @@ -42,12 +56,13 @@ export class Skier extends Actor { } override update(engine: Engine): void { - const skierAction = this.getSkierCurrentAction(engine); + this.updateSkierIntentions(engine); + const skierAction = this.getSkierCurrentAction(); this.updateGraphics(skierAction); if (this.racing || this.finish) { - this.updateRotation(engine); - this.updateSpeed(skierAction, engine); - this.updateVelocity(engine); + this.updateRotation(this.skierIntentions); + this.updateSpeed(skierAction, this.skierIntentions); + this.updateVelocity(this.skierIntentions); } else { if ( engine.input.keyboard.wasPressed(Config.KEYBOARD_START_KEY) || @@ -57,8 +72,8 @@ export class Skier extends Actor { (this.scene as Race).startRace(); } } - this.emitParticles(engine, skierAction); - this.emitSounds(engine as Game, this.finish); + this.emitParticles(engine, skierAction, this.skierIntentions); + this.emitSounds(engine as Game, this.finish, this.skierIntentions); } public finishRace(): void { @@ -71,28 +86,34 @@ export class Skier extends Actor { (this.scene!.engine as Game).soundPlayer.playSound(Resources.TurningSound, 0, true, false); } - public getSkierCurrentAction(engine: Engine): SkierActions { + public getSkierCurrentAction(): SkierActions { if (this.racing) { - if (this.slidingIntention(engine)) { - return this.leftSlidingIntention(engine) ? SkierActions.SLIDE_LEFT : SkierActions.SLIDE_RIGHT; + if (Skier.slidingIntention(this.skierIntentions)) { + return this.skierIntentions.leftCarvingIntention ? SkierActions.SLIDE_LEFT : SkierActions.SLIDE_RIGHT; } - if (this.hasBreakingIntention(engine)) { + if (this.skierIntentions.hasBrakingIntention) { return SkierActions.BRAKE; } - if (this.carvingIntention(engine)) { - return this.leftCarvingIntention(engine) ? SkierActions.CARVE_LEFT : SkierActions.CARVE_RIGHT; + if (Skier.carvingIntention(this.skierIntentions)) { + return this.skierIntentions.leftCarvingIntention ? SkierActions.CARVE_LEFT : SkierActions.CARVE_RIGHT; } return SkierActions.NOTHING; } return SkierActions.BRAKE; } - private updateRotation(engine: Engine): void { + private updateSkierIntentions(engine: Engine): void { + this.skierIntentions.hasBrakingIntention = this.hasBreakingIntention(engine); + this.skierIntentions.leftCarvingIntention = this.leftCarvingIntention(engine); + this.skierIntentions.rightCarvingIntention = this.rightCarvingIntention(engine); + } + + private updateRotation(skierIntentions: SkierIntentions): void { let rotationRate = 0; let futurRotation = 0; - if (this.hasTurningIntention(engine)) { - if (this.slidingIntention(engine)) { + if (Skier.hasTurningIntention(skierIntentions)) { + if (Skier.slidingIntention(skierIntentions)) { const rotationSpeedMultiplier = this.speed < this.skierConfig.slidingOptimalSpeed ? Math.max(this.speed, 1) / this.skierConfig.slidingOptimalSpeed @@ -100,8 +121,8 @@ export class Skier extends Actor { rotationRate = (this.skierConfig.slidingRotationRate / (180 / Math.PI)) * rotationSpeedMultiplier * - this.slidingIntention(engine); - } else if (this.carvingIntention(engine)) { + Skier.slidingIntention(skierIntentions); + } else if (Skier.carvingIntention(skierIntentions)) { const rotationSpeedMultiplier = this.speed < this.skierConfig.carvingOptimalSpeed ? Math.max(this.speed, 1) / this.skierConfig.carvingOptimalSpeed @@ -109,9 +130,9 @@ export class Skier extends Actor { rotationRate = (this.skierConfig.carvingRotationRate / (180 / Math.PI)) * rotationSpeedMultiplier * - this.carvingIntention(engine); + Skier.carvingIntention(skierIntentions); } - futurRotation = this.hasLeftTurningIntention(engine) + futurRotation = Skier.hasLeftTurningIntention(skierIntentions) ? this.rotation - rotationRate : this.rotation + rotationRate; } else { @@ -134,7 +155,7 @@ export class Skier extends Actor { } } - private updateSpeed(skierAction: SkierActions, engine: Engine): void { + private updateSpeed(skierAction: SkierActions, skierIntentions: SkierIntentions): void { let angleOfSkier = this.rotation * (180 / Math.PI); if (angleOfSkier >= 270) { angleOfSkier = 360 - angleOfSkier; @@ -144,9 +165,9 @@ export class Skier extends Actor { acceleration -= (acceleration * angleOfSkier) / 90; acceleration -= this.skierConfig.windFrictionRate * this.speed; if (skierAction === SkierActions.SLIDE_LEFT || skierAction === SkierActions.SLIDE_RIGHT) { - acceleration -= Config.SLIDING_BRAKING_RATE * this.slidingIntention(engine); + acceleration -= Config.SLIDING_BRAKING_RATE * Skier.slidingIntention(skierIntentions); } else if (skierAction === SkierActions.CARVE_LEFT || skierAction === SkierActions.CARVE_RIGHT) { - acceleration -= Config.CARVING_BRAKING_RATE * this.carvingIntention(engine); + acceleration -= Config.CARVING_BRAKING_RATE * Skier.carvingIntention(skierIntentions); } else if (skierAction === SkierActions.BRAKE) { acceleration -= Config.BRAKING_RATE; } @@ -160,10 +181,10 @@ export class Skier extends Actor { } } - private updateVelocity(engine: Engine): void { + private updateVelocity(skierIntentions: SkierIntentions): void { let xVelocity = 0; let yVelocity = 0; - const adherenceRate = this.getAdherenceRate(engine); + const adherenceRate = this.getAdherenceRate(skierIntentions); const normalizedRotation = this.rotation * (180 / Math.PI); if (normalizedRotation === 0) { xVelocity = 0; @@ -180,10 +201,10 @@ export class Skier extends Actor { this.vel = vec(xVelocity * Config.VELOCITY_MULTIPLIER_RATE, -yVelocity * Config.VELOCITY_MULTIPLIER_RATE); } - private getAdherenceRate(engine: Engine): number { + private getAdherenceRate(skierIntentions: SkierIntentions): number { let adherenceRate = 1; - if (this.hasTurningIntention(engine)) { - adherenceRate = this.slidingIntention(engine) + if (Skier.hasTurningIntention(skierIntentions)) { + adherenceRate = Skier.slidingIntention(skierIntentions) ? Config.SLIDING_ADHERENCE_RATE : Config.CARVING_ADHERENCE_RATE; } @@ -196,23 +217,23 @@ export class Skier extends Actor { this.graphics.flipHorizontal = !!graphic.flipHorizontal; } - private emitSounds(engine: Game, forceBreaking: boolean): void { - if ((this.hasBreakingIntention(engine) || forceBreaking) && this.speed) { + private emitSounds(engine: Game, forceBreaking: boolean, skierIntentions: SkierIntentions): void { + if ((skierIntentions.hasBrakingIntention || forceBreaking) && this.speed) { Resources.TurningSound.volume = Math.min( Config.BRAKING_SOUND_VOLUME, (this.speed / Config.MAX_SPEED) * Config.BRAKING_SOUND_VOLUME ); - } else if (this.carvingIntention(engine) && this.speed) { + } else if (Skier.carvingIntention(skierIntentions) && this.speed) { Resources.TurningSound.volume = Math.min( Config.CARVING_SOUND_VOLUME, - (this.speed / Config.MAX_SPEED) * Config.CARVING_SOUND_VOLUME * this.carvingIntention(engine) + (this.speed / Config.MAX_SPEED) * Config.CARVING_SOUND_VOLUME * Skier.carvingIntention(skierIntentions) ); } else { Resources.TurningSound.volume = 0; } } - private emitParticles(engine: Engine, skierAction: SkierActions): void { + private emitParticles(engine: Engine, skierAction: SkierActions, skierIntentions: SkierIntentions): void { if ( this.leftParticlesEmitter && this.rightParticlesEmitter && @@ -223,9 +244,9 @@ export class Skier extends Actor { this.computeParticlesAngle(); if (skierAction === SkierActions.SLIDE_LEFT || skierAction === SkierActions.SLIDE_RIGHT) { - this.emitSlidingParticles(speedPercentage, this.slidingIntention(engine), skierAction); + this.emitSlidingParticles(speedPercentage, Skier.slidingIntention(skierIntentions), skierAction); } else if (skierAction === SkierActions.CARVE_LEFT || skierAction === SkierActions.CARVE_RIGHT) { - this.emitCarvingParticles(speedPercentage, this.carvingIntention(engine), skierAction); + this.emitCarvingParticles(speedPercentage, Skier.carvingIntention(skierIntentions), skierAction); } else if (skierAction === SkierActions.BRAKE) { this.emitBrakingParticles(speedPercentage); } @@ -278,27 +299,31 @@ export class Skier extends Actor { ); } - private carvingIntention(engine: Engine): number { - return Math.min(1, this.leftCarvingIntention(engine) + this.rightCarvingIntention(engine)); + private static carvingIntention(skierIntentions: SkierIntentions): number { + return Math.min(1, skierIntentions.leftCarvingIntention + skierIntentions.rightCarvingIntention); + } + + private static slidingIntention(skierIntentions: SkierIntentions): number { + return Math.min(1, Skier.leftSlidingIntention(skierIntentions) + Skier.rightSlidingIntention(skierIntentions)); } - private slidingIntention(engine: Engine): number { - return Math.min(1, this.leftSlidingIntention(engine) + this.rightSlidingIntention(engine)); + private static hasTurningIntention(skierIntentions: SkierIntentions): boolean { + return Skier.carvingIntention(skierIntentions) > 0 || Skier.slidingIntention(skierIntentions) > 0; } - private hasLeftTurningIntention(engine: Engine): boolean { - return this.leftCarvingIntention(engine) + this.leftSlidingIntention(engine) > 0; + private static hasLeftTurningIntention(skierIntentions: SkierIntentions): boolean { + return skierIntentions.leftCarvingIntention + Skier.leftSlidingIntention(skierIntentions) > 0; } - private leftSlidingIntention(engine: Engine): number { - return this.hasBreakingIntention(engine) && this.leftCarvingIntention(engine) > 0 - ? this.leftCarvingIntention(engine) + private static leftSlidingIntention(skierIntentions: SkierIntentions): number { + return skierIntentions.hasBrakingIntention && skierIntentions.leftCarvingIntention > 0 + ? skierIntentions.leftCarvingIntention : 0; } - private rightSlidingIntention(engine: Engine): number { - return this.hasBreakingIntention(engine) && this.rightCarvingIntention(engine) > 0 - ? this.rightCarvingIntention(engine) + private static rightSlidingIntention(skierIntentions: SkierIntentions): number { + return skierIntentions.hasBrakingIntention && skierIntentions.rightCarvingIntention > 0 + ? skierIntentions.rightCarvingIntention : 0; } @@ -328,10 +353,6 @@ export class Skier extends Actor { return 0; } - private hasTurningIntention(engine: Engine): boolean { - return this.carvingIntention(engine) > 0 || this.slidingIntention(engine) > 0; - } - private computeParticlesAngle(): void { const leftAngle = this.rotation ? (this.rotation - LEFT_ANGLE_OFFSET) % RADIAN_PI : 3; const rightAngle = this.rotation ? (this.rotation - RIGHT_ANGLE_OFFSET) % RADIAN_PI : 3; diff --git a/src/app/game/scenes/race.ts b/src/app/game/scenes/race.ts index 531e355..4f6320a 100644 --- a/src/app/game/scenes/race.ts +++ b/src/app/game/scenes/race.ts @@ -162,7 +162,7 @@ export class Race extends Scene { this.skier!.pos.x, this.skier!.pos.y, this.skier!.rotation, - this.skier!.getSkierCurrentAction(this.engine) + this.skier!.getSkierCurrentAction() ) ); } diff --git a/src/app/game/utils/touch-manager.ts b/src/app/game/utils/touch-manager.ts index 276211f..2ab6859 100644 --- a/src/app/game/utils/touch-manager.ts +++ b/src/app/game/utils/touch-manager.ts @@ -1,19 +1,46 @@ -import type { Engine, Vector } from 'excalibur'; +import type { Engine, GlobalCoordinates, Vector } from 'excalibur'; import { Config } from '../config'; export class TouchManager { private engine: Engine; public isTouching = false; - public isTouchingBack = false; - public isTouchingLeft = false; - public isTouchingRight = false; + // public isTouchingBack = false; + // public isTouchingLeft = false; + // public isTouchingRight = false; constructor(engine: Engine) { this.engine = engine; this.listenTouch(); } + public get isTouchingBack(): boolean { + const value = Array.from(this.engine.input.pointers.lastFramePointerCoords).findIndex( + v => v[1].pagePos.y > window.innerHeight - Config.TOUCH_BRAKE_ZONE_RATIO * window.innerHeight + ); + return value > -1; + } + + public get isTouchingRight(): boolean { + const value = Array.from(this.engine.input.pointers.lastFramePointerCoords).findIndex(v => { + return ( + v[1].pagePos.y < window.innerHeight - Config.TOUCH_BRAKE_ZONE_RATIO * window.innerHeight && + v[1].pagePos.x > window.innerWidth / 2 + ); + }); + return value > -1; + } + + public get isTouchingLeft(): boolean { + const value = Array.from(this.engine.input.pointers.lastFramePointerCoords).findIndex(v => { + return ( + v[1].pagePos.y < window.innerHeight - Config.TOUCH_BRAKE_ZONE_RATIO * window.innerHeight && + v[1].pagePos.x < window.innerWidth / 2 + ); + }); + return value > -1; + } + private listenTouch(): void { this.engine.input.pointers.on('down', event => { this.recomputeTouchStatus('down', event.pagePos); @@ -26,11 +53,11 @@ export class TouchManager { private recomputeTouchStatus(type: 'down' | 'up', position: Vector): void { if (this.getTouchZone(position) === 'back') { - this.isTouchingBack = type === 'down'; + // this.isTouchingBack = type === 'down'; } else if (this.getTouchZone(position) === 'left') { - this.isTouchingLeft = type === 'down'; + // this.isTouchingLeft = type === 'down'; } else if (this.getTouchZone(position) === 'right') { - this.isTouchingRight = type === 'down'; + // this.isTouchingRight = type === 'down'; } this.isTouching = this.isTouchingBack || this.isTouchingLeft || this.isTouchingRight; } From 82975aae22847814e20777e47a07c1a78ec305bc Mon Sep 17 00:00:00 2001 From: Mathieu Hermann Date: Wed, 1 Jan 2025 19:53:45 +0100 Subject: [PATCH 2/9] fix(touch-manager): try fixing the ghosting pointers when multitouching --- angular.json | 245 +++++++++++++--------------- ngsw-config.json | 30 ---- src/app/app.config.ts | 14 +- src/app/game/utils/touch-manager.ts | 6 +- 4 files changed, 123 insertions(+), 172 deletions(-) delete mode 100644 ngsw-config.json diff --git a/angular.json b/angular.json index 7cb0126..1e77407 100644 --- a/angular.json +++ b/angular.json @@ -1,136 +1,125 @@ { - "$schema": "./node_modules/@angular/cli/lib/config/schema.json", - "version": 1, - "newProjectRoot": "projects", - "projects": { - "RetroSki": { - "projectType": "application", - "schematics": { - "@schematics/angular:component": { - "style": "scss", - "changeDetection": "OnPush", - "skipTests": true - } - }, - "root": "", - "sourceRoot": "src", - "prefix": "app", - "architect": { - "build": { - "builder": "@angular-devkit/build-angular:browser", - "options": { - "outputPath": "dist/", - "index": "src/index.html", - "main": "src/main.ts", - "polyfills": [ - "zone.js" - ], - "tsConfig": "tsconfig.app.json", - "inlineStyleLanguage": "scss", - "assets": [ - "src/assets", - { - "glob": "**/*", - "input": "src/app/game/images", - "output": "assets/images" - }, - { - "glob": "**/*", - "input": "src/app/game/sounds", - "output": "assets/sounds" - }, - { - "glob": "**/*", - "input": "src/app/game/ghosts", - "output": "assets/ghosts" - }, - { - "glob": "**/*", - "input": "src/app/game/tracks", - "output": "assets/tracks" - }, - { - "glob": "**/*", - "input": "public" - } - ], - "styles": [ - "src/styles.scss" - ], - "scripts": [], - "serviceWorker": true, - "ngswConfigPath": "ngsw-config.json" - }, - "configurations": { - "production": { - "budgets": [ - { - "type": "initial", - "maximumWarning": "500kB", - "maximumError": "2MB" - }, - { - "type": "anyComponentStyle", - "maximumWarning": "2kB", - "maximumError": "4kB" + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "newProjectRoot": "projects", + "projects": { + "RetroSki": { + "projectType": "application", + "schematics": { + "@schematics/angular:component": { + "style": "scss", + "changeDetection": "OnPush", + "skipTests": true } - ], - "outputHashing": "all" }, - "development": { - "optimization": false, - "extractLicenses": false, - "sourceMap": true, - "fileReplacements": [ - { - "replace": "src/environments/environment.ts", - "with": "src/environments/environment.development.ts" + "root": "", + "sourceRoot": "src", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:browser", + "options": { + "outputPath": "dist/", + "index": "src/index.html", + "main": "src/main.ts", + "polyfills": ["zone.js"], + "tsConfig": "tsconfig.app.json", + "inlineStyleLanguage": "scss", + "assets": [ + "src/assets", + { + "glob": "**/*", + "input": "src/app/game/images", + "output": "assets/images" + }, + { + "glob": "**/*", + "input": "src/app/game/sounds", + "output": "assets/sounds" + }, + { + "glob": "**/*", + "input": "src/app/game/ghosts", + "output": "assets/ghosts" + }, + { + "glob": "**/*", + "input": "src/app/game/tracks", + "output": "assets/tracks" + }, + { + "glob": "**/*", + "input": "public" + } + ], + "styles": ["src/styles.scss"], + "scripts": [] + }, + "configurations": { + "production": { + "budgets": [ + { + "type": "initial", + "maximumWarning": "500kB", + "maximumError": "2MB" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "2kB", + "maximumError": "4kB" + } + ], + "outputHashing": "all" + }, + "development": { + "optimization": false, + "extractLicenses": false, + "sourceMap": true, + "fileReplacements": [ + { + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.development.ts" + } + ] + } + }, + "defaultConfiguration": "production" + }, + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "options": { + "ssl": true + }, + "configurations": { + "production": { + "buildTarget": "RetroSki:build:production" + }, + "development": { + "buildTarget": "RetroSki:build:development" + } + }, + "defaultConfiguration": "development" + }, + "extract-i18n": { + "builder": "@angular-devkit/build-angular:extract-i18n" + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "polyfills": ["zone.js", "zone.js/testing"], + "tsConfig": "tsconfig.spec.json", + "inlineStyleLanguage": "scss", + "assets": [ + { + "glob": "**/*", + "input": "public" + } + ], + "styles": ["src/styles.scss"], + "scripts": [] + } } - ] - } - }, - "defaultConfiguration": "production" - }, - "serve": { - "builder": "@angular-devkit/build-angular:dev-server", - "options": { - "ssl": true - }, - "configurations": { - "production": { - "buildTarget": "RetroSki:build:production" - }, - "development": { - "buildTarget": "RetroSki:build:development" } - }, - "defaultConfiguration": "development" - }, - "extract-i18n": { - "builder": "@angular-devkit/build-angular:extract-i18n" - }, - "test": { - "builder": "@angular-devkit/build-angular:karma", - "options": { - "polyfills": [ - "zone.js", - "zone.js/testing" - ], - "tsConfig": "tsconfig.spec.json", - "inlineStyleLanguage": "scss", - "assets": [ - { - "glob": "**/*", - "input": "public" - } - ], - "styles": [ - "src/styles.scss" - ], - "scripts": [] - } } - } } - } } diff --git a/ngsw-config.json b/ngsw-config.json deleted file mode 100644 index 69edd28..0000000 --- a/ngsw-config.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "$schema": "./node_modules/@angular/service-worker/config/schema.json", - "index": "/index.html", - "assetGroups": [ - { - "name": "app", - "installMode": "prefetch", - "resources": { - "files": [ - "/favicon.ico", - "/index.csr.html", - "/index.html", - "/manifest.webmanifest", - "/*.css", - "/*.js" - ] - } - }, - { - "name": "assets", - "installMode": "lazy", - "updateMode": "prefetch", - "resources": { - "files": [ - "/**/*.(svg|cur|jpg|jpeg|png|apng|webp|avif|gif|otf|ttf|woff|woff2)" - ] - } - } - ] -} diff --git a/src/app/app.config.ts b/src/app/app.config.ts index a8f0c5a..51b684b 100644 --- a/src/app/app.config.ts +++ b/src/app/app.config.ts @@ -1,16 +1,8 @@ -import { type ApplicationConfig, provideZoneChangeDetection, isDevMode } from '@angular/core'; -import { provideRouter, withComponentInputBinding, withHashLocation } from '@angular/router'; +import { type ApplicationConfig, provideZoneChangeDetection } from '@angular/core'; +import { provideRouter, withHashLocation } from '@angular/router'; import { routes } from './app.routes'; -import { provideServiceWorker } from '@angular/service-worker'; export const appConfig: ApplicationConfig = { - providers: [ - provideZoneChangeDetection({ eventCoalescing: true }), - provideRouter(routes, withHashLocation()), - provideServiceWorker('ngsw-worker.js', { - enabled: !isDevMode(), - registrationStrategy: 'registerWhenStable:30000' - }) - ] + providers: [provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes, withHashLocation())] }; diff --git a/src/app/game/utils/touch-manager.ts b/src/app/game/utils/touch-manager.ts index 2ab6859..d18e025 100644 --- a/src/app/game/utils/touch-manager.ts +++ b/src/app/game/utils/touch-manager.ts @@ -15,14 +15,14 @@ export class TouchManager { } public get isTouchingBack(): boolean { - const value = Array.from(this.engine.input.pointers.lastFramePointerCoords).findIndex( + const value = Array.from(this.engine.input.pointers.currentFramePointerCoords).findIndex( v => v[1].pagePos.y > window.innerHeight - Config.TOUCH_BRAKE_ZONE_RATIO * window.innerHeight ); return value > -1; } public get isTouchingRight(): boolean { - const value = Array.from(this.engine.input.pointers.lastFramePointerCoords).findIndex(v => { + const value = Array.from(this.engine.input.pointers.currentFramePointerCoords).findIndex(v => { return ( v[1].pagePos.y < window.innerHeight - Config.TOUCH_BRAKE_ZONE_RATIO * window.innerHeight && v[1].pagePos.x > window.innerWidth / 2 @@ -32,7 +32,7 @@ export class TouchManager { } public get isTouchingLeft(): boolean { - const value = Array.from(this.engine.input.pointers.lastFramePointerCoords).findIndex(v => { + const value = Array.from(this.engine.input.pointers.currentFramePointerCoords).findIndex(v => { return ( v[1].pagePos.y < window.innerHeight - Config.TOUCH_BRAKE_ZONE_RATIO * window.innerHeight && v[1].pagePos.x < window.innerWidth / 2 From c538efe2e663c2740f35de079085d07d55080f51 Mon Sep 17 00:00:00 2001 From: Mathieu Hermann Date: Wed, 1 Jan 2025 20:14:02 +0100 Subject: [PATCH 3/9] refactor(game): try changing pointers to document vs. canvas --- src/app/game/game.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/app/game/game.ts b/src/app/game/game.ts index 36961c0..17eea63 100644 --- a/src/app/game/game.ts +++ b/src/app/game/game.ts @@ -1,4 +1,4 @@ -import { Color, DisplayMode, Engine, Loader } from 'excalibur'; +import { Color, DisplayMode, Engine, Loader, PointerScope } from 'excalibur'; import { Resources } from './resources'; import { SoundPlayer } from './utils/sounds-player'; import { LogoManager } from './utils/logo-manager'; @@ -87,7 +87,8 @@ export class Game extends Engine { canvasElementId: 'game', suppressConsoleBootMessage: true, antialiasing: true, - suppressHiDPIScaling: true + suppressHiDPIScaling: true, + pointerScope: PointerScope.Document }); this.raceConfig = raceConfig; From a125b5d668df5cc1444f9bea923e262ab5a38422 Mon Sep 17 00:00:00 2001 From: Mathieu Hermann Date: Wed, 1 Jan 2025 21:38:30 +0100 Subject: [PATCH 4/9] refactor(touch-manager): WIP try improving multi-touch behavior --- src/app/game/game.ts | 3 +-- src/app/game/utils/touch-manager.ts | 3 --- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/app/game/game.ts b/src/app/game/game.ts index 17eea63..31b613e 100644 --- a/src/app/game/game.ts +++ b/src/app/game/game.ts @@ -87,8 +87,7 @@ export class Game extends Engine { canvasElementId: 'game', suppressConsoleBootMessage: true, antialiasing: true, - suppressHiDPIScaling: true, - pointerScope: PointerScope.Document + suppressHiDPIScaling: true }); this.raceConfig = raceConfig; diff --git a/src/app/game/utils/touch-manager.ts b/src/app/game/utils/touch-manager.ts index d18e025..40b5bbb 100644 --- a/src/app/game/utils/touch-manager.ts +++ b/src/app/game/utils/touch-manager.ts @@ -5,9 +5,6 @@ export class TouchManager { private engine: Engine; public isTouching = false; - // public isTouchingBack = false; - // public isTouchingLeft = false; - // public isTouchingRight = false; constructor(engine: Engine) { this.engine = engine; From efeea12b288357ce1855b029caebba8b0655a960 Mon Sep 17 00:00:00 2001 From: Mathieu Hermann Date: Wed, 1 Jan 2025 21:48:22 +0100 Subject: [PATCH 5/9] fix(touch-manager): try clearing multipoint pointers when there is to much multipoint detected --- src/app/game/utils/touch-manager.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/app/game/utils/touch-manager.ts b/src/app/game/utils/touch-manager.ts index 40b5bbb..64029a9 100644 --- a/src/app/game/utils/touch-manager.ts +++ b/src/app/game/utils/touch-manager.ts @@ -40,6 +40,9 @@ export class TouchManager { private listenTouch(): void { this.engine.input.pointers.on('down', event => { + if (this.engine.input.pointers.count() > 2) { + this.engine.input.pointers.clear(); + } this.recomputeTouchStatus('down', event.pagePos); }); From c1b630edac37e8d868c0eeeaedf566a5f87bb16d Mon Sep 17 00:00:00 2001 From: Mathieu Hermann Date: Wed, 1 Jan 2025 22:03:52 +0100 Subject: [PATCH 6/9] refactor(touch-manager): use lastframe instead of current frame --- src/app/game/utils/touch-manager.ts | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/app/game/utils/touch-manager.ts b/src/app/game/utils/touch-manager.ts index 64029a9..161ce23 100644 --- a/src/app/game/utils/touch-manager.ts +++ b/src/app/game/utils/touch-manager.ts @@ -1,4 +1,4 @@ -import type { Engine, GlobalCoordinates, Vector } from 'excalibur'; +import type { Engine, Vector } from 'excalibur'; import { Config } from '../config'; export class TouchManager { @@ -12,27 +12,27 @@ export class TouchManager { } public get isTouchingBack(): boolean { - const value = Array.from(this.engine.input.pointers.currentFramePointerCoords).findIndex( - v => v[1].pagePos.y > window.innerHeight - Config.TOUCH_BRAKE_ZONE_RATIO * window.innerHeight + const value = Array.from(this.engine.input.pointers.lastFramePointerCoords).findIndex( + p => p[1].pagePos.y > window.innerHeight - Config.TOUCH_BRAKE_ZONE_RATIO * window.innerHeight ); return value > -1; } public get isTouchingRight(): boolean { - const value = Array.from(this.engine.input.pointers.currentFramePointerCoords).findIndex(v => { + const value = Array.from(this.engine.input.pointers.lastFramePointerCoords).findIndex(p => { return ( - v[1].pagePos.y < window.innerHeight - Config.TOUCH_BRAKE_ZONE_RATIO * window.innerHeight && - v[1].pagePos.x > window.innerWidth / 2 + p[1].pagePos.y < window.innerHeight - Config.TOUCH_BRAKE_ZONE_RATIO * window.innerHeight && + p[1].pagePos.x > window.innerWidth / 2 ); }); return value > -1; } public get isTouchingLeft(): boolean { - const value = Array.from(this.engine.input.pointers.currentFramePointerCoords).findIndex(v => { + const value = Array.from(this.engine.input.pointers.lastFramePointerCoords).findIndex(p => { return ( - v[1].pagePos.y < window.innerHeight - Config.TOUCH_BRAKE_ZONE_RATIO * window.innerHeight && - v[1].pagePos.x < window.innerWidth / 2 + p[1].pagePos.y < window.innerHeight - Config.TOUCH_BRAKE_ZONE_RATIO * window.innerHeight && + p[1].pagePos.x < window.innerWidth / 2 ); }); return value > -1; @@ -40,9 +40,6 @@ export class TouchManager { private listenTouch(): void { this.engine.input.pointers.on('down', event => { - if (this.engine.input.pointers.count() > 2) { - this.engine.input.pointers.clear(); - } this.recomputeTouchStatus('down', event.pagePos); }); From 48e8973a2d3321c9340b0da34d236a6480b5c6a1 Mon Sep 17 00:00:00 2001 From: Mathieu Hermann Date: Wed, 1 Jan 2025 22:26:40 +0100 Subject: [PATCH 7/9] refactor(touch-manager): WIP test coming back to actual master mechanics --- src/app/game/utils/touch-manager.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/app/game/utils/touch-manager.ts b/src/app/game/utils/touch-manager.ts index 161ce23..6425221 100644 --- a/src/app/game/utils/touch-manager.ts +++ b/src/app/game/utils/touch-manager.ts @@ -5,12 +5,16 @@ export class TouchManager { private engine: Engine; public isTouching = false; + public isTouchingBack = false; + public isTouchingLeft = false; + public isTouchingRight = false; constructor(engine: Engine) { this.engine = engine; this.listenTouch(); } + /* public get isTouchingBack(): boolean { const value = Array.from(this.engine.input.pointers.lastFramePointerCoords).findIndex( p => p[1].pagePos.y > window.innerHeight - Config.TOUCH_BRAKE_ZONE_RATIO * window.innerHeight @@ -37,6 +41,7 @@ export class TouchManager { }); return value > -1; } + */ private listenTouch(): void { this.engine.input.pointers.on('down', event => { @@ -50,11 +55,11 @@ export class TouchManager { private recomputeTouchStatus(type: 'down' | 'up', position: Vector): void { if (this.getTouchZone(position) === 'back') { - // this.isTouchingBack = type === 'down'; + this.isTouchingBack = type === 'down'; } else if (this.getTouchZone(position) === 'left') { - // this.isTouchingLeft = type === 'down'; + this.isTouchingLeft = type === 'down'; } else if (this.getTouchZone(position) === 'right') { - // this.isTouchingRight = type === 'down'; + this.isTouchingRight = type === 'down'; } this.isTouching = this.isTouchingBack || this.isTouchingLeft || this.isTouchingRight; } From 42a3b7daf8a9af1ddf89ba9155a96ce73dd0d1a8 Mon Sep 17 00:00:00 2001 From: Mathieu Hermann Date: Wed, 1 Jan 2025 22:34:33 +0100 Subject: [PATCH 8/9] refactor(touch-manage): add a fix to resetting when up zone is different that down zone --- src/app/game/utils/touch-manager.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/app/game/utils/touch-manager.ts b/src/app/game/utils/touch-manager.ts index 6425221..635c69e 100644 --- a/src/app/game/utils/touch-manager.ts +++ b/src/app/game/utils/touch-manager.ts @@ -55,15 +55,30 @@ export class TouchManager { private recomputeTouchStatus(type: 'down' | 'up', position: Vector): void { if (this.getTouchZone(position) === 'back') { + if (!this.isTouchingBack && type === 'up') { + this.resetTouching(); + } this.isTouchingBack = type === 'down'; } else if (this.getTouchZone(position) === 'left') { + if (!this.isTouchingLeft && type === 'up') { + this.resetTouching(); + } this.isTouchingLeft = type === 'down'; } else if (this.getTouchZone(position) === 'right') { + if (!this.isTouchingRight && type === 'up') { + this.resetTouching(); + } this.isTouchingRight = type === 'down'; } this.isTouching = this.isTouchingBack || this.isTouchingLeft || this.isTouchingRight; } + private resetTouching(): void { + this.isTouchingBack = false; + this.isTouchingLeft = false; + this.isTouchingRight = false; + } + private getTouchZone(position: Vector): 'back' | 'left' | 'right' { if (position.y > window.innerHeight - Config.TOUCH_BRAKE_ZONE_RATIO * window.innerHeight) { return 'back'; From ea91e7fd5bf9d09b5cb4b224703fbacad3fa3cba Mon Sep 17 00:00:00 2001 From: Mathieu Hermann Date: Wed, 1 Jan 2025 22:42:57 +0100 Subject: [PATCH 9/9] refactor(touch-manager): clean useless code --- src/app/game/utils/touch-manager.ts | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/src/app/game/utils/touch-manager.ts b/src/app/game/utils/touch-manager.ts index 635c69e..1cf57de 100644 --- a/src/app/game/utils/touch-manager.ts +++ b/src/app/game/utils/touch-manager.ts @@ -14,35 +14,6 @@ export class TouchManager { this.listenTouch(); } - /* - public get isTouchingBack(): boolean { - const value = Array.from(this.engine.input.pointers.lastFramePointerCoords).findIndex( - p => p[1].pagePos.y > window.innerHeight - Config.TOUCH_BRAKE_ZONE_RATIO * window.innerHeight - ); - return value > -1; - } - - public get isTouchingRight(): boolean { - const value = Array.from(this.engine.input.pointers.lastFramePointerCoords).findIndex(p => { - return ( - p[1].pagePos.y < window.innerHeight - Config.TOUCH_BRAKE_ZONE_RATIO * window.innerHeight && - p[1].pagePos.x > window.innerWidth / 2 - ); - }); - return value > -1; - } - - public get isTouchingLeft(): boolean { - const value = Array.from(this.engine.input.pointers.lastFramePointerCoords).findIndex(p => { - return ( - p[1].pagePos.y < window.innerHeight - Config.TOUCH_BRAKE_ZONE_RATIO * window.innerHeight && - p[1].pagePos.x < window.innerWidth / 2 - ); - }); - return value > -1; - } - */ - private listenTouch(): void { this.engine.input.pointers.on('down', event => { this.recomputeTouchStatus('down', event.pagePos);