@gg-web-engine/mobile-controls 0.0.0-stage → 0.0.78
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +151 -2
- package/dist/controls/touch-axis-control.d.ts +38 -0
- package/dist/controls/touch-axis-control.js +91 -0
- package/dist/controls/touch-button.d.ts +49 -0
- package/dist/controls/touch-button.js +111 -0
- package/dist/controls/touch-control.d.ts +76 -0
- package/dist/controls/touch-control.js +151 -0
- package/dist/controls/touch-dpad.d.ts +27 -0
- package/dist/controls/touch-dpad.js +61 -0
- package/dist/controls/touch-look-area.d.ts +35 -0
- package/dist/controls/touch-look-area.js +61 -0
- package/dist/controls/touch-stick.d.ts +37 -0
- package/dist/controls/touch-stick.js +71 -0
- package/dist/icons.d.ts +19 -0
- package/dist/icons.js +20 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +15 -0
- package/dist/inputs/tilt.input.d.ts +40 -0
- package/dist/inputs/tilt.input.js +106 -0
- package/dist/layouts/car.layout.d.ts +31 -0
- package/dist/layouts/car.layout.js +99 -0
- package/dist/layouts/character-2d.layout.d.ts +15 -0
- package/dist/layouts/character-2d.layout.js +60 -0
- package/dist/layouts/character.layout.d.ts +25 -0
- package/dist/layouts/character.layout.js +87 -0
- package/dist/layouts/free-camera.layout.d.ts +16 -0
- package/dist/layouts/free-camera.layout.js +73 -0
- package/dist/mobile-controls-layout.d.ts +54 -0
- package/dist/mobile-controls-layout.js +47 -0
- package/dist/mobile-controls.entity.d.ts +112 -0
- package/dist/mobile-controls.entity.js +274 -0
- package/dist/styles.d.ts +16 -0
- package/dist/styles.js +158 -0
- package/package.json +68 -4
- package/src/controls/touch-axis-control.ts +105 -0
- package/src/controls/touch-button.ts +136 -0
- package/src/controls/touch-control.ts +204 -0
- package/src/controls/touch-dpad.ts +78 -0
- package/src/controls/touch-look-area.ts +78 -0
- package/src/controls/touch-stick.ts +100 -0
- package/src/icons.ts +23 -0
- package/src/index.ts +15 -0
- package/src/inputs/tilt.input.ts +128 -0
- package/src/layouts/car.layout.ts +139 -0
- package/src/layouts/character-2d.layout.ts +85 -0
- package/src/layouts/character.layout.ts +125 -0
- package/src/layouts/free-camera.layout.ts +103 -0
- package/src/mobile-controls-layout.ts +86 -0
- package/src/mobile-controls.entity.ts +350 -0
- package/src/styles.ts +160 -0
- package/test/helpers.ts +38 -0
- package/test/mobile-controls.spec.ts +204 -0
- package/test/tilt.input.spec.ts +95 -0
- package/test/touch-axis-controls.spec.ts +172 -0
- package/test/touch-button.spec.ts +162 -0
- package/tsconfig.json +12 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { PlayerCharacterController2d } from '@gg-web-engine/core';
|
|
2
|
+
import { LayoutCustomization, MobileControlsLayoutFactory } from '../mobile-controls-layout';
|
|
3
|
+
export type Character2dLayoutControlId = 'move-left' | 'move-right' | 'move' | 'jump' | 'run' | 'crouch';
|
|
4
|
+
export type Character2dLayoutOptions = LayoutCustomization<PlayerCharacterController2d<any>, Character2dLayoutControlId> & {
|
|
5
|
+
/**
|
|
6
|
+
* How the character is moved: `'buttons'` (default) - a left and a right button, `'stick'` - an
|
|
7
|
+
* analog stick moving sideways.
|
|
8
|
+
*/
|
|
9
|
+
movement?: 'buttons' | 'stick';
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* The built-in layout for `PlayerCharacterController2d`: left/right on the left, actions on the
|
|
13
|
+
* right.
|
|
14
|
+
*/
|
|
15
|
+
export declare function character2dLayout(options?: Character2dLayoutOptions): MobileControlsLayoutFactory<PlayerCharacterController2d<any>>;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { TouchButton } from '../controls/touch-button';
|
|
2
|
+
import { TouchStick } from '../controls/touch-stick';
|
|
3
|
+
import { MobileControlsIcons } from '../icons';
|
|
4
|
+
import { LayoutBuilder } from '../mobile-controls-layout';
|
|
5
|
+
/**
|
|
6
|
+
* The built-in layout for `PlayerCharacterController2d`: left/right on the left, actions on the
|
|
7
|
+
* right.
|
|
8
|
+
*/
|
|
9
|
+
export function character2dLayout(options = {}) {
|
|
10
|
+
return (controller, context) => {
|
|
11
|
+
const b = new LayoutBuilder(options);
|
|
12
|
+
const direction = controller.directionsInput;
|
|
13
|
+
if ((options.movement || 'buttons') === 'buttons') {
|
|
14
|
+
b.add('move-left', () => new TouchButton({
|
|
15
|
+
id: 'move-left',
|
|
16
|
+
label: 'Move left',
|
|
17
|
+
content: b.icon('move-left', MobileControlsIcons.left),
|
|
18
|
+
placement: b.placement('move-left', { left: 4, bottom: 4, width: 10, height: 10 }),
|
|
19
|
+
}).bindDirection(direction, { x: -1 }));
|
|
20
|
+
b.add('move-right', () => new TouchButton({
|
|
21
|
+
id: 'move-right',
|
|
22
|
+
label: 'Move right',
|
|
23
|
+
content: b.icon('move-right', MobileControlsIcons.right),
|
|
24
|
+
placement: b.placement('move-right', { left: 16, bottom: 4, width: 10, height: 10 }),
|
|
25
|
+
}).bindDirection(direction, { x: 1 }));
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
b.add('move', () => new TouchStick({
|
|
29
|
+
id: 'move',
|
|
30
|
+
label: 'Move',
|
|
31
|
+
axes: 'x',
|
|
32
|
+
placement: b.placement('move', { left: 4, bottom: 3 }),
|
|
33
|
+
}).bindDirection(direction));
|
|
34
|
+
}
|
|
35
|
+
b.add('jump', () => new TouchButton({
|
|
36
|
+
id: 'jump',
|
|
37
|
+
label: 'Jump',
|
|
38
|
+
content: b.icon('jump', MobileControlsIcons.jump),
|
|
39
|
+
placement: b.placement('jump', { right: 4, bottom: 4, width: 10, height: 10 }),
|
|
40
|
+
}).bindKey(controller.keyboard, controller.options.jumpKey));
|
|
41
|
+
b.add('run', () => new TouchButton({
|
|
42
|
+
id: 'run',
|
|
43
|
+
label: 'Run',
|
|
44
|
+
mode: 'toggle',
|
|
45
|
+
content: b.icon('run', MobileControlsIcons.run),
|
|
46
|
+
placement: b.placement('run', { right: 16, bottom: 4, width: 8, height: 8 }),
|
|
47
|
+
}).bindKey(controller.keyboard, controller.options.runKey));
|
|
48
|
+
b.add('crouch', () => {
|
|
49
|
+
var _a;
|
|
50
|
+
return new TouchButton({
|
|
51
|
+
id: 'crouch',
|
|
52
|
+
label: 'Crouch',
|
|
53
|
+
mode: ((_a = controller.character) === null || _a === void 0 ? void 0 : _a.options.crouchMode) === 'toggle' ? 'hold' : 'toggle',
|
|
54
|
+
content: b.icon('crouch', MobileControlsIcons.crouch),
|
|
55
|
+
placement: b.placement('crouch', { right: 5, bottom: 16, width: 8, height: 8 }),
|
|
56
|
+
}).bindKey(controller.keyboard, controller.options.crouchKey);
|
|
57
|
+
});
|
|
58
|
+
return b.finish(controller, context);
|
|
59
|
+
};
|
|
60
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { PlayerCharacterController } from '@gg-web-engine/core';
|
|
2
|
+
import { LayoutCustomization, MobileControlsLayoutFactory } from '../mobile-controls-layout';
|
|
3
|
+
export type CharacterLayoutControlId = 'look' | 'look-stick' | 'move' | 'jump' | 'run' | 'crouch' | 'view';
|
|
4
|
+
export type CharacterLayoutOptions = LayoutCustomization<PlayerCharacterController<any>, CharacterLayoutControlId> & {
|
|
5
|
+
/**
|
|
6
|
+
* How the character is moved: `'stick'` (default) - an analog stick that appears under the left
|
|
7
|
+
* thumb wherever it lands, `'dpad'` - a directional pad in the corner.
|
|
8
|
+
*/
|
|
9
|
+
movement?: 'stick' | 'dpad';
|
|
10
|
+
/**
|
|
11
|
+
* How the view is turned: `'drag'` (default) - by dragging anywhere the other controls are not,
|
|
12
|
+
* `'stick'` - with a second stick on the right, `false` - not by this layout.
|
|
13
|
+
*/
|
|
14
|
+
look?: 'drag' | 'stick' | false;
|
|
15
|
+
/**
|
|
16
|
+
* Multiplies the turning: of a drag, on top of the controller's `mouseSensitivity` (3 by default,
|
|
17
|
+
* a finger covers much less distance than a mouse), or of the look stick's speed.
|
|
18
|
+
*/
|
|
19
|
+
lookSensitivity?: number;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* The built-in layout for `PlayerCharacterController`: movement on the left, actions on the right,
|
|
23
|
+
* looking around by dragging over the rest of the screen.
|
|
24
|
+
*/
|
|
25
|
+
export declare function characterLayout(options?: CharacterLayoutOptions): MobileControlsLayoutFactory<PlayerCharacterController<any>>;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { TouchButton } from '../controls/touch-button';
|
|
2
|
+
import { TouchDPad } from '../controls/touch-dpad';
|
|
3
|
+
import { TouchLookArea } from '../controls/touch-look-area';
|
|
4
|
+
import { TouchStick } from '../controls/touch-stick';
|
|
5
|
+
import { MobileControlsIcons } from '../icons';
|
|
6
|
+
import { LayoutBuilder } from '../mobile-controls-layout';
|
|
7
|
+
/**
|
|
8
|
+
* The built-in layout for `PlayerCharacterController`: movement on the left, actions on the right,
|
|
9
|
+
* looking around by dragging over the rest of the screen.
|
|
10
|
+
*/
|
|
11
|
+
export function characterLayout(options = {}) {
|
|
12
|
+
return (controller, context) => {
|
|
13
|
+
var _a;
|
|
14
|
+
const b = new LayoutBuilder(options);
|
|
15
|
+
const look = (_a = options.look) !== null && _a !== void 0 ? _a : 'drag';
|
|
16
|
+
if (look === 'drag') {
|
|
17
|
+
b.add('look', () => {
|
|
18
|
+
var _a;
|
|
19
|
+
return new TouchLookArea({
|
|
20
|
+
id: 'look',
|
|
21
|
+
placement: b.placement('look', { left: '0', top: '0', width: '100%', height: '100%' }),
|
|
22
|
+
}).bindMouse(controller.mouseInput, (_a = options.lookSensitivity) !== null && _a !== void 0 ? _a : 3);
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
if ((options.movement || 'stick') === 'stick') {
|
|
26
|
+
b.add('move', () => new TouchStick({
|
|
27
|
+
id: 'move',
|
|
28
|
+
label: 'Move',
|
|
29
|
+
mode: 'floating',
|
|
30
|
+
placement: b.placement('move', { left: '0', bottom: '0', width: '45%', height: '75%' }),
|
|
31
|
+
}).bindDirection(controller.directionsInput));
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
b.add('move', () => new TouchDPad({
|
|
35
|
+
id: 'move',
|
|
36
|
+
label: 'Move',
|
|
37
|
+
placement: b.placement('move', { left: 4, bottom: 4 }),
|
|
38
|
+
}).bindDirection(controller.directionsInput));
|
|
39
|
+
}
|
|
40
|
+
// with a look stick in the corner the action buttons move into a row above it
|
|
41
|
+
const stick = look === 'stick';
|
|
42
|
+
if (stick) {
|
|
43
|
+
b.add('look-stick', () => {
|
|
44
|
+
var _a;
|
|
45
|
+
return new TouchStick({
|
|
46
|
+
id: 'look-stick',
|
|
47
|
+
label: 'Look',
|
|
48
|
+
placement: b.placement('look-stick', { right: 4, bottom: 3 }),
|
|
49
|
+
}).bindLook(controller.mouseInput, 900 * ((_a = options.lookSensitivity) !== null && _a !== void 0 ? _a : 1));
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
b.add('jump', () => new TouchButton({
|
|
53
|
+
id: 'jump',
|
|
54
|
+
label: 'Jump',
|
|
55
|
+
content: b.icon('jump', MobileControlsIcons.jump),
|
|
56
|
+
placement: b.placement('jump', stick ? { right: 4, bottom: 20, width: 8, height: 8 } : { right: 4, bottom: 5, width: 10, height: 10 }),
|
|
57
|
+
}).bindKey(controller.keyboard, controller.options.jumpKey));
|
|
58
|
+
b.add('run', () => new TouchButton({
|
|
59
|
+
id: 'run',
|
|
60
|
+
label: 'Run',
|
|
61
|
+
mode: 'toggle',
|
|
62
|
+
content: b.icon('run', MobileControlsIcons.run),
|
|
63
|
+
placement: b.placement('run', stick ? { right: 14, bottom: 20, width: 8, height: 8 } : { right: 16, bottom: 4, width: 8, height: 8 }),
|
|
64
|
+
}).bindKey(controller.keyboard, controller.options.runKey));
|
|
65
|
+
b.add('crouch', () => {
|
|
66
|
+
var _a;
|
|
67
|
+
return new TouchButton({
|
|
68
|
+
id: 'crouch',
|
|
69
|
+
label: 'Crouch',
|
|
70
|
+
// a crouch that lasts while its key is held gets a latching button instead of a parked finger
|
|
71
|
+
mode: ((_a = controller.character) === null || _a === void 0 ? void 0 : _a.options.crouchMode) === 'toggle' ? 'hold' : 'toggle',
|
|
72
|
+
content: b.icon('crouch', MobileControlsIcons.crouch),
|
|
73
|
+
placement: b.placement('crouch', stick ? { right: 24, bottom: 20, width: 8, height: 8 } : { right: 5, bottom: 17, width: 8, height: 8 }),
|
|
74
|
+
}).bindKey(controller.keyboard, controller.options.crouchKey);
|
|
75
|
+
});
|
|
76
|
+
const toggleViewKey = controller.options.toggleViewKey;
|
|
77
|
+
if (toggleViewKey) {
|
|
78
|
+
b.add('view', () => new TouchButton({
|
|
79
|
+
id: 'view',
|
|
80
|
+
label: 'Switch view',
|
|
81
|
+
content: b.icon('view', MobileControlsIcons.view),
|
|
82
|
+
placement: b.placement('view', { right: 4, top: 4, width: 6.5, height: 6.5 }),
|
|
83
|
+
}).bindKey(controller.keyboard, toggleViewKey));
|
|
84
|
+
}
|
|
85
|
+
return b.finish(controller, context);
|
|
86
|
+
};
|
|
87
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { FreeCameraController } from '@gg-web-engine/core';
|
|
2
|
+
import { LayoutCustomization, MobileControlsLayoutFactory } from '../mobile-controls-layout';
|
|
3
|
+
export type FreeCameraLayoutControlId = 'look' | 'look-stick' | 'move' | 'ascend' | 'descend' | 'boost';
|
|
4
|
+
export type FreeCameraLayoutOptions = LayoutCustomization<FreeCameraController, FreeCameraLayoutControlId> & {
|
|
5
|
+
/** How the camera is moved - see `CharacterLayoutOptions.movement`. `'stick'` by default. */
|
|
6
|
+
movement?: 'stick' | 'dpad';
|
|
7
|
+
/** How the camera is turned - see `CharacterLayoutOptions.look`. `'drag'` by default. */
|
|
8
|
+
look?: 'drag' | 'stick' | false;
|
|
9
|
+
/** Multiplies the turning - see `CharacterLayoutOptions.lookSensitivity`. */
|
|
10
|
+
lookSensitivity?: number;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* The built-in layout for `FreeCameraController`: flying on the left, up/down/boost on the right,
|
|
14
|
+
* looking around by dragging over the rest of the screen.
|
|
15
|
+
*/
|
|
16
|
+
export declare function freeCameraLayout(options?: FreeCameraLayoutOptions): MobileControlsLayoutFactory<FreeCameraController>;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { TouchButton } from '../controls/touch-button';
|
|
2
|
+
import { TouchDPad } from '../controls/touch-dpad';
|
|
3
|
+
import { TouchLookArea } from '../controls/touch-look-area';
|
|
4
|
+
import { TouchStick } from '../controls/touch-stick';
|
|
5
|
+
import { MobileControlsIcons } from '../icons';
|
|
6
|
+
import { LayoutBuilder } from '../mobile-controls-layout';
|
|
7
|
+
/**
|
|
8
|
+
* The built-in layout for `FreeCameraController`: flying on the left, up/down/boost on the right,
|
|
9
|
+
* looking around by dragging over the rest of the screen.
|
|
10
|
+
*/
|
|
11
|
+
export function freeCameraLayout(options = {}) {
|
|
12
|
+
return (controller, context) => {
|
|
13
|
+
var _a;
|
|
14
|
+
const b = new LayoutBuilder(options);
|
|
15
|
+
const look = (_a = options.look) !== null && _a !== void 0 ? _a : 'drag';
|
|
16
|
+
if (look === 'drag') {
|
|
17
|
+
b.add('look', () => {
|
|
18
|
+
var _a;
|
|
19
|
+
return new TouchLookArea({
|
|
20
|
+
id: 'look',
|
|
21
|
+
placement: b.placement('look', { left: '0', top: '0', width: '100%', height: '100%' }),
|
|
22
|
+
}).bindMouse(controller.mouseInput, (_a = options.lookSensitivity) !== null && _a !== void 0 ? _a : 3);
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
if ((options.movement || 'stick') === 'stick') {
|
|
26
|
+
b.add('move', () => new TouchStick({
|
|
27
|
+
id: 'move',
|
|
28
|
+
label: 'Move',
|
|
29
|
+
mode: 'floating',
|
|
30
|
+
placement: b.placement('move', { left: '0', bottom: '0', width: '45%', height: '75%' }),
|
|
31
|
+
}).bindDirection(controller.directionsInput));
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
b.add('move', () => new TouchDPad({
|
|
35
|
+
id: 'move',
|
|
36
|
+
label: 'Move',
|
|
37
|
+
placement: b.placement('move', { left: 4, bottom: 4 }),
|
|
38
|
+
}).bindDirection(controller.directionsInput));
|
|
39
|
+
}
|
|
40
|
+
const stick = look === 'stick';
|
|
41
|
+
if (stick) {
|
|
42
|
+
b.add('look-stick', () => {
|
|
43
|
+
var _a;
|
|
44
|
+
return new TouchStick({
|
|
45
|
+
id: 'look-stick',
|
|
46
|
+
label: 'Look',
|
|
47
|
+
placement: b.placement('look-stick', { right: 4, bottom: 3 }),
|
|
48
|
+
}).bindLook(controller.mouseInput, 900 * ((_a = options.lookSensitivity) !== null && _a !== void 0 ? _a : 1));
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
// the keys `FreeCameraController` moves up/down and boosts with are fixed
|
|
52
|
+
b.add('ascend', () => new TouchButton({
|
|
53
|
+
id: 'ascend',
|
|
54
|
+
label: 'Up',
|
|
55
|
+
content: b.icon('ascend', MobileControlsIcons.up),
|
|
56
|
+
placement: b.placement('ascend', stick ? { right: 4, bottom: 20, width: 8, height: 8 } : { right: 4, bottom: 15, width: 9, height: 9 }),
|
|
57
|
+
}).bindKey(controller.keyboard, 'KeyE'));
|
|
58
|
+
b.add('descend', () => new TouchButton({
|
|
59
|
+
id: 'descend',
|
|
60
|
+
label: 'Down',
|
|
61
|
+
content: b.icon('descend', MobileControlsIcons.down),
|
|
62
|
+
placement: b.placement('descend', stick ? { right: 14, bottom: 20, width: 8, height: 8 } : { right: 4, bottom: 4, width: 9, height: 9 }),
|
|
63
|
+
}).bindKey(controller.keyboard, 'KeyQ'));
|
|
64
|
+
b.add('boost', () => new TouchButton({
|
|
65
|
+
id: 'boost',
|
|
66
|
+
label: 'Boost',
|
|
67
|
+
mode: 'toggle',
|
|
68
|
+
content: b.icon('boost', MobileControlsIcons.run),
|
|
69
|
+
placement: b.placement('boost', stick ? { right: 24, bottom: 20, width: 8, height: 8 } : { right: 15, bottom: 4, width: 8, height: 8 }),
|
|
70
|
+
}).bindKey(controller.keyboard, 'ShiftLeft'));
|
|
71
|
+
return b.finish(controller, context);
|
|
72
|
+
};
|
|
73
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { GgWorld, IEntity } from '@gg-web-engine/core';
|
|
2
|
+
import { ControlPlacement, TouchControl } from './controls/touch-control';
|
|
3
|
+
import type { MobileControls } from './mobile-controls.entity';
|
|
4
|
+
export type MobileControlsLayoutContext = {
|
|
5
|
+
world: GgWorld<any, any>;
|
|
6
|
+
/** The overlay the layout is built for. */
|
|
7
|
+
controls: MobileControls;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* One piece of a layout: a `TouchControl`, or anything else that lives as long as the layout does (a
|
|
11
|
+
* `TiltInput` wired to the controller, a subscription) and is ended through `dispose`.
|
|
12
|
+
*/
|
|
13
|
+
export type MobileControlsLayoutItem = TouchControl | {
|
|
14
|
+
dispose(): void;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Builds the on-screen controls for one controller entity, already bound to it. Called every time
|
|
18
|
+
* the controller becomes active; the returned items are disposed when it stops being active or
|
|
19
|
+
* leaves the world. The controls are stacked in the returned order, the last one on top.
|
|
20
|
+
*/
|
|
21
|
+
export type MobileControlsLayoutFactory<T extends IEntity = IEntity> = (controller: T, context: MobileControlsLayoutContext) => MobileControlsLayoutItem[];
|
|
22
|
+
/**
|
|
23
|
+
* What every built-in layout lets an app adjust without replacing the layout. `Id` are the ids of
|
|
24
|
+
* the controls that layout can contain.
|
|
25
|
+
*/
|
|
26
|
+
export type LayoutCustomization<T extends IEntity, Id extends string> = {
|
|
27
|
+
/** Moves/resizes a control. Merged over that control's default placement, edge by edge. */
|
|
28
|
+
placements?: {
|
|
29
|
+
[id in Id]?: ControlPlacement;
|
|
30
|
+
};
|
|
31
|
+
/** Replaces what a button shows: markup (an inline SVG, plain text) or a DOM node. */
|
|
32
|
+
icons?: {
|
|
33
|
+
[id in Id]?: string | Node;
|
|
34
|
+
};
|
|
35
|
+
/** Leaves controls out of the layout. */
|
|
36
|
+
hide?: Id[];
|
|
37
|
+
/** Adds controls of the app's own to the layout. */
|
|
38
|
+
extra?: MobileControlsLayoutFactory<T>;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Applies a `LayoutCustomization` while a built-in layout is put together.
|
|
42
|
+
*/
|
|
43
|
+
export declare class LayoutBuilder<T extends IEntity, Id extends string> {
|
|
44
|
+
private readonly customization;
|
|
45
|
+
readonly result: MobileControlsLayoutItem[];
|
|
46
|
+
constructor(customization: LayoutCustomization<T, Id>);
|
|
47
|
+
/** Whether the control with this id is to be built at all. */
|
|
48
|
+
has(id: Id): boolean;
|
|
49
|
+
placement(id: Id, defaults: ControlPlacement): ControlPlacement;
|
|
50
|
+
icon(id: Id, defaults: string): string | Node;
|
|
51
|
+
/** Adds the control `create` makes, unless the app hid it. */
|
|
52
|
+
add(id: Id, create: () => TouchControl): void;
|
|
53
|
+
finish(controller: T, context: MobileControlsLayoutContext): MobileControlsLayoutItem[];
|
|
54
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Applies a `LayoutCustomization` while a built-in layout is put together.
|
|
3
|
+
*/
|
|
4
|
+
export class LayoutBuilder {
|
|
5
|
+
constructor(customization) {
|
|
6
|
+
this.customization = customization;
|
|
7
|
+
this.result = [];
|
|
8
|
+
}
|
|
9
|
+
/** Whether the control with this id is to be built at all. */
|
|
10
|
+
has(id) {
|
|
11
|
+
return !(this.customization.hide || []).includes(id);
|
|
12
|
+
}
|
|
13
|
+
placement(id, defaults) {
|
|
14
|
+
var _a;
|
|
15
|
+
const custom = (_a = this.customization.placements) === null || _a === void 0 ? void 0 : _a[id];
|
|
16
|
+
if (!custom) {
|
|
17
|
+
return defaults;
|
|
18
|
+
}
|
|
19
|
+
// an edge given by the app takes over from the opposite default one instead of stretching between
|
|
20
|
+
const merged = Object.assign(Object.assign({}, defaults), custom);
|
|
21
|
+
if (custom.left !== undefined && custom.right === undefined)
|
|
22
|
+
delete merged.right;
|
|
23
|
+
if (custom.right !== undefined && custom.left === undefined)
|
|
24
|
+
delete merged.left;
|
|
25
|
+
if (custom.top !== undefined && custom.bottom === undefined)
|
|
26
|
+
delete merged.bottom;
|
|
27
|
+
if (custom.bottom !== undefined && custom.top === undefined)
|
|
28
|
+
delete merged.top;
|
|
29
|
+
return merged;
|
|
30
|
+
}
|
|
31
|
+
icon(id, defaults) {
|
|
32
|
+
var _a, _b;
|
|
33
|
+
return (_b = (_a = this.customization.icons) === null || _a === void 0 ? void 0 : _a[id]) !== null && _b !== void 0 ? _b : defaults;
|
|
34
|
+
}
|
|
35
|
+
/** Adds the control `create` makes, unless the app hid it. */
|
|
36
|
+
add(id, create) {
|
|
37
|
+
if (this.has(id)) {
|
|
38
|
+
this.result.push(create());
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
finish(controller, context) {
|
|
42
|
+
if (this.customization.extra) {
|
|
43
|
+
this.result.push(...this.customization.extra(controller, context));
|
|
44
|
+
}
|
|
45
|
+
return this.result;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { GgWorld, IEntity, TickOrder } from '@gg-web-engine/core';
|
|
2
|
+
import { TouchControl } from './controls/touch-control';
|
|
3
|
+
import { CarLayoutOptions } from './layouts/car.layout';
|
|
4
|
+
import { Character2dLayoutOptions } from './layouts/character-2d.layout';
|
|
5
|
+
import { CharacterLayoutOptions } from './layouts/character.layout';
|
|
6
|
+
import { FreeCameraLayoutOptions } from './layouts/free-camera.layout';
|
|
7
|
+
import { MobileControlsLayoutFactory } from './mobile-controls-layout';
|
|
8
|
+
/**
|
|
9
|
+
* Whether the device is operated by touch first (a phone, a tablet) - what `enabled: 'auto'` goes by.
|
|
10
|
+
* A laptop that merely has a touch screen next to its mouse does not count.
|
|
11
|
+
*/
|
|
12
|
+
export declare function isTouchFirstDevice(): boolean;
|
|
13
|
+
export type MobileControlsOptions = {
|
|
14
|
+
/**
|
|
15
|
+
* `'auto'` (default): the overlay exists only on a touch-first device (`isTouchFirstDevice`).
|
|
16
|
+
* `true`: always, e.g. to try the controls with a mouse. `false`: never.
|
|
17
|
+
*/
|
|
18
|
+
enabled: boolean | 'auto';
|
|
19
|
+
/**
|
|
20
|
+
* The element the overlay is added to. `null` (default) puts it on `document.body`, covering the
|
|
21
|
+
* viewport - right for a game filling the page. For a canvas that takes a part of the page, or one
|
|
22
|
+
* that enters fullscreen through the Fullscreen API, pass an element wrapping the canvas (it has to
|
|
23
|
+
* be positioned, e.g. `position: relative`): the overlay then covers exactly that element.
|
|
24
|
+
*/
|
|
25
|
+
container: HTMLElement | null;
|
|
26
|
+
/** Extra CSS class names for the overlay element, e.g. one overriding its `--gg-mc-*` properties. */
|
|
27
|
+
className: string;
|
|
28
|
+
/** Whether to add the default stylesheet to the page. `true` by default. */
|
|
29
|
+
injectStyles: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Options of the built-in layout for `GgCarHandlingController` and a bare
|
|
32
|
+
* `CarHandlingController`, or `false` to have none.
|
|
33
|
+
*/
|
|
34
|
+
car: CarLayoutOptions | false;
|
|
35
|
+
/** Options of the built-in layout for `PlayerCharacterController`, or `false` to have none. */
|
|
36
|
+
character: CharacterLayoutOptions | false;
|
|
37
|
+
/** Options of the built-in layout for `PlayerCharacterController2d`, or `false` to have none. */
|
|
38
|
+
character2d: Character2dLayoutOptions | false;
|
|
39
|
+
/** Options of the built-in layout for `FreeCameraController`, or `false` to have none. */
|
|
40
|
+
freeCamera: FreeCameraLayoutOptions | false;
|
|
41
|
+
};
|
|
42
|
+
type ControllerClass<T extends IEntity> = abstract new (...args: any[]) => T;
|
|
43
|
+
/**
|
|
44
|
+
* An overlay of on-screen touch controls over the game canvas. Add it to a world once, and it shows
|
|
45
|
+
* the controls matching whichever input controller is active in that world at the moment, swapping
|
|
46
|
+
* them as controllers are activated, deactivated, spawned and removed:
|
|
47
|
+
*
|
|
48
|
+
* ```ts
|
|
49
|
+
* world.addEntity(new MobileControls());
|
|
50
|
+
* ```
|
|
51
|
+
*
|
|
52
|
+
* Out of the box it knows the car controllers (`GgCarHandlingController`,
|
|
53
|
+
* `CarHandlingController`), `PlayerCharacterController`, `PlayerCharacterController2d` and
|
|
54
|
+
* `FreeCameraController`. Each of those layouts is adjusted through
|
|
55
|
+
* its options (see `MobileControlsOptions`), replaced by `registerLayout`, and any other controller
|
|
56
|
+
* class - including an app's own - gets a layout the same way. Controls that belong to no controller
|
|
57
|
+
* (pause, a menu button) are added with `addControls`.
|
|
58
|
+
*/
|
|
59
|
+
export declare class MobileControls extends IEntity {
|
|
60
|
+
static readonly entityTypeName: string;
|
|
61
|
+
readonly tickOrder = TickOrder.INPUT_CONTROLLERS;
|
|
62
|
+
readonly options: MobileControlsOptions;
|
|
63
|
+
/** The overlay element. Created up front, attached to the page only while the overlay is shown. */
|
|
64
|
+
readonly element: HTMLElement;
|
|
65
|
+
private readonly registrations;
|
|
66
|
+
private readonly tracked;
|
|
67
|
+
private readonly layouts;
|
|
68
|
+
private readonly ownControls;
|
|
69
|
+
private _enabled;
|
|
70
|
+
private _visible;
|
|
71
|
+
get enabled(): boolean | 'auto';
|
|
72
|
+
/** See `MobileControlsOptions.enabled`. */
|
|
73
|
+
set enabled(value: boolean | 'auto');
|
|
74
|
+
get visible(): boolean;
|
|
75
|
+
/**
|
|
76
|
+
* Hides the overlay without disabling it, for the moments touch controls are in the way (a menu,
|
|
77
|
+
* a cutscene). Everything held is released.
|
|
78
|
+
*/
|
|
79
|
+
set visible(value: boolean);
|
|
80
|
+
/** Whether the overlay is on the page right now. */
|
|
81
|
+
get shown(): boolean;
|
|
82
|
+
/** The controllers that currently have their controls on screen. */
|
|
83
|
+
get activeControllers(): IEntity[];
|
|
84
|
+
constructor(options?: Partial<MobileControlsOptions>);
|
|
85
|
+
/**
|
|
86
|
+
* Sets the layout shown while a controller of `controllerClass` (or of a subclass) is active,
|
|
87
|
+
* replacing a layout registered for the same class before; `null` leaves that class without one.
|
|
88
|
+
* Of several registered classes a controller is an instance of, the one registered last wins.
|
|
89
|
+
*/
|
|
90
|
+
registerLayout<T extends IEntity>(controllerClass: ControllerClass<T>, factory: MobileControlsLayoutFactory<T> | null): void;
|
|
91
|
+
/**
|
|
92
|
+
* Adds controls that stay on screen whichever controller is active, on top of the layouts.
|
|
93
|
+
*/
|
|
94
|
+
addControls(...controls: TouchControl[]): void;
|
|
95
|
+
/** Removes controls added by `addControls` and disposes them. */
|
|
96
|
+
removeControls(...controls: TouchControl[]): void;
|
|
97
|
+
/**
|
|
98
|
+
* Builds the layout of `controller` anew - after something its layout was built from has changed
|
|
99
|
+
* (the car it drives, an option), since a layout is otherwise built once per activation.
|
|
100
|
+
*/
|
|
101
|
+
refresh(controller?: IEntity): void;
|
|
102
|
+
onSpawned(world: GgWorld<any, any>): void;
|
|
103
|
+
onRemoved(): void;
|
|
104
|
+
dispose(): void;
|
|
105
|
+
/** Returns every control on screen to its untouched state. */
|
|
106
|
+
releaseAll(): void;
|
|
107
|
+
private findFactory;
|
|
108
|
+
private rescan;
|
|
109
|
+
private sync;
|
|
110
|
+
private disposeLayout;
|
|
111
|
+
}
|
|
112
|
+
export {};
|