import { AccelerometerCallback } from 'pal/input'; import { systemInfo } from 'pal/system-info'; import { screenAdapter } from 'pal/screen-adapter'; import { EventTarget } from '../../../cocos/core/event/event-target'; import { BrowserType, OS } from '../../system-info/enum-type'; import { EventAcceleration, Acceleration } from '../../../cocos/input/types'; import { InputEventType } from '../../../cocos/input/types/event-enum'; export class AccelerometerInputSource { private _intervalInMileSeconds = 200; private _accelTimer = 0; private _eventTarget: EventTarget = new EventTarget(); private _deviceEventName: 'devicemotion' |'deviceorientation'; private _globalEventClass: typeof window.DeviceMotionEvent | typeof window.DeviceOrientationEvent; private _didAccelerateFunc: (event: DeviceMotionEvent | DeviceOrientationEvent) => void; constructor () { // init event name this._globalEventClass = window.DeviceMotionEvent || window.DeviceOrientationEvent; // TODO fix DeviceMotionEvent bug on QQ Browser version 4.1 and below. if (systemInfo.browserType === BrowserType.MOBILE_QQ) { this._globalEventClass = window.DeviceOrientationEvent; } this._deviceEventName = this._globalEventClass === window.DeviceMotionEvent ? 'devicemotion' : 'deviceorientation'; this._didAccelerateFunc = this._didAccelerate.bind(this); } private _registerEvent () { this._accelTimer = performance.now(); window.addEventListener(this._deviceEventName, this._didAccelerateFunc, false); } private _unregisterEvent () { this._accelTimer = 0; window.removeEventListener(this._deviceEventName, this._didAccelerateFunc, false); } private _didAccelerate (event: DeviceMotionEvent | DeviceOrientationEvent) { const now = performance.now(); if (now - this._accelTimer < this._intervalInMileSeconds) { return; } this._accelTimer = now; let x = 0; let y = 0; let z = 0; if (this._globalEventClass === window.DeviceMotionEvent) { const deviceMotionEvent = event as DeviceMotionEvent; const eventAcceleration = deviceMotionEvent.accelerationIncludingGravity; x = (eventAcceleration?.x || 0) * 0.1; y = (eventAcceleration?.y || 0) * 0.1; z = (eventAcceleration?.z || 0) * 0.1; } else { const deviceOrientationEvent = event as DeviceOrientationEvent; x = ((deviceOrientationEvent.gamma || 0) / 90) * 0.981; y = -((deviceOrientationEvent.beta || 0) / 90) * 0.981; z = ((deviceOrientationEvent.alpha || 0) / 90) * 0.981; } if (screenAdapter.isFrameRotated) { const tmp = x; x = -y; y = tmp; } // TODO: window.orientation is deprecated: https://developer.mozilla.org/en-US/docs/Web/API/Window/orientation // try to use experimental screen.orientation: https://developer.mozilla.org/en-US/docs/Web/API/Screen/orientation const PORTRAIT = 0; const LANDSCAPE_LEFT = -90; const PORTRAIT_UPSIDE_DOWN = 180; const LANDSCAPE_RIGHT = 90; const tmpX = x; if (window.orientation === LANDSCAPE_RIGHT) { x = -y; y = tmpX; } else if (window.orientation === LANDSCAPE_LEFT) { x = y; y = -tmpX; } else if (window.orientation === PORTRAIT_UPSIDE_DOWN) { x = -x; y = -y; } // fix android acc values are opposite if (systemInfo.os === OS.ANDROID && systemInfo.browserType !== BrowserType.MOBILE_QQ) { x = -x; y = -y; } const timestamp = performance.now(); const acceleration = new Acceleration(x, y, z, timestamp); const eventAcceleration = new EventAcceleration(acceleration); this._eventTarget.emit(InputEventType.DEVICEMOTION, eventAcceleration); } public start () { // for iOS 13+, safari if (window.DeviceMotionEvent && typeof DeviceMotionEvent.requestPermission === 'function') { DeviceMotionEvent.requestPermission().then((response) => { if (response === 'granted') { this._registerEvent(); } }).catch((e) => {}); } else { this._registerEvent(); } } public stop () { this._unregisterEvent(); } public setInterval (intervalInMileSeconds: number) { this._intervalInMileSeconds = intervalInMileSeconds; } public on (eventType: InputEventType, callback: AccelerometerCallback, target?: any) { this._eventTarget.on(eventType, callback, target); } }