Plugin Developer Guide
How to Make
Custom Shapes
A developer guide to creating shapes for Project Gravity using the plugin system.
01 Step 1: File Location
Project Gravity loads shapes from the GravityShapes folder inside your executor's
workspace. Drop any .lua or .txt file here to automatically add
it to the UI dropdown.
Files with the exact same name as official shapes (e.g., Meteor Shower.lua) will
completely replace the official GitHub version.
Syntax errors will prevent the shape from loading and trigger a warning in the F9 console. Other shapes will continue running normally.
02 Step 2: Barebones Template
Use this minimal boilerplate code structure to build a new shape module:
local M = {}
function M.px(t, c, x6, x9, x1)
end
function M.f2(p, cen, d, t, c, x1, x6, x9)
return (cen - p.Position) * (x1.k10 * x9.c1), cen
end
M.Controls = {}
return M
03 Step 3: Variable Reference
A description of variables available in your shape calculation functions:
pcentcM.Controls can be
read via their key (e.g., c.k11).dx6.preM.px for all parts.x1x1.MaxSpeed,
x1.k10 the strength multiplier). Avoid modifying.x904 Step 4: The Functions
Every shape returns a module table containing these three members:
Runs exactly once per frame prior to physics loop. Used to pre-calculate global offsets and save
to x6.pre to optimize performance.
Runs for every individual part in the gravity zone, every frame. Must return a
Vector3 target velocity vector representing the push to apply to the part.
To prevent jitter when the system skips frames to optimize high part counts, return your
raw target_position as a second argument. The engine uses
this for feed-forward trajectory smoothing.
return (target_position - p.Position) * (x1.k10 * x9.c1), target_position
Defines interactive settings in the UI dropdown. Supports Slider (with
Min, Max, optional Div and Default),
Toggle, and TextBox (with Default, Desc,
MaxChars) controls. Toggles return true or false in the
c table.
M.Controls = {
{ Type = "Slider", Name = "Radius", Min = 5, Max = 100, Key = "k11" },
{ Type = "Toggle", Name = "Cut In Half", Key = "k12" },
{ Type = "TextBox", Name = "Label", Key = "k13", Default = "hello" }
}
To use a toggle (e.g., "Cut In Half"), check its key in your physics loop:
if c.k12 then
-- Toggle is ON: Apply the effect
radius = radius / 2
end
05 Important Rules
- Roblox Coordinate System: Standard axes are used:
+Yis Up,+Xis Right, and-Zis Forward. - Scaling Multiplier: Scale your return value by the engine convention
(x1.k10 * x9.c1)— the same multiplier every official shape uses. With defaults (x1.k10 = 20,x9.c1 = 0.15) that is a scale of3.0. Do not hardcode large constants: the engine applies its own gain, and oversized values fight the built-in PID controller and speed limit. - Individual Spacing: Randomize or offset target positions utilizing the part's
persistent table
dto prevent all parts from collapsing into a single point.
06 Full Example: "Floating Ring"
Here is a complete, working template containing custom controls, pre-computation, and orbital part
offsets. Save this as Floating Ring.lua in your shapes directory.
local M = {}
function M.px(t, c, x6, x9, x1)
if not x6.pre["Floating Ring"] then
x6.pre["Floating Ring"] = {}
end
local memory = x6.pre["Floating Ring"]
local dt = t - (memory.last_t or t)
memory.last_t = t
local speed = c.k13 or 10
memory.rotation = (memory.rotation or 0) + (dt * speed)
end
function M.f2(p, cen, d, t, c, x1, x6, x9)
local memory = x6.pre["Floating Ring"] or {}
local rotation = memory.rotation or 0
local radius = c.k11 or 20
local height = c.k12 or 5
if not d.my_spot then
d.my_spot = math.random() * (math.pi * 2)
end
local x = math.cos(rotation + d.my_spot) * radius
local z = math.sin(rotation + d.my_spot) * radius
local target_position = cen + Vector3.new(x, height, z)
return (target_position - p.Position) * (x1.k10 * x9.c1), target_position
end
M.Controls = {
{ Type = "Slider", Name = "Ring Radius", Min = 5, Max = 100, Key = "k11" },
{ Type = "Slider", Name = "Ring Height", Min = -50, Max = 50, Key = "k12" },
{ Type = "Slider", Name = "Spin Speed", Min = 0, Max = 100, Key = "k13" }
}
return M