I’ve been unable to find anything on how controls on different platforms translate to PC equivalents (I know at least that tapping on a phone/tablet is an M1 click), or on how to access input such as tablet movement controls.
Since most of my players are non-PC, I really should be catering to them.
Look into UserInputService, they have platform-specific controls.
Doesn’t help.
It says everything but what I asked.
What information are you looking for specifically?
If you are designing cross-platform input controls for your game, UserInputService (access all types of input devices and their data) and ContextActionService (bind actions to input events across all platforms) are the way to go. By building these into your game from the start, you will be able to link keyboard keys, touchscreen buttons and gamepad buttons to the same input actions.
If you are just looking for information on how different inputs translate to actions Roblox defaults by default, the most basic behavior (click) can be activated through:
- Mouse click
- Touch tap
- Gamepad right trigger press (clicks at cursor position)
If there is any specific inputs you are looking for, feel free to ask.
I don’t think you have access to the default mobile/tablet control buttons if that’s what you’re asking. You’ll need to make your own custom controls.
Movement input.
I have a fly script that converts WASD and space to a unit vector for flying direction relative to camera, and I need to be able to produce a vector like that on different platforms.
Convert Humanoid.MoveDirection to a vector relative to the camera? It might not work but anything’s possible
It says that it’s for the Move() method only, but I tested it and it works for regular movement too.
Now I need to find out if I can figure out if jump is held continuously on non-PC clients.
IIRC, you should be able to check if Humanoid:GetState == Enum.Humanoid.Jumping
Using Humanoid.MoveDirection is the best way to tap into the movement scripts, so if you make your fly script read from that value it will automatically work across all devices
Alright, I think I’ve figured out a way to figure out if the jump key is held on any platform.
JumpRequest fires every time the jump button is pushed, no matter what that button is.
That will also fire an InputBegan event at the exact same time, telling me what to check for in the InputEnded event to know when the jump button is no longer held.
As for figuring out which InputBegan event relates to the jump button push, it’s simply the one that’s closest.
The closest is found by storing the time of the last InputBegan event, and when the jump request fires then the first InputBegan event to fire is assumed to be the correct one since it’s the closet in time. At least, until a span of time passes such that an new InputBegan event would be further after the jump request than the last InputBegan was before the jump request.
Alright, here it is, a function that when invoked will cause the variable “jumping” to say whether the jump button is held, no matter what device is used.
It’d be better if I could give the variable to write to as an argument, but this is Lua so as far as I know I can’t give a pointer to a boolean instead of a boolean value.
One odd quirk in the function is return (input.Position-jumpInput.Position).magnitude == 0
, which obviously doesn’t make sense since checking for zero magnitude is the same as checking for equality, and the Vector3 class compares values instead of pointers. However, I’ve found that comparing input.Position with jumpInput.Position always returns false, even if the magnitude of their difference is zero; I’m suspecting that Vector3 has a doppelganger that only compares pointers.
jumping = false
function identifyWhenJumping()
local closestInput
local closestInputTime
local waitForChange
local checkJumpEnded
InputService.InputBegan:Connect(
function(input)
closestInput = input
closestInputTime = tick()
waitForChange = false
end)
local jumpRequestBusy = false
InputService.JumpRequest:Connect(
function()
if jumping == false and jumpRequestBusy == false then
jumpRequestBusy = true
waitForChange = true
local newInput
if closestInputTime then
newInput = waitUntilTimeout(InputService.InputBegan, tick()-closestInputTime) --An overwrite of closestInput can only be closer in this time window
else
newInput = InputService.InputBegan:Wait() --In this case, the first ever input occurs after JumpRequest.
end
if newInput then
while waitForChange do wait() end
end
local jumpInput = closestInput
if jumpInput.KeyCode == Enum.KeyCode.Unknown then
checkJumpEnded = function(input)
return (input.Position - jumpInput.Position).magnitude == 0 --Jump activated by screen button, release will always be reported in same position
end
else
checkJumpEnded = function(input)
return input.KeyCode == jumpInput.KeyCode --Jump activated by physical button, simply checks if button released is the button
end
end
jumping = true
jumpRequestBusy = false
end
end)
InputService.InputEnded:Connect(
function(input)
if jumping then
if checkJumpEnded(input) then
jumping = false
end
end
end)
end
function waitUntilTimeout(event, timeout)
local Signal = Instance.new('BindableEvent')
local conn = nil
conn = event:Connect(function(...)
conn:Disconnect()
Signal:Fire(...)
end)
delay(timeout, function()
if (conn ~= nil) then
conn:Disconnect()
conn = nil
Signal:Fire(nil)
end
end)
return Signal.Event:Wait()
end
if closestInputTime then
newInput = InputService.InputBegan:Wait(tick()-closestInputTime) --An overwrite of closestInput can only be closer in this time window
else
newInput = InputService.InputBegan:Wait() --In this case, the first ever input occurs after JumpRequest.
end
FYI RBXScriptSignal’s :Wait() does not take any parameters, so you’re doing the same thing in both branches of this if-statement.
In that case my code is working purely because the input event always follows the jump request.
Which seems odd, since the control input triggers the jump request.
I designed the code assuming that there was a chance their order would be reversed, and I’ll still build that extra security into it since a single order reversal (which could occur for a number of reasons) would break the script permanently.
I’ve improved it by including Merely’s waitUntilTimeout function, changed to use BindableEvents since his direct access of Roblox signals isn’t even possible anymore as far as I know.