Common patterns¶
Drop-in replacements for the APIs that come up most often when porting. Each snippet is self-contained; adapt names and types to your place.
Raycast¶
local function raycast(origin: Vector3, direction: Vector3, ignore: { Instance })
local ray = Ray.new(origin, direction)
local part, position, normal, material =
workspace:FindPartOnRayWithIgnoreList(ray, ignore, false, true)
if part then
return {
Instance = part,
Position = position,
Normal = normal,
Material = material,
}
end
end
See Physics and spatial queries.
Region query¶
local function partsInBox(center: Vector3, size: Vector3, ignore: { Instance })
local half = size / 2
local region = Region3.new(center - half, center + half)
return workspace:FindPartsInRegion3WithIgnoreList(region, ignore, 100)
end
Vertical list (UIListLayout)¶
local function layoutColumn(container: ScrollingFrame, padding: number)
local y = 0
for _, child in ipairs(container:GetChildren()) do
if child:IsA("GuiObject") and child.Visible then
child.Position = UDim2.new(0, 0, 0, y)
y += child.AbsoluteSize.Y + padding
end
end
container.CanvasSize = UDim2.new(0, 0, 0, y)
end
See Build a UI without UIListLayout.
Aspect ratio (UIAspectRatioConstraint)¶
local function keepAspect(gui: GuiObject, aspect: number)
local function update()
local width = gui.AbsoluteSize.X
if width > 0 then
gui.Size = UDim2.new(0, width, 0, math.floor(width / aspect))
end
end
gui:GetPropertyChangedSignal("AbsoluteSize"):Connect(update)
update()
end
Rounded corners (UICorner)¶
Use a 9-slice image:
local corner = Instance.new("ImageLabel")
corner.Name = "Corner"
corner.Size = UDim2.fromScale(1, 1)
corner.BackgroundTransparency = 1
corner.Image = "rbxassetid://0" -- your rounded-rect 9-slice
corner.ScaleType = Enum.ScaleType.Slice
corner.SliceCenter = Rect.new(16, 16, 48, 48)
corner.ZIndex = 0
corner.Parent = frame
Set the content's ZIndex above the corner image. If you do not have a 9-slice
asset, keep square corners until you can author one.
ProximityPrompt¶
Use a ClickDetector for the interaction and a BillboardGui for the prompt:
local detector = Instance.new("ClickDetector")
detector.MaxActivationDistance = 10
detector.Parent = part
detector.MouseClick:Connect(function(player)
print(player.Name, "interacted with", part:GetFullName())
end)
local billboard = Instance.new("BillboardGui")
billboard.Name = "Prompt"
billboard.Size = UDim2.new(0, 160, 0, 32)
billboard.StudsOffset = Vector3.new(0, 3, 0)
billboard.AlwaysOnTop = true
billboard.Adornee = part
local label = Instance.new("TextLabel")
label.Size = UDim2.fromScale(1, 1)
label.BackgroundTransparency = 1
label.Text = "[E] Interact"
label.TextColor3 = Color3.new(1, 1, 1)
label.TextStrokeTransparency = 0
label.Parent = billboard
billboard.Parent = part
ClickDetector is mouse-oriented. For keyboard interaction, combine it with
UserInputService and a distance check on the server.
TextChatService¶
Nova does not implement TextChatService. Use the legacy chat and
Player.Chatted, or build a custom chat UI:
game:GetService("Players").PlayerAdded:Connect(function(player)
player.Chatted:Connect(function(message)
print(player.Name, "said", message)
end)
end)
For custom chat, create a ScreenGui with a ScrollingFrame and a TextBox,
and send messages through a RemoteEvent. Keep filtering/moderation in mind if
your deployment requires it.
UnreliableRemoteEvent¶
Use RemoteEvent and batch high-frequency updates:
-- Client: send at most 20 times per second
local accumulator = 0
game:GetService("RunService").RenderStepped:Connect(function(dt)
accumulator += dt
if accumulator >= 0.05 then
accumulator = 0
remote:FireServer(lookDirection)
end
end)
HumanoidDescription¶
Apply classic body parts and clothing instead:
local function dress(character: Model)
local bodyColors = character:FindFirstChildOfClass("BodyColors")
if bodyColors then
bodyColors.HeadColor = BrickColor.new("Bright yellow")
bodyColors.TorsoColor = BrickColor.new("Bright blue")
end
local shirt = Instance.new("Shirt")
shirt.ShirtTemplate = "rbxassetid://123456789"
shirt.Parent = character
local pants = Instance.new("Pants")
pants.PantsTemplate = "rbxassetid://987654321"
pants.Parent = character
end
SurfaceAppearance¶
Bake the PBR maps into a texture and use MeshPart.TextureID (or a
SpecialMesh TextureId). SurfaceAppearance itself is not rendered.
Atmosphere and Clouds¶
Use Lighting fog and a Sky:
local Lighting = game:GetService("Lighting")
Lighting.FogEnd = 500
Lighting.FogStart = 100
Lighting.FogColor = Color3.fromRGB(180, 190, 200)
local sky = Instance.new("Sky")
sky.SkyboxBk = "rbxassetid://0"
sky.SkyboxDn = "rbxassetid://0"
sky.SkyboxFt = "rbxassetid://0"
sky.SkyboxLf = "rbxassetid://0"
sky.SkyboxRt = "rbxassetid://0"
sky.SkyboxUp = "rbxassetid://0"
sky.Parent = Lighting
DataStoreOptions¶
Nova's GetDataStore takes only (name, scope). Drop the options table and use
the supported methods:
-- Before
local store = DataStoreService:GetDataStore("Data", "scope", options)
-- After
local store = DataStoreService:GetDataStore("Data", "scope")
See Data stores.
buffer and vector¶
buffer and vector are not registered. Use strings/tables for byte data and
Vector3/Vector2 for vector math. If you need binary packing, do it in small
chunks with string.byte/string.char, or keep the data server-side.
ViewportFrame¶
There is no ViewportFrame. Use a world camera or a pre-rendered thumbnail. For
an inventory preview, render the item once in Studio and use the image.