Scripting UI
UI belongs to each player, so it is driven by ClientScripts. You can build it in KHIM Engine (Designing UI) and script it, or build it entirely from code.
Reaching UI made in KHIM Engine
Each player gets a copy of everything under UI when they join. A ClientScript inside a UI object has it as script.parent:
-- ClientScript inside the "Shop" Canvas
local shop = script.parent
local buy = shop.Panel.BuyButton
buy.clicked:connect(function()
net.send("buy", "Sword")
end)Any other ClientScript finds it in the player's gui:
local shop = players.me.gui:waitFor("Shop")
shop.visible = falseBuilding UI from code
-- ClientScript: a coin counter at the top of the screen
local canvas = new("Canvas", players.me.gui)
local label = new("Label", canvas)
label.anchor = vec2(0.5, 0)
label.position = dim(0.5, 0, 0, 80) -- the middle of the screen, 80 points from the top
label.size = dim.px(200, 40)
label.text = "Coins: 0"
label.textSize = 22
label.textColor = rgb(255, 255, 255)
label.backgroundColor = rgb(20, 20, 20)
label.backgroundTransparency = 0.3
new("Corner", label).radius = 12
net.on("coinCollected", function(total)
label.text = "Coins: " .. total
end)Sizes and positions: dim
UI is laid out in dim values - a scale (a share of the parent) and an offset (UI points) for X and Y:
dim(xScale, xOffset, yScale, yOffset)
dim(0.5, 0, 0, 40) -- half the parent's width, 40 points high
dim.scale(1, 1) -- the parent's whole size
dim.px(200, 50) -- 200 x 50 pointsanchor (a vec2, 0 to 1) is the point of the object that position places: vec2(0.5, 0.5) centres it.
UI points grow with the player's HUD scale, so a layout looks the same on a big monitor. camera.viewportSize gives the screen's size in UI points.
Common properties
Every UI object (Panel, Label, Button, TextInput, Image, ImageButton...):
| Member | |
|---|---|
position, size | dim |
anchor | vec2 |
backgroundColor, backgroundTransparency | The box |
visible | Shown or not |
zIndex | Drawn over lower ones (never under its parent) |
layoutOrder | Its place in a ListLayout or GridLayout sorted by LayoutOrder |
Text objects (Label, Button, TextInput) add text, textColor, textSize, textAlign ("Left", "Center", "Right"), textWrapped, textScaled and font ("Default", "Bold", "Display").
Images add image (a picture's name - "coin", "icon-star"... - or "asset:123"), imageColor, imageTransparency.
Buttons and input
button.clicked:connect(function() print("clicked") end) -- Button, ImageButton
box.focusLost:connect(function(enterPressed) -- TextInput
if enterPressed then print("typed: " .. box.text) end
end)
box.placeholder = "Enter a code"KHIM's own controls:
local bar = new("ProgressBar", canvas) -- health, XP, loading
bar.min, bar.max, bar.value = 0, 100, 75
bar.fillColor = rgb(80, 220, 120)
local slider = new("SliderInput", canvas) -- the player drags it
slider.min, slider.max, slider.step = 0, 1, 0.05
slider.changed:connect(function(value) print("volume", value) end)
local toggle = new("Toggle", canvas) -- on/off
toggle.text = "Music"
toggle.changed:connect(function(on) print("music", on) end)Layout objects
Put these inside a UI object to arrange or style it:
local list = new("ListLayout", panel)
list.direction = "Vertical" -- or "Horizontal"
list.padding = 8 -- points between children
list.alignX = "Center"
list.sortOrder = "LayoutOrder" -- or "Name"
local grid = new("GridLayout", panel)
grid.cellSize = dim.px(80, 80)
grid.cellPadding = dim.px(6, 6)
local pad = new("Padding", panel)
pad.top, pad.bottom, pad.left, pad.right = 10, 10, 12, 12
new("Corner", panel).radius = 10
local border = new("Border", panel)
border.color = rgb(255, 255, 255)
border.thickness = 2
border.transparency = 0.6
local grad = new("Gradient", panel)
grad.color0 = rgb(40, 40, 60)
grad.color1 = rgb(10, 10, 20)
grad.rotation = 90 -- top to bottom| Modifier | Members |
|---|---|
AspectRatio | aspectRatio, aspectType ("FitWithinMaxSize", "ScaleWithParentSize"), dominantAxis |
Scale | scale - draws it and its contents bigger or smaller (tween it for a hover effect) |
SizeLimit | minSize, maxSize (vec2, points) |
TextSizeLimit | minTextSize, maxTextSize |
Border | applyTo = "Text" outlines the letters instead of the box |
Gradient | colorMid, midpoint - a third colour in the middle |
Scrolling lists
A ScrollPanel scrolls its contents with the mouse wheel or a drag:
local scroller = new("ScrollPanel", canvas)
scroller.size = dim.px(300, 400)
scroller.automaticCanvasSize = "Y" -- grows to fit what is inside
new("ListLayout", scroller).padding = 6
for i = 1, 20 do
local row = new("Button", scroller)
row.size = dim(1, 0, 0, 40)
row.text = "Item " .. i
endA button inside a ScrollPanel clicks when released without dragging, so scrolling a shop with a finger doesn't buy things on the way.
Animating UI
UI objects tween like parts:
panel.position = dim(0.5, 0, 1.2, 0) -- off the bottom
panel:tween({position = dim(0.5, 0, 0.5, 0)}, {time = 0.4, style = "Back", direction = "Out"})
local grow = new("Scale", button)
-- on hover, a script can tween grow.scale from 1 to 1.1Showing a menu over a first-person game
In first person or camera lock the pointer is held in the middle. Free it while a menu is open:
input.onKey("B", function()
shop.visible = not shop.visible
input.mouseFree = shop.visible
end)Text over the world
For text attached to a part - a name, a price, a health bar over an enemy - use a Billboard on the part instead of screen UI.