URGENT! Movement works in studio but not Roblox

I dont know how to help you really, but cant you use remote event if you need to transfer data and combine local script and server script?

You should prob research more on run service API

I just tried using a remote event, but it had the exact same effect. Plus, wouldn’t it overload the server having multiple players firing remote events every frame? That’s sixty times per second for every player in the server.

Hmm then what about a remote function? Also i have no clue, im not really expedienced im sorry, and im bad at roblox physics

I don’t think the remote function would be any different. I’m pretty sure that the gravity bug is caused by calculating it on the server, but the local script won’t affect it. I might open a bug report.

You can try, i tried my best atleast

Thank you for your help! I really appreciate it.

1 Like

roblox is bad sometimes. try copying the script and deleting the original, and then publish changes. i have no guarantee if this would work but i do it sometimes to fix weird errors

2 Likes

Sadly, that didn’t work either.

Any help? It’s been months and I still haven’t found anything for this.

1 Like

First off, I think your problem is caused because you’re only waiting a miniscule amount of time before trying to find the sphere and root. I’d recommend using :WaitForChild because an actual server is dozens of times slower than your computer on studio because studio has zero latency. (Time between client and server)

I also noticed that your code has some problems, and I would recommend not using some of the practices you’re using. For one, Roblox is horrible at updating parts positions and not having it be choppy with scripts. To this, I would recommend using network ownership for your marble.

Note, that if the sphere is anchored this will not work.

However, it did bother me that nobody would help you on this, so I made a functional version for this.

Server script:

local physicsService = game:GetService("PhysicsService")
physicsService:RegisterCollisionGroup("Character")
physicsService:RegisterCollisionGroup("Sphere")

physicsService:CollisionGroupSetCollidable("Character","Sphere",false)

local onDescendant = function(descendant)
	if descendant:IsA("BasePart") and descendant.CollisionGroup ~= "Sphere" then
		descendant.CollisionGroup = "Character"
	end
end

local onCharacter = function(player,character)
	local sphere = Instance.new("Part")
	sphere.Size = Vector3.new(5,5,5)
	sphere.Shape = Enum.PartType.Ball
	sphere.Parent = character
	sphere.Name = "Sphere"
	sphere.CFrame = character.HumanoidRootPart.CFrame
	sphere.CollisionGroup = "Sphere"
	sphere.Massless = true
	sphere:SetNetworkOwner(player)
	
	character.DescendantAdded:Connect(onDescendant)
	for _,descendant in pairs(character:GetDescendants()) do
		onDescendant(descendant)
	end
end

local onPlayer = function(player)
	player.CharacterAdded:Connect(function(character)
		onCharacter(player,character)
	end)
	if player.Character then
		onCharacter(player,player.Character)
	end
end

game.Players.PlayerAdded:Connect(onPlayer)
for k,v in pairs(game.Players:GetPlayers()) do
	onPlayer(v)
end

Client script: (CharacterScripts)

local runService = game:GetService("RunService")
local character = script.Parent

local sphere = character:WaitForChild("Sphere")
local root = character:WaitForChild("HumanoidRootPart")
local humanoid = character:WaitForChild("Humanoid")

-- Align position will smoothly move parts to each other
-- https://create.roblox.com/docs/reference/engine/classes/AlignPosition

local alignPosition = Instance.new("AlignPosition")
alignPosition.Parent = sphere
alignPosition.Mode = Enum.PositionAlignmentMode.TwoAttachment
alignPosition.MaxForce = 9999999
alignPosition.MaxVelocity = 999999

-- Align orientation will align the rotation of parts
-- https://create.roblox.com/docs/reference/engine/classes/AlignOrientation

local alignOrientation = Instance.new("AlignOrientation")
alignOrientation.Parent = sphere
alignOrientation.Mode = Enum.OrientationAlignmentMode.TwoAttachment
alignOrientation.MaxTorque = 9999999
alignOrientation.MaxAngularVelocity = 999999

-- They're based off of "attachments", which are these cute thingies:
-- Think of them as 3d points, like in math when you learn about (x,y)
-- These have (x,y,z) and you can base things like rotation of off them
-- They're super cool!

-- https://create.roblox.com/docs/reference/engine/classes/Attachment

local attachment0 = Instance.new("Attachment")
attachment0.Parent = sphere

local attachment1 = Instance.new("Attachment")
attachment1.Parent = root

alignPosition.Attachment0 = attachment0
alignPosition.Attachment1 = attachment1

alignOrientation.Attachment0 = attachment0
alignOrientation.Attachment1 = attachment1

local lastPosition = root.Position
local totalRotation = Vector3.new(0, 0, 0)

local function safeNormalize(vector) -- Because of small updates, this can make it not work right >:(
	if vector.Magnitude > 0.001 then
		return vector.Unit
	else
		return Vector3.new(0, 0, -1)  -- Default direction if movement is too small
	end
end

-- Place the sphere at the bottom of the player's character:

local characterHeight = humanoid.HipHeight * 2
local sphereRadius = sphere.Size.Y / 2
local offsetY = -(characterHeight / 2) + sphereRadius
attachment1.Position = Vector3.new(0, -offsetY, 0)

-- ChatGPT wrote like 90% of this part below: (beyond me, it bothered me that the sphere didn't rotate lol)
-- I did do a little fixing, but that's not much

runService.Heartbeat:Connect(function(deltaTime)	
	local currentPosition = root.Position
	local movement = currentPosition - lastPosition
	local speed = movement.Magnitude / math.max(deltaTime, 0.001)  -- Prevent division by zero

	if speed > 0.1 then  -- Adjust this threshold to determine when the player is "walking"
		-- Calculate rotation based on movement
		local direction = safeNormalize(movement)
		local up = Vector3.new(0, 1, 0)
		local right = direction:Cross(up)
		local forward = up:Cross(right)

		local distance = movement.Magnitude
		local sphereCircumference = 2 * math.pi * sphere.Size.X
		local rotationAmount = (distance / sphereCircumference) * 360  -- Calculate rotation based on sphere circumference

		-- Update rotation
		local newRotation = Vector3.new(
			-direction:Dot(forward) * rotationAmount,
			0,
			-direction:Dot(right) * rotationAmount
		)
		totalRotation = totalRotation + newRotation

		-- Apply rotation to attachment1
		attachment1.Orientation = totalRotation
	end

	lastPosition = currentPosition
end)

Uncopylocked functional place:

File:
ball.rbxl (55.2 KB)

FYI: RenderStepped is only on the client because RenderStepped runs after every frame is rendered, however the server doesn’t have a rendering engine.

1 Like

Thank you for helping, but I’m not sure this is what I’m looking for.
Here’s a video I took of what it should look like:
robloxapp-20240716-1402237.wmv (2.8 MB)
Note how I’m able to control the sphere to some degree while still being affected by physics. This is what I’m trying to achieve. However, it simply does nothing when playing outside of Studio.
Here is my current script:

human = script.Parent:WaitForChild("Humanoid")
sphere = script.Parent:WaitForChild("Sphere")
root = script.Parent:WaitForChild("HumanoidRootPart")

isOnGround = false

function move()

	isOnGround = workspace:Raycast(root.Position, Vector3.new(0, -3, 0))

	if isOnGround then
		sphere.Velocity = sphere.Velocity + (human.MoveDirection * human.WalkSpeed)
	else
		sphere.Velocity = sphere.Velocity + (human.MoveDirection * (human.WalkSpeed / 2))
	end
end

human:GetPropertyChangedSignal("Jump"):Connect(function()
	if human.Jump == true then
		if isOnGround then
			sphere.Velocity = Vector3.new(sphere.Velocity.X, human.JumpPower, sphere.Velocity.Z)
		end
	end
end)

while wait(1/60) do
	move()
end

Note how this is in a local script. I believe this is where the problem comes from, however when I move it to a server script it stops working and the physics get all wonky.
My current setup has the starter character as a model with a humanoid, a humanoid root part, and a sphere parented under it. The root part has an align orientation constraint with an anchor to keep it upright, and also a no collision constraint to keep it from colliding with the sphere. The sphere has an align position constraint to keep it in place with the root part.
I also have a script to set the root part and sphere’s network owner to the player, but it doesn’t seem to do anything.
Note that my character is not generated with a script, it is a model placed under the starter player folder.
I hope this can provide some clarity to the situation and to my goal.

1 Like

I’ve created a minimal example of this:
Marble.rbxl (59.4 KB)

1 Like

Okay, I figured it out. It was because scripts don’t run when placed in StarterCharacter, I don’t understand why, but they don’t.

local players = game:GetService("Players")
local runService = game:GetService("RunService")

local function onPlayer(player)
	local function onCharacter(character)
		local sphere = character:WaitForChild("Sphere")
		local root = character:WaitForChild("HumanoidRootPart")
		sphere:SetNetworkOwner(player)
	end
	
	player.CharacterAdded:Connect(onCharacter)
	if player.Character then 
		onCharacter(player.Character)
	end
end

players.PlayerAdded:Connect(onPlayer)
for _,player in pairs(players:GetPlayers()) do
	task.spawn(onPlayer,player)
end

This script goes in ServerScriptService and will accordingly set network ownership, I believe studio has different replication properties than live games that allowed this to go unnoticed. The script was not running either way.

This solution also makes AlignPosition and AlignOrientation obselete.

local runService = game:GetService("RunService")
local character = script.Parent
local camera = workspace.CurrentCamera

local human = character:WaitForChild("Humanoid")
local sphere = character:WaitForChild("Sphere")
local root = character:WaitForChild("HumanoidRootPart")

isOnGround = false
camera.CameraType = Enum.CameraType.Follow
camera.CameraSubject = sphere

local function move()
	isOnGround = workspace:Raycast(root.Position, Vector3.new(0, -3, 0))	
	if isOnGround then
		sphere:ApplyImpulse(human.MoveDirection * human.WalkSpeed * sphere.Mass)
	else
		sphere:ApplyImpulse(human.MoveDirection * (human.WalkSpeed / 2) * sphere.Mass)
	end
end

human:GetPropertyChangedSignal("Jump"):Connect(function()
	if human.Jump == true then
		if isOnGround then
			sphere.Velocity = Vector3.new(sphere.Velocity.X, human.JumpPower, sphere.Velocity.Z)
		end
	end
end)

sphere.Changed:Connect(function()
	root.CFrame = sphere.CFrame
end)

runService.RenderStepped:Connect(move)

I’d recommend using RenderStepped over wait(), noting that wait is also a deprecated function. That or task.wait().

Thank you so much! I was able to get this working by moving the script into StarterCharacterScripts.
Also, I use wait instead of RenderStepped because I want the movement to be consistent, while the render stepped varies because of performance.

1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.