]> git.r.bdr.sh - rbdr/sumo/blame_incremental - lib/systems/control_mapper.js
Add gamepad support (#10)
[rbdr/sumo] / lib / systems / control_mapper.js
... / ...
CommitLineData
1import { System } from '@serpentity/serpentity';
2
3import ControllableNode from '../nodes/controllable';
4
5/* global navigator window */
6
7const internals = {
8 gamepadState: {
9 },
10 keyboardState: {
11 }
12};
13
14/**
15 * Updates control status based on the controller map
16 *
17 * @extends {external:Serpentity.System}
18 * @class ControlMapperSystem
19 * @param {object} config a configuration object to extend.
20 */
21export default class ControlMapperSystem extends System {
22
23 constructor(config = {}) {
24
25 super();
26
27 /**
28 * The node collection of controllable entities
29 *
30 * @property {external:Serpentity.NodeCollection} controllables
31 * @instance
32 * @memberof RenderSystem
33 */
34 this.controllables = null;
35
36 this._initializeKeyboard();
37 this._initializeGamepad();
38 }
39
40 /**
41 * Initializes system when added. Requests controllable nodes.
42 *
43 * @function added
44 * @memberof RenderSystem
45 * @instance
46 * @param {external:Serpentity.Engine} engine the serpentity engine to
47 * which we are getting added
48 */
49 added(engine) {
50
51 this.controllables = engine.getNodes(ControllableNode);
52 }
53
54 /**
55 * Clears system resources when removed.
56 *
57 * @function removed
58 * @instance
59 * @memberof RenderSystem
60 */
61 removed() {
62
63 this.controllables = null;
64 }
65
66 /**
67 * Runs on every update of the loop. Maps the actions given the current state of the inputs
68 *
69 * @function update
70 * @instance
71 * @param {Number} currentFrameDuration the duration of the current
72 * frame
73 * @memberof RenderSystem
74 */
75 update(currentFrameDuration) {
76
77 this._updateGamepads();
78
79 for (const controllable of this.controllables) {
80 for (const map of controllable.controlMap.map) {
81 if (map.source.type === 'keyboard') {
82 this._setValue(controllable.entity, map.target, !!internals.keyboardState[map.source.index]);
83 }
84
85 if (map.source.type === 'gamepad') {
86 const gamepad = internals.gamepadState[map.source.gamepadNumber];
87 if (gamepad) {
88 const source = gamepad[map.source.gamepadInputSource];
89 source && this._setValue(controllable.entity, map.target, source[map.source.index]);
90 }
91 }
92 }
93 }
94 }
95
96 // Listens to keyboard to update internal map
97
98 _initializeKeyboard() {
99
100 window.addEventListener('keydown', (event) => {
101
102 internals.keyboardState[event.keyCode] = true;
103 });
104
105 window.addEventListener('keyup', (event) => {
106
107 internals.keyboardState[event.keyCode] = false;
108 });
109 }
110
111 // Requests gamepad access and binds to the events
112
113 _initializeGamepad() {
114
115 window.addEventListener('gamepadconnected', (event) => {
116
117 internals.gamepadState[event.gamepad.index] = event.gamepad;
118 window.gamepad = event.gamepad;
119 });
120
121 window.addEventListener('gamepaddisconnected', (event) => {
122
123 delete internals.gamepadState[event.gamepad.index];
124 });
125 }
126
127 // Update Gamepad
128
129 _updateGamepads() {
130
131 const gamepads = navigator.getGamepads();
132 for (const index of Object.keys(internals.gamepadState)) {
133 internals.gamepadState[index] = gamepads[index];
134 }
135 }
136
137 // Sets the value to a target
138
139 _setValue(entity, target, value) {
140
141 const component = entity.getComponent(target.component);
142
143 if (component) {
144 const keyFragments = target.property.split('.');
145 let currentObject = component;
146 for (const keyFragment of keyFragments.slice(0, keyFragments.length - 1)) {
147 currentObject = currentObject[keyFragment] = currentObject[keyFragment] || {};
148 }
149
150
151 const finalValue = !!target.value ? target.value(value) : value;
152 const finalProperty = keyFragments.pop();
153 currentObject[finalProperty] += finalValue;
154 }
155 }
156};
157