Skip to content


At a high level, Core Lua types can be divided into two groups: data structures and Objects. Data structures are owned by Lua, while Objects are owned by the engine and could be destroyed while still referenced by Lua. Any such object will inherit from this type. These include CoreObject, Player, and Projectile.


Property Name Return Type Description Tags
serverUserData table Table in which users can store any data they want on the server. Read-Write, Server-Only
clientUserData table Table in which users can store any data they want on the client. Read-Write, Client-Only

Class Functions

Class Function Name Return Type Description Tags
Object.IsValid(Object object) boolean Returns true if object is still a valid Object, or false if it has been destroyed. Also returns false if passed a nil value or something that's not an Object, such as a Vector3 or a string. None


Example using:


The example below shows the importance of using Object.IsValid() instead of a simple nil check (that is if object then). An object can be in a situation where it's invalid, but not yet nil. This can happen if a script is retaining a reference to it or it began the destroy process but hasn't completed it yet.

In this example, the script has a cube child that it finds with the GetChildren() call. It then prints information about the cube as it progresses through the steps of being destroyed and its variable reference cleared.

local CUBE = script:GetChildren()[1]

function PrintCubeInfo()
    print(".:.:. Information about CUBE object .:.:.")

    if CUBE then
        print("CUBE is NOT nil")
        print("CUBE is nil")

    if Object.IsValid(CUBE) then
        print("CUBE is valid")
        print("CUBE is NOT valid")

    local childrenCount = #script:GetChildren()
    print("Number of children of this script: " .. tostring(childrenCount))


-- The cube is destroyed, but we still have a variable pointing to it.


-- Variable reference is cleared, releasing the cube.
CUBE = nil


See also: CoreObject.Destroy | CoreLua.print | Object.IsValid

Example using:


In this example, multiple copies of the same script are placed into the scene. At startup, they search for each other and build a follow chain. The last script that can't find another script to follow is set to follow the local player. As the player moves around the chain of objects follows along in a smooth motion. The clientUserData property is leveraged in building the chain of object references.

For this to work all scripts should be in a client context. In order to visualize the effect, objects (for example a Cube) can be added as children of the scripts.

As the name implies, clientUserData is a non-dynamic property on the client only.

local allScripts = World.FindObjectsByName(

for _, otherScript in ipairs(allScripts) do
    if otherScript ~= script
    and == nil then = otherScript

if == nil then = Game.GetLocalPlayer()

local velocity = Vector3.ZERO
local DRAG = 0.96
local ACCELERATION = 0.5

function Tick()
    if not then return end

    local myPos = script:GetWorldPosition()

    myPos = myPos + velocity
    velocity = velocity * DRAG

    local targetPos =
    local direction = (targetPos - myPos):GetNormalized()
    velocity = velocity + direction * ACCELERATION


See also: World.FindObjectsByName | Game.GetLocalPlayer | CoreObject.GetWorldPosition | Vector3:GetNormalized() | CoreLua.Tick

Example using:


In this example we are trying to figure out which player was the first to join the game and promote them with some gameplay advantage. That's easy for the first player joining, but because players can join and leave at any moment, the first player to join might leave, at which point we need to promote the next (oldest) player. To accomplish this, we keep count of how many players have joined and save that number onto each player's serverUserData--a kind of waiting list.

As the name implies, serverUserData is a non-dynamic property on the server only.

local primaryPlayer = nil
local joinCounter = 0

function OnPlayerJoined(player)
    joinCounter = joinCounter + 1
    -- Save the waiting number onto the player itself
    player.serverUserData.joinNumber = joinCounter

function PromotePlayer(player)
    -- Give some gameplay advantage or leadership ability
    print("PROMOTING: " ..

function Tick()
    if (not Object.IsValid(primaryPlayer)) then
        -- Find the oldest player
        local oldestPlayer = nil
        local oldestJoinNumber = 999999

        local allConnectedPlayers = Game.GetPlayers()

        for _, player in ipairs(allConnectedPlayers) do
            local joinNumber = player.serverUserData.joinNumber
            if joinNumber < oldestJoinNumber then
                oldestJoinNumber = joinNumber
                oldestPlayer = player

        -- If we found a player, promote them
        if oldestPlayer then
            primaryPlayer = oldestPlayer



See also: CoreLua.print | | Object.IsValid | Game.GetPlayers | Event.Connect

Last update: February 2, 2022