Add touch controls for mobile support

This commit is contained in:
Flashfyre committed 2023-12-25 15:03:50 -05:00
1 parent de29ea9c05
commit 41d1a84c76
8 files changed
+332 -24

No files matched your search

+4
View File
@@ -552,15 +552,19 @@ export default class BattleScene extends Phaser.Scene {
[Button.SPEED_UP]: [keyCodes.PLUS],
[Button.SLOW_DOWN]: [keyCodes.MINUS]
};
const mobileKeyConfig = {};
this.buttonKeys = [];
for (let b of Utils.getEnumValues(Button)) {
const keys: Phaser.Input.Keyboard.Key[] = [];
if (keyConfig.hasOwnProperty(b)) {
for (let k of keyConfig[b])
keys.push(this.input.keyboard.addKey(k));
mobileKeyConfig[Button[b]] = keys[0];
}
this.buttonKeys[b] = keys;
}
initTouchControls(mobileKeyConfig);
}
getParty(): PlayerPokemon[] {
+2
View File
@@ -243,6 +243,8 @@ export class GameData {
}
private loadSettings(): boolean {
Object.values(Setting).map(setting => setting as Setting).forEach(setting => setSetting(this.scene, setting, settingDefaults[setting]));
if (!localStorage.hasOwnProperty('settings'))
return false;
+11 -3
View File
@@ -7,7 +7,8 @@ export enum Setting {
BGM_Volume = "BGM_VOLUME",
SE_Volume = "SE_VOLUME",
Show_Stats_on_Level_Up = "SHOW_LEVEL_UP_STATS",
Window_Type = "WINDOW_TYPE"
Window_Type = "WINDOW_TYPE",
Touch_Controls = "TOUCH_CONTROLS"
}
export interface SettingOptions {
@@ -24,7 +25,8 @@ export const settingOptions: SettingOptions = {
[Setting.BGM_Volume]: new Array(11).fill(null).map((_, i) => i ? (i * 10).toString() : 'Mute'),
[Setting.SE_Volume]: new Array(11).fill(null).map((_, i) => i ? (i * 10).toString() : 'Mute'),
[Setting.Show_Stats_on_Level_Up]: [ 'Off', 'On' ],
[Setting.Window_Type]: new Array(4).fill(null).map((_, i) => (i + 1).toString())
[Setting.Window_Type]: new Array(4).fill(null).map((_, i) => (i + 1).toString()),
[Setting.Touch_Controls]: [ 'Auto', 'Disabled' ]
};
export const settingDefaults: SettingDefaults = {
@@ -33,7 +35,8 @@ export const settingDefaults: SettingDefaults = {
[Setting.BGM_Volume]: 10,
[Setting.SE_Volume]: 10,
[Setting.Show_Stats_on_Level_Up]: 1,
[Setting.Window_Type]: 1
[Setting.Window_Type]: 1,
[Setting.Touch_Controls]: 0
};
export function setSetting(scene: BattleScene, setting: Setting, value: integer): boolean {
@@ -59,6 +62,11 @@ export function setSetting(scene: BattleScene, setting: Setting, value: integer)
case Setting.Window_Type:
updateWindowType(scene, parseInt(settingOptions[setting][value]));
break;
case Setting.Touch_Controls:
const touchControls = document.getElementById('touchControls');
if (touchControls)
touchControls.classList.toggle('visible', settingOptions[setting][value] !== 'Disabled' && hasTouchscreen());
break;
}
return true;
+106
View File
@@ -0,0 +1,106 @@
const keys = new Map();
const keysDown = new Map();
let lastTouchedId;
function initTouchControls(buttonMap) {
for (const button of document.querySelectorAll('[data-key]')) {
// @ts-ignore
bindKey(button, button.dataset.key, buttonMap);
}
}
function hasTouchscreen() {
return window.matchMedia('(hover: none), (pointer: coarse)').matches;
}
/**
* Simulate a keyboard event on the canvas
*
* @param {string} eventType Type of the keyboard event
* @param {string} button Button to simulate
* @param {object} buttonMap Map of buttons to key objects
*/
function simulateKeyboardEvent(eventType, button, buttonMap) {
const key = buttonMap[button];
switch (eventType) {
case 'keydown':
key.onDown({});
break;
case 'keyup':
key.onUp({});
break;
}
}
/**
* Simulate a keyboard input from 'keydown' to 'keyup'
*
* @param {string} key Key to simulate
* @param {object} buttonMap Map of buttons to key objects
*/
function simulateKeyboardInput(key, buttonMap) {
simulateKeyboardEvent('keydown', key, buttonMap);
window.setTimeout(() => {
simulateKeyboardEvent('keyup', key, buttonMap);
}, 100);
}
/**
* Bind a node by a specific key to simulate on touch
*
* @param {*} node The node to bind a key to
* @param {string} key Key to simulate
* @param {object} buttonMap Map of buttons to key objects
*/
function bindKey(node, key, buttonMap) {
keys.set(node.id, key);
node.addEventListener('touchstart', event => {
event.preventDefault();
simulateKeyboardEvent('keydown', key, buttonMap);
keysDown.set(event.target.id, node.id);
node.classList.add('active');
});
node.addEventListener('touchend', event => {
event.preventDefault();
const pressedKey = keysDown.get(event.target.id);
if (pressedKey && keys.has(pressedKey)) {
const key = keys.get(pressedKey);
simulateKeyboardEvent('keyup', key, buttonMap);
}
keysDown.delete(event.target.id);
node.classList.remove('active');
if (lastTouchedId) {
document.getElementById(lastTouchedId).classList.remove('active');
}
});
// Inspired by https://github.com/pulsejet/mkxp-web/blob/262a2254b684567311c9f0e135ee29f6e8c3613e/extra/js/dpad.js
node.addEventListener('touchmove', event => {
const { target, clientX, clientY } = event.changedTouches[0];
const origTargetId = keysDown.get(target.id);
const nextTargetId = document.elementFromPoint(clientX, clientY).id;
if (origTargetId === nextTargetId)
return;
if (origTargetId) {
const key = keys.get(origTargetId);
simulateKeyboardEvent('keyup', key, buttonMap);
keysDown.delete(target.id);
document.getElementById(origTargetId).classList.remove('active');
}
if (keys.has(nextTargetId)) {
const key = keys.get(nextTargetId);
simulateKeyboardEvent('keydown', key, buttonMap);
keysDown.set(target.id, nextTargetId);
lastTouchedId = nextTargetId;
document.getElementById(nextTargetId).classList.add('active');
}
});
}
+3
View File
@@ -280,6 +280,9 @@ export default class UI extends Phaser.GameObjects.Container {
if (chainMode && this.mode && !clear)
this.modeChain.push(this.mode);
this.mode = mode;
const touchControls = document.getElementById('touchControls');
if (touchControls)
touchControls.dataset.uiMode = Mode[mode];
this.getHandler().show(args);
}
resolve();
+10
View File
@@ -1,5 +1,12 @@
import BattleScene from "../battle-scene";
const windowTypeControlColors = {
0: [ '#706880', '#8888c8', '#484868' ],
1: [ '#d04028', '#e0a028', '#902008' ],
2: [ '#48b840', '#88d880', '#089040' ],
3: [ '#2068d0', '#80b0e0', '#104888' ]
};
export function addWindow(scene: BattleScene, x: number, y: number, width: number, height: number, mergeMaskTop?: boolean, mergeMaskLeft?: boolean, maskOffsetX?: number, maskOffsetY?: number): Phaser.GameObjects.NineSlice {
const window = scene.add.nineslice(x, y, `window_${scene.windowType}`, null, width, height, 8, 8, 8, 8);
window.setOrigin(0, 0);
@@ -36,6 +43,9 @@ export function updateWindowType(scene: BattleScene, windowTypeIndex: integer):
scene.windowType = windowTypeIndex;
const rootStyle = document.documentElement.style;
[ 'base', 'light', 'dark' ].map((k, i) => rootStyle.setProperty(`--color-${k}`, windowTypeControlColors[windowTypeIndex - 1][i]));
const windowKey = `window_${windowTypeIndex}`;
for (let window of windowObjects)