Hi all! I’m relaunching a game tomorrow where you ride a Marble down a lengthy track. Part of the relaunch includes controlling your marble but I’m totally stuck on how to get anything more than WASD. Looking for any help, even code to solve this. Wiki isn’t giving me enough info as a mostly non-programmer.
I know I need Joystick detecting and reading the X,Y. I don’t know how to handle it on both mobile and gamepad.
Here’s the existing important code:
local sp = script.Parent
local push_factor = 30 -- How much force is applied to the ball to make it move.
local drainrate = 1 -- How much power to drain.
local uis = game:GetService("UserInputService")
local power_level = script.Parent:WaitForChild("Frame"):WaitForChild("amount")
local max_power = power_level.MaxValue
local powerbar = script.Parent.Frame:WaitForChild("powerbar")
local powerbar_bar = powerbar:WaitForChild("power")
local marble = game.Players.LocalPlayer.Character:WaitForChild("Marble")
local camera = game.Workspace.CurrentCamera
local throttle = 0
local steer = 0
local is_moving = false
local forward = false
local prompted_power = false
local riding = true
refilling = false
function move()
wait(0.05)
if is_moving and power_level.Value > 0 and refilling == false then
if script.Parent.Moving.IsPlaying == false then
script.Parent.Moving:Play()
end
power_level.Value = power_level.Value - drainrate
print(power_level.Value)
powerbar_bar:TweenSize(UDim2.new((power_level.Value/100),0,0.35,0),"Out","Linear",0.1,true)
--print(power_level.Value/max_power)
if forward == false then
bf.force = camera.CoordinateFrame:vectorToWorldSpace(Vector3.new(steer,0,throttle).unit*marble:GetMass()*push_factor)
else
bf.force = camera.CoordinateFrame:vectorToWorldSpace(Vector3.new(steer,0,throttle).unit*marble:GetMass()*push_factor/2)
end
move()
elseif power_level.Value <= 0 and refilling == false then
refilling = true
script.Parent.Controls.Reloading.Visible = true
script.Parent.Reload:Play()
script.Parent.Moving:Stop()
for refill = 1, 200 do
powerbar_bar:TweenSize(UDim2.new((power_level.Value/100),0,0.35,0),"Out","Linear",0.1,true)
power_level.Value = power_level.Value + .5
wait(.1)
end
refilling = false
script.Parent.Controls.Reloading.Visible = false
end
end
uis.InputBegan:connect(function(input)
if input.KeyCode == Enum.KeyCode.W then
throttle = -1
if not is_moving then
is_moving = true
--forward = true
move()
end
elseif input.KeyCode == Enum.KeyCode.S then
throttle = 1
--forward = true
if not is_moving then
is_moving = true
move()
end
elseif input.KeyCode == Enum.KeyCode.A then
steer = -1
--forward = false
if not is_moving then
is_moving = true
move()
end
elseif input.KeyCode == Enum.KeyCode.D then
steer = 1
--forward = false
if not is_moving then
is_moving = true
move()
end
end
end)
uis.InputEnded:connect(function(input)
if input.KeyCode == Enum.KeyCode.W or input.KeyCode == Enum.KeyCode.S then
throttle = 0
forward = false
elseif input.KeyCode == Enum.KeyCode.A or input.KeyCode == Enum.KeyCode.D then
steer = 0
end
if steer == 0 and throttle == 0 then
bf.force = Vector3.new()
is_moving = false
script.Parent.Moving:Stop()
end
end)
Again, appreciate any and all help. I’ve been at this for just too long and I’m giving up!
To add additional context, I tried the below code for Gamepads but it was draining the power amount almost instantly, I think it threw move() into a loop which confuses me because WASD doesn’t throw move() into a loop.
uis.InputChanged:Connect(function(Input)
if Input.KeyCode == Enum.KeyCode.Thumbstick1 then
local joystickX = Input.Position.X
local joystickY = Input.Position.Y
--print("X"..joystickX, "Y"..joystickY)
if joystickX > 0.5 then
steer = 1
forward = true
if not is_moving then
is_moving = true
move()
end
elseif joystickX < -0.5 then
steer = -1
forward = true
if not is_moving then
is_moving = true
move()
end
else
steer = 0
is_moving = false
end
if joystickY > 0.5 then
throttle = 1
if not is_moving then
is_moving = true
move()
end
elseif joystickY < -0.5 then
throttle = -1
if not is_moving then
is_moving = true
move()
end
else
throttle = 0
is_moving = false
end
if steer == 0 and throttle == 0 then
bf.force = Vector3.new()
is_moving = false
end
end
end)
(This above code doesn’t solve for mobile which is a problem I don’t know where to start. It’s Gamepad only.)
Try this method we are requiring the core Controller Module. GetMoveVector returns the current direction of movement.
local Players = game:GetService("Players")
local LocalPlayer = Players.LocalPlayer
local ControlModule = require(LocalPlayer.PlayerScripts:WaitForChild("PlayerModule"):WaitForChild("ControlModule"))
while true do
task.wait()
local currentDirection = ControlModule:GetMoveVector()
print(currentDirection)
end
I think the most standard way, as long as humanoid is present, is to simply read Humanoid.MoveDirection every frame, which the value is set by the default character controller. So, the body force’s force would be as simple as
local RunService = game:GetService("RunService")
local Players = game:GetService("Players")
local me = Players.LocalPlayer
local character = me.Character or me.CharacterAdded:Wait()
local humanoid = character:WaitForChild("Humanoid")
local bodyforce = -- path.to.BodyForce
local push_factor = 30
local drain_rate = 1
local con = RunService.Stepped:Connect(function(delta_time)
power_level.Value -= drain_rate * delta_time
body_force.Force = humanoid.MoveDirection * push_factor
end)
humanoid.Died:Connect(function()
con:Disconnect()
end)
The orientation of character, WalkSpeed, and even the fact that PlatformStand / Sit is set or not (so that physics can take control) of humanoid does not affect which plane the moveVector will be on (it will always be on X Z plane) and will always have magnitude of 1 when input is made, or 0 when no inputs are made, making it an ideal property to read to determine which direction the player wants to move. To add on, all WASD, Gamepad input, and mobile inputs are applied here so you won’t have to have different reading method per device!
You could also fork the PlayerModule as the player above suggests (reading from the injected PlayerModule does not work if I recall, you have to have a fork the module and disable default injection), but tbh I think its better to just read from Humanoid as if ever there is any update to PlayerModule, you will have to manually fork the new PlayerModule, apply same edits, etc, whereas with Humanoid any updates will be auto-reflected.
This is coming from a novice programmer, so take anything I say with a grain of salt. That said, I’m fairly certain move() is being thrown into a loop as InputChanged runs every time that the JoyStick position changes at all, meaning that even just going straight forward can run 40+ times with half of them being greater than 0.5. I’ve edited the code below a bit to hopefully work around that, by checking if the position matches what it was previously (and resetting when it’s put back to rest so legit repeat inputs work).
-- Hold the current Vertical and Horizontal Direction
local vertDir = 0
local horzDir = 0
uis.InputChanged:Connect(function(Input)
if Input.KeyCode == Enum.KeyCode.Thumbstick1 then
-- Check if the Joystick has been put back to rest
if joystickX < 0.5 and joystickX > -0.5 then
horzDir = 0
end
if joystickY < 0.5 and joystickY > -0.5 then
vertDir = 0
end
local joystickX = Input.Position.X
local joystickY = Input.Position.Y
--print("X"..joystickX, "Y"..joystickY)
-- Check Horizontal Direction
if joystickX > 0.5 and horzDir ~= 1 then
steer = 1
forward = true
horzDir = 1
if not is_moving then
is_moving = true
move()
end
elseif joystickX < -0.5 and horzDir ~= 2 then
steer = -1
forward = true
horzDir = 2
if not is_moving then
is_moving = true
move()
end
else
steer = 0
is_moving = false
end
-- Check Vertical Direction
if joystickY > 0.5 and vertDir ~= 1 then
throttle = 1
vertDir = 1
if not is_moving then
is_moving = true
move()
end
elseif joystickY < -0.5 and vertDir ~= 2 then
throttle = -1
vertDir = 2
if not is_moving then
is_moving = true
move()
end
else
throttle = 0
is_moving = false
end
end
end)
I don’t know how much help this could be used for, but for the latest game I worked on, I used the UserInputService’s TouchMoved. It allows you to see if the player is moving while touching and tells you the change in direction.
What you could do with this is if there is some sort of change, check if the player is interacting with the joystick, take the input.delta as seen in the example, and apply the changes to the object.
Again I don’t know if this is the best way, but I used it for Holiday Frenzy for the building toys on mobile!
Apart from this not being the best method by far, this is also highly unreliable because said module gets updated by Roblox quite often which could literally mean the code can break at any given moment and since asimo isn’t a scripter it’ll 100% take him a while to figure out why it broke out of nowhere.
Thank you so much, and thank you to all here who offered ideas and code.
I woke up this morning to tackle this problem again and after about an hour of moving code around and trying things out, I found my solution. Using Movedirection is a great solution for figuring out what the player is doing and definitely what I’ll be doing moving forward for future games.
So others can possibly learn from this, this is what the (working) code now looks like. I’ve included my commented out force calculation mistakes.
local con = RunService.Stepped:Connect(function(delta_time)
--local con = RunService.Heartbeat:Connect(function()
if humanoid.MoveDirection.X > 0 then
steer = 1
elseif humanoid.MoveDirection.X < 0 then
steer = -1
else
steer = 0
end
if humanoid.MoveDirection.Z > 0 then
throttle = 1
elseif humanoid.MoveDirection.Z < 0 then
throttle = -1
else
throttle = 0
end
--print(humanoid.MoveDirection)
bodyforce.Force = Vector3.new(0,0,0)
if throttle ~= 0 or steer ~= 0 then
if script.Parent.Controls.Reloading.Visible == false and power_level.Value > 0 then
--bodyforce.Force = camera.CoordinateFrame:vectorToWorldSpace(Vector3.new(steer,0,throttle).unit*marble:GetMass()*push_factor)
--bodyforce.Force = character.PrimaryPart.CFrame:vectorToWorldSpace(Vector3.new(steer,0,throttle).unit*marble:GetMass()*push_factor)
if script.Parent.Moving.IsPlaying == false then
script.Parent.Moving:Play()
end
bodyforce.Force= humanoid.MoveDirection*marble:GetMass()*push_factor
power_level.Value = power_level.Value - drain_rate
powerbar_bar:TweenSize(UDim2.new((power_level.Value/100)/3,0,0.35,0),"Out","Linear",0.1,true)
--print(power_level.Value)
end
end
if throttle == 0 and steer == 0 then
script.Parent.Moving:Stop()
end
if power_level.Value <= 0 then
script.Parent.Controls.Reloading.Visible = true
script.Parent.Reload:Play()
script.Parent.Moving:Stop()
for refill = 1, 300 do
bodyforce.Force = Vector3.new(0,0,0)
powerbar_bar:TweenSize(UDim2.new((power_level.Value/100)/3,0,0.35,0),"Out","Linear",0.1,true)
power_level.Value = power_level.Value + 1
wait()
end
script.Parent.Controls.Reloading.Visible = false
end
end)
humanoid.Died:Connect(function()
con:Disconnect()
end)