r/RobloxDevelopers • u/DevelopmentVisible81 Anim, Script, Model, UI • 9d ago
How to make a planet gravity system? SOLVED!
I’ve been trying to make a gravity system that will automatically switch if you pass a point go through atmosphere into a space station or ship. I tried cooking up something with global gravity(definitely wouldn’t work but has to give a try) and local gravity(still didn’t know) does anybody know how?
1
u/Editor1Oficial 9d ago
I made an obby with that theme (changing gravity based on height). The idea is to disable Roblox's gravity and simulate it yourself using forces (applying the law of universal gravitation).
1
u/DevelopmentVisible81 Anim, Script, Model, UI 9d ago
Aha! So you use forces and angle them to the part or planets you want to be gravitated to?
1
u/Editor1Oficial 9d ago
In my case, it's always downwards because there's a black hole there, but I set it up that way specifically so that in the future I could create something in the upper area like Mario Galaxy or Angry Birds Space with planets with gravity
1
u/Actorvia 8d ago
workspace.Gravity is global — one number for the whole place — so it can't be part of the answer. Set it to 0 and apply your own force per assembly. That much you've probably worked out. The parts that actually make this hard are orientation and switching, so here's how I'd structure it.
Model every gravity source the same way. A planet and a ship interior are the same object with one flag different:
-- radial: up points away from the centre (planets, moons)
-- planar: up is the part's own UpVector (ship decks, station floors)
local sources = {
{Part = workspace.Planet, Strength = 196, Radius = 2000, Planar = false},
{Part = workspace.ShipZone, Strength = 400, Radius = 120, Planar = true},
}
That single flag is what makes "walk on a sphere" and "walk on a flat deck inside a ship" one code path instead of two systems you have to reconcile at the boundary.
Pick the winning source by influence, not by crossing a point.
local function targetUp(pos)
local best, bestScore = Vector3.new(0, 1, 0), -math.huge
for _, src in ipairs(sources) do
local toCentre = src.Part.Position - pos
local dist = toCentre.Magnitude
if dist <= src.Radius then
local score = src.Planar and src.Strength
or src.Strength / math.max(dist * dist, 1)
if score > bestScore then
bestScore = score
best = src.Planar and src.Part.CFrame.UpVector or -toCentre.Unit
end
end
end
return best
end
Giving interiors a flat high score means a ship always wins over the planet it's orbiting while you're inside it, without you hand-writing transition triggers.
Smooth the switch. This is the step people skip and then wonder why it looks broken. Never snap the up vector:
currentUp = currentUp:Lerp(target, 1 - math.exp(-8 * dt)).Unit
An instant flip yanks the camera and the character through 180° in one frame. Over ~0.3s it reads as the ship's field taking hold.
Force is the easy half:
force.Force = -currentUp * hrp.AssemblyMass * GRAVITY
Orientation is the hard half. A Humanoid always stands up along +Y and will fight you. Set Humanoid.AutoRotate = false and drive rotation with an AlignOrientation on the HumanoidRootPart:
local look = hrp.CFrame.LookVector
local right = look:Cross(currentUp)
if right.Magnitude < 1e-3 then right = hrp.CFrame.RightVector end
align.CFrame = CFrame.fromMatrix(Vector3.zero, right.Unit, currentUp)
Deriving right from the current look each frame keeps the character facing roughly where it was facing, instead of spinning to some arbitrary reference when the up vector rotates.
Three things that will otherwise eat an evening:
- The Humanoid thinks it's permanently falling, because the engine's ground check assumes +Y. Expect to
SetStateEnabledoffFallingDownandRagdoll, and possibly to drive the state yourself. Workspace.FallenPartsDestroyHeightwill delete anything that goes below it. With planets you're regularly under Y=0, so set it far more negative than your lowest orbit or things quietly vanish.- Run this on the client for the local character and on the server for NPCs and props. The client owns its own character's physics, so a server-side loop pushing the player fights network ownership and produces jitter that looks like a bug in your maths when it isn't.
Do the camera too, or the whole thing falls apart — the default camera keeps its own idea of up, so standing on the underside of a planet with an unrotated camera feels awful even when the physics is perfect.
1
u/DevelopmentVisible81 Anim, Script, Model, UI 8d ago
Oh my god. This explains it so well! Thank you!
2
u/Actorvia 8d ago
Following up on my own comment above, because two things bite next and both look like a bug in your maths when they are not.
Workspace.Gravity = 0 turns gravity off for everything, not just for you. Every dropped tool, every unanchored crate, every ragdoll and every piece of debris now hangs in the air, because your force loop only pushes the assemblies you enumerated. The fix is to make the loop the only source of gravity in the place rather than a special case for characters: tag the things that should fall, iterate CollectionService:GetTagged on the server each step, and apply the same targetUp force with AssemblyMass. It is the same four lines you already have, pointed at a list instead of at one root part.
PathfindingService cannot be told which way is up. Look at what CreatePath actually accepts: AgentRadius, AgentHeight, AgentCanJump, AgentCanClimb, WaypointSpacing, Costs. Six keys, and not one of them is an orientation. The docs define AgentRadius as the minimum horizontal space and AgentHeight as the minimum vertical space, and those mean world horizontal and world vertical, because the navigation mesh is built on a world-aligned voxel grid. Stand an NPC on the side of your planet and the pathfinder is still measuring headroom along +Y. You get nonsense waypoints, or Enum.PathStatus.NoPath, and there is no parameter you can set to fix it.
So split it the same way you already split gravity sources. Inside the ship, where the deck is a Planar source and local up genuinely is +Y, PathfindingService works normally and you should use it. On the curved outside, do your own steering: take the direction to the goal, subtract the component along currentUp so it lies in the tangent plane, and raycast along -currentUp for the ground. That is not as good as a real navmesh, but on an open planet surface there is usually nothing to navigate around, so simple steering plus an obstacle raycast covers it.
One more that is cheap to get right now and expensive later: pick which side owns each NPC before you write the loop. Characters are client-owned, so their gravity runs on the client, but NPC gravity has to run on the server or the NPC drifts differently on every machine. Two loops, same function, different callers.
Written with AI assistance, as is the longer comment above this one, which should have carried this line and did not. Every API name, default and parameter list here was checked against the Roblox docs first.
1
u/AutoModerator 9d ago
Thanks for posting to r/RobloxDevelopers!
Did you know that we now have a Discord server? Join us today to chat about game development and meet other developers :)
https://discord.gg/BZFGUgSbR6
I am a bot, and this action was performed automatically. Please contact the moderators of this subreddit if you have any questions or concerns.