Cubzh Cheat Sheet

The goal of this document is to show you in a single page most of the attributes and functions available.

This list is not exaustive but is a great start to learn Cubzh if you are already an experienced developer.

Base Functions

Client.OnStart = function()
    -- trigger once on start
end

Client.Tick = function(dt)
    -- tick at each frame
end

Client.OnPlayerJoin = function(p)
    print(p.Username.." joins the game.")
end

Client.OnPlayerLeave = function(p)
    print(p.Username.." lefts the game.")
end

World / Object

Map

Config = {
  Map = "aduermael.rockies"
}
Config.Map = "aduermael.rockies"

Client.OnStart = function()
    Map.Scale = 5 -- default

    Map:AddBlock(Color.White,0,0,0) -- bottom left corner

    local b = Map:GetBlock(0,0,0)
    b:Replace(Color.Red)
    b:Remove()
end

Shapes

Config = {
  Map = "aduermael.rockies",
  Items = { "aduermael.rainbow_sword" }
}

Client.OnStart = function()
    local shape = Shape(Items.aduermael.rainbow_sword)
    shape:SetParent(Map) -- adds created shape in the map
    shape.Position = Number3(Map.Width * 0.5, Map.Height, Map.Depth * 0.5) * Map.Scale
    shape.LocalPosition = { Map.Width * 0.5, Map.Height, Map.Depth * 0.5 }
    shape.IsHidden = true -- false by default

    shape:SetParent(World) -- adds created shape in the world
    shape.LocalPosition = Number3(Map.Width * 0.5, Map.Height, Map.Depth * 0.5) * Map.Scale

    shape.LocalRotation = Number3(0, math.pi / 2, 0)
    shape.LocalScale = 3

    shape.Tick = function(object, dt)
        shape.Position.Y = shape.Position.Y + dt
    end

    -- You can also load shapes asynchronously without adding them in Config.Items
    Object:Load("caillef.basicaxe", function(axe)
        if not axe then return end
        Player:EquipRightHand(axe)
    end)

    -- MutableShape
    local mutableShape = MutableShape(Items.aduermael.rainbow_sword)
    mutableShape:AddBlock(Color(0,255,0), 10, 10, 10)

    local block = mutableShape:GetBlock(10, 10, 10)
    if block then
        block:Remove()
    end

    Player:EquipRightHand(mutableShape)
end

Physics / Collision

shape.Physics = PhysicsMode.Disabled -- excluded from all physics features.
shape.Physics = PhysicsMode.Trigger -- casts, collision callbacks, passed through by other dynamic objects.
shape.Physics = PhysicsMode.Static -- casts, collision callbacks, obstacle for other dynamic objects.
shape.Physics = PhysicsMode.Dynamic -- fall and has Velocity, world-aligned collision box, casts, collision callbacks, may act as obstacles for other dynamic objects, fully simulated

shape.OnCollision = function(shape, other) end
shape.OnCollisionBegin = function(shape, other) end
shape.OnCollisionEnd = function(shape, other) end

local object1 = Object()
local object2 = Object()

-- making sure 2 objects collide with each other
-- NOTE: by default:
-- Map.CollisionGroups == {1},
-- Player.CollisionGroups == {2},
-- Object.CollisionGroups == {3}
object1.CollisionGroups = {5}
object2.CollisionGroups = {5}
object1.CollidesWithGroups = {1, 5} -- collides with Map + objects in group 5
object2.CollidesWithGroups = {1, 5} -- collides with Map + objects in group 5
-- OR
object1.CollidesWithGroups = Map.CollisionGroups + {5}
object2.CollidesWithGroups = Map.CollisionGroups + {5}

-- making an object collides with the Map and Players
local object = Object()
object.CollidesWithGroups = Map.CollisionGroups + Player.CollisionGroups

-- for Player (local player) to collide with other players and the Map
Player.CollidesWithGroups = Map.CollisionGroups + Player.CollisionGroups

Camera

Camera:SetModeFirstPerson(Player)
Camera:SetModeThirdPerson(Player)
Camera:SetModeSatellite(Player, 10.0)
Camera:SetModeFree()

Camera:FitToScreen(myShape, 0.6)

Camera.FOV = 40.0

Player

Player.Scale = 0.5 -- default
Player.Head.Scale = 2
Player.RightArm.IsHidden = true

Equipments

Config = {
  Items = { "aduermael.rainbow_sword" }
}

Client.OnStart = function()
    Player:EquipRightHand(Items.aduermael.rainbow_sword)
    -- or
    local s = Shape(Items.aduermael.rainbow_sword)
    Player:EquipRightHand(s)

    Player:EquipRightHand(nil) -- unequips the sword

    -- other socket
    Player:EquipLeftHand(Items.aduermael.rainbow_sword)
    Player:EquipBackpack(Items.aduermael.rainbow_sword)

    Player:SwapHands() -- swap items

    -- Animation, can be used in Action2
    Player:SwingRight()
    Player:SwingLeft()
end

Raycast

Pointer.Down = function( pointerEvent )
    local impact = pointerEvent:CastRay()
    -- or
    local ray = Ray(pointerEvent.Position, pointerEvent.Direction)
    local impact = ray:Cast()

    if impact.Block ~= nil then
        local position = pointerEvent.Position + pointerEvent.Direction * impact.Distance
        print("block hit:", impact.Block)
    end
end

-- cast rays from Camera's position to remove cubes in the map
Client.Action2 = function()
    local impact = Camera:CastRay(Map.CollisionGroups) -- only consider the map for collisions
    -- or
    local ray = Ray(Camera.Position, Camera.Forward)
    local impact = ray:Cast(Map.CollisionGroups) -- only consider the map for collisions
    if impact.Block ~= nil then
        impact.Block:Remove()
    end
end

-- cast ray down from Player's position to see
-- if there's something under it:
Client.Action3 = function()
    local ray = Ray(Player.Position, {0, -1, 0})
    local impact = ray:Cast(nil, Player) -- filter out Player to avoid direct impacts with it
    if impact ~= nil then
        print("found something under the player, distance:", impact.Distance)
    end
end

Controls

Inputs

Client.Action1 = function()
    print("Press Space or button on mobile")
end

Client.Action1Release = function()
    print("Press Space or button on mobile")
end

Client.Action2 = function()
    print("Press Left Click or button on mobile")
    local impact = Player:CastRay()
    if impact.Block ~= nil then
      print("block hit:", impact.Block)
    end

    local impact = Camera:CastRay()
    if impact.Block ~= nil then
      print("block hit:", impact.Block)
    end
end

Client.Action2Release = function()
    print("Release Left Click or button on mobile")
end

Client.Action3 = function()
    print("Press Right Click or button on mobile")
end

Client.Action3Release = function()
    print("Release Right Click or button on mobile")
end

Movements

-- DEFAULT IMPLEMENTATION
-- (those functions can be redefined)

Client.AnalogPad = function(dx, dy)
    Player.LocalRotation.Y = Player.LocalRotation.Y + dx * 0.01
    Player.LocalRotation.X = Player.LocalRotation.X + -dy * 0.01

    if dpadX ~= nil and dpadY ~= nil then
        Player.Motion = (Player.Forward * dpadY + Player.Right * dpadX) * 50
    end
end

Client.DirectionalPad = function(x, y)
    -- storing globals here for AnalogPad
    -- to update Player.Motion
    dpadX = x dpadY = y
    Player.Motion = (Player.Forward * y + Player.Right * x) * 50
end

Pointer / TouchScreen

More info on PointerEvent

Client.OnStart = function()
    Pointer:Show() -- if third person, hold click to move the camera
    UI.Crosshair = false
end

Pointer.Zoom = function(zoomValue)
    -- for example, move the camera back and forth
    Camera.Position = Camera.Position + Camera.Forward * zoomSpeed * zoomValue
    -- we could also play with Camera.FieldOfView for a more convincing zoom effect,
    -- use a velocity-based zoom to have a smooth motion, or any other custom logic
end

Pointer.Up = function(pointerEvent)
    local impact = pointerEvent:CastRay()
    if impact.Block ~= nil then
      print("block hit:", impact.Block)
    end

    -- this can also be done using a Ray object:
    local ray = Ray(pointerEvent.Position, pointerEvent.Direction)
    impact = ray:Cast(Map) -- filter in Map
    if impact.Block ~= nil then
      print("block hit:", impact.Block)
    end
end

Pointer.Down = function(pointerEvent)
    print(pointerEvent.X, pointerEvent.Y)
end

Pointer.Drag = function(pointerEvent)
    print(pointerEvent.DX, pointerEvent.DY)
end
Pointer.DragBegin = function(pointerEvent) end
Pointer.DragEnd = function(pointerEvent) end

-- right click or 2 fingers drag
Pointer.Drag2 = function(pointerEvent) end
Pointer.Drag2Begin = function(pointerEvent) end
Pointer.Drag2End = function(pointerEvent) end

Colors

local blue = Color(0,0,255) -- integers 0-255
-- or
local blue = Color(0.0, 0.0, 1.0) -- decimals 0.0-1.0

Sounds and Music

List of all the sounds available here: https://docs.cu.bzh/guides/quick/adding-sounds#list-of-available-sounds

Client.OnStart = function()
    -- add the AudioListener to the player's head
    -- to "glue the microphone" to this object
    Player.Head:AddChild(AudioListener)

    -- create a new source
    as = AudioSource()
    as.Sound = "death_scream_guy_1"
    as:SetParent(Player.Head)
    as.Volume = 0.3
    as.StartAt = 200
    as.StopAt = 1000
    as.Pitch = 0.5
    as.Spatialized = false -- if true, the closer you are from the source, the louder it will be

    as:Play()
    Timer(2, false, function()
        as:Stop()
    end)
end

Delay and Clock

Timer(5, function()
    print("5 seconds")
end)

Timer(2, true, function()
    print("tick every 2 seconds")
end)

object.Tick = function(o, dt)
    print("Tick of an object")
end

Lights

local l = Light()

-- change light properties
l.Radius = 50
l.Hardness = 0.5 -- Default 0.5, core intensity of the emitted light
l.Color = Color(1.0, 1.0, 0.5)
l.On = true

-- use it as a normal object in the scene
l:SetParent(World)
l.Position = { x, y, z }

Text

local t = Text()

-- change text properties
t.Text = "Hello world!"
t.Type = TextType.Screen -- The text type can be set to TextType.World (default) or TextType.Screen.
t.IsUnlit = true
t.Tail = true
t.Color = Color.White
t.BackgroundColor = Color.Red
t.FontSize = 30 -- Font size in points, 22.0 by default

-- use it as a normal object in the scene
t:SetParent(Player)
t.LocalPosition = { 0, 34, 0 }

UI (internal module)

Setup

Client.OnStart = function()
    ui = require("uikit")
    ui:init()
end

Pointer.Down = function(pe)
    ui:pointerDown(pe)
end

Pointer.Up = function(pe)
    ui:pointerUp(pe)
end

Screen.DidResize = function()
    ui:fitScreen()
end

Elements

Client.OnStart = function()
    ui = require("uikit")
    ui:init()

    local frame = ui:createFrame(Color.Red)
    frame:setParent(ui.rootFrame)
    frame.Width = 200
    frame.Height = 100
    frame.LocalPosition = Number3(50,50,0) -- Z cannot be set like that, it must be set independently

    local text = ui:createText("HP", Color.White, "small") -- size can be "small", "default", "big"
    text:setParent(frame)
    text.object.Anchor = { 0.5, 0.5 } -- place anchor in the middle of the text
    text.LocalPosition.Z = -1 -- place the frame in front of the parent
    text.parentDidResize = function() -- called if the window is resized (responsiveness)
        text.LocalPosition = Number3(frame.Width / 2, frame.Height / 2, 0)
    end
    text:parentDidResize()

    -- after 5 seconds, update frame height
    Timer(5, function()
        frame.Height = 200
    end)

    local button = ui:createButton("Click here")
    button:setParent(ui.rootFrame) -- must be set to ui.rootFrame or another ui element
    button:setColor(Color.Blue, Color.White) -- set background color and text color
    button.LocalPosition = Number3(Screen.Width - button.Width - 5, 100, 0)
    button.onRelease = function()
        print("Click")
    end
end

Chat

Client.OnChat = function(payload)
    local message = payload.message -- access the message field from the payload param
    print(message) -- shows the message only for this client, not synced, see Events
end

HTTP / External API

local url = "https://mybestapi.com/api/users" -- replace with the URL you want to request
local headers = {}
headers["Content-Type"] = "application/json"
headers["Authorization"] = "Bearer 298H3298H329839823" -- if the API requires authentication

-- GET
HTTP:Get(url, headers, function(res)
  if res.StatusCode ~= 200 then
    print("Error " .. res.StatusCode)
    return
  end
  -- body is [{"id": 289733, "name": "Mike", "age": 15}]
  users,err = JSON:Decode(res.Body)
  local user = users[1]
  print(user.id, user.name, user.age)
  -- prints 289734 Mike 15.0
end)

-- POST
local body = {}
body.name = "Bob"
body.age = 28
HTTP:Post(url, headers, body, function(res)
  if res.StatusCode ~= 200 then
    print("Error " .. res.StatusCode)
    return
  end
  -- body is {"id": 289734, "name": "Bob", "age": 28}
  user,err = JSON:Decode(res.Body)
  print(user.id, user.name, user.age)
  -- prints 289734 Bob 28.0
end)

Events / Multiplayer

Send Events

local e = Event()
e.action = "ping"
e.someMessage = "Something I'd like to say!"
e.someNumber = 42
e:SendTo(Server) -- send to Server

-- other possible recipients:
e:SendTo(Players) -- send to all players
e:SendTo(Players[2]) -- send to player 2
e:SendTo(OtherPlayers) -- send to all players but self

Receive Events

Client.DidReceiveEvent = function(event)
  if event.action == "pong" then
    print("received pong")
  end
end

Server.DidReceiveEvent = function(event)
  if event.action == "ping" then
    local response = Event()
    response.action = "pong"
    response:SendTo(event.Sender)
  end
end

Multi (internal module)

This module will sync the movement of all the players so that you can see other players in the World.
You can also use playerAction to play an animation when a player press a key for example.

Client.OnStart = function()
    multi = require("multi")
    multi.teleportTriggerDistance = 100

    onFire = function(player, data)
        print(data.name)
        player:SwingRight()
    end

    multi:registerPlayerAction("fire", onFire)

    -- rest of the code ...
end

Client.Tick = function(dt)
       multi:tick(dt)
end

Client.OnPlayerJoin = function(p)
    multi:initPlayer(p)
end

Client.OnPlayerLeave = function(p)
    multi:removePlayer(p)
end

Client.DidReceiveEvent = function(e)
    multi:receive(e)
end

-- jump function, triggered with Action1
Client.Action1 = function()
    if Player.IsOnGround then
        Player.Velocity.Y = 100 -- This is synced by default by the module because all the movements are synchronised
    end
end

Client.Action2 = function()
    -- send action to other players
    multi:playerAction("fire", { name=Player.Username })
    -- play action locally
    onFire(Player, { name=Player.Username })
end

Storage

To save data between two sessions or to save highscores, you can use KeyValueStore

Server.DidReceiveEvent = function(event)
    if event.action == "getXP" then

        -- retrieve and return player's experience:
        local store = KeyValueStore(event.Sender.UserID) -- use UserID as store name

        -- get value for "xp" key
        store:Get("xp", function(success, results)
            if success then
            local response = Event()
            response.xp = results.xp
            response:SendTo(event.Sender)
            end
        end)
    end
end

local store = KeyValueStore("settings")
store:Set("currentChallenge", "halloween", "jumpStrength", 10, function(success)
    if success then
        -- operation was successful
    end
end)

store:Get("currentChallenge", "jumpStrength", function(success, results)
    if success then
    -- do something with results.currentChallenge
    -- and results.jumpStrength
    end
end)