hihi
So alot of games ive played with mobile support had a poor setups with one of them being fire buttons, some games ive played don’t have ways to let users fire and look around which in some cases could hurt the gameplay even more, Today im going to solve how it should be done.
Scenario
let’s say you had a plan to make mobile support for your game as you speak to many, you feel like implementing that couldn’t go wrong with ContextActionService or a lovely way of using InputBegan with UserInputService, but once you made the game compatible with mobile, you realize that controls are very awful and any game like Arsenal, Energy Assault or Weaponry apparently solved this issue.
so all the listed issue, you would see this
- You cannot look around while holding a button
- Disabling Active solved but your script still runs no matter out of bound or released when tap pos is not in the button radius
- InputBegan triggers without any proper checks
In most FPS Mobile game, given with buttons you’ll atleast find another joystick GUI made for fire n’ spray around the map but as roblox doesnt have that option to use (unless you’re a mastermind with Classic Thumbstick left on module, we dont wanna spend hours getting it right) we still have to rely on using the common input events for the function to trigger.
Brown line represent as input from finger (NOT THE TEXT)
Green as the button
Arrow as the direction you swipe to
as you swipe around while Active is false, your function still runs but the moment you release your finger out of there, you realize that it still runs no matter when you tap anywhere else until you press the button again
However if your tap pos was in the button of where you started pressing, it will trigger the same event and as you release it in there, the InputEnded triggers after it finally found the zone, This applies to ContextActionService aswell
and most notably, you cannot swipe around while button is holding which in some game, i find this a very nuisance as you want to spray around too but got to see this being an issue
Getting to solve this issue
There are 2 ways you can solve this issue, mine involves with binding Render step and update the GUI position to where your touch pos is, and other being the most common used technique in some Roblox FPS Games, one being Energy Assault
Hitbox < NOT MY IMPLEMENTATION
Using only 2 components, FireButton and a Frame
When in most FPS Games on roblox, if you don’t want an extra steps on making detection, developers will create one GUI elements that act as a zone to let the events or function run until you release your finger inside a gesture zone, This is a cheaper way as it does not require such things like RunService at all and rather the fastest method.
To do this, you have to create a zone on the right side, it should be half the screen unless you have someone that accidentally swipes to the left, Optionally you can add buttons on whatever you can and make sure to disable Active after buttons created
It should look like this (i had an extra buttons for something later)
Now we need to get into a scripting, we won’t use ContextActionService to do this and instead use it’s Inherited events from UserInputService which is already embedded into all GUI elements.
note that even if there’s InputBegan and InputEnded into the buttons, you still need to put some input type and state checking otherwise it’ll trigger when swipe pos touched other UI and the main one.
First top line will be the variables where you set it up to reuse instead of repeatedly indexing from other.
The firebutton input began/input ended as said earlier are Inherited events from the service, Inside contains an if statement checking if input type is a touch and state is beginning or not, if conditions are met, the function will run. oops looks like i put the unused arg on the parameters, don’t put GPE in
now your functions can literally be anything as long as it involves a loop but if you have a function that uses RenderStep, you can just use BindToRenderStep easily, That way you don’t have to unbind by making a blank var ref and assign it later to disconnect unless you love Heartbeat or Stepped and you don’t trust RenderStepped anymore.
local runSV = game:GetService("RunService")
local GUI = script.Parent
local hitbox = GUI.hitbox
local FireButton = GUI.FireButton
local disc = GUI.cool
local disc2 = GUI.woooo
local function test(dt)
print("yahoo", dt)
end
local function input_run(inputOBJ, GPE) --you can just create function from events, it's up to you but doing on there would be easier
if inputOBJ.UserInputType == Enum.UserInputType.Touch and inputOBJ.UserInputState == Enum.UserInputState.Begin then
runSV:BindToRenderStep("test", Enum.RenderPriority.Camera.Value + 1, test)
end
end
after InputBegan, we also had InputEnded, same checking as the first and this time with the UserInputState being End, we put them in 2 UI elements, one for the gesture zone and the main button
local runSV = game:GetService("RunService")
local GUI = script.Parent
local hitbox = GUI.hitbox
local FireButton = GUI.FireButton
local disc = GUI.cool
local disc2 = GUI.woooo
local function test(dt)
print("yahoo: ", dt)
end
local function input_run(inputOBJ) --you can just create function from events, it's up to you but doing on there would be easier
if inputOBJ.UserInputType == Enum.UserInputType.Touch and inputOBJ.UserInputState == Enum.UserInputState.Begin then
runSV:BindToRenderStep("test", Enum.RenderPriority.Camera.Value + 1, test)
end
end
local function input_end(inputOBJ) --you can just create function from events, it's up to you but doing on there would be easier
if inputOBJ.UserInputType == Enum.UserInputType.Touch and inputOBJ.UserInputState == Enum.UserInputState.End then
runSV:UnbindFromRenderStep("test")
end
end
FireButton.InputBegan:Connect(input_run)
FireButton.InputEnded:Connect(input_end)
hitbox.InputEnded:Connect(input_end)
The reason we put them in 2 instead of Gesture zone only is because sometimes we still could end the swipe pos at the fire button and that could still fire, having to make it 2 would solve that problem at all!
lastly, i need to hide all buttons incase unexpected events trigger, by inserting them in a table called “hide_src” and insert all the UI we need to hide if FireButton is holding
And there you have it, a simple button now functions properly, a happy ending for mobile mates who cannot afford PC also to those seeing RunService on top, it’s to bind the test function and to demonstrate the function running.
https://www.youtube.com/watch?v=9Xk7S2YLgSE
local runSV = game:GetService("RunService")
local GUI = script.Parent
local hitbox = GUI.hitbox
local FireButton = GUI.FireButton
local disc = GUI.cool
local disc2 = GUI.woooo
local hide_src = { --directly insert them or use table.insert later
button1 = disc,
button2 = disc2
}
local function test(dt)
print("yahoo: ", dt)
end
local function input_run(inputOBJ) --you can just create function from events, it's up to you but doing on there would be easier
if inputOBJ.UserInputType == Enum.UserInputType.Touch and inputOBJ.UserInputState == Enum.UserInputState.Begin then
runSV:BindToRenderStep("test", Enum.RenderPriority.Camera.Value + 1, test)
for _, ui in pairs(hide_src) do
ui.Visible = false
end
end
end
local function input_end(inputOBJ)
if inputOBJ.UserInputType == Enum.UserInputType.Touch and inputOBJ.UserInputState == Enum.UserInputState.End then
runSV:UnbindFromRenderStep("test")
for _, ui in pairs(hide_src) do
ui.Visible = true
end
end
end
FireButton.InputBegan:Connect(input_run)
FireButton.InputEnded:Connect(input_end)
hitbox.InputEnded:Connect(input_end)
Dynamic (NOT)
This method applies the same as the Hitbox method above except that buttons move, when you use this technique, it’s only usable when in that case you need it to follow a touch pos, this involves a RenderStep instead of moved events as they don’t create a memory leak
if you’re skipping from the Hitbox method here, i suggest going back to that and come here later as this is all just about modifying the code.
When i first implement this method, it involves using a mouse as the input and some touch started for events, having to separate the input by making a ref for the first input which was a convenient method to do but problem comes with that being the else function triggering unexpectedly and the button rapidly swapping it’s location during swipe, resulting in the event to run continuously even if i tapped the button to stop.
But after realizing input Object has this weird thing that shouldn’t belong and instead supposed to be Vector2, i had to use only Vector3 which somehow works and solved the issue with inputs stuttering
To do this, i have to make another BindToRenderStep function where it triggers, currently it’s not possible to make another function and connect it to the events as the last so this instead has to go inside the input began function since we have InputObject as the parameter, but we need the origin aka the original position to place after input has ended so that it returns back to where it was
and there you have it.,.,.,.,.,.,.,.,., a freaky ways to prevent inputbegan freezing but you may notice something here, why in the jolly GAWD is it not aligned in the center?? well the buttons here were calculated before and it’s size dependent, the easy fix is just subtracting or add to the Y or either X to adjust the pos, and if you want, resize the buttons a bit bigger, on input began, that way you can prevent the function freezing if there’s a lag spike
again a full heaps of code who just want to read instead of followin zzzzzzzzzzz
local runSV = game:GetService("RunService")
local GUI = script.Parent
local hitbox = GUI.hitbox
local FireButton = GUI.FireButton
local disc = GUI.cool
local disc2 = GUI.woooo
local origin_pos = FireButton.Position
local hide_src = { --directly insert them or use table.insert later
button1 = disc,
button2 = disc2
}
local function test(dt)
print("yahoo: ", dt)
end
local function input_run(inputOBJ: InputObject) --type checking helps :3
if inputOBJ.UserInputType == Enum.UserInputType.Touch and inputOBJ.UserInputState == Enum.UserInputState.Begin then
runSV:BindToRenderStep("test", Enum.RenderPriority.Camera.Value + 1, test)
runSV:BindToRenderStep("move_buttonz", Enum.RenderPriority.Camera.Value + 2, function(dt)
FireButton.Position = UDim2.new(0, inputOBJ.Position.X, 0, inputOBJ.Position.Y + 50)
end)
for _, ui in pairs(hide_src) do
ui.Visible = false
end
end
end
local function input_end(inputOBJ: InputObject)
if inputOBJ.UserInputType == Enum.UserInputType.Touch and inputOBJ.UserInputState == Enum.UserInputState.End then
runSV:UnbindFromRenderStep("test")
runSV:UnbindFromRenderStep("move_buttonz")
FireButton.Position = origin_pos
for _, ui in pairs(hide_src) do
ui.Visible = true
end
end
end
FireButton.InputBegan:Connect(input_run)
FireButton.InputEnded:Connect(input_end)
hitbox.InputEnded:Connect(input_end)
with these 2 methods, you can use this to make fire button function, something that involves using a camera to look at or related to holding and aiming at something else.