@rbxts/touch-button 1.0.10 → 1.0.11
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 +5 -0
- package/package.json +8 -22
- package/src/Client.lua +395 -0
- package/src/Server.lua +161 -0
- package/src/Shared.lua +38 -0
- package/src/index.d.ts +36 -0
- package/src/init.lua +12 -0
- package/out/client/touch-button.d.ts +0 -28
- package/out/client/touch-button.luau +0 -334
- package/out/index.d.ts +0 -2
- package/out/init.luau +0 -6
- package/out/server/index.d.ts +0 -3
- package/out/server/init.luau +0 -115
- package/out/shared/remotes.d.ts +0 -9
- package/out/shared/remotes.luau +0 -16
package/README.md
ADDED
package/package.json
CHANGED
|
@@ -1,38 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rbxts/touch-button",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.11",
|
|
4
4
|
"description": "",
|
|
5
|
-
"main": "
|
|
6
|
-
"scripts": {
|
|
7
|
-
"build": "rbxtsc",
|
|
8
|
-
"watch": "rbxtsc -w",
|
|
9
|
-
"prepublishOnly": "npm run build"
|
|
10
|
-
},
|
|
5
|
+
"main": "src/init.lua",
|
|
6
|
+
"scripts": {},
|
|
11
7
|
"keywords": [],
|
|
12
8
|
"author": "CriShoux",
|
|
13
9
|
"license": "ISC",
|
|
14
10
|
"type": "commonjs",
|
|
15
|
-
"types": "
|
|
11
|
+
"types": "src/index.d.ts",
|
|
16
12
|
"files": [
|
|
17
|
-
"
|
|
18
|
-
"!**/*.tsbuildinfo"
|
|
13
|
+
"src"
|
|
19
14
|
],
|
|
20
15
|
"publishConfig": {
|
|
21
16
|
"access": "public"
|
|
22
17
|
},
|
|
23
18
|
"devDependencies": {
|
|
24
19
|
"@rbxts/compiler-types": "^3.0.0-types.0",
|
|
25
|
-
"@rbxts/types": "^1.0.
|
|
20
|
+
"@rbxts/types": "^1.0.896",
|
|
26
21
|
"roblox-ts": "^3.0.0",
|
|
27
|
-
"typescript": "^5.
|
|
28
|
-
},
|
|
29
|
-
"dependencies": {
|
|
30
|
-
"@rbxts/charm": "^0.10.0",
|
|
31
|
-
"@rbxts/remo": "^1.5.1",
|
|
32
|
-
"@rbxts/roblox-observers": "^1.0.10",
|
|
33
|
-
"@rbxts/services": "^1.5.5",
|
|
34
|
-
"@rbxts/sleitnick-signal": "^1.0.8",
|
|
35
|
-
"@rbxts/sleitnick-trove": "^1.0.0",
|
|
36
|
-
"@rbxts/t": "^3.2.1"
|
|
22
|
+
"typescript": "^5.9.3"
|
|
37
23
|
}
|
|
38
|
-
}
|
|
24
|
+
}
|
package/src/Client.lua
ADDED
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
--!strict
|
|
2
|
+
local Players = game:GetService('Players');
|
|
3
|
+
local UserInputService = game:GetService('UserInputService');
|
|
4
|
+
local Workspace = game:GetService('Workspace');
|
|
5
|
+
|
|
6
|
+
local Shared = require(script.Parent.Shared);
|
|
7
|
+
local isTypeScriptEnv = script.Parent.Name == 'src';
|
|
8
|
+
local dependencies = if isTypeScriptEnv then script.Parent.Parent.Parent else script.Parent.Parent;
|
|
9
|
+
local Charm = require(isTypeScriptEnv and dependencies.charm or dependencies.Charm);
|
|
10
|
+
local Trove = require(isTypeScriptEnv and dependencies['sleitnick-trove'] or dependencies.Trove);
|
|
11
|
+
|
|
12
|
+
type TouchButton = {
|
|
13
|
+
setIcon: (self: TouchButton, icon: string) -> (),
|
|
14
|
+
setSize: (self: TouchButton, size: UDim2) -> (),
|
|
15
|
+
setPosition: (self: TouchButton, position: UDim2) -> (),
|
|
16
|
+
setConfig: (self: TouchButton, config: Shared.TouchButtonConfig) -> (),
|
|
17
|
+
destroy: (self: TouchButton) -> (),
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
type TouchButtonOptions = {
|
|
21
|
+
name: string,
|
|
22
|
+
icon: string,
|
|
23
|
+
size: UDim2,
|
|
24
|
+
position: UDim2,
|
|
25
|
+
sinkInput: boolean?,
|
|
26
|
+
onPress: (() -> ())?,
|
|
27
|
+
onRelease: (() -> ())?,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
local localPlayer = Players.LocalPlayer;
|
|
31
|
+
local camera = Workspace.CurrentCamera :: Camera;
|
|
32
|
+
|
|
33
|
+
local touchGui: ScreenGui?;
|
|
34
|
+
local jumpBtn: Frame?;
|
|
35
|
+
|
|
36
|
+
local initialized = false;
|
|
37
|
+
|
|
38
|
+
local EDITING_MODE_BUTTON_COLOR = Color3.fromRGB(15, 143, 255);
|
|
39
|
+
local MIN_RESIZE = 40;
|
|
40
|
+
|
|
41
|
+
local function getJumpButtonLayout()
|
|
42
|
+
local minAxis = math.min(camera.ViewportSize.X, camera.ViewportSize.Y);
|
|
43
|
+
local isSmallScreen = minAxis <= 500;
|
|
44
|
+
local jumpButtonSize = if isSmallScreen then 70 else 120;
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
size = UDim2.fromOffset(jumpButtonSize, jumpButtonSize),
|
|
48
|
+
position = if isSmallScreen
|
|
49
|
+
then UDim2.new(1, -(jumpButtonSize * 1.5 - 10), 1, -jumpButtonSize - 20)
|
|
50
|
+
else UDim2.new(1, -(jumpButtonSize * 1.5 - 10), 1, -jumpButtonSize * 1.75),
|
|
51
|
+
};
|
|
52
|
+
end;
|
|
53
|
+
|
|
54
|
+
local function getTouchGui()
|
|
55
|
+
if touchGui and jumpBtn then
|
|
56
|
+
return touchGui, jumpBtn;
|
|
57
|
+
end;
|
|
58
|
+
|
|
59
|
+
local newTouchGui = Instance.new('ScreenGui');
|
|
60
|
+
newTouchGui.Name = 'CustomTouchGui';
|
|
61
|
+
newTouchGui.ResetOnSpawn = false;
|
|
62
|
+
newTouchGui.Parent = localPlayer:WaitForChild('PlayerGui');
|
|
63
|
+
touchGui = newTouchGui;
|
|
64
|
+
|
|
65
|
+
local newJumpBtn = Instance.new('Frame');
|
|
66
|
+
newJumpBtn.Name = 'Jump';
|
|
67
|
+
newJumpBtn.BackgroundTransparency = 1;
|
|
68
|
+
|
|
69
|
+
local function updateLayout()
|
|
70
|
+
local jumpBtnLayout = getJumpButtonLayout();
|
|
71
|
+
newJumpBtn.Position = jumpBtnLayout.position;
|
|
72
|
+
newJumpBtn.Size = jumpBtnLayout.size;
|
|
73
|
+
end;
|
|
74
|
+
|
|
75
|
+
camera:GetPropertyChangedSignal('ViewportSize'):Connect(updateLayout);
|
|
76
|
+
updateLayout();
|
|
77
|
+
|
|
78
|
+
newJumpBtn.Parent = newTouchGui;
|
|
79
|
+
jumpBtn = newJumpBtn;
|
|
80
|
+
|
|
81
|
+
local characterExists = Charm.atom(localPlayer.Character ~= nil);
|
|
82
|
+
local lastInputType = Charm.atom(UserInputService:GetLastInputType());
|
|
83
|
+
|
|
84
|
+
Charm.effect(function()
|
|
85
|
+
local charExists = characterExists();
|
|
86
|
+
local inputType = lastInputType();
|
|
87
|
+
|
|
88
|
+
newTouchGui.Enabled = (inputType == Enum.UserInputType.Touch) and charExists;
|
|
89
|
+
end);
|
|
90
|
+
|
|
91
|
+
localPlayer.CharacterAdded:Connect(function()
|
|
92
|
+
characterExists(true);
|
|
93
|
+
end);
|
|
94
|
+
localPlayer.CharacterRemoving:Connect(function()
|
|
95
|
+
characterExists(false);
|
|
96
|
+
end);
|
|
97
|
+
|
|
98
|
+
UserInputService.LastInputTypeChanged:Connect(lastInputType);
|
|
99
|
+
|
|
100
|
+
return newTouchGui, newJumpBtn;
|
|
101
|
+
end;
|
|
102
|
+
|
|
103
|
+
local Client = {};
|
|
104
|
+
Client.__index = Client;
|
|
105
|
+
|
|
106
|
+
Client.configEditingMode = Charm.atom(false);
|
|
107
|
+
Client.touchButtons = {} :: { TouchButton };
|
|
108
|
+
|
|
109
|
+
function Client._init()
|
|
110
|
+
if initialized then return; end;
|
|
111
|
+
initialized = true;
|
|
112
|
+
|
|
113
|
+
local editingTrove = Trove.new();
|
|
114
|
+
|
|
115
|
+
Charm.effect(function()
|
|
116
|
+
local isEditing = Client.configEditingMode();
|
|
117
|
+
if isEditing then
|
|
118
|
+
local function createButton(props: { name: string, text: string, backgroundColor: Color3, offsetY: number })
|
|
119
|
+
local btn = Instance.new('TextButton');
|
|
120
|
+
btn.Name = props.name;
|
|
121
|
+
btn.AnchorPoint = Vector2.new(0.5, 0.5);
|
|
122
|
+
btn.BackgroundColor3 = props.backgroundColor;
|
|
123
|
+
btn.BackgroundTransparency = 0.25;
|
|
124
|
+
btn.FontFace = Font.new('rbxasset://fonts/families/GothamSSm.json', Enum.FontWeight.Medium, Enum.FontStyle.Normal);
|
|
125
|
+
btn.Position = UDim2.new(0.5, 0, 0.5, props.offsetY);
|
|
126
|
+
btn.Size = UDim2.fromOffset(120, 32);
|
|
127
|
+
btn.Text = props.text;
|
|
128
|
+
btn.TextColor3 = Color3.new(1, 1, 1);
|
|
129
|
+
btn.TextSize = 16;
|
|
130
|
+
btn.Parent = touchGui;
|
|
131
|
+
editingTrove:Add(btn);
|
|
132
|
+
|
|
133
|
+
local uiCorner = Instance.new('UICorner');
|
|
134
|
+
uiCorner.CornerRadius = UDim.new(0, 5);
|
|
135
|
+
uiCorner.Parent = btn;
|
|
136
|
+
|
|
137
|
+
return btn;
|
|
138
|
+
end;
|
|
139
|
+
|
|
140
|
+
createButton({
|
|
141
|
+
name = 'Reset',
|
|
142
|
+
text = 'Reset Buttons',
|
|
143
|
+
backgroundColor = Color3.new(),
|
|
144
|
+
offsetY = -19,
|
|
145
|
+
}).MouseButton1Click:Connect(function()
|
|
146
|
+
for _, touchBtn in Client.touchButtons do
|
|
147
|
+
touchBtn:_resetConfigToDefault();
|
|
148
|
+
end;
|
|
149
|
+
end);
|
|
150
|
+
|
|
151
|
+
createButton({
|
|
152
|
+
name = 'Finish',
|
|
153
|
+
text = 'Finish Editing',
|
|
154
|
+
backgroundColor = Color3.fromRGB(30, 175, 30),
|
|
155
|
+
offsetY = 19,
|
|
156
|
+
}).MouseButton1Click:Connect(function()
|
|
157
|
+
Client.configEditingMode(false);
|
|
158
|
+
end);
|
|
159
|
+
else
|
|
160
|
+
editingTrove:Clean();
|
|
161
|
+
end;
|
|
162
|
+
end);
|
|
163
|
+
end;
|
|
164
|
+
|
|
165
|
+
function Client.new(options: TouchButtonOptions)
|
|
166
|
+
Client._init();
|
|
167
|
+
|
|
168
|
+
for _, otherTouchBtn in Client.touchButtons do
|
|
169
|
+
assert(otherTouchBtn._name ~= options.name, `A TouchButton with the name {options.name} already exists`);
|
|
170
|
+
end;
|
|
171
|
+
|
|
172
|
+
local self = setmetatable({}, Client);
|
|
173
|
+
|
|
174
|
+
self._name = options.name;
|
|
175
|
+
self._desiredBtnColor = Color3.fromRGB(255, 255, 255);
|
|
176
|
+
self._defaultConfig = {
|
|
177
|
+
position = options.position,
|
|
178
|
+
size = options.size,
|
|
179
|
+
};
|
|
180
|
+
self._configUpdateScheduled = false;
|
|
181
|
+
|
|
182
|
+
task.spawn(function()
|
|
183
|
+
local config = Shared.getTouchButtonConfig:InvokeServer(options.name);
|
|
184
|
+
if not config then return; end;
|
|
185
|
+
self:setConfig(config);
|
|
186
|
+
end);
|
|
187
|
+
|
|
188
|
+
local touchBtn = Instance.new('ImageButton');
|
|
189
|
+
touchBtn.Name = 'TouchButton';
|
|
190
|
+
touchBtn.Active = if options.sinkInput then true else false;
|
|
191
|
+
touchBtn.Image = 'http://www.roblox.com/asset/?id=15340864550';
|
|
192
|
+
touchBtn.ImageColor3 = Color3.fromRGB(255, 255, 255);
|
|
193
|
+
touchBtn.ImageTransparency = 0.5;
|
|
194
|
+
touchBtn.AnchorPoint = Vector2.new(0.5, 0.5);
|
|
195
|
+
touchBtn.BackgroundTransparency = 1;
|
|
196
|
+
touchBtn.Size = UDim2.fromScale(1, 1);
|
|
197
|
+
|
|
198
|
+
local iconImage = Instance.new('ImageLabel');
|
|
199
|
+
iconImage.Name = 'Icon';
|
|
200
|
+
iconImage.AnchorPoint = Vector2.new(0.5, 0.5);
|
|
201
|
+
iconImage.BackgroundTransparency = 1;
|
|
202
|
+
iconImage.ImageColor3 = self._desiredBtnColor;
|
|
203
|
+
iconImage.ImageTransparency = 0.2;
|
|
204
|
+
iconImage.Position = UDim2.fromScale(0.5, 0.5);
|
|
205
|
+
iconImage.Size = UDim2.fromScale(0.56, 0.56);
|
|
206
|
+
iconImage.Parent = touchBtn;
|
|
207
|
+
|
|
208
|
+
self._touchBtn = touchBtn;
|
|
209
|
+
|
|
210
|
+
touchBtn.InputBegan:Connect(function(input)
|
|
211
|
+
if (input.UserInputType ~= Enum.UserInputType.MouseButton1) and (input.UserInputType ~= Enum.UserInputType.Touch) then return; end;
|
|
212
|
+
if input.UserInputState ~= Enum.UserInputState.Begin then return; end;
|
|
213
|
+
|
|
214
|
+
if Client.configEditingMode() then return; end;
|
|
215
|
+
|
|
216
|
+
touchBtn.ImageColor3 = Color3.fromRGB(200, 200, 200);
|
|
217
|
+
iconImage.ImageColor3 = Color3.fromRGB(200, 200, 200);
|
|
218
|
+
|
|
219
|
+
if options.onPress then options.onPress(); end;
|
|
220
|
+
|
|
221
|
+
local connection;
|
|
222
|
+
connection = input:GetPropertyChangedSignal('UserInputState'):Connect(function()
|
|
223
|
+
if input.UserInputState ~= Enum.UserInputState.End then return; end;
|
|
224
|
+
|
|
225
|
+
connection:Disconnect();
|
|
226
|
+
|
|
227
|
+
touchBtn.ImageColor3 = Color3.fromRGB(255, 255, 255);
|
|
228
|
+
iconImage.ImageColor3 = self._desiredBtnColor;
|
|
229
|
+
|
|
230
|
+
if options.onRelease then options.onRelease(); end;
|
|
231
|
+
end);
|
|
232
|
+
end);
|
|
233
|
+
|
|
234
|
+
local editingTrove = Trove.new();
|
|
235
|
+
local lastActive = touchBtn.Active;
|
|
236
|
+
|
|
237
|
+
self._editEffect = Charm.effect(function()
|
|
238
|
+
local isEditing = Client.configEditingMode();
|
|
239
|
+
|
|
240
|
+
if isEditing then
|
|
241
|
+
lastActive = touchBtn.Active;
|
|
242
|
+
|
|
243
|
+
touchBtn.Active = true;
|
|
244
|
+
touchBtn.ImageColor3 = EDITING_MODE_BUTTON_COLOR;
|
|
245
|
+
iconImage.ImageColor3 = EDITING_MODE_BUTTON_COLOR;
|
|
246
|
+
|
|
247
|
+
local resizeBtn = Instance.new('ImageButton');
|
|
248
|
+
resizeBtn.AnchorPoint = Vector2.new(0.5, 0.5);
|
|
249
|
+
resizeBtn.BackgroundTransparency = 1;
|
|
250
|
+
resizeBtn.Image = 'http://www.roblox.com/asset/?id=101680714548913';
|
|
251
|
+
resizeBtn.Position = UDim2.fromScale(0.82, 0.82);
|
|
252
|
+
resizeBtn.Size = UDim2.fromOffset(18, 18);
|
|
253
|
+
resizeBtn.Parent = touchBtn;
|
|
254
|
+
editingTrove:Add(resizeBtn);
|
|
255
|
+
|
|
256
|
+
resizeBtn.InputBegan:Connect(function(input)
|
|
257
|
+
if (input.UserInputType ~= Enum.UserInputType.MouseButton1) and (input.UserInputType ~= Enum.UserInputType.Touch) then return; end;
|
|
258
|
+
if input.UserInputState ~= Enum.UserInputState.Begin then return; end;
|
|
259
|
+
|
|
260
|
+
local parent = touchBtn.Parent :: GuiObject;
|
|
261
|
+
local parentSize = parent.AbsoluteSize;
|
|
262
|
+
local initialAbsSize = touchBtn.AbsoluteSize;
|
|
263
|
+
local initialPos = Vector2.new(input.Position.X, input.Position.Y);
|
|
264
|
+
|
|
265
|
+
local moveConn = UserInputService.InputChanged:Connect(function(moveInput)
|
|
266
|
+
if (moveInput.UserInputType ~= Enum.UserInputType.MouseMovement) and (moveInput.UserInputType ~= Enum.UserInputType.Touch) then return; end;
|
|
267
|
+
|
|
268
|
+
local delta = Vector2.new(moveInput.Position.X, moveInput.Position.Y) - initialPos;
|
|
269
|
+
local maxDelta = math.max(delta.X, delta.Y);
|
|
270
|
+
local newAbsSize = initialAbsSize.X + maxDelta;
|
|
271
|
+
|
|
272
|
+
local finalAbsSize = math.max(MIN_RESIZE, newAbsSize);
|
|
273
|
+
local finalScale = finalAbsSize / parentSize.X;
|
|
274
|
+
|
|
275
|
+
self:setSize(UDim2.fromScale(finalScale, finalScale));
|
|
276
|
+
end);
|
|
277
|
+
|
|
278
|
+
local endConn;
|
|
279
|
+
endConn = UserInputService.InputEnded:Connect(function(endInput)
|
|
280
|
+
if (endInput.UserInputType ~= Enum.UserInputType.MouseButton1) and (endInput.UserInputType ~= Enum.UserInputType.Touch) then return; end;
|
|
281
|
+
|
|
282
|
+
moveConn:Disconnect();
|
|
283
|
+
endConn:Disconnect();
|
|
284
|
+
end);
|
|
285
|
+
end);
|
|
286
|
+
|
|
287
|
+
editingTrove:Add(touchBtn.InputBegan:Connect(function(input)
|
|
288
|
+
if (input.UserInputType ~= Enum.UserInputType.MouseButton1) and (input.UserInputType ~= Enum.UserInputType.Touch) then return; end;
|
|
289
|
+
if input.UserInputState ~= Enum.UserInputState.Begin then return; end;
|
|
290
|
+
|
|
291
|
+
local parent = touchBtn.Parent :: GuiObject;
|
|
292
|
+
local parentSize = parent.AbsoluteSize;
|
|
293
|
+
local initialInputPos = Vector2.new(input.Position.X, input.Position.Y);
|
|
294
|
+
local initialBtnPos = touchBtn.Position;
|
|
295
|
+
|
|
296
|
+
local moveConn = UserInputService.InputChanged:Connect(function(moveInput)
|
|
297
|
+
if (moveInput.UserInputType ~= Enum.UserInputType.MouseMovement) and (moveInput.UserInputType ~= Enum.UserInputType.Touch) then return; end;
|
|
298
|
+
|
|
299
|
+
local currentPos = Vector2.new(moveInput.Position.X, moveInput.Position.Y);
|
|
300
|
+
local delta = currentPos - initialInputPos;
|
|
301
|
+
|
|
302
|
+
local deltaScale = Vector2.new(delta.X / parentSize.X, delta.Y / parentSize.Y);
|
|
303
|
+
|
|
304
|
+
self:setPosition(UDim2.fromScale(initialBtnPos.X.Scale + deltaScale.X, initialBtnPos.Y.Scale + deltaScale.Y));
|
|
305
|
+
end);
|
|
306
|
+
|
|
307
|
+
local endConn;
|
|
308
|
+
endConn = UserInputService.InputEnded:Connect(function(endInput)
|
|
309
|
+
if (endInput.UserInputType ~= Enum.UserInputType.MouseButton1) and (endInput.UserInputType ~= Enum.UserInputType.Touch) then return; end;
|
|
310
|
+
|
|
311
|
+
moveConn:Disconnect();
|
|
312
|
+
endConn:Disconnect();
|
|
313
|
+
end);
|
|
314
|
+
end));
|
|
315
|
+
else
|
|
316
|
+
editingTrove:Clean();
|
|
317
|
+
|
|
318
|
+
touchBtn.Active = lastActive;
|
|
319
|
+
touchBtn.ImageColor3 = Color3.fromRGB(255, 255, 255);
|
|
320
|
+
iconImage.ImageColor3 = self._desiredBtnColor;
|
|
321
|
+
end;
|
|
322
|
+
end);
|
|
323
|
+
|
|
324
|
+
self:setIcon(options.icon);
|
|
325
|
+
self:setSize(options.size);
|
|
326
|
+
self:setPosition(options.position);
|
|
327
|
+
|
|
328
|
+
local _, currentJumpBtn = getTouchGui();
|
|
329
|
+
touchBtn.Parent = currentJumpBtn;
|
|
330
|
+
|
|
331
|
+
table.insert(Client.touchButtons, self);
|
|
332
|
+
|
|
333
|
+
return self;
|
|
334
|
+
end;
|
|
335
|
+
|
|
336
|
+
function Client:setIcon(icon: string)
|
|
337
|
+
self._touchBtn.Icon.Image = icon;
|
|
338
|
+
end;
|
|
339
|
+
|
|
340
|
+
function Client:setColor(color: Color3)
|
|
341
|
+
self._desiredBtnColor = color;
|
|
342
|
+
self._touchBtn.Icon.ImageColor3 = color;
|
|
343
|
+
end;
|
|
344
|
+
|
|
345
|
+
function Client:setSize(size: UDim2)
|
|
346
|
+
self._touchBtn.Size = size;
|
|
347
|
+
self:_scheduleConfigUpdate();
|
|
348
|
+
end;
|
|
349
|
+
|
|
350
|
+
function Client:setPosition(position: UDim2)
|
|
351
|
+
self._touchBtn.Position = position;
|
|
352
|
+
self:_scheduleConfigUpdate();
|
|
353
|
+
end;
|
|
354
|
+
|
|
355
|
+
function Client:setConfig(config: Shared.TouchButtonConfig)
|
|
356
|
+
self:setPosition(config.position);
|
|
357
|
+
self:setSize(config.size);
|
|
358
|
+
end;
|
|
359
|
+
|
|
360
|
+
function Client:_scheduleConfigUpdate()
|
|
361
|
+
if self._configUpdateScheduled then return; end;
|
|
362
|
+
self._configUpdateScheduled = true;
|
|
363
|
+
|
|
364
|
+
task.defer(function()
|
|
365
|
+
self._configUpdateScheduled = false;
|
|
366
|
+
self:_updateConfig();
|
|
367
|
+
end);
|
|
368
|
+
end;
|
|
369
|
+
|
|
370
|
+
function Client:_updateConfig()
|
|
371
|
+
Shared.setTouchButtonConfig:FireServer(self._name, {
|
|
372
|
+
position = self._touchBtn.Position,
|
|
373
|
+
size = self._touchBtn.Size,
|
|
374
|
+
});
|
|
375
|
+
end;
|
|
376
|
+
|
|
377
|
+
function Client:_resetConfigToDefault()
|
|
378
|
+
self:setConfig(self._defaultConfig);
|
|
379
|
+
end;
|
|
380
|
+
|
|
381
|
+
function Client:destroy()
|
|
382
|
+
self._touchBtn:Destroy();
|
|
383
|
+
|
|
384
|
+
if self._editEffect then
|
|
385
|
+
self._editEffect();
|
|
386
|
+
self._editEffect = nil;
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
local index = table.find(Client.touchButtons, self);
|
|
390
|
+
if index then
|
|
391
|
+
table.remove(Client.touchButtons, index);
|
|
392
|
+
end;
|
|
393
|
+
end;
|
|
394
|
+
|
|
395
|
+
return Client;
|
package/src/Server.lua
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
--!strict
|
|
2
|
+
local DataStoreService = game:GetService('DataStoreService');
|
|
3
|
+
local Players = game:GetService('Players');
|
|
4
|
+
|
|
5
|
+
local Shared = require(script.Parent.Shared);
|
|
6
|
+
local isTypeScriptEnv = script.Parent.Name == 'src';
|
|
7
|
+
local dependencies = if isTypeScriptEnv then script.Parent.Parent.Parent else script.Parent.Parent;
|
|
8
|
+
local Signal = require(isTypeScriptEnv and dependencies['sleitnick-signal'] or dependencies.Signal);
|
|
9
|
+
|
|
10
|
+
type DataStoreEntry = { [string]: Shared.TouchButtonConfig };
|
|
11
|
+
type SerializedTouchButtonConfig = { [string]: {number} };
|
|
12
|
+
type SerializedDataStoreEntry = { [string]: SerializedTouchButtonConfig };
|
|
13
|
+
|
|
14
|
+
local entriesCache: { [Player]: DataStoreEntry } = {};
|
|
15
|
+
local playerDataLoaded = Signal.new();
|
|
16
|
+
local initialized = false;
|
|
17
|
+
|
|
18
|
+
local function getDataStoreKeyForPlayer(player: Player)
|
|
19
|
+
return `Player{player.UserId}`;
|
|
20
|
+
end;
|
|
21
|
+
|
|
22
|
+
local function waitForEntry(player: Player)
|
|
23
|
+
local entry = entriesCache[player];
|
|
24
|
+
if not entry then
|
|
25
|
+
local loadedPlayer, loadedEntry;
|
|
26
|
+
repeat
|
|
27
|
+
loadedPlayer, loadedEntry = playerDataLoaded:Wait();
|
|
28
|
+
until loadedPlayer == player;
|
|
29
|
+
entry = loadedEntry;
|
|
30
|
+
end;
|
|
31
|
+
return entry;
|
|
32
|
+
end;
|
|
33
|
+
|
|
34
|
+
local function serializeEntry(entry: DataStoreEntry)
|
|
35
|
+
local res: SerializedDataStoreEntry = {};
|
|
36
|
+
|
|
37
|
+
for buttonName, props in entry do
|
|
38
|
+
local serializedProps: SerializedTouchButtonConfig = {};
|
|
39
|
+
|
|
40
|
+
for propKey, propValue in props do
|
|
41
|
+
local serializedProp;
|
|
42
|
+
|
|
43
|
+
if typeof(propValue) == 'Vector2' then
|
|
44
|
+
serializedProp = { propValue.X, propValue.Y };
|
|
45
|
+
elseif typeof(propValue) == 'UDim2' then
|
|
46
|
+
serializedProp = { propValue.X.Scale, propValue.X.Offset, propValue.Y.Scale, propValue.Y.Offset };
|
|
47
|
+
end;
|
|
48
|
+
|
|
49
|
+
if serializedProp then
|
|
50
|
+
serializedProps[propKey] = serializedProp;
|
|
51
|
+
end;
|
|
52
|
+
end;
|
|
53
|
+
|
|
54
|
+
res[buttonName] = serializedProps;
|
|
55
|
+
end;
|
|
56
|
+
|
|
57
|
+
return res;
|
|
58
|
+
end;
|
|
59
|
+
|
|
60
|
+
local function deserializeEntry(entry: SerializedDataStoreEntry)
|
|
61
|
+
local res: DataStoreEntry = {};
|
|
62
|
+
|
|
63
|
+
for buttonName, serializedProps in entry do
|
|
64
|
+
local props: Shared.TouchButtonConfig = {};
|
|
65
|
+
|
|
66
|
+
for propKey, serializedValue in serializedProps do
|
|
67
|
+
local deserializedProp;
|
|
68
|
+
|
|
69
|
+
if #serializedValue == 2 then
|
|
70
|
+
deserializedProp = Vector2.new(serializedValue[1], serializedValue[2]);
|
|
71
|
+
elseif #serializedValue == 4 then
|
|
72
|
+
deserializedProp = UDim2.new(serializedValue[1], serializedValue[2], serializedValue[3], serializedValue[4]);
|
|
73
|
+
end;
|
|
74
|
+
|
|
75
|
+
props[propKey] = deserializedProp;
|
|
76
|
+
end;
|
|
77
|
+
|
|
78
|
+
res[buttonName] = props;
|
|
79
|
+
end;
|
|
80
|
+
|
|
81
|
+
return res;
|
|
82
|
+
end;
|
|
83
|
+
|
|
84
|
+
local function isDictOfSize(dict: { [unknown]: unknown }, size: number)
|
|
85
|
+
local dictSize = 0;
|
|
86
|
+
for _ in dict do
|
|
87
|
+
dictSize += 1;
|
|
88
|
+
if dictSize > size then return false; end;
|
|
89
|
+
end;
|
|
90
|
+
return dictSize == size;
|
|
91
|
+
end;
|
|
92
|
+
|
|
93
|
+
local Server = {};
|
|
94
|
+
|
|
95
|
+
function Server.init(validTouchButtonNames: { string })
|
|
96
|
+
assert(not initialized, 'TouchButton.Server already initialized');
|
|
97
|
+
initialized = true;
|
|
98
|
+
|
|
99
|
+
local buttonsStore = DataStoreService:GetDataStore('TouchButtonConfigs');
|
|
100
|
+
|
|
101
|
+
Shared.getTouchButtonConfig.OnServerInvoke = function(player: Player, buttonName: string)
|
|
102
|
+
local entry = waitForEntry(player);
|
|
103
|
+
return entry[buttonName];
|
|
104
|
+
end;
|
|
105
|
+
|
|
106
|
+
Shared.setTouchButtonConfig.OnServerEvent:Connect(function(player: Player, buttonName: string, config: Shared.TouchButtonConfig)
|
|
107
|
+
assert(table.find(validTouchButtonNames, buttonName), `TouchButton: {buttonName} is not a valid savable TouchButton name`);
|
|
108
|
+
|
|
109
|
+
if typeof(config) ~= 'table' then return; end;
|
|
110
|
+
if not config.size then return; end;
|
|
111
|
+
if not config.position then return; end;
|
|
112
|
+
if not isDictOfSize(config, 2) then return; end;
|
|
113
|
+
|
|
114
|
+
local entry = waitForEntry(player);
|
|
115
|
+
entry[buttonName] = config;
|
|
116
|
+
end);
|
|
117
|
+
|
|
118
|
+
local function onPlayerAdded(player: Player)
|
|
119
|
+
local key = getDataStoreKeyForPlayer(player);
|
|
120
|
+
|
|
121
|
+
local suc, entryOrUndefinedOrErr = pcall(function()
|
|
122
|
+
return buttonsStore:GetAsync(key);
|
|
123
|
+
end);
|
|
124
|
+
if not suc then
|
|
125
|
+
warn('TouchButton: Failed to load DataStore entry:', entryOrUndefinedOrErr);
|
|
126
|
+
return;
|
|
127
|
+
end;
|
|
128
|
+
|
|
129
|
+
if not player:IsDescendantOf(Players) then return; end;
|
|
130
|
+
|
|
131
|
+
local entry = if entryOrUndefinedOrErr then deserializeEntry(entryOrUndefinedOrErr) else {};
|
|
132
|
+
|
|
133
|
+
entriesCache[player] = entry;
|
|
134
|
+
playerDataLoaded:Fire(player, entry);
|
|
135
|
+
end;
|
|
136
|
+
|
|
137
|
+
local function onPlayerRemoving(player: Player)
|
|
138
|
+
local entry = entriesCache[player];
|
|
139
|
+
if not entry then return; end;
|
|
140
|
+
|
|
141
|
+
local key = getDataStoreKeyForPlayer(player);
|
|
142
|
+
local serialized = serializeEntry(entry);
|
|
143
|
+
|
|
144
|
+
local suc, err = pcall(function()
|
|
145
|
+
buttonsStore:SetAsync(key, serialized, { player.UserId });
|
|
146
|
+
end);
|
|
147
|
+
if not suc then
|
|
148
|
+
warn(`TouchButton: Failed to save data for {player.Name}:`, err);
|
|
149
|
+
end;
|
|
150
|
+
|
|
151
|
+
entriesCache[player] = nil;
|
|
152
|
+
end;
|
|
153
|
+
|
|
154
|
+
for _, player in Players:GetPlayers() do
|
|
155
|
+
task.spawn(onPlayerAdded, player);
|
|
156
|
+
end;
|
|
157
|
+
Players.PlayerAdded:Connect(onPlayerAdded);
|
|
158
|
+
Players.PlayerRemoving:Connect(onPlayerRemoving);
|
|
159
|
+
end;
|
|
160
|
+
|
|
161
|
+
return Server;
|
package/src/Shared.lua
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
--!strict
|
|
2
|
+
local RunService = game:GetService('RunService');
|
|
3
|
+
|
|
4
|
+
export type TouchButtonConfig = {
|
|
5
|
+
position: UDim2,
|
|
6
|
+
size: UDim2,
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
local IS_SERVER = RunService:IsServer();
|
|
10
|
+
|
|
11
|
+
local function getRemoteEvent(name: string)
|
|
12
|
+
if IS_SERVER then
|
|
13
|
+
local remote = Instance.new('RemoteEvent');
|
|
14
|
+
remote.Name = name;
|
|
15
|
+
remote.Parent = script;
|
|
16
|
+
return remote;
|
|
17
|
+
end;
|
|
18
|
+
|
|
19
|
+
return script:WaitForChild(name) :: RemoteEvent;
|
|
20
|
+
end;
|
|
21
|
+
|
|
22
|
+
local function getRemoteFunction(name: string)
|
|
23
|
+
if IS_SERVER then
|
|
24
|
+
local remote = Instance.new('RemoteFunction');
|
|
25
|
+
remote.Name = name;
|
|
26
|
+
remote.Parent = script;
|
|
27
|
+
return remote;
|
|
28
|
+
end;
|
|
29
|
+
|
|
30
|
+
return script:WaitForChild(name) :: RemoteFunction;
|
|
31
|
+
end;
|
|
32
|
+
|
|
33
|
+
local Shared = {};
|
|
34
|
+
|
|
35
|
+
Shared.getTouchButtonConfig = getRemoteFunction('GetTouchButtonConfig');
|
|
36
|
+
Shared.setTouchButtonConfig = getRemoteEvent('SetTouchButtonConfig');
|
|
37
|
+
|
|
38
|
+
return Shared;
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
declare namespace TouchButton {
|
|
2
|
+
interface TouchButtonConfig {
|
|
3
|
+
position: UDim2;
|
|
4
|
+
size: UDim2;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
interface TouchButtonOptions {
|
|
8
|
+
name: string;
|
|
9
|
+
icon: string;
|
|
10
|
+
size: UDim2;
|
|
11
|
+
position: UDim2;
|
|
12
|
+
sinkInput?: boolean;
|
|
13
|
+
onPress?: () => void;
|
|
14
|
+
onRelease?: () => void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
namespace Server {
|
|
18
|
+
function init(validTouchButtonNames: string[]): void;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
class Client {
|
|
22
|
+
static touchButtons: Client[];
|
|
23
|
+
static configEditingMode: (enabled?: boolean) => boolean;
|
|
24
|
+
|
|
25
|
+
constructor(options: TouchButtonOptions);
|
|
26
|
+
|
|
27
|
+
setIcon(icon: string): void;
|
|
28
|
+
setColor(color: Color3): void;
|
|
29
|
+
setSize(size: UDim2): void;
|
|
30
|
+
setPosition(position: UDim2): void;
|
|
31
|
+
setConfig(config: TouchButtonConfig): void;
|
|
32
|
+
destroy(): void;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export = TouchButton;
|
package/src/init.lua
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
--!strict
|
|
2
|
+
local RunService = game:GetService('RunService');
|
|
3
|
+
|
|
4
|
+
local TouchButton = {};
|
|
5
|
+
|
|
6
|
+
if RunService:IsServer() then
|
|
7
|
+
TouchButton.Server = require(script.Server);
|
|
8
|
+
else
|
|
9
|
+
TouchButton.Client = require(script.Client);
|
|
10
|
+
end;
|
|
11
|
+
|
|
12
|
+
return TouchButton;
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { TouchButtonConfig } from '../shared/remotes';
|
|
2
|
-
export declare class TouchButton {
|
|
3
|
-
static configEditingMode: import("@rbxts/charm").Atom<boolean>;
|
|
4
|
-
static touchButtons: TouchButton[];
|
|
5
|
-
private static initialized;
|
|
6
|
-
private touchBtn;
|
|
7
|
-
private name;
|
|
8
|
-
private defaultConfig;
|
|
9
|
-
private configUpdateScheduled;
|
|
10
|
-
private static init;
|
|
11
|
-
constructor(options: {
|
|
12
|
-
name: string;
|
|
13
|
-
icon: string;
|
|
14
|
-
size: UDim2;
|
|
15
|
-
position: UDim2;
|
|
16
|
-
sinkInput?: boolean;
|
|
17
|
-
onPress?: () => void;
|
|
18
|
-
onRelease?: () => void;
|
|
19
|
-
});
|
|
20
|
-
setIcon(icon: string): void;
|
|
21
|
-
setSize(size: UDim2): void;
|
|
22
|
-
setPosition(position: UDim2): void;
|
|
23
|
-
setConfig(config: TouchButtonConfig): void;
|
|
24
|
-
private scheduleConfigUpdate;
|
|
25
|
-
private updateConfig;
|
|
26
|
-
private resetConfigToDefault;
|
|
27
|
-
destroy(): void;
|
|
28
|
-
}
|
|
@@ -1,334 +0,0 @@
|
|
|
1
|
-
-- Compiled with roblox-ts v3.0.0
|
|
2
|
-
local TS = _G[script]
|
|
3
|
-
local observeProperty = TS.import(script, TS.getModule(script, "@rbxts", "roblox-observers").src).observeProperty
|
|
4
|
-
local _services = TS.import(script, TS.getModule(script, "@rbxts", "services"))
|
|
5
|
-
local Players = _services.Players
|
|
6
|
-
local UserInputService = _services.UserInputService
|
|
7
|
-
local Workspace = _services.Workspace
|
|
8
|
-
local remotes = TS.import(script, script.Parent.Parent, "shared", "remotes").remotes
|
|
9
|
-
local _charm = TS.import(script, TS.getModule(script, "@rbxts", "charm"))
|
|
10
|
-
local atom = _charm.atom
|
|
11
|
-
local effect = _charm.effect
|
|
12
|
-
local Trove = TS.import(script, TS.getModule(script, "@rbxts", "sleitnick-trove").src)
|
|
13
|
-
local localPlayer = Players.LocalPlayer
|
|
14
|
-
local camera = Workspace.CurrentCamera
|
|
15
|
-
local touchGui
|
|
16
|
-
local jumpBtn
|
|
17
|
-
local EDITING_MODE_BUTTON_COLOR = Color3.fromRGB(15, 143, 255)
|
|
18
|
-
local MIN_RESIZE = 40
|
|
19
|
-
-- This is taken from TouchJump.lua in PlayerScripts.
|
|
20
|
-
local function getJumpButtonLayout()
|
|
21
|
-
local minAxis = math.min(camera.ViewportSize.X, camera.ViewportSize.Y)
|
|
22
|
-
local isSmallScreen = minAxis <= 500
|
|
23
|
-
local jumpButtonSize = if isSmallScreen then 70 else 120
|
|
24
|
-
return {
|
|
25
|
-
size = UDim2.fromOffset(jumpButtonSize, jumpButtonSize),
|
|
26
|
-
position = if isSmallScreen then UDim2.new(1, -(jumpButtonSize * 1.5 - 10), 1, -jumpButtonSize - 20) else UDim2.new(1, -(jumpButtonSize * 1.5 - 10), 1, -jumpButtonSize * 1.75),
|
|
27
|
-
}
|
|
28
|
-
end
|
|
29
|
-
local function getTouchGui()
|
|
30
|
-
if touchGui and jumpBtn then
|
|
31
|
-
return { touchGui, jumpBtn }
|
|
32
|
-
end
|
|
33
|
-
touchGui = Instance.new("ScreenGui")
|
|
34
|
-
touchGui.Name = "CustomTouchGui"
|
|
35
|
-
touchGui.ResetOnSpawn = false
|
|
36
|
-
touchGui.Parent = localPlayer.PlayerGui
|
|
37
|
-
jumpBtn = Instance.new("Frame")
|
|
38
|
-
jumpBtn.Name = "Jump"
|
|
39
|
-
jumpBtn.BackgroundTransparency = 1
|
|
40
|
-
observeProperty(camera, "ViewportSize", function()
|
|
41
|
-
local jumpBtnLayout = getJumpButtonLayout()
|
|
42
|
-
jumpBtn.Position = jumpBtnLayout.position
|
|
43
|
-
jumpBtn.Size = jumpBtnLayout.size
|
|
44
|
-
end)
|
|
45
|
-
jumpBtn.Parent = touchGui
|
|
46
|
-
local characterExists = atom(not not localPlayer.Character)
|
|
47
|
-
local lastInputType = atom(UserInputService:GetLastInputType())
|
|
48
|
-
effect(function()
|
|
49
|
-
local charExists = characterExists()
|
|
50
|
-
local inputType = lastInputType()
|
|
51
|
-
touchGui.Enabled = (inputType == Enum.UserInputType.Touch) and charExists
|
|
52
|
-
end)
|
|
53
|
-
localPlayer.CharacterAdded:Connect(function()
|
|
54
|
-
characterExists(true)
|
|
55
|
-
end)
|
|
56
|
-
localPlayer.CharacterRemoving:Connect(function()
|
|
57
|
-
characterExists(false)
|
|
58
|
-
end)
|
|
59
|
-
UserInputService.LastInputTypeChanged:Connect(lastInputType)
|
|
60
|
-
return { touchGui, jumpBtn }
|
|
61
|
-
end
|
|
62
|
-
local TouchButton
|
|
63
|
-
do
|
|
64
|
-
TouchButton = setmetatable({}, {
|
|
65
|
-
__tostring = function()
|
|
66
|
-
return "TouchButton"
|
|
67
|
-
end,
|
|
68
|
-
})
|
|
69
|
-
TouchButton.__index = TouchButton
|
|
70
|
-
function TouchButton.new(...)
|
|
71
|
-
local self = setmetatable({}, TouchButton)
|
|
72
|
-
return self:constructor(...) or self
|
|
73
|
-
end
|
|
74
|
-
function TouchButton:constructor(options)
|
|
75
|
-
self.configUpdateScheduled = false
|
|
76
|
-
TouchButton:init()
|
|
77
|
-
for _1, otherTouchBtn in TouchButton.touchButtons do
|
|
78
|
-
local _arg0 = otherTouchBtn.name ~= options.name
|
|
79
|
-
local _arg1 = `A TouchButton with the name {options.name} already exists`
|
|
80
|
-
assert(_arg0, _arg1)
|
|
81
|
-
end
|
|
82
|
-
self.name = options.name
|
|
83
|
-
self.defaultConfig = {
|
|
84
|
-
position = options.position,
|
|
85
|
-
size = options.size,
|
|
86
|
-
}
|
|
87
|
-
local configPromise = remotes.getTouchButtonConfig:request(options.name)
|
|
88
|
-
local touchBtn = Instance.new("ImageButton")
|
|
89
|
-
touchBtn.Name = "TouchButton"
|
|
90
|
-
touchBtn.Active = not not options.sinkInput
|
|
91
|
-
touchBtn.Image = "http://www.roblox.com/asset/?id=15340864550"
|
|
92
|
-
touchBtn.ImageColor3 = Color3.fromRGB(255, 255, 255)
|
|
93
|
-
touchBtn.ImageTransparency = 0.5
|
|
94
|
-
touchBtn.AnchorPoint = Vector2.new(0.5, 0.5)
|
|
95
|
-
touchBtn.BackgroundTransparency = 1
|
|
96
|
-
touchBtn.Size = UDim2.fromScale(1, 1)
|
|
97
|
-
local iconImage = Instance.new("ImageLabel")
|
|
98
|
-
iconImage.Name = "Icon"
|
|
99
|
-
iconImage.AnchorPoint = Vector2.new(0.5, 0.5)
|
|
100
|
-
iconImage.BackgroundTransparency = 1
|
|
101
|
-
iconImage.ImageTransparency = 0.2
|
|
102
|
-
iconImage.Position = UDim2.fromScale(0.5, 0.5)
|
|
103
|
-
iconImage.Size = UDim2.fromScale(0.56, 0.56)
|
|
104
|
-
iconImage.Parent = touchBtn
|
|
105
|
-
touchBtn.InputBegan:Connect(function(input)
|
|
106
|
-
if (input.UserInputType ~= Enum.UserInputType.MouseButton1) and (input.UserInputType ~= Enum.UserInputType.Touch) then
|
|
107
|
-
return nil
|
|
108
|
-
end
|
|
109
|
-
if input.UserInputState ~= Enum.UserInputState.Begin then
|
|
110
|
-
return nil
|
|
111
|
-
end
|
|
112
|
-
if TouchButton.configEditingMode() then
|
|
113
|
-
return nil
|
|
114
|
-
end
|
|
115
|
-
touchBtn.ImageColor3 = Color3.fromRGB(200, 200, 200)
|
|
116
|
-
iconImage.ImageColor3 = Color3.fromRGB(200, 200, 200)
|
|
117
|
-
local _result = options.onPress
|
|
118
|
-
if _result ~= nil then
|
|
119
|
-
_result()
|
|
120
|
-
end
|
|
121
|
-
local connection
|
|
122
|
-
connection = input:GetPropertyChangedSignal("UserInputState"):Connect(function()
|
|
123
|
-
if input.UserInputState ~= Enum.UserInputState.End then
|
|
124
|
-
return nil
|
|
125
|
-
end
|
|
126
|
-
connection:Disconnect()
|
|
127
|
-
touchBtn.ImageColor3 = Color3.fromRGB(255, 255, 255)
|
|
128
|
-
iconImage.ImageColor3 = Color3.fromRGB(255, 255, 255)
|
|
129
|
-
local _result_1 = options.onRelease
|
|
130
|
-
if _result_1 ~= nil then
|
|
131
|
-
_result_1()
|
|
132
|
-
end
|
|
133
|
-
end)
|
|
134
|
-
end)
|
|
135
|
-
local editingTrove = Trove.new()
|
|
136
|
-
local lastActive = touchBtn.Active
|
|
137
|
-
effect(function()
|
|
138
|
-
local isEditing = TouchButton.configEditingMode()
|
|
139
|
-
if isEditing then
|
|
140
|
-
lastActive = touchBtn.Active
|
|
141
|
-
touchBtn.Active = true
|
|
142
|
-
touchBtn.ImageColor3 = EDITING_MODE_BUTTON_COLOR
|
|
143
|
-
iconImage.ImageColor3 = EDITING_MODE_BUTTON_COLOR
|
|
144
|
-
local resizeBtn = Instance.new("ImageButton")
|
|
145
|
-
resizeBtn.AnchorPoint = Vector2.new(0.5, 0.5)
|
|
146
|
-
resizeBtn.BackgroundTransparency = 1
|
|
147
|
-
resizeBtn.Image = "http://www.roblox.com/asset/?id=101680714548913"
|
|
148
|
-
resizeBtn.Position = UDim2.fromScale(0.82, 0.82)
|
|
149
|
-
resizeBtn.Size = UDim2.fromOffset(18, 18)
|
|
150
|
-
resizeBtn.Parent = touchBtn
|
|
151
|
-
editingTrove:Add(resizeBtn)
|
|
152
|
-
resizeBtn.InputBegan:Connect(function(input)
|
|
153
|
-
if (input.UserInputType ~= Enum.UserInputType.MouseButton1) and (input.UserInputType ~= Enum.UserInputType.Touch) then
|
|
154
|
-
return nil
|
|
155
|
-
end
|
|
156
|
-
if input.UserInputState ~= Enum.UserInputState.Begin then
|
|
157
|
-
return nil
|
|
158
|
-
end
|
|
159
|
-
local parentSize = (touchBtn.Parent).AbsoluteSize
|
|
160
|
-
local initialAbsSize = touchBtn.AbsoluteSize
|
|
161
|
-
local initialPos = Vector2.new(input.Position.X, input.Position.Y)
|
|
162
|
-
local moveConn = UserInputService.InputChanged:Connect(function(moveInput)
|
|
163
|
-
if (moveInput.UserInputType ~= Enum.UserInputType.MouseMovement) and (moveInput.UserInputType ~= Enum.UserInputType.Touch) then
|
|
164
|
-
return nil
|
|
165
|
-
end
|
|
166
|
-
local delta = Vector2.new(moveInput.Position.X, moveInput.Position.Y) - initialPos
|
|
167
|
-
local maxDelta = math.max(delta.X, delta.Y)
|
|
168
|
-
local newAbsSize = initialAbsSize.X + maxDelta
|
|
169
|
-
local finalAbsSize = math.max(MIN_RESIZE, newAbsSize)
|
|
170
|
-
local finalScale = finalAbsSize / parentSize.X
|
|
171
|
-
self:setSize(UDim2.fromScale(finalScale, finalScale))
|
|
172
|
-
end)
|
|
173
|
-
local endConn
|
|
174
|
-
endConn = UserInputService.InputEnded:Connect(function(endInput)
|
|
175
|
-
if (endInput.UserInputType ~= Enum.UserInputType.MouseButton1) and (endInput.UserInputType ~= Enum.UserInputType.Touch) then
|
|
176
|
-
return nil
|
|
177
|
-
end
|
|
178
|
-
moveConn:Disconnect()
|
|
179
|
-
endConn:Disconnect()
|
|
180
|
-
end)
|
|
181
|
-
end)
|
|
182
|
-
editingTrove:Add(touchBtn.InputBegan:Connect(function(input)
|
|
183
|
-
if (input.UserInputType ~= Enum.UserInputType.MouseButton1) and (input.UserInputType ~= Enum.UserInputType.Touch) then
|
|
184
|
-
return nil
|
|
185
|
-
end
|
|
186
|
-
if input.UserInputState ~= Enum.UserInputState.Begin then
|
|
187
|
-
return nil
|
|
188
|
-
end
|
|
189
|
-
local parentSize = (touchBtn.Parent).AbsoluteSize
|
|
190
|
-
local initialInputPos = Vector2.new(input.Position.X, input.Position.Y)
|
|
191
|
-
local initialBtnPos = touchBtn.Position
|
|
192
|
-
local moveConn = UserInputService.InputChanged:Connect(function(moveInput)
|
|
193
|
-
if (moveInput.UserInputType ~= Enum.UserInputType.MouseMovement) and (moveInput.UserInputType ~= Enum.UserInputType.Touch) then
|
|
194
|
-
return nil
|
|
195
|
-
end
|
|
196
|
-
local currentPos = Vector2.new(moveInput.Position.X, moveInput.Position.Y)
|
|
197
|
-
local delta = currentPos - initialInputPos
|
|
198
|
-
local deltaScale = Vector2.new(delta.X / parentSize.X, delta.Y / parentSize.Y)
|
|
199
|
-
self:setPosition(UDim2.fromScale(initialBtnPos.X.Scale + deltaScale.X, initialBtnPos.Y.Scale + deltaScale.Y))
|
|
200
|
-
end)
|
|
201
|
-
local endConn
|
|
202
|
-
endConn = UserInputService.InputEnded:Connect(function(endInput)
|
|
203
|
-
if (endInput.UserInputType ~= Enum.UserInputType.MouseButton1) and (endInput.UserInputType ~= Enum.UserInputType.Touch) then
|
|
204
|
-
return nil
|
|
205
|
-
end
|
|
206
|
-
moveConn:Disconnect()
|
|
207
|
-
endConn:Disconnect()
|
|
208
|
-
end)
|
|
209
|
-
end))
|
|
210
|
-
else
|
|
211
|
-
editingTrove:Clean()
|
|
212
|
-
touchBtn.Active = lastActive
|
|
213
|
-
touchBtn.ImageColor3 = Color3.fromRGB(255, 255, 255)
|
|
214
|
-
iconImage.ImageColor3 = Color3.fromRGB(255, 255, 255)
|
|
215
|
-
end
|
|
216
|
-
end)
|
|
217
|
-
self.touchBtn = touchBtn
|
|
218
|
-
self:setIcon(options.icon)
|
|
219
|
-
self:setSize(options.size)
|
|
220
|
-
self:setPosition(options.position)
|
|
221
|
-
configPromise:andThen(function(config)
|
|
222
|
-
if not config then
|
|
223
|
-
return nil
|
|
224
|
-
end
|
|
225
|
-
self:setConfig(config)
|
|
226
|
-
end)
|
|
227
|
-
local _binding = getTouchGui()
|
|
228
|
-
local _ = _binding[1]
|
|
229
|
-
local jumpBtn = _binding[2]
|
|
230
|
-
touchBtn.Parent = jumpBtn
|
|
231
|
-
local _touchButtons = TouchButton.touchButtons
|
|
232
|
-
local _self = self
|
|
233
|
-
table.insert(_touchButtons, _self)
|
|
234
|
-
end
|
|
235
|
-
function TouchButton:init()
|
|
236
|
-
if self.initialized then
|
|
237
|
-
return nil
|
|
238
|
-
end
|
|
239
|
-
self.initialized = true
|
|
240
|
-
local editingTrove = Trove.new()
|
|
241
|
-
effect(function()
|
|
242
|
-
local isEditing = TouchButton.configEditingMode()
|
|
243
|
-
if isEditing then
|
|
244
|
-
local createButton = function(props)
|
|
245
|
-
local btn = Instance.new("TextButton")
|
|
246
|
-
btn.Name = props.name
|
|
247
|
-
btn.AnchorPoint = Vector2.new(0.5, 0.5)
|
|
248
|
-
btn.BackgroundColor3 = props.backgroundColor
|
|
249
|
-
btn.BackgroundTransparency = 0.25
|
|
250
|
-
btn.FontFace = Font.new("rbxasset://fonts/families/GothamSSm.json", Enum.FontWeight.Medium, Enum.FontStyle.Normal)
|
|
251
|
-
btn.Position = UDim2.new(0.5, 0, 0.5, props.offsetY)
|
|
252
|
-
btn.Size = UDim2.fromOffset(120, 32)
|
|
253
|
-
btn.Text = props.text
|
|
254
|
-
btn.TextColor3 = Color3.new(1, 1, 1)
|
|
255
|
-
btn.TextSize = 16
|
|
256
|
-
btn.Parent = touchGui
|
|
257
|
-
editingTrove:Add(btn)
|
|
258
|
-
local UICorner = Instance.new("UICorner")
|
|
259
|
-
UICorner.CornerRadius = UDim.new(0, 5)
|
|
260
|
-
UICorner.Parent = btn
|
|
261
|
-
return btn
|
|
262
|
-
end
|
|
263
|
-
createButton({
|
|
264
|
-
name = "Reset",
|
|
265
|
-
text = "Reset Buttons",
|
|
266
|
-
backgroundColor = Color3.new(),
|
|
267
|
-
offsetY = -19,
|
|
268
|
-
}).MouseButton1Click:Connect(function()
|
|
269
|
-
for _, touchBtn in TouchButton.touchButtons do
|
|
270
|
-
touchBtn:resetConfigToDefault()
|
|
271
|
-
end
|
|
272
|
-
end)
|
|
273
|
-
createButton({
|
|
274
|
-
name = "Finish",
|
|
275
|
-
text = "Finish Editing",
|
|
276
|
-
backgroundColor = Color3.fromRGB(30, 175, 30),
|
|
277
|
-
offsetY = 19,
|
|
278
|
-
}).MouseButton1Click:Connect(function()
|
|
279
|
-
self.configEditingMode(false)
|
|
280
|
-
end)
|
|
281
|
-
else
|
|
282
|
-
editingTrove:Clean()
|
|
283
|
-
end
|
|
284
|
-
end)
|
|
285
|
-
end
|
|
286
|
-
function TouchButton:setIcon(icon)
|
|
287
|
-
self.touchBtn.Icon.Image = icon
|
|
288
|
-
end
|
|
289
|
-
function TouchButton:setSize(size)
|
|
290
|
-
self.touchBtn.Size = size
|
|
291
|
-
self:scheduleConfigUpdate()
|
|
292
|
-
end
|
|
293
|
-
function TouchButton:setPosition(position)
|
|
294
|
-
self.touchBtn.Position = position
|
|
295
|
-
self:scheduleConfigUpdate()
|
|
296
|
-
end
|
|
297
|
-
function TouchButton:setConfig(config)
|
|
298
|
-
self:setPosition(config.position)
|
|
299
|
-
self:setSize(config.size)
|
|
300
|
-
end
|
|
301
|
-
function TouchButton:scheduleConfigUpdate()
|
|
302
|
-
if self.configUpdateScheduled then
|
|
303
|
-
return nil
|
|
304
|
-
end
|
|
305
|
-
self.configUpdateScheduled = true
|
|
306
|
-
task.defer(function()
|
|
307
|
-
self.configUpdateScheduled = false
|
|
308
|
-
self:updateConfig()
|
|
309
|
-
end)
|
|
310
|
-
end
|
|
311
|
-
function TouchButton:updateConfig()
|
|
312
|
-
remotes.setTouchButtonConfig:fire(self.name, {
|
|
313
|
-
position = self.touchBtn.Position,
|
|
314
|
-
size = self.touchBtn.Size,
|
|
315
|
-
})
|
|
316
|
-
end
|
|
317
|
-
function TouchButton:resetConfigToDefault()
|
|
318
|
-
self:setConfig(self.defaultConfig)
|
|
319
|
-
end
|
|
320
|
-
function TouchButton:destroy()
|
|
321
|
-
self.touchBtn:Destroy()
|
|
322
|
-
local _touchButtons = TouchButton.touchButtons
|
|
323
|
-
local _touchButtons_1 = TouchButton.touchButtons
|
|
324
|
-
local _self = self
|
|
325
|
-
local _arg0 = (table.find(_touchButtons_1, _self) or 0) - 1
|
|
326
|
-
table.remove(_touchButtons, _arg0 + 1)
|
|
327
|
-
end
|
|
328
|
-
TouchButton.configEditingMode = atom(false)
|
|
329
|
-
TouchButton.touchButtons = {}
|
|
330
|
-
TouchButton.initialized = false
|
|
331
|
-
end
|
|
332
|
-
return {
|
|
333
|
-
TouchButton = TouchButton,
|
|
334
|
-
}
|
package/out/index.d.ts
DELETED
package/out/init.luau
DELETED
package/out/server/index.d.ts
DELETED
package/out/server/init.luau
DELETED
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
-- Compiled with roblox-ts v3.0.0
|
|
2
|
-
local TS = _G[script]
|
|
3
|
-
local _services = TS.import(script, TS.getModule(script, "@rbxts", "services"))
|
|
4
|
-
local DataStoreService = _services.DataStoreService
|
|
5
|
-
local RunService = _services.RunService
|
|
6
|
-
local remotes = TS.import(script, script.Parent, "shared", "remotes").remotes
|
|
7
|
-
local observePlayers = TS.import(script, TS.getModule(script, "@rbxts", "roblox-observers").src).observePlayers
|
|
8
|
-
local Signal = TS.import(script, TS.getModule(script, "@rbxts", "sleitnick-signal"))
|
|
9
|
-
local entriesCache = {}
|
|
10
|
-
local playerDataLoaded = Signal.new()
|
|
11
|
-
local initialized = false
|
|
12
|
-
local function getDataStoreKeyForPlayer(player)
|
|
13
|
-
return `Player{player.UserId}`
|
|
14
|
-
end
|
|
15
|
-
local function waitForEntry(player)
|
|
16
|
-
local _player = player
|
|
17
|
-
local entry = entriesCache[_player]
|
|
18
|
-
if not entry then
|
|
19
|
-
local loadedPlayer
|
|
20
|
-
local loadedEntry
|
|
21
|
-
repeat
|
|
22
|
-
do
|
|
23
|
-
loadedPlayer, loadedEntry = playerDataLoaded:Wait()
|
|
24
|
-
end
|
|
25
|
-
until not (loadedPlayer ~= player)
|
|
26
|
-
entry = loadedEntry
|
|
27
|
-
end
|
|
28
|
-
return entry
|
|
29
|
-
end
|
|
30
|
-
local function serializeEntry(entry)
|
|
31
|
-
local res = {}
|
|
32
|
-
for buttonName, props in pairs(entry) do
|
|
33
|
-
local serializedProps = {}
|
|
34
|
-
for propKey, propValue in pairs(props) do
|
|
35
|
-
local serializedProp
|
|
36
|
-
if typeof(propValue) == "Vector2" then
|
|
37
|
-
serializedProp = { propValue.X, propValue.Y }
|
|
38
|
-
elseif typeof(propValue) == "UDim2" then
|
|
39
|
-
serializedProp = { propValue.X.Scale, propValue.X.Offset, propValue.Y.Scale, propValue.Y.Offset }
|
|
40
|
-
end
|
|
41
|
-
serializedProps[propKey] = serializedProp
|
|
42
|
-
end
|
|
43
|
-
res[buttonName] = serializedProps
|
|
44
|
-
end
|
|
45
|
-
return res
|
|
46
|
-
end
|
|
47
|
-
local function deserializeEntry(entry)
|
|
48
|
-
local res = {}
|
|
49
|
-
for buttonName, serializedProps in pairs(entry) do
|
|
50
|
-
local props = {}
|
|
51
|
-
for propKey, serializedValue in pairs(serializedProps) do
|
|
52
|
-
local deserializedProp
|
|
53
|
-
if #serializedValue == 2 then
|
|
54
|
-
deserializedProp = Vector2.new(serializedValue[1], serializedValue[2])
|
|
55
|
-
elseif #serializedValue == 4 then
|
|
56
|
-
deserializedProp = UDim2.new(serializedValue[1], serializedValue[2], serializedValue[3], serializedValue[4])
|
|
57
|
-
end
|
|
58
|
-
props[propKey] = deserializedProp
|
|
59
|
-
end
|
|
60
|
-
res[buttonName] = props
|
|
61
|
-
end
|
|
62
|
-
return res
|
|
63
|
-
end
|
|
64
|
-
local TouchButtonServer = {}
|
|
65
|
-
do
|
|
66
|
-
local _container = TouchButtonServer
|
|
67
|
-
local function init(validTouchButtonNames)
|
|
68
|
-
local _arg0 = RunService:IsServer()
|
|
69
|
-
assert(_arg0, "TouchButtonServer can only be initialized from the server")
|
|
70
|
-
local _arg0_1 = not initialized
|
|
71
|
-
assert(_arg0_1, "TouchButtonServer already initialized")
|
|
72
|
-
initialized = true
|
|
73
|
-
local buttonsStore = DataStoreService:GetDataStore("TouchButtonConfigs")
|
|
74
|
-
remotes.getTouchButtonConfig:onRequest(function(player, buttonName)
|
|
75
|
-
local entry = waitForEntry(player)
|
|
76
|
-
return entry[buttonName]
|
|
77
|
-
end)
|
|
78
|
-
remotes.setTouchButtonConfig:connect(function(player, buttonName, config)
|
|
79
|
-
local _validTouchButtonNames = validTouchButtonNames
|
|
80
|
-
local _buttonName = buttonName
|
|
81
|
-
local _arg0_2 = _validTouchButtonNames[_buttonName] ~= nil
|
|
82
|
-
local _arg1 = `[TOUCH BUTTONS] {buttonName} is not a valid savable TouchButton name`
|
|
83
|
-
assert(_arg0_2, _arg1)
|
|
84
|
-
local entry = waitForEntry(player)
|
|
85
|
-
entry[buttonName] = config
|
|
86
|
-
end)
|
|
87
|
-
observePlayers(function(player)
|
|
88
|
-
local key = getDataStoreKeyForPlayer(player)
|
|
89
|
-
local suc, entryOrUndefinedOrErr = pcall(function()
|
|
90
|
-
return buttonsStore:GetAsync(key)
|
|
91
|
-
end)
|
|
92
|
-
if not suc then
|
|
93
|
-
warn(`[TOUCH BUTTONS] Failed to load DataStore entry:`, entryOrUndefinedOrErr)
|
|
94
|
-
return nil
|
|
95
|
-
end
|
|
96
|
-
local entry = if entryOrUndefinedOrErr ~= 0 and entryOrUndefinedOrErr == entryOrUndefinedOrErr and entryOrUndefinedOrErr ~= "" and entryOrUndefinedOrErr then deserializeEntry(entryOrUndefinedOrErr) else {}
|
|
97
|
-
local _player = player
|
|
98
|
-
local _entry = entry
|
|
99
|
-
entriesCache[_player] = _entry
|
|
100
|
-
playerDataLoaded:Fire(player, entry)
|
|
101
|
-
return function()
|
|
102
|
-
local _player_1 = player
|
|
103
|
-
local entry = entriesCache[_player_1]
|
|
104
|
-
if not entry then
|
|
105
|
-
return nil
|
|
106
|
-
end
|
|
107
|
-
buttonsStore:SetAsync(key, serializeEntry(entry), { player.UserId })
|
|
108
|
-
end
|
|
109
|
-
end)
|
|
110
|
-
end
|
|
111
|
-
_container.init = init
|
|
112
|
-
end
|
|
113
|
-
return {
|
|
114
|
-
TouchButtonServer = TouchButtonServer,
|
|
115
|
-
}
|
package/out/shared/remotes.d.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { Server } from '@rbxts/remo';
|
|
2
|
-
export interface TouchButtonConfig {
|
|
3
|
-
position: UDim2;
|
|
4
|
-
size: UDim2;
|
|
5
|
-
}
|
|
6
|
-
export declare const remotes: import("@rbxts/remo").Remotes<{
|
|
7
|
-
getTouchButtonConfig: import("@rbxts/remo").RemoteBuilder<(buttonName: string) => TouchButtonConfig | undefined, Server>;
|
|
8
|
-
setTouchButtonConfig: import("@rbxts/remo").RemoteBuilder<(buttonName: string, config: TouchButtonConfig) => void, Server>;
|
|
9
|
-
}>;
|
package/out/shared/remotes.luau
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
-- Compiled with roblox-ts v3.0.0
|
|
2
|
-
local TS = _G[script]
|
|
3
|
-
local _remo = TS.import(script, TS.getModule(script, "@rbxts", "remo").src)
|
|
4
|
-
local createRemotes = _remo.createRemotes
|
|
5
|
-
local remote = _remo.remote
|
|
6
|
-
local t = TS.import(script, TS.getModule(script, "@rbxts", "t").lib.ts).t
|
|
7
|
-
local remotes = createRemotes({
|
|
8
|
-
getTouchButtonConfig = remote(t.string).returns(),
|
|
9
|
-
setTouchButtonConfig = remote(t.string, t.strictInterface({
|
|
10
|
-
position = t.UDim2,
|
|
11
|
-
size = t.UDim2,
|
|
12
|
-
})),
|
|
13
|
-
})
|
|
14
|
-
return {
|
|
15
|
-
remotes = remotes,
|
|
16
|
-
}
|