You can now get a FREE 500 BRICKS for uploading a Youtube video with the tag "#polyworldsandbox"

Scripting Guide

Server-side Lua API for PolyWorld places. Local scripts have a smaller subset.

PolyWorld Scripts are things in a PolyWorld game that preform tasks on the server.

LocalScripts run on each player's client. Create them in Studio (blue script icon). They use a smaller API focused on local visuals, input-side events, and talking to the server.


How scripts work

If the script is parented to a part, script.Parent (or script.parent) will be it's parent part. If not, you still have access to the entire workspace, not much changes except script.Parent

Useful Globals:

WhatNotes
wait(seconds)Pause this script (like Roblox task.wait / wait)
print(...)Print to the server log
script.Parent / script.parentThe Part this Script is under (if any)
workspace / WorkspaceThe world
SharedTable shared by all Scripts in this place (same as bare globals)

Scripts in the same place share globals (Score = 1 in one script is readable in another). Touched / PlayerAdded / PlayerDied / CharacterAdded stay per-script. Don't worry, games can't touch your variables.

Tip: wait() only works in the main script body (loops, etc.). Inside Touched / PlayerAdded, set a flag and handle timing in a while true loop.

Some other things:

WhatNotes
Part touchedfunction Touched(player) on the Script under that Part
Player joinedfunction PlayerAdded(player)
Player diedfunction PlayerDied(player)
Player respawnedfunction CharacterAdded(player)
Disable reset buttonPlayers.ResetEnabled = false
Player interactionplayer.health, player.x/y/z, player.name, player.transparency, player.customNameColor, player:Kill(), player:SetPosition
Part propertiesPosition, Size, Anchored, CanCollide (or lowercase)
ColorRGBColor.new(r, g, b) with 0-255 for each field
Vector3 CreationVector3.new(x, y, z)
Spawn toolsMap respawn points, or player:SetSpawn(x, y, z) for checkpoints
MusicSound.play, Sound.playMusic (Upload your own on the Edit page of your game)
Player UIUI.notify, UI.hud, Chat.broadcast, player:Notify

Events

Touched(player)

Fires when a player enters the script's parent part (OBB overlap with the full avatar; enter-only — not every frame while standing on it).

Example killbrick:

function Touched(player)
    player:Kill()
end

PlayerAdded(player)

Fires when someone joins the place.

function PlayerAdded(player)
    print("Hello there, " .. player.name)
    Sound.playMusic("https://polyworld.games/assets/bgm/and-remix.ogg", 0.4)
end

PlayerDied(player)

Fires whenever health hits zero (via Kill(), reset character, health naturally hitting zero...)

function PlayerDied(player)
    Chat.broadcast("Someone died!")
end

CharacterAdded(player)

Fires when a dead player respawns.

function CharacterAdded(player)
    player:Notify("ELIMINATED")
end

OnRemoteEvent(name, player_id, data)

Fired when the client sends a remote event (e.g. tool use). data is a table.

If the payload is at least 24 bytes (6 floats), these fields are set:

FieldMeaning
dir_x, dir_y, dir_zAim direction
origin_x, origin_y, origin_zAim origin
data_lenRaw payload length in bytes
function OnRemoteEvent(name, player_id, data)
    if name == "tool_use" and data.dir_x then
        print("Aim", data.dir_x, data.dir_y, data.dir_z)
    end
end

Players

local players = workspace:GetPlayers()  -- you can also do Players:GetPlayers()
for _, p in ipairs(players) do
    print(p.id, p.health, p.x, p.y, p.z)
end

Place settings

FieldMeaning
Players.ResetEnabledDefault true. Set false to ignore pause-menu Respawn
Players.ResetEnabled = false

Read

FieldMeaning
idPlayer id
username / name / NameDisplay name
health0–100
x, y, zPosition
yawFacing
animationAnim state number
equippedHead / Shirt / PantsCatalog ids
transparency / Transparency0–1 (avatar fade)
customNameColor / CustomNameColor{r,g,b} or nil if unset

Write

FieldEffect
healthClamped 0–100
x, y, zTeleport (syncs to client)
animationSet anim
transparency / Transparency0–1
customNameColor / CustomNameColor{r,g,b} (0–1 or 0–255); nil clears
player.transparency = 0.5
player.customNameColor = RGBColor.new(255, 80, 0)
player.customNameColor = nil  -- back to default name color

Methods

CallEffect
player:Kill()Health → 0 (respawn)
player:Damage(n)Take damage
player:TakeDamage(n)Same as Damage
player:SetPosition(x, y, z)Teleport now
player:SetSpawn(x, y, z)Next respawn goes here (checkpoints!)
player:ClearSpawn()Back to normal SpawnLocations
player:Message(text)System chat to this player
player:Notify(text, seconds?)Center toast
player:Hud(text, seconds?)Top HUD (0 = until clear)
player:ClearUI()Clear toast + HUD
player:PlayMusic(url, volume?)BGM for this player only
player:StopMusic()Stop that player's BGM

Parts & Workspace

You can use these functions to find parts:

local part = workspace:FindFirstChild("PartName")
local part = workspace:FindByName("PartName") -- search entire world
local kids = workspace:GetChildren()
local child = model:WaitForChild("Name", 5)
local brick = workspace:FindFirstChildOfClass("Part")  -- also "BasePart", "Model"

You can also use dot fields:

local part = workspace.PartName

Part properties

PropertyNotes
Namestring
ClassName"Part", "Model", or "Workspace"
ParentInstance or nil
Color / color{r,g,b} as 0-1, or use RGBColor.new (0–255)
Position / position{x,y,z}
Rotation / rotationDegrees
Size / size{x,y,z}
Anchored / anchoredbool
CanCollide / cancollidebool — whether players collide with this part

Methods

:Destroy(), :Clone(), :FindFirstChild(name), :FindFirstChildOfClass(class), :GetChildren(), :WaitForChild(name, timeout?)

Create parts

local p = Instance.new("Part")  -- also Instance.new("Brick") or "Model"
p.Name = "MyBrick"
p.Parent = workspace
p.Position = Vector3.new(0, 5, 0)
p.Size = Vector3.new(4, 1, 4)
p.Color = RGBColor.new(255, 0, 0)
p.Anchored = true
p.CanCollide = true

Colors & vectors

RGBColor.new(255, 0, 0)   -- 0-255 in.
RGBColor.random()
Vector3.new(0, 10, 0)

script.Parent.Color = RGBColor.random()
script.Parent.Position = Vector3.new(0, 10, 0)
script.Parent.Rotation = { x = 0, y = 45, z = 0 }

Sound

CallNotes
Sound.play("tick")Full volume SFX
Sound.playAt("explosion", x, y, z)Quieter with distance
Sound.playMusic(url, volume?)Looping BGM for everyone — use an .ogg URL
Sound.stopMusic()Stop world BGM
Sound.playMusicFor(player, url, volume?)BGM for one player only
Sound.stopMusicFor(player)Stop that player's BGM
player:PlayMusic(url, volume?)Same as playMusicFor
player:StopMusic()Same as stopMusicFor

Built-in SFX names: jump, land, footstep / footsteps, explosion, chat, rocket, tick.

You can also pass numeric ids 06 (same order as the names above).

Hosting your own BGM: open My Places > Edit > Place music and upload OGG, MP3, WAV, FLAC, or M4A (converted to OGG on upload). Copy the URL into your script. Warning! Your music will be converted to an .ogg

function PlayerAdded(player)
    player:PlayMusic("https://polyworld.games/assets/bgm/jumper-cminor.ogg", 0.4)
end

Chat / UI

CallWho sees itNotes
Chat.broadcast(text)EveryoneChat panel as Server
player:Message(text)One playerChat panel as Server
UI.notify(text, seconds?)EveryoneCenter toast (default 3s)
player:Notify(text, seconds?)One playerCenter toast
UI.hud(text, seconds?)EveryoneTop HUD (default 8s; 0 = until clear)
player:Hud(text, seconds?)One playerTop HUD
UI.clear()EveryoneClears toast + HUD
player:ClearUI()One playerClears that player's toast + HUD
function Touched(player)
    player:Notify("Checkpoint saved!")
    Chat.broadcast("Someone reached a checkpoint")
    UI.hud("Find the exit", 30)
end

Explosions

Explosion.new(x, y, z, radius)  -- radius optional, default 8
-- Not deadly! You'd have to kill the player yourself...

NPCs

local id = NPCs.create(
    "Guide",      -- name (shows with [NPC])
    shirtId,
    pantsId,
    headId,
    "#eaeaea",    -- skin hex
    0, 5, 0       -- position (optional; default 0, 4, 0)
)

NPCs.chat(id, "Welcome!")
NPCs.moveTo(id, x, y, z)
NPCs.tpTo(id, x, y, z)
NPCs.setYaw(id, 90)
NPCs.setAnim(id, NPCs.Animations.Walking)
print(NPCs.getX(id), NPCs.getY(id), NPCs.getZ(id), NPCs.getYaw(id))
NPCs.destroy(id)
CallNotes
NPCs.create(...)Returns slot id (max 32 NPCs)
NPCs.destroy(id)Remove NPC
NPCs.chat(id, text)Bubble / chat line
NPCs.moveTo(id, x, y, z)Walk toward point
NPCs.tpTo(id, x, y, z)Instant teleport
NPCs.setYaw(id, deg)Face direction
NPCs.setAnim(id, anim)See Animations table
NPCs.getX(id) / getY / getZCurrent position
NPCs.getYaw(id)Current yaw (degrees)
NPCs.AnimationsValue
Idle0
Walking1
Jumping2
Dancing3
Dancing24
Dancing35

Examples

Killbrick

Put this Script under the kill Part:

function Touched(player)
    player:Kill()
end

Color-changing Part

while true do
    script.Parent.Color = RGBColor.random()
    wait(1)
end

Spinner + boom

function Touched(player)
    local p = script.Parent.Position
    Explosion.new(p.x, p.y, p.z, 10)
    player:Kill()
end

local t = 0
while true do
    t = t + 1
    script.Parent.Rotation = { x = t * 2, y = t * 5, z = t * 10 }
    wait(0.016)
end

Trap platform (rises, then returns)

Parent this to the trap Part. (wait can't run inside Touched, so we use a flag.)

local busy = false

function Touched(player)
    if busy then return end
    busy = true
end

local start = script.Parent.Position
local sx, sy, sz = start.x, start.y, start.z

while true do
    if busy then
        script.Parent.Position = Vector3.new(sx, sy + 11, sz)
        wait(1)
        script.Parent.Position = Vector3.new(sx, sy, sz)
        busy = false
    end
    wait(0.05)
end

Checkpoints (obby)

1. Name every checkpoint Part exactly Checkpoint. 2. Add one Script in the place (doesn't need a Part parent) with this:

-- Checkpoint Manager: Create parts named "Checkpoint" to make checkpoints

local checkpoints = {}
local on_pad = {}

local function collect(node)
    local ok, kids = pcall(function() return node:GetChildren() end)
    if not ok or not kids then return end
    for _, child in ipairs(kids) do
        local class = child.ClassName
        local name = child.Name
        if class == "Part" and name == "Checkpoint" then
            table.insert(checkpoints, child)
        elseif class == "Model" then
            collect(child)
        end
    end
end

local function overlaps(part, px, py, pz)
    local pos = part.Position
    local size = part.Size
    local hx = size.x * 0.5 + 0.4
    local hy = size.y * 0.5 + 0.4
    local hz = size.z * 0.5 + 0.4
    -- Touching?
    return math.abs(px - pos.x) < 1.0 + hx
        and math.abs(py - pos.y) < 2.3 + hy
        and math.abs(pz - pos.z) < 0.5 + hz
end

local function spawn_above(part)
    local pos = part.Position
    local size = part.Size
    -- Stand on top
    return pos.x, pos.y + size.y * 0.5 + 3.0, pos.z
end

wait(0.2)
collect(workspace)
print("[Checkpoints] Found", #checkpoints, "Checkpoint parts")

while true do
    local players = workspace:GetPlayers()
    local seen = {}

    for _, p in ipairs(players) do
        local id = p.id
        seen[id] = true

        local hit = nil
        for i, cp in ipairs(checkpoints) do
            if overlaps(cp, p.x, p.y, p.z) then
                hit = i
                break
            end
        end

        if hit then
            if on_pad[id] ~= hit then
                on_pad[id] = hit
                local x, y, z = spawn_above(checkpoints[hit])
                p:SetSpawn(x, y, z)
                print("[Checkpoints] Player", id, "-> checkpoint", hit)
            end
        else
            on_pad[id] = nil
        end
    end

    for id, _ in pairs(on_pad) do
        if not seen[id] then
            on_pad[id] = nil
        end
    end

    wait(0.05)
end

When a player steps on a Checkpoint, their respawn point updates. Die later → come back there instead of the start.

Welcome NPC

wait(1)
local npc = NPCs.create("Guide", 31, 32, 19, "#eaeaea", 0, 5, 0)
NPCs.setAnim(npc, NPCs.Animations.Idle)
NPCs.chat(npc, "Welcome!")

while true do
    local players = workspace:GetPlayers()
    if players[1] and players[1].health > 0 then
        NPCs.moveTo(npc, players[1].x, players[1].y, players[1].z)
        NPCs.setAnim(npc, NPCs.Animations.Walking)
    end
    wait(0.1)
end

LocalScripts (client)

LocalScripts run on each player's client (Studio: blue script icon). Use them for local VFX, UI toasts, and sending events to server Scripts.

WhatNotes
wait(seconds)Pause this LocalScript
print(...)Print to the client console
script.Parent / script.parentParent Part (if any)
playerLocal player (name, health, x/y/z, position, dead)
player:GetHealth() / :GetPosition() / :SetPosition(...) / :Notify(text, secs?)
FireServer(name [, data])Sends to server OnRemoteEvent(name, player_id, data)
UI.notify(text, secs?) / UI.hud(text)Local toast / HUD
RGBColor / Vector3 / Explosion.newSame as server (Explosion is local visual)
function Touched(player)Fires when you enter the parent Part

Parent part properties (local visual only — not authoritative on the server): color/Color, position/Position, rotation/Rotation, size/Size, Transparency (0=opaque).

Example — client button feel + tell the server:

function Touched(player)
    UI.notify("Checkpoint!", 2)
    FireServer("CheckpointTouch")
end

Server Script:

function OnRemoteEvent(name, player_id, data)
    if name == "CheckpointTouch" then
        print("Player " .. player_id .. " hit a checkpoint")
    end
end

Tips & gotchas

  • Server Scripts are the source of truth for world/player state. LocalScripts can tweak local visuals and call FireServer.
  • Touched will only trigger on enter.
  • Prefer RGBColor when assigning new colors to parts.
  • Player methods use PascalCase (PlayMusic, Notify); Sound.* uses camelCase (playMusic).
  • You can have many Scripts per place as you need, duplicate scripts will be turned into a single file, while being loaded in the context of multple parts.
  • Upload places as .rbxlx from Create Place. Scripts inside of it will convert over!
  • You can use a pcall with Workspace:GetPlayers() to detect if you are running it in Roblox or PolyWorld.

Have fun making your game!