@quenty/inputkeymaputils 4.2.1-canary.256.edbbcfc.0 → 5.0.0
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/CHANGELOG.md +9 -1
- package/LICENSE.md +1 -1
- package/README.md +6 -2
- package/package.json +14 -5
- package/src/Client/InputKeyMap.lua +55 -0
- package/src/Client/InputKeyMapList.lua +364 -0
- package/src/Client/InputKeyMapListProvider.lua +147 -0
- package/src/Client/InputKeyMapServiceClient.lua +66 -0
- package/src/Client/ProximityPromptInputUtils.lua +7 -6
- package/src/Client/Types/InputTypeUtils.lua +67 -0
- package/src/Client/Types/SlottedTouchButtonUtils.lua +98 -0
- package/test/default.project.json +27 -0
- package/test/modules/Client/TestInputKeyMap.lua +25 -0
- package/test/scripts/Client/ClientMain.client.lua +28 -0
- package/test/scripts/Server/ServerMain.server.lua +7 -0
- package/src/Client/InputKeyMapUtils.lua +0 -320
package/CHANGELOG.md
CHANGED
|
@@ -3,7 +3,15 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
# [5.0.0](https://github.com/Quenty/NevermoreEngine/compare/@quenty/inputkeymaputils@4.3.0...@quenty/inputkeymaputils@5.0.0) (2022-05-21)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @quenty/inputkeymaputils
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# [4.3.0](https://github.com/Quenty/NevermoreEngine/compare/@quenty/inputkeymaputils@4.2.0...@quenty/inputkeymaputils@4.3.0) (2022-03-27)
|
|
7
15
|
|
|
8
16
|
**Note:** Version bump only for package @quenty/inputkeymaputils
|
|
9
17
|
|
package/LICENSE.md
CHANGED
package/README.md
CHANGED
|
@@ -13,9 +13,13 @@
|
|
|
13
13
|
|
|
14
14
|
Utility methods for input map
|
|
15
15
|
|
|
16
|
-
<div align="center"><a href="https://quenty.github.io/NevermoreEngine/api/
|
|
16
|
+
<div align="center"><a href="https://quenty.github.io/NevermoreEngine/api/InputKeyMapList">View docs →</a></div>
|
|
17
17
|
|
|
18
18
|
## Installation
|
|
19
19
|
```
|
|
20
20
|
npm install @quenty/inputkeymaputils --save
|
|
21
|
-
```
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Design
|
|
24
|
+
|
|
25
|
+
Note this system is designed to simple hold data, it doesn't do any binding with that data. So basically, an InputKeyMap can hold the information on what to bind, and what preferences are in a structured way.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quenty/inputkeymaputils",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0",
|
|
4
4
|
"description": "Utility methods for input map",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"Roblox",
|
|
@@ -26,12 +26,21 @@
|
|
|
26
26
|
"Quenty"
|
|
27
27
|
],
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@quenty/
|
|
30
|
-
"@quenty/
|
|
31
|
-
"@quenty/
|
|
29
|
+
"@quenty/baseobject": "^5.0.0",
|
|
30
|
+
"@quenty/brio": "^6.0.0",
|
|
31
|
+
"@quenty/inputmode": "^5.0.0",
|
|
32
|
+
"@quenty/loader": "^5.0.0",
|
|
33
|
+
"@quenty/maid": "^2.3.0",
|
|
34
|
+
"@quenty/observablecollection": "^3.0.0",
|
|
35
|
+
"@quenty/rx": "^5.0.0",
|
|
36
|
+
"@quenty/servicebag": "^5.0.0",
|
|
37
|
+
"@quenty/statestack": "^6.0.0",
|
|
38
|
+
"@quenty/table": "^3.0.0",
|
|
39
|
+
"@quenty/valuebaseutils": "^5.0.0",
|
|
40
|
+
"@quenty/valueobject": "^5.0.0"
|
|
32
41
|
},
|
|
33
42
|
"publishConfig": {
|
|
34
43
|
"access": "public"
|
|
35
44
|
},
|
|
36
|
-
"gitHead": "
|
|
45
|
+
"gitHead": "9f7eaea7543c33c89d2e32c38491b13f9271f4f7"
|
|
37
46
|
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
--[=[
|
|
2
|
+
This represents a list of key bindings for a specific mode. While this is a useful object to query
|
|
3
|
+
for showing icons and input hints to the user, in general, it is recommended that binding occur
|
|
4
|
+
at the list level instead of at the input mode level. That way, if the user switches to another input
|
|
5
|
+
mode then input is immediately processed.
|
|
6
|
+
|
|
7
|
+
@class InputKeyMap
|
|
8
|
+
]=]
|
|
9
|
+
|
|
10
|
+
local require = require(script.Parent.loader).load(script)
|
|
11
|
+
|
|
12
|
+
local BaseObject = require("BaseObject")
|
|
13
|
+
local ValueObject = require("ValueObject")
|
|
14
|
+
local InputMode = require("InputMode")
|
|
15
|
+
|
|
16
|
+
local InputKeyMap = setmetatable({}, BaseObject)
|
|
17
|
+
InputKeyMap.ClassName = "InputKeyMap"
|
|
18
|
+
InputKeyMap.__index = InputKeyMap
|
|
19
|
+
|
|
20
|
+
function InputKeyMap.new(inputMode, inputTypes)
|
|
21
|
+
assert(InputMode.isInputMode(inputMode), "Bad inputMode")
|
|
22
|
+
assert(type(inputTypes) == "table" or inputTypes == nil, "Bad inputTypes")
|
|
23
|
+
|
|
24
|
+
local self = setmetatable(BaseObject.new(), InputKeyMap)
|
|
25
|
+
|
|
26
|
+
self._inputMode = assert(inputMode, "No inputMode")
|
|
27
|
+
|
|
28
|
+
self._inputType = ValueObject.new(inputTypes or {})
|
|
29
|
+
self._maid:GiveTask(self._inputType)
|
|
30
|
+
|
|
31
|
+
return self
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
--[=[
|
|
35
|
+
Gets the input mode for this keymap. This will not change.
|
|
36
|
+
]=]
|
|
37
|
+
function InputKeyMap:GetInputMode()
|
|
38
|
+
return self._inputMode
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
function InputKeyMap:SetInputTypesList(inputTypes)
|
|
42
|
+
assert(type(inputTypes) == "table", "Bad inputTypes")
|
|
43
|
+
|
|
44
|
+
self._inputType.Value = inputTypes
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
function InputKeyMap:ObserveInputTypesList()
|
|
48
|
+
return self._inputType:Observe()
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
function InputKeyMap:GetInputTypesList()
|
|
52
|
+
return self._inputType.Value
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
return InputKeyMap
|
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
--[=[
|
|
2
|
+
An input key map list provides a mapping of input modes to input keys.
|
|
3
|
+
One of these should generally exist per an action with unique bindings.
|
|
4
|
+
|
|
5
|
+
All inputs should be bound while this action is active. We can further
|
|
6
|
+
query inputs per an input mode to display only relevant key bindings to
|
|
7
|
+
the user.
|
|
8
|
+
|
|
9
|
+
```lua
|
|
10
|
+
local inputKeyMapList = InputKeyMapList.new("BOOST", {
|
|
11
|
+
InputKeyMap.new(INPUT_MODES.KeyboardAndMouse, { Enum.KeyCode.LeftControl });
|
|
12
|
+
InputKeyMap.new(INPUT_MODES.Gamepads, { Enum.KeyCode.ButtonX });
|
|
13
|
+
InputKeyMap.new(INPUT_MODES.Touch, { SlottedTouchButtonUtils.createSlottedTouchButton("primary1") });
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
maid:GiveTask(Rx.combineLatest({
|
|
17
|
+
isRobloxTouchButton = inputKeyMapList:ObserveIsRobloxTouchButton();
|
|
18
|
+
inputEnumsList = inputKeyMapList:ObserveInputEnumsList();
|
|
19
|
+
}):Subscribe(function(state)
|
|
20
|
+
maid._contextMaid = nil
|
|
21
|
+
|
|
22
|
+
local contextMaid = Maid.new()
|
|
23
|
+
|
|
24
|
+
ContextActionService:BindActionAtPriority(
|
|
25
|
+
"MyAction",
|
|
26
|
+
function(_actionName, userInputState, inputObject)
|
|
27
|
+
print("Process input", inputObject)
|
|
28
|
+
end,
|
|
29
|
+
state.isRobloxTouchButton,
|
|
30
|
+
Enum.ContextActionPriority.High.Value,
|
|
31
|
+
unpack(state.inputEnumsList))
|
|
32
|
+
|
|
33
|
+
maid._contextMaid = contextMaid
|
|
34
|
+
end))
|
|
35
|
+
end))
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
@class InputKeyMapList
|
|
39
|
+
]=]
|
|
40
|
+
|
|
41
|
+
local require = require(script.Parent.loader).load(script)
|
|
42
|
+
|
|
43
|
+
local InputKeyMap = require("InputKeyMap")
|
|
44
|
+
local ObservableMap = require("ObservableMap")
|
|
45
|
+
local BaseObject = require("BaseObject")
|
|
46
|
+
local InputModeSelector = require("InputModeSelector")
|
|
47
|
+
local Observable = require("Observable")
|
|
48
|
+
local Maid = require("Maid")
|
|
49
|
+
local Rx = require("Rx")
|
|
50
|
+
local ObservableCountingMap = require("ObservableCountingMap")
|
|
51
|
+
local InputMode = require("InputMode")
|
|
52
|
+
local SlottedTouchButtonUtils = require("SlottedTouchButtonUtils")
|
|
53
|
+
local RxBrioUtils = require("RxBrioUtils")
|
|
54
|
+
local Brio = require("Brio")
|
|
55
|
+
local StateStack = require("StateStack")
|
|
56
|
+
local InputTypeUtils = require("InputTypeUtils")
|
|
57
|
+
|
|
58
|
+
local InputKeyMapList = setmetatable({}, BaseObject)
|
|
59
|
+
InputKeyMapList.ClassName = "InputKeyMapList"
|
|
60
|
+
InputKeyMapList.__index = InputKeyMapList
|
|
61
|
+
|
|
62
|
+
--[=[
|
|
63
|
+
Constructs a new InputKeyMapList
|
|
64
|
+
|
|
65
|
+
@param inputMapName string
|
|
66
|
+
@param inputKeyMapList { InputKeyMap }
|
|
67
|
+
@return InputKeyMapList
|
|
68
|
+
]=]
|
|
69
|
+
function InputKeyMapList.new(inputMapName, inputKeyMapList)
|
|
70
|
+
local self = setmetatable(BaseObject.new(), InputKeyMapList)
|
|
71
|
+
|
|
72
|
+
self._inputKeyMapListName = assert(inputMapName, "No inputMapName")
|
|
73
|
+
|
|
74
|
+
self._inputModeToInputKeyMap = ObservableMap.new()
|
|
75
|
+
self._maid:GiveTask(self._inputModeToInputKeyMap)
|
|
76
|
+
|
|
77
|
+
for _, inputKeyMap in pairs(inputKeyMapList) do
|
|
78
|
+
self:Add(inputKeyMap)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
return self
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
--[=[
|
|
85
|
+
Returns whether this value is an InputKeyMapList
|
|
86
|
+
|
|
87
|
+
@param value any
|
|
88
|
+
@return boolean
|
|
89
|
+
]=]
|
|
90
|
+
function InputKeyMapList.isInputKeyMapList(value)
|
|
91
|
+
return type(value) == "table" and getmetatable(value) == InputKeyMapList
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
--[=[
|
|
95
|
+
Adds an input key map into the actual list
|
|
96
|
+
@param inputKeyMap InputKeyMap
|
|
97
|
+
]=]
|
|
98
|
+
function InputKeyMapList:Add(inputKeyMap)
|
|
99
|
+
assert(inputKeyMap, "Bad inputKeyMap")
|
|
100
|
+
|
|
101
|
+
self._maid[inputKeyMap:GetInputMode()] = inputKeyMap
|
|
102
|
+
self._inputModeToInputKeyMap:Set(inputKeyMap:GetInputMode(), inputKeyMap)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
--[=[
|
|
106
|
+
Gets the list name and returns it. Used by an input key map provider
|
|
107
|
+
@return string
|
|
108
|
+
]=]
|
|
109
|
+
function InputKeyMapList:GetListName()
|
|
110
|
+
return self._inputKeyMapListName
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
function InputKeyMapList:SetInputTypesList(inputMode, inputTypes)
|
|
114
|
+
assert(InputMode.isInputMode(inputMode), "Bad inputMode")
|
|
115
|
+
assert(type(inputTypes) == "table" or inputTypes == nil, "Bad inputTypes")
|
|
116
|
+
|
|
117
|
+
if inputTypes == nil then
|
|
118
|
+
self._inputModeToInputKeyMap:Remove(inputMode)
|
|
119
|
+
self._maid[inputMode] = nil
|
|
120
|
+
else
|
|
121
|
+
local inputKeyMap = self._inputModeToInputKeyMap:Get(inputMode)
|
|
122
|
+
if not inputKeyMap then
|
|
123
|
+
self:Add(InputKeyMap.new(inputMode, inputTypes))
|
|
124
|
+
else
|
|
125
|
+
inputKeyMap:SetInputTypesList(inputTypes)
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
--[=[
|
|
131
|
+
Removes the entry for the inputmode
|
|
132
|
+
|
|
133
|
+
@param inputMode InputMode
|
|
134
|
+
]=]
|
|
135
|
+
function InputKeyMapList:RemoveInputMode(inputMode)
|
|
136
|
+
assert(InputMode.isInputMode(inputMode), "Bad inputMode")
|
|
137
|
+
|
|
138
|
+
self:SetInputTypesList(inputMode, nil)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
--[=[
|
|
142
|
+
Observes the input enums list
|
|
143
|
+
|
|
144
|
+
@return InputModeSelector
|
|
145
|
+
]=]
|
|
146
|
+
function InputKeyMapList:GetNewInputModeSelector()
|
|
147
|
+
return InputModeSelector.fromObservableBrio(self:ObserveInputModesBrio())
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
--[=[
|
|
151
|
+
@return Observable<Brio<InputKeyMap>>
|
|
152
|
+
]=]
|
|
153
|
+
function InputKeyMapList:ObserveInputKeyMapsBrio()
|
|
154
|
+
return self._inputModeToInputKeyMap:ObserveValuesBrio()
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
--[=[
|
|
158
|
+
@return Observable<Brio<InputMode>>
|
|
159
|
+
]=]
|
|
160
|
+
function InputKeyMapList:ObserveInputModesBrio()
|
|
161
|
+
return self._inputModeToInputKeyMap:ObserveKeysBrio()
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
--[=[
|
|
165
|
+
Observes the input types for the active input map
|
|
166
|
+
|
|
167
|
+
@return Observable<InputKeyMap>
|
|
168
|
+
]=]
|
|
169
|
+
function InputKeyMapList:ObserveActiveInputKeyMap()
|
|
170
|
+
return self:ObserveActiveInputMode():Pipe({
|
|
171
|
+
Rx.switchMap(function(activeInputMode)
|
|
172
|
+
if activeInputMode then
|
|
173
|
+
return self._inputModeToInputKeyMap:ObserveValueForKey(activeInputMode)
|
|
174
|
+
else
|
|
175
|
+
return Rx.of(nil)
|
|
176
|
+
end
|
|
177
|
+
end);
|
|
178
|
+
})
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
--[=[
|
|
182
|
+
Observes the input types for the active input map.
|
|
183
|
+
|
|
184
|
+
:::warning
|
|
185
|
+
This should be used for hinting inputs, but it's preferred to
|
|
186
|
+
bind inputs for all modes. See [InputKeyMapList.ObserveInputEnumsList]
|
|
187
|
+
:::
|
|
188
|
+
|
|
189
|
+
@return Observable<{ InputType }?>
|
|
190
|
+
]=]
|
|
191
|
+
function InputKeyMapList:ObserveActiveInputTypesList()
|
|
192
|
+
return self:ObserveActiveInputKeyMap():Pipe({
|
|
193
|
+
Rx.switchMap(function(activeInputMap)
|
|
194
|
+
if activeInputMap then
|
|
195
|
+
return activeInputMap:ObserveInputTypesList()
|
|
196
|
+
else
|
|
197
|
+
return Rx.of(nil)
|
|
198
|
+
end
|
|
199
|
+
end);
|
|
200
|
+
Rx.distinct();
|
|
201
|
+
})
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
--[=[
|
|
205
|
+
Observes the active input mode currently selected.
|
|
206
|
+
|
|
207
|
+
@return Observable<InputMode?>
|
|
208
|
+
]=]
|
|
209
|
+
function InputKeyMapList:ObserveActiveInputMode()
|
|
210
|
+
return Observable.new(function(sub)
|
|
211
|
+
local maid = Maid.new()
|
|
212
|
+
|
|
213
|
+
local selector = self:GetNewInputModeSelector()
|
|
214
|
+
maid:GiveTask(selector)
|
|
215
|
+
|
|
216
|
+
maid:GiveTask(selector.Changed:Connect(function()
|
|
217
|
+
sub:Fire(selector.Value)
|
|
218
|
+
end))
|
|
219
|
+
sub:Fire(selector.Value)
|
|
220
|
+
|
|
221
|
+
return maid
|
|
222
|
+
end)
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
--[=[
|
|
226
|
+
Observes whether the input list includes tapping in the world somewhere.
|
|
227
|
+
|
|
228
|
+
@return Observable<boolean>
|
|
229
|
+
]=]
|
|
230
|
+
function InputKeyMapList:ObserveIsTapInWorld()
|
|
231
|
+
self:_ensureInit()
|
|
232
|
+
|
|
233
|
+
return self._isTapInWorld:Observe()
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
--[=[
|
|
237
|
+
Observes whether the input list includes a Roblox button.
|
|
238
|
+
|
|
239
|
+
@return Observable<boolean>
|
|
240
|
+
]=]
|
|
241
|
+
function InputKeyMapList:ObserveIsRobloxTouchButton()
|
|
242
|
+
self:_ensureInit()
|
|
243
|
+
|
|
244
|
+
return self._isRobloxTouchButton:Observe()
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
--[=[
|
|
248
|
+
Gets whether the input list includes a Roblox button.
|
|
249
|
+
|
|
250
|
+
@return boolean
|
|
251
|
+
]=]
|
|
252
|
+
function InputKeyMapList:IsRobloxTouchButton()
|
|
253
|
+
self:_ensureInit()
|
|
254
|
+
|
|
255
|
+
return self._isRobloxTouchButton:GetState()
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
--[=[
|
|
259
|
+
Gets whether the input list includes a Roblox button.
|
|
260
|
+
|
|
261
|
+
@return boolean
|
|
262
|
+
]=]
|
|
263
|
+
function InputKeyMapList:IsTouchTapInWorld()
|
|
264
|
+
self:_ensureInit()
|
|
265
|
+
|
|
266
|
+
return self._isTapInWorld:GetState()
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
--[=[
|
|
270
|
+
Observes the input enums list, which can be used for bindings.
|
|
271
|
+
|
|
272
|
+
@return Observable<{UserInputType | KeyCode}>
|
|
273
|
+
]=]
|
|
274
|
+
function InputKeyMapList:ObserveInputEnumsList()
|
|
275
|
+
self:_ensureInit()
|
|
276
|
+
|
|
277
|
+
return self._inputTypesForBinding:ObserveKeysList()
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
--[=[
|
|
281
|
+
Observes the input enums set
|
|
282
|
+
|
|
283
|
+
@return Observable<{[UserInputType | KeyCode]: true }>
|
|
284
|
+
]=]
|
|
285
|
+
function InputKeyMapList:ObserveInputEnumsSet()
|
|
286
|
+
self:_ensureInit()
|
|
287
|
+
|
|
288
|
+
return self._inputTypesForBinding:ObserveKeysSet()
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
--[=[
|
|
292
|
+
Observes slotted touch button data in the input modes.
|
|
293
|
+
|
|
294
|
+
@return Observable<SlottedTouchButton>
|
|
295
|
+
]=]
|
|
296
|
+
function InputKeyMapList:ObserveSlottedTouchButtonDataBrio()
|
|
297
|
+
return self._inputModeToInputKeyMap:ObservePairsBrio():Pipe({
|
|
298
|
+
RxBrioUtils.flatMapBrio(function(inputMode, inputKeyMap)
|
|
299
|
+
return inputKeyMap:ObserveInputTypesList():Pipe({
|
|
300
|
+
Rx.switchMap(function(inputTypesList)
|
|
301
|
+
local valid = {}
|
|
302
|
+
for _, inputType in pairs(inputTypesList) do
|
|
303
|
+
if SlottedTouchButtonUtils.isSlottedTouchButton(inputType) then
|
|
304
|
+
local data = SlottedTouchButtonUtils.createTouchButtonData(inputType.slotId, inputMode)
|
|
305
|
+
table.insert(valid, Brio.new(data))
|
|
306
|
+
end
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
if not next(valid) then
|
|
310
|
+
return Rx.EMPTY
|
|
311
|
+
else
|
|
312
|
+
return Rx.of(unpack(valid))
|
|
313
|
+
end
|
|
314
|
+
end)
|
|
315
|
+
})
|
|
316
|
+
end);
|
|
317
|
+
})
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
function InputKeyMapList:_ensureInit()
|
|
321
|
+
if self._inputTypesForBinding then
|
|
322
|
+
return self._inputTypesForBinding
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
self._inputTypesForBinding = ObservableCountingMap.new()
|
|
326
|
+
self._maid:GiveTask(self._inputTypesForBinding)
|
|
327
|
+
|
|
328
|
+
self._isTapInWorld = StateStack.new(false)
|
|
329
|
+
self._maid:GiveTask(self._isTapInWorld)
|
|
330
|
+
|
|
331
|
+
self._isRobloxTouchButton = StateStack.new(false)
|
|
332
|
+
self._maid:GiveTask(self._isRobloxTouchButton)
|
|
333
|
+
|
|
334
|
+
-- Listen
|
|
335
|
+
self._maid:GiveTask(self._inputModeToInputKeyMap:ObserveValuesBrio():Subscribe(function(brio)
|
|
336
|
+
if brio:IsDead() then
|
|
337
|
+
return
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
local inputKeyMapMaid = brio:ToMaid()
|
|
341
|
+
local inputKeyMap = brio:GetValue()
|
|
342
|
+
|
|
343
|
+
inputKeyMapMaid:GiveTask(inputKeyMap:ObserveInputTypesList():Subscribe(function(inputTypes)
|
|
344
|
+
local maid = Maid.new()
|
|
345
|
+
|
|
346
|
+
for _, inputType in pairs(inputTypes) do
|
|
347
|
+
-- only emit enum items
|
|
348
|
+
if typeof(inputType) == "EnumItem" then
|
|
349
|
+
maid:GiveTask(self._inputTypesForBinding:Add(inputType))
|
|
350
|
+
elseif InputTypeUtils.isTapInWorld(inputType) then
|
|
351
|
+
maid:GiveTask(self._isTapInWorld:PushState(true))
|
|
352
|
+
elseif InputTypeUtils.isRobloxTouchButton(inputType) then
|
|
353
|
+
maid:GiveTask(self._isRobloxTouchButton:PushState(true))
|
|
354
|
+
end
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
inputKeyMapMaid._current = maid
|
|
358
|
+
end))
|
|
359
|
+
end))
|
|
360
|
+
|
|
361
|
+
return self._inputTypesForBinding
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
return InputKeyMapList
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
--[=[
|
|
2
|
+
Centralizes input of keys. You can construct a new provider in a
|
|
3
|
+
package and key bindings can be recovered from it. This is designed
|
|
4
|
+
for user configuration/rebindings.
|
|
5
|
+
|
|
6
|
+
```lua
|
|
7
|
+
local inputMapProvider = InputKeyMapListProvider.new("General", function(self)
|
|
8
|
+
self:Add(InputKeyMapList.new("JUMP", {
|
|
9
|
+
InputKeyMap.new(INPUT_MODES.KeyboardAndMouse, { Enum.KeyCode.Space });
|
|
10
|
+
InputKeyMap.new(INPUT_MODES.Gamepads, { Enum.KeyCode.ButtonA });
|
|
11
|
+
InputKeyMap.new(INPUT_MODES.Touch, { SlottedTouchButtonUtils.createSlottedTouchButton("primary3") });
|
|
12
|
+
}))
|
|
13
|
+
self:Add(InputKeyMapList.new("HONK", {
|
|
14
|
+
InputKeyMap.new(INPUT_MODES.KeyboardAndMouse, { Enum.KeyCode.H });
|
|
15
|
+
InputKeyMap.new(INPUT_MODES.Gamepads, { Enum.KeyCode.DPadUp });
|
|
16
|
+
InputKeyMap.new(INPUT_MODES.Touch, { SlottedTouchButtonUtils.createSlottedTouchButton("primary2") });
|
|
17
|
+
}))
|
|
18
|
+
self:Add(InputKeyMapList.new("BOOST", {
|
|
19
|
+
InputKeyMap.new(INPUT_MODES.KeyboardAndMouse, { Enum.KeyCode.LeftControl });
|
|
20
|
+
InputKeyMap.new(INPUT_MODES.Gamepads, { Enum.KeyCode.ButtonX });
|
|
21
|
+
InputKeyMap.new(INPUT_MODES.Touch, { SlottedTouchButtonUtils.createSlottedTouchButton("primary4") });
|
|
22
|
+
}))
|
|
23
|
+
end)
|
|
24
|
+
|
|
25
|
+
local inputMap = serviceBag:GetService(inputMapProvider)
|
|
26
|
+
|
|
27
|
+
serviceBag:Init()
|
|
28
|
+
serviceBag:Start()
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
@class InputKeyMapListProvider
|
|
32
|
+
]=]
|
|
33
|
+
|
|
34
|
+
local require = require(script.Parent.loader).load(script)
|
|
35
|
+
|
|
36
|
+
local RunService = game:GetService("RunService")
|
|
37
|
+
|
|
38
|
+
local Maid = require("Maid")
|
|
39
|
+
local InputKeyMapServiceClient = require("InputKeyMapServiceClient")
|
|
40
|
+
|
|
41
|
+
local InputKeyMapListProvider = {}
|
|
42
|
+
InputKeyMapListProvider.ClassName = "InputKeyMapListProvider"
|
|
43
|
+
InputKeyMapListProvider.__index = InputKeyMapListProvider
|
|
44
|
+
|
|
45
|
+
--[=[
|
|
46
|
+
Constructs a new InputKeyMapListProvider. The name will be used for retrieval,
|
|
47
|
+
for example, if the dialog system needs to get a general input hint to show
|
|
48
|
+
to the user.
|
|
49
|
+
|
|
50
|
+
@param providerName string -- Name to use for global specification.
|
|
51
|
+
@param createDefaults callback -- Callback to construct the default items on init
|
|
52
|
+
@return InputKeyMapList
|
|
53
|
+
]=]
|
|
54
|
+
function InputKeyMapListProvider.new(providerName, createDefaults)
|
|
55
|
+
local self = setmetatable({}, InputKeyMapListProvider)
|
|
56
|
+
|
|
57
|
+
self._providerName = assert(providerName, "No providerName")
|
|
58
|
+
self._createDefaults = assert(createDefaults, "No createDefaults")
|
|
59
|
+
|
|
60
|
+
return self
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
--[=[
|
|
64
|
+
Gets this providers name
|
|
65
|
+
@return string
|
|
66
|
+
]=]
|
|
67
|
+
function InputKeyMapListProvider:GetProviderName()
|
|
68
|
+
return self._providerName
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
--[=[
|
|
72
|
+
Gets an input key map list for the given name. Errors if it is not
|
|
73
|
+
defined.
|
|
74
|
+
|
|
75
|
+
@param keyMapListName string
|
|
76
|
+
@return InputKeyMapList
|
|
77
|
+
]=]
|
|
78
|
+
function InputKeyMapListProvider:GetInputKeyMapList(keyMapListName)
|
|
79
|
+
local keyMapList = self:FindInputKeyMapList(keyMapListName)
|
|
80
|
+
if not keyMapList then
|
|
81
|
+
error(("Bad keyMapListName %q"):format(tostring(keyMapListName)))
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
return keyMapList
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
--[=[
|
|
88
|
+
Finds an input key map list for the given name
|
|
89
|
+
@param keyMapListName string
|
|
90
|
+
@return InputKeyMapList
|
|
91
|
+
]=]
|
|
92
|
+
function InputKeyMapListProvider:FindInputKeyMapList(keyMapListName)
|
|
93
|
+
assert(type(keyMapListName) == "string", "Bad keyMapListName")
|
|
94
|
+
|
|
95
|
+
if not self._inputKeyMapLists then
|
|
96
|
+
if not RunService:IsRunning() then
|
|
97
|
+
-- Test mode initialize
|
|
98
|
+
self._maid = Maid.new()
|
|
99
|
+
self:_ensureDefaultsInit()
|
|
100
|
+
else
|
|
101
|
+
error("Not initialized, make sure to retrieve via serviceBag and init")
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
return self._inputKeyMapLists[keyMapListName]
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
function InputKeyMapListProvider:Add(inputKeyMapList)
|
|
109
|
+
assert(inputKeyMapList, "Bad inputKeyMapList")
|
|
110
|
+
assert(self._maid, "Not initialized")
|
|
111
|
+
|
|
112
|
+
if self._inputKeyMapLists[inputKeyMapList:GetListName()] then
|
|
113
|
+
error(("Already added %q"):format(inputKeyMapList:GetListName()))
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
self._inputKeyMapLists[inputKeyMapList:GetListName()] = inputKeyMapList
|
|
117
|
+
self._maid:GiveTask(inputKeyMapList)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
function InputKeyMapListProvider:Init(serviceBag)
|
|
121
|
+
assert(not self._serviceBag, "Already initialized")
|
|
122
|
+
|
|
123
|
+
self._serviceBag = assert(serviceBag, "No serviceBag")
|
|
124
|
+
self._serviceBag:GetService(InputKeyMapServiceClient):RegisterProvider(self)
|
|
125
|
+
self._maid = Maid.new()
|
|
126
|
+
|
|
127
|
+
self:_ensureDefaultsInit()
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
function InputKeyMapListProvider:_ensureDefaultsInit()
|
|
131
|
+
if not self._inputKeyMapLists then
|
|
132
|
+
self._inputKeyMapLists = {}
|
|
133
|
+
|
|
134
|
+
self._createDefaults(self, self._serviceBag)
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
function InputKeyMapListProvider:Start()
|
|
139
|
+
-- empty function
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
function InputKeyMapListProvider:Destroy()
|
|
143
|
+
self._maid:DoCleaning()
|
|
144
|
+
self._inputKeyMapLists = nil
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
return InputKeyMapListProvider
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
--[=[
|
|
2
|
+
Not required to be initialized
|
|
3
|
+
@class InputKeyMapServiceClient
|
|
4
|
+
]=]
|
|
5
|
+
|
|
6
|
+
local require = require(script.Parent.loader).load(script)
|
|
7
|
+
|
|
8
|
+
local RunService = game:GetService("RunService")
|
|
9
|
+
|
|
10
|
+
local InputKeyMapServiceClient = {}
|
|
11
|
+
|
|
12
|
+
function InputKeyMapServiceClient:Init(serviceBag)
|
|
13
|
+
assert(not self._serviceBag, "Already initialized")
|
|
14
|
+
self._serviceBag = assert(serviceBag, "No serviceBag")
|
|
15
|
+
|
|
16
|
+
self._providers = {}
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
function InputKeyMapServiceClient:RegisterProvider(provider)
|
|
20
|
+
assert(provider, "Bad provider")
|
|
21
|
+
assert(self._providers, "Not initialized")
|
|
22
|
+
|
|
23
|
+
local providerName = provider:GetProviderName()
|
|
24
|
+
if self._providers[providerName] then
|
|
25
|
+
error(("Already have a provider with name %q"):format(providerName))
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
self._providers[providerName] = provider
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
function InputKeyMapServiceClient:GetProvider(providerName)
|
|
32
|
+
assert(type(providerName) == "string", "Bad providerName")
|
|
33
|
+
|
|
34
|
+
return self._providers[providerName]
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
-- function InputKeyMapServiceClient:ObserveInputKeyMapList(providerName, inputKeyMapListName)
|
|
38
|
+
-- assert(type(providerName) == "string", "Bad providerName")
|
|
39
|
+
-- assert(type(inputKeyMapListName) == "string", "Bad inputKeyMapListName")
|
|
40
|
+
|
|
41
|
+
-- end
|
|
42
|
+
|
|
43
|
+
function InputKeyMapServiceClient:FindInputKeyMapList(providerName, inputKeyMapListName)
|
|
44
|
+
assert(type(providerName) == "string", "Bad providerName")
|
|
45
|
+
assert(type(inputKeyMapListName) == "string", "Bad inputKeyMapListName")
|
|
46
|
+
|
|
47
|
+
if not RunService:IsRunning() then
|
|
48
|
+
return nil
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
assert(self._providers, "Not initialized")
|
|
52
|
+
for _, provider in pairs(self._providers) do
|
|
53
|
+
if provider:GetProviderName() == providerName then
|
|
54
|
+
local found = provider:FindInputKeyMapList(inputKeyMapListName)
|
|
55
|
+
if found then
|
|
56
|
+
return found
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
return nil
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
return InputKeyMapServiceClient
|
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
|
|
7
7
|
local require = require(script.Parent.loader).load(script)
|
|
8
8
|
|
|
9
|
-
local
|
|
9
|
+
local InputKeyMapList = require("InputKeyMapList")
|
|
10
10
|
local INPUT_MODES = require("INPUT_MODES")
|
|
11
|
+
local InputKeyMap = require("InputKeyMap")
|
|
11
12
|
|
|
12
13
|
local ProximityPromptInputUtils = {}
|
|
13
14
|
|
|
@@ -17,13 +18,13 @@ local ProximityPromptInputUtils = {}
|
|
|
17
18
|
@param prompt ProximityPrompt
|
|
18
19
|
@return InputKeyMapList
|
|
19
20
|
]=]
|
|
20
|
-
function ProximityPromptInputUtils.
|
|
21
|
+
function ProximityPromptInputUtils.newInputKeyMapFromPrompt(prompt)
|
|
21
22
|
assert(typeof(prompt) == "Instance", "Bad prompt")
|
|
22
23
|
|
|
23
|
-
return {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
24
|
+
return InputKeyMapList.new("custom", {
|
|
25
|
+
InputKeyMap.new(INPUT_MODES.Gamepads, { prompt.GamepadKeyCode });
|
|
26
|
+
InputKeyMap.new(INPUT_MODES.Keyboard, { prompt.KeyboardKeyCode })
|
|
27
|
+
})
|
|
27
28
|
end
|
|
28
29
|
|
|
29
30
|
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
--[=[
|
|
2
|
+
@class InputTypeUtils
|
|
3
|
+
]=]
|
|
4
|
+
|
|
5
|
+
local require = require(script.Parent.loader).load(script)
|
|
6
|
+
|
|
7
|
+
local SlottedTouchButtonUtils = require("SlottedTouchButtonUtils")
|
|
8
|
+
|
|
9
|
+
local InputTypeUtils = {}
|
|
10
|
+
|
|
11
|
+
--[=[
|
|
12
|
+
A valid input type that can be represented here.
|
|
13
|
+
@type InputType KeyCode | UserInputType | SlottedTouchButton | "TouchButton" | "Tap" | any
|
|
14
|
+
@within InputTypeUtils
|
|
15
|
+
]=]
|
|
16
|
+
|
|
17
|
+
--[=[
|
|
18
|
+
Returns true if the input type is specifying a tap in the world
|
|
19
|
+
@param inputKey any
|
|
20
|
+
@return boolean
|
|
21
|
+
]=]
|
|
22
|
+
function InputTypeUtils.isTapInWorld(inputKey)
|
|
23
|
+
return inputKey == "Tap"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
--[=[
|
|
27
|
+
Returns true if the input type is specifying a Roblox touch button
|
|
28
|
+
@param inputKey any
|
|
29
|
+
@return boolean
|
|
30
|
+
]=]
|
|
31
|
+
function InputTypeUtils.isRobloxTouchButton(inputKey)
|
|
32
|
+
return inputKey == "TouchButton"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
--[=[
|
|
36
|
+
Specifies a tap in the world
|
|
37
|
+
@return "Tap"
|
|
38
|
+
]=]
|
|
39
|
+
function InputTypeUtils.createTapInWorld()
|
|
40
|
+
return "Tap"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
--[=[
|
|
44
|
+
Specifies a roblox touch button
|
|
45
|
+
@return "Tap"
|
|
46
|
+
]=]
|
|
47
|
+
function InputTypeUtils.createRobloxTouchButton()
|
|
48
|
+
return "TouchButton"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
--[=[
|
|
52
|
+
Computes a unique id for an inputType which can be used
|
|
53
|
+
in a set to deduplicate/compare the objects. Used to know
|
|
54
|
+
when to exclude different types from each other.
|
|
55
|
+
|
|
56
|
+
@param inputType InputType
|
|
57
|
+
@return any
|
|
58
|
+
]=]
|
|
59
|
+
function InputTypeUtils.getUniqueKeyForInputType(inputType)
|
|
60
|
+
if SlottedTouchButtonUtils.isSlottedTouchButton(inputType) then
|
|
61
|
+
return inputType.slotId
|
|
62
|
+
else
|
|
63
|
+
return inputType
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
return InputTypeUtils
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
--[=[
|
|
2
|
+
@class SlottedTouchButtonUtils
|
|
3
|
+
]=]
|
|
4
|
+
|
|
5
|
+
local require = require(script.Parent.loader).load(script)
|
|
6
|
+
|
|
7
|
+
local SlottedTouchButtonUtils = {}
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
--[=[
|
|
11
|
+
Internal data representing a slotted touch button
|
|
12
|
+
@interface SlottedTouchButtonData
|
|
13
|
+
.slotId string
|
|
14
|
+
.inputMode InputMode
|
|
15
|
+
@within SlottedTouchButtonUtils
|
|
16
|
+
]=]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
--[=[
|
|
20
|
+
A touch button that goes into a specific slot. This ensures
|
|
21
|
+
consistent slot positions.
|
|
22
|
+
|
|
23
|
+
@interface SlottedTouchButton
|
|
24
|
+
.type "SlottedTouchButton"
|
|
25
|
+
.slotId string
|
|
26
|
+
@within SlottedTouchButtonUtils
|
|
27
|
+
]=]
|
|
28
|
+
|
|
29
|
+
--[=[
|
|
30
|
+
Touch buttons should always show up in the same position
|
|
31
|
+
We use the SlotId to determine which slot we should put
|
|
32
|
+
these buttons in.
|
|
33
|
+
|
|
34
|
+
@param slotId string
|
|
35
|
+
@return SlottedTouchButton
|
|
36
|
+
]=]
|
|
37
|
+
function SlottedTouchButtonUtils.createSlottedTouchButton(slotId)
|
|
38
|
+
assert(slotId == "primary1"
|
|
39
|
+
or slotId == "primary2"
|
|
40
|
+
or slotId == "primary3"
|
|
41
|
+
or slotId == "primary4"
|
|
42
|
+
or slotId == "touchpad1", "Bad slotId")
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
type = "SlottedTouchButton";
|
|
46
|
+
slotId = slotId;
|
|
47
|
+
}
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
--[=[
|
|
51
|
+
Returns whether an inputType is a SlottedTouchButton type
|
|
52
|
+
|
|
53
|
+
@param inputType any
|
|
54
|
+
@return boolean
|
|
55
|
+
]=]
|
|
56
|
+
function SlottedTouchButtonUtils.isSlottedTouchButton(inputType)
|
|
57
|
+
return type(inputType) == "table" and inputType.type == "SlottedTouchButton"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
--[=[
|
|
61
|
+
Gets slotted touch button data for an inputKeyMapList
|
|
62
|
+
|
|
63
|
+
@param slotId string
|
|
64
|
+
@param inputMode InputMode
|
|
65
|
+
@return SlottedTouchButtonData
|
|
66
|
+
]=]
|
|
67
|
+
function SlottedTouchButtonUtils.createTouchButtonData(slotId, inputMode)
|
|
68
|
+
return {
|
|
69
|
+
slotId = slotId;
|
|
70
|
+
inputMode = inputMode;
|
|
71
|
+
}
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
--[=[
|
|
75
|
+
Gets slotted touch button data for an inputKeyMapList
|
|
76
|
+
|
|
77
|
+
@param inputKeyMapList InputKeyMapList
|
|
78
|
+
@return { SlottedTouchButtonData }
|
|
79
|
+
]=]
|
|
80
|
+
function SlottedTouchButtonUtils.getSlottedTouchButtonData(inputKeyMapList)
|
|
81
|
+
local slottedTouchButtons = {}
|
|
82
|
+
|
|
83
|
+
for _, inputKeyMap in pairs(inputKeyMapList) do
|
|
84
|
+
assert(inputKeyMap.inputMode, "Bad inputKeyMap.inputMode")
|
|
85
|
+
assert(inputKeyMap.inputTypes, "Bad inputKeyMap.inputTypes")
|
|
86
|
+
|
|
87
|
+
for _, touchButtonData in pairs(inputKeyMap.inputTypes) do
|
|
88
|
+
if SlottedTouchButtonUtils.isSlottedTouchButton(touchButtonData) then
|
|
89
|
+
table.insert(slottedTouchButtons, SlottedTouchButtonUtils.createTouchButtonData(
|
|
90
|
+
touchButtonData.slotId, inputKeyMap.inputMode))
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
return slottedTouchButtons
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
return SlottedTouchButtonUtils
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "InputKeyMapUtilsTest",
|
|
3
|
+
"tree": {
|
|
4
|
+
"$className": "DataModel",
|
|
5
|
+
"ServerScriptService": {
|
|
6
|
+
"inputkeymaputils": {
|
|
7
|
+
"$className": "Folder",
|
|
8
|
+
"inputkeymaputils": {
|
|
9
|
+
"$path": ".."
|
|
10
|
+
},
|
|
11
|
+
"test": {
|
|
12
|
+
"$path": "modules"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"Script": {
|
|
16
|
+
"$path": "scripts/Server"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"StarterPlayer": {
|
|
20
|
+
"StarterPlayerScripts": {
|
|
21
|
+
"Main": {
|
|
22
|
+
"$path": "scripts/Client"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
--[=[
|
|
2
|
+
Test input key map provider
|
|
3
|
+
@class TestInputKeyMap
|
|
4
|
+
]=]
|
|
5
|
+
local require = require(script.Parent.loader).load(script)
|
|
6
|
+
|
|
7
|
+
local INPUT_MODES = require("INPUT_MODES")
|
|
8
|
+
local InputKeyMap = require("InputKeyMap")
|
|
9
|
+
local InputKeyMapList = require("InputKeyMapList")
|
|
10
|
+
local InputKeyMapListProvider = require("InputKeyMapListProvider")
|
|
11
|
+
local SlottedTouchButtonUtils = require("SlottedTouchButtonUtils")
|
|
12
|
+
|
|
13
|
+
return InputKeyMapListProvider.new(script.Name, function(self)
|
|
14
|
+
self:Add(InputKeyMapList.new("JUMP", {
|
|
15
|
+
InputKeyMap.new(INPUT_MODES.KeyboardAndMouse, { Enum.KeyCode.Q });
|
|
16
|
+
InputKeyMap.new(INPUT_MODES.Gamepads, { Enum.KeyCode.ButtonY });
|
|
17
|
+
InputKeyMap.new(INPUT_MODES.Touch, { SlottedTouchButtonUtils.createSlottedTouchButton("primary3") });
|
|
18
|
+
}))
|
|
19
|
+
|
|
20
|
+
self:Add(InputKeyMapList.new("HONK", {
|
|
21
|
+
InputKeyMap.new(INPUT_MODES.KeyboardAndMouse, { Enum.KeyCode.H });
|
|
22
|
+
InputKeyMap.new(INPUT_MODES.Gamepads, { Enum.KeyCode.ButtonL1 });
|
|
23
|
+
InputKeyMap.new(INPUT_MODES.Touch, { SlottedTouchButtonUtils.createSlottedTouchButton("primary2") });
|
|
24
|
+
}))
|
|
25
|
+
end)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
--[[
|
|
2
|
+
@class ClientMain
|
|
3
|
+
]]
|
|
4
|
+
local packages = game:GetService("ReplicatedStorage"):WaitForChild("Packages")
|
|
5
|
+
|
|
6
|
+
local ContextActionService = game:GetService("ContextActionService")
|
|
7
|
+
|
|
8
|
+
local INPUT_MODES = require(packages.INPUT_MODES)
|
|
9
|
+
local serviceBag = require(packages.ServiceBag).new()
|
|
10
|
+
|
|
11
|
+
serviceBag:GetService(packages.InputKeyMapServiceClient)
|
|
12
|
+
local inputKeyMap = serviceBag:GetService(packages.TestInputKeyMap)
|
|
13
|
+
|
|
14
|
+
-- Start game
|
|
15
|
+
serviceBag:Init()
|
|
16
|
+
serviceBag:Start()
|
|
17
|
+
|
|
18
|
+
local keyMapList = inputKeyMap:GetInputKeyMapList("HONK")
|
|
19
|
+
|
|
20
|
+
keyMapList:ObserveInputEnumsList():Subscribe(function(...)
|
|
21
|
+
print("activeInputTypes", ...)
|
|
22
|
+
ContextActionService:BindAction("actionTypes", function(_, _, inputObject)
|
|
23
|
+
print("Activated", inputObject.UserInputState)
|
|
24
|
+
end, false, ...)
|
|
25
|
+
end)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
keyMapList:SetForInputMode(INPUT_MODES.Keypad, { Enum.KeyCode.Space })
|
|
@@ -1,320 +0,0 @@
|
|
|
1
|
-
--[=[
|
|
2
|
-
Utility methods for input. Centralizes input. In the future, this will allow
|
|
3
|
-
user configuration.
|
|
4
|
-
|
|
5
|
-
```lua
|
|
6
|
-
local inputMap = {
|
|
7
|
-
JUMP = {
|
|
8
|
-
InputKeyMapUtils.createKeyMap(INPUT_MODES.KeyboardAndMouse, { Enum.KeyCode.Space });
|
|
9
|
-
InputKeyMapUtils.createKeyMap(INPUT_MODES.Gamepads, { Enum.KeyCode.ButtonA });
|
|
10
|
-
InputKeyMapUtils.createKeyMap(INPUT_MODES.Touch, { InputKeyMapUtils.createSlottedTouchButton("primary3") });
|
|
11
|
-
};
|
|
12
|
-
HONK = {
|
|
13
|
-
InputKeyMapUtils.createKeyMap(INPUT_MODES.KeyboardAndMouse, { Enum.KeyCode.H });
|
|
14
|
-
InputKeyMapUtils.createKeyMap(INPUT_MODES.Gamepads, { Enum.KeyCode.DPadUp });
|
|
15
|
-
InputKeyMapUtils.createKeyMap(INPUT_MODES.Touch, { InputKeyMapUtils.createSlottedTouchButton("primary2") });
|
|
16
|
-
};
|
|
17
|
-
BOOST = {
|
|
18
|
-
InputKeyMapUtils.createKeyMap(INPUT_MODES.KeyboardAndMouse, { Enum.KeyCode.LeftControl });
|
|
19
|
-
InputKeyMapUtils.createKeyMap(INPUT_MODES.Gamepads, { Enum.KeyCode.ButtonX });
|
|
20
|
-
InputKeyMapUtils.createKeyMap(INPUT_MODES.Touch, { InputKeyMapUtils.createSlottedTouchButton("primary4") });
|
|
21
|
-
};
|
|
22
|
-
}
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
Then, we can use these input maps in a variety of services, including [ScoredActionService] or
|
|
26
|
-
just binding directly to [ContextActionService].
|
|
27
|
-
|
|
28
|
-
```lua
|
|
29
|
-
local inputKeyMapList = inputMap.JUMP
|
|
30
|
-
|
|
31
|
-
ContextActionService:BindActionAtPriority(
|
|
32
|
-
"MyAction",
|
|
33
|
-
function(_actionName, userInputState, inputObject)
|
|
34
|
-
print("Process input", inputObject)
|
|
35
|
-
end,
|
|
36
|
-
InputKeyMapUtils.isRobloxTouchButton(inputKeyMapList),
|
|
37
|
-
Enum.ContextActionPriority.High.Value,
|
|
38
|
-
unpack(InputKeyMapUtils.getInputTypesForActionBinding(inputKeyMapList)))
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
@class InputKeyMapUtils
|
|
42
|
-
]=]
|
|
43
|
-
|
|
44
|
-
local require = require(script.Parent.loader).load(script)
|
|
45
|
-
|
|
46
|
-
local Set = require("Set")
|
|
47
|
-
local Table = require("Table")
|
|
48
|
-
|
|
49
|
-
local InputKeyMapUtils = {}
|
|
50
|
-
|
|
51
|
-
--[=[
|
|
52
|
-
A valid input type that can be represented here.
|
|
53
|
-
@type InputType KeyCode | UserInputType | SlottedTouchButton | "TouchButton" | "Tap" | any
|
|
54
|
-
@within InputKeyMapUtils
|
|
55
|
-
]=]
|
|
56
|
-
|
|
57
|
-
--[=[
|
|
58
|
-
A grouping of input types for a specific input mode to use.
|
|
59
|
-
|
|
60
|
-
@interface InputKeyMap
|
|
61
|
-
.inputMode InputMode
|
|
62
|
-
.inputTypes { InputType }
|
|
63
|
-
@within InputKeyMapUtils
|
|
64
|
-
]=]
|
|
65
|
-
|
|
66
|
-
--[=[
|
|
67
|
-
A mapping of input keys to maps
|
|
68
|
-
@type InputKeyMapList { InputKeyMap }
|
|
69
|
-
@within InputKeyMapUtils
|
|
70
|
-
]=]
|
|
71
|
-
|
|
72
|
-
--[=[
|
|
73
|
-
Should be called "createInputKeyMap". Creates a new InputKeyMap.
|
|
74
|
-
|
|
75
|
-
@param inputMode InputMode
|
|
76
|
-
@param inputTypes { InputType }
|
|
77
|
-
@return InputKeyMap
|
|
78
|
-
]=]
|
|
79
|
-
function InputKeyMapUtils.createKeyMap(inputMode, inputTypes)
|
|
80
|
-
assert(type(inputMode) == "table", "Bad inputMode")
|
|
81
|
-
assert(type(inputTypes) == "table", "Bad inputTypes")
|
|
82
|
-
|
|
83
|
-
return Table.readonly({
|
|
84
|
-
inputMode = inputMode;
|
|
85
|
-
inputTypes = inputTypes;
|
|
86
|
-
})
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
--[=[
|
|
90
|
-
@param inputKeyMapList InputKeyMapList
|
|
91
|
-
@return { KeyCode | UserInputType }
|
|
92
|
-
]=]
|
|
93
|
-
function InputKeyMapUtils.getInputTypesSetForActionBinding(inputKeyMapList)
|
|
94
|
-
return Set.fromList(InputKeyMapUtils.getInputTypesForActionBinding(inputKeyMapList))
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
--[=[
|
|
98
|
-
Converts keymap into ContextActionService friendly types
|
|
99
|
-
@param inputKeyMapList InputKeyMapList
|
|
100
|
-
@return { KeyCode | UserInputType }
|
|
101
|
-
]=]
|
|
102
|
-
function InputKeyMapUtils.getInputTypesForActionBinding(inputKeyMapList)
|
|
103
|
-
assert(type(inputKeyMapList) == "table", "inputKeyMapList must be a table")
|
|
104
|
-
local types = {}
|
|
105
|
-
|
|
106
|
-
for _, inputKeyMap in pairs(inputKeyMapList) do
|
|
107
|
-
assert(inputKeyMap.inputMode, "Bad inputKeyMap.inputMode")
|
|
108
|
-
assert(inputKeyMap.inputTypes, "Bad inputKeyMap.inputTypes")
|
|
109
|
-
|
|
110
|
-
for _, _type in pairs(inputKeyMap.inputTypes) do
|
|
111
|
-
if typeof(_type) == "EnumItem" then
|
|
112
|
-
table.insert(types, _type)
|
|
113
|
-
end
|
|
114
|
-
end
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
return types
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
--[=[
|
|
121
|
-
Given an inputMode, gets the relevant lists available
|
|
122
|
-
@param inputKeyMapList InputKeyMapList
|
|
123
|
-
@param inputMode InputMode
|
|
124
|
-
@return { InputKeyMap }
|
|
125
|
-
]=]
|
|
126
|
-
function InputKeyMapUtils.getInputTypeListForMode(inputKeyMapList, inputMode)
|
|
127
|
-
assert(type(inputKeyMapList) == "table", "inputKeyMapList must be a table")
|
|
128
|
-
|
|
129
|
-
local results = {}
|
|
130
|
-
local seen = {}
|
|
131
|
-
|
|
132
|
-
for _, inputKeyMap in pairs(inputKeyMapList) do
|
|
133
|
-
if inputKeyMap.inputMode == inputMode then
|
|
134
|
-
for _, inputType in pairs(inputKeyMap.inputTypes) do
|
|
135
|
-
if not seen then
|
|
136
|
-
seen[inputType] = true
|
|
137
|
-
table.insert(results, inputType)
|
|
138
|
-
end
|
|
139
|
-
end
|
|
140
|
-
end
|
|
141
|
-
end
|
|
142
|
-
|
|
143
|
-
return results
|
|
144
|
-
end
|
|
145
|
-
|
|
146
|
-
--[=[
|
|
147
|
-
Gets a set of input types for a given mode from the list.
|
|
148
|
-
|
|
149
|
-
@param inputKeyMapList InputKeyMapList
|
|
150
|
-
@param inputMode InputMode
|
|
151
|
-
@return { [InputType] = true }
|
|
152
|
-
]=]
|
|
153
|
-
function InputKeyMapUtils.getInputTypeSetForMode(inputKeyMapList, inputMode)
|
|
154
|
-
assert(type(inputKeyMapList) == "table", "inputKeyMapList must be a table")
|
|
155
|
-
|
|
156
|
-
local results = {}
|
|
157
|
-
|
|
158
|
-
for _, inputKeyMap in pairs(inputKeyMapList) do
|
|
159
|
-
if inputKeyMap.inputMode == inputMode then
|
|
160
|
-
for _, inputType in pairs(inputKeyMap.inputTypes) do
|
|
161
|
-
results[inputType] = true
|
|
162
|
-
end
|
|
163
|
-
end
|
|
164
|
-
end
|
|
165
|
-
|
|
166
|
-
return results
|
|
167
|
-
end
|
|
168
|
-
|
|
169
|
-
--[=[
|
|
170
|
-
Retrieves the set of input modes for a given list.
|
|
171
|
-
|
|
172
|
-
@param inputKeyMapList InputKeyMapList
|
|
173
|
-
@return { InputMode }
|
|
174
|
-
]=]
|
|
175
|
-
function InputKeyMapUtils.getInputModes(inputKeyMapList)
|
|
176
|
-
assert(type(inputKeyMapList) == "table", "inputKeyMapList must be a table")
|
|
177
|
-
|
|
178
|
-
local modes = {}
|
|
179
|
-
for _, inputKeyMap in pairs(inputKeyMapList) do
|
|
180
|
-
local mode = assert(inputKeyMap.inputMode, "Bad inputKeyMap.inputMode")
|
|
181
|
-
table.insert(modes, mode)
|
|
182
|
-
end
|
|
183
|
-
|
|
184
|
-
return modes
|
|
185
|
-
end
|
|
186
|
-
|
|
187
|
-
--[=[
|
|
188
|
-
Internal data representing a slotted touch button
|
|
189
|
-
@interface SlottedTouchButtonData
|
|
190
|
-
.slotId string
|
|
191
|
-
.inputMode InputMode
|
|
192
|
-
@within InputKeyMapUtils
|
|
193
|
-
]=]
|
|
194
|
-
|
|
195
|
-
--[=[
|
|
196
|
-
Gets slotted touch button data for an inputKeyMapList
|
|
197
|
-
|
|
198
|
-
@param inputKeyMapList InputKeyMapList
|
|
199
|
-
@return { SlottedTouchButtonData }
|
|
200
|
-
]=]
|
|
201
|
-
function InputKeyMapUtils.getSlottedTouchButtonData(inputKeyMapList)
|
|
202
|
-
local slottedTouchButtons = {}
|
|
203
|
-
|
|
204
|
-
for _, inputKeyMap in pairs(inputKeyMapList) do
|
|
205
|
-
assert(inputKeyMap.inputMode, "Bad inputKeyMap.inputMode")
|
|
206
|
-
assert(inputKeyMap.inputTypes, "Bad inputKeyMap.inputTypes")
|
|
207
|
-
|
|
208
|
-
for _, touchButtonData in pairs(inputKeyMap.inputTypes) do
|
|
209
|
-
if InputKeyMapUtils.isSlottedTouchButton(touchButtonData) then
|
|
210
|
-
table.insert(slottedTouchButtons, {
|
|
211
|
-
slotId = touchButtonData.slotId;
|
|
212
|
-
inputMode = inputKeyMap.inputMode;
|
|
213
|
-
})
|
|
214
|
-
end
|
|
215
|
-
end
|
|
216
|
-
end
|
|
217
|
-
|
|
218
|
-
return slottedTouchButtons
|
|
219
|
-
end
|
|
220
|
-
|
|
221
|
-
--[=[
|
|
222
|
-
Returns whether an inputType is a SlottedTouchButton type
|
|
223
|
-
|
|
224
|
-
@param inputType any
|
|
225
|
-
@return boolean
|
|
226
|
-
]=]
|
|
227
|
-
function InputKeyMapUtils.isSlottedTouchButton(inputType)
|
|
228
|
-
return type(inputType) == "table" and inputType.type == "SlottedTouchButton"
|
|
229
|
-
end
|
|
230
|
-
|
|
231
|
-
--[=[
|
|
232
|
-
A touch button that goes into a specific slot. This ensures
|
|
233
|
-
consistent slot positions.
|
|
234
|
-
|
|
235
|
-
@interface SlottedTouchButton
|
|
236
|
-
.type "SlottedTouchButton"
|
|
237
|
-
.slotId string
|
|
238
|
-
@within InputKeyMapUtils
|
|
239
|
-
]=]
|
|
240
|
-
|
|
241
|
-
--[=[
|
|
242
|
-
Touch buttons should always show up in the same position
|
|
243
|
-
We use the SlotId to determine which slot we should put
|
|
244
|
-
these buttons in.
|
|
245
|
-
|
|
246
|
-
@param slotId string
|
|
247
|
-
@return SlottedTouchButton
|
|
248
|
-
]=]
|
|
249
|
-
function InputKeyMapUtils.createSlottedTouchButton(slotId)
|
|
250
|
-
assert(slotId == "primary1"
|
|
251
|
-
or slotId == "primary2"
|
|
252
|
-
or slotId == "primary3"
|
|
253
|
-
or slotId == "primary4"
|
|
254
|
-
or slotId == "touchpad1", "Bad slotId")
|
|
255
|
-
|
|
256
|
-
return {
|
|
257
|
-
type = "SlottedTouchButton";
|
|
258
|
-
slotId = slotId;
|
|
259
|
-
}
|
|
260
|
-
end
|
|
261
|
-
|
|
262
|
-
--[=[
|
|
263
|
-
Computes a unique id for an inputType which can be used
|
|
264
|
-
in a set to deduplicate/compare the objects. Used to know
|
|
265
|
-
when to exclude different types from each other.
|
|
266
|
-
|
|
267
|
-
@param inputType InputType
|
|
268
|
-
@return any
|
|
269
|
-
]=]
|
|
270
|
-
function InputKeyMapUtils.getUniqueKeyForInputType(inputType)
|
|
271
|
-
if InputKeyMapUtils.isSlottedTouchButton(inputType) then
|
|
272
|
-
return inputType.slotId
|
|
273
|
-
else
|
|
274
|
-
return inputType
|
|
275
|
-
end
|
|
276
|
-
end
|
|
277
|
-
|
|
278
|
-
--[=[
|
|
279
|
-
Only returns true if we're a Roblox touch button
|
|
280
|
-
@param inputKeyMapList InputKeyMapList
|
|
281
|
-
@return boolean
|
|
282
|
-
]=]
|
|
283
|
-
function InputKeyMapUtils.isRobloxTouchButton(inputKeyMapList)
|
|
284
|
-
for _, inputKeyMap in pairs(inputKeyMapList) do
|
|
285
|
-
assert(inputKeyMap.inputMode, "Bad inputKeyMap.inputMode")
|
|
286
|
-
assert(inputKeyMap.inputTypes, "Bad inputKeyMap.inputTypes")
|
|
287
|
-
|
|
288
|
-
for _, _type in pairs(inputKeyMap.inputTypes) do
|
|
289
|
-
if _type == "TouchButton" then
|
|
290
|
-
return true
|
|
291
|
-
end
|
|
292
|
-
end
|
|
293
|
-
end
|
|
294
|
-
|
|
295
|
-
return false
|
|
296
|
-
end
|
|
297
|
-
|
|
298
|
-
--[=[
|
|
299
|
-
Whether this input type is a tap in the world input (for touched events)
|
|
300
|
-
@param inputKeyMapList InputKeyMapList
|
|
301
|
-
@return boolean
|
|
302
|
-
]=]
|
|
303
|
-
function InputKeyMapUtils.isTapInWorld(inputKeyMapList)
|
|
304
|
-
assert(type(inputKeyMapList) == "table", "inputKeyMap must be a table")
|
|
305
|
-
|
|
306
|
-
for _, inputKeyMap in pairs(inputKeyMapList) do
|
|
307
|
-
assert(inputKeyMap.inputMode, "Bad inputKeyMap.inputMode")
|
|
308
|
-
assert(inputKeyMap.inputTypes, "Bad inputKeyMap.inputTypes")
|
|
309
|
-
|
|
310
|
-
for _, _type in pairs(inputKeyMap.inputTypes) do
|
|
311
|
-
if _type == "Tap" then
|
|
312
|
-
return true
|
|
313
|
-
end
|
|
314
|
-
end
|
|
315
|
-
end
|
|
316
|
-
|
|
317
|
-
return false
|
|
318
|
-
end
|
|
319
|
-
|
|
320
|
-
return InputKeyMapUtils
|