Asset
Load images, sounds, shaders, models, fonts, and arbitrary files. Three
entry points: GetAsset (project bundle),
ImportAsset (anywhere on disk), and
FromString / FromPixels (memory).
local Asset = import("Asset")
local logo = Asset.GetAsset("Image", "ui.logo") -- assets/ui/logo.png
local mesh = Asset.GetAsset("Model", "props.crate") -- assets/props/crate.fbx
local snd = Asset.GetAsset("Sound", "sfx.explode")
Asset kinds
| Kind | Returns | Extensions probed |
|---|---|---|
"Image" | ImageAsset | .png .jpg .jpeg .bmp .gif .webp |
"Sound" | SoundData | .ogg .mp3 .wav .flac |
"Shader" | ShaderAsset | .shader .glsl .wgsl .hlsl .vert .metal |
"Fragment" | FragmentAsset | .frag .fragment .fs .glslf |
"Model" | ModelAsset | .obj .fbx |
"Font" | FontAsset | .ttf .otf |
"File" | string | any, raw UTF-8 contents |
Path forms
"foo.bar.baz" |
Same package as the caller. Dots are folder separators
, resolves to assets/foo/bar/baz.
|
"@PkgId/foo/bar" |
Cross-package lookup. Reads from another loaded .managed package by id. |
"foo/bar.png" |
Literal extension. Loader uses it directly instead of probing the kind's extension list. |
Asset API
Load from your project's assets/ folder (during
ruzit test) or the bundled .managed
package (in a built game). Cached, the same path
returns the same instance on subsequent calls.
Build an asset from a raw byte string. label is
cosmetic (shown by :Source()). Useful for assets
fetched over the network or assembled procedurally.
Load from any disk path, mod folders, Workshop items,
user profile. Pass "Auto" or "" as
the kind to infer it from the file extension.
Build an ImageAsset from raw RGBA8 pixels. rgba
must be exactly width * height * 4 bytes.
Bulk loading and unloading
Three companion APIs for managing groups of assets together: warm a
cache before a level, block-load a manifest at a checkpoint, and
forcefully unload everything when leaving the level. All three
accept entries in "Kind:path" form (e.g.
"Image:ui.logo", "Sound:sfx.boom",
"Model:props.crate") so a single list can mix kinds.
The cache is process-wide: keyed by resolved
target package + kind + path, not by which script made the call.
Any script can preload, look up, or drop any asset, and the same
userdata instance is shared across the whole game. Same-named bare
paths in different packages stay separate (the resolved package id
is part of the key), and "@PkgId/..." paths always
name the explicit package.
Kick off background reads (and zstd decompression for
bundled packages) on a worker thread. Final parsing into the
typed userdata happens on the next heart tick on the main
thread, the returned signal fires once when every entry is
ready. Cached after that, future
Asset.GetAsset calls hit the cache without
disk / package access.
Synchronous version. Blocks the caller until every asset
is decoded, then returns a dictionary keyed by the original
"Kind:path" string. Same cache-population
effect as PreloadAsync, you also get the
values directly without a follow-up GetAsset.
Forcefully unload a previously-loaded asset and destroy
every live consumer of it. Pass the same
"Kind:path" string used to load it. After
Drop the next GetAsset for that
path genuinely re-decrypts and re-decompresses from disk.
Load a video plus its companion audio in one call.
Always returns two values, the second is nil
for sources that don't carry audio (animated GIFs).
Hand the SoundData to SFX.LoadSound if
you want it to play. Supported formats: .gif
(no audio) and .ruzitvid (Ruzit's bundled-
video format with frames + audio). See
Video for usage.
-- Background-warm a level's assets while the loading screen plays.
local ready = Asset.PreloadAsync({
"Image:levels.forest.skybox",
"Image:levels.forest.terrain",
"Sound:music.forest_theme",
"Model:levels.forest.cabin",
})
ready:Connect(function() loading_screen:Hide() end)
-- Or block-load at a checkpoint.
local bag = Asset.BulkLoad({
"Image:ui.menu_bg",
"Font:ui.title",
})
local title = GUI.Basic.Font(bag["Font:ui.title"])
-- Leaving the forest level: free everything.
Asset.Drop("Image:levels.forest.skybox")
Asset.Drop("Image:levels.forest.terrain")
Asset.Drop("Sound:music.forest_theme")
Asset.Drop("Model:levels.forest.cabin")
Image, every BasePart whose.Textureis this image and every GUI Primitive backed by it is destroyed (theirChanged/Destroyedsignals fire).Sound, every Sound loaded from this SoundData is stopped, marked dead, and any:Play()afterward errors.Model, every BasePart backed by this ModelAsset is destroyed.Font, every Text Primitive using this font is destroyed.Shader/Fragment, nothing is destroyed. The shader is removed from every BasePart / Primitive's attach list (those objects keep rendering, just without this shader's contribution), and the skybox / post-effect slot is cleared if it was bound there. Useful for hot-swapping post effects between scenes without destroying the scene geometry.
:Free() — per-instance free
Every asset userdata (ImageAsset, SoundData,
ShaderAsset, FragmentAsset,
ModelAsset, FontAsset) exposes a
:Free() method. It does the same engine-side work as
Asset.Drop, but you call it on the handle directly,
no path required:
Asset.Drop("Image:foo.bar"), free by path. Handy when you only know the path.asset:Free(), free this exact instance. Works on procedurally-generated assets too (Asset.FromString,Asset.FromPixels,Renderable.SimplifyMesh), which never had a path in the first place.
Effects are identical to Drop: every BasePart /
Primitive / Sound currently using the asset is detached or
destroyed, and the asset is removed from the cache. The only
difference is that :Free() doesn't assume the asset
came from disk, so the local Lua variable in your hand keeps
pointing at the same userdata. The engine just stops tracking
it. Memory is reclaimed once nothing in Lua holds it either.
-- Generate a procedural texture, use it, then free engine refs.
local noise = Asset.FromPixels(256, 256, my_rgba)
part.Texture = noise
-- ...later...
noise:Free() -- detaches part.Texture, drops cache entry
-- Same effect on a file-loaded asset, no path string needed.
local mesh = Asset.GetAsset("Model", "levels.forest.cabin")
mesh:Free()
ImageAsset
Origin string, file path or "<pixels:WxH>" for in-memory variants.
Raw RGBA8 buffer (Width * Height * 4 bytes).
Useful for inspection or hand-rolled image processing.
See :Free(). Detaches every Part / Primitive textured with this image and drops it from the cache.
ModelAsset
AABB of the raw vertex positions in mesh-local
space — BEFORE part.Size /
part.CFrame is applied. Each field is a
Vector. Exported model units vary wildly
(some FBX exports are ±1, some are
±100), so use this to size
DistortionBox
volumes or DynMesh:Weld
anchors against the mesh's actual extents instead of
guessing.
local b = model:LocalBounds()
-- Cover the entire mesh with one box, centred where the mesh actually is.
Renderable.DistortionBox(part, CFrame.new(b.Center), b.Size * 1.05)
See :Free(). Destroys every BasePart
spawned from this model (BaseModel /
SimplifyMesh output) and drops it from the
cache.
Models carry geometry only as of 1.2.8 — animation clips are
loaded separately as an AnimationSet
and bound to parts via
part:GetAnimatedTrack(animation).
AnimationSetAsset
A bundle of vertex-deformation animation clips extracted from an
FBX file. Models and animations live in separate assets so one FBX
of clips (e.g. walk, run, jump)
can drive any number of meshes that match the imported skeleton's
vertex layout. Load with
Asset.GetAsset("AnimationSet", "Animations/Walk.fbx").
local Asset = import("Asset")
local Renderable = import("Renderable")
local mesh = Asset.GetAsset("Model", "chars.hero")
local anims = Asset.GetAsset("AnimationSet", "chars.hero_anims")
print(anims:ListAnimations()) -- { "Idle", "Walk", "Run", ... }
local walk = anims:GetAnimation("Walk")
local hero = Renderable.BaseModel(mesh)
local track = hero:GetAnimatedTrack(walk)
track.Looped = true
track:Play()
-- Bind the same Animation to a second part -- they tick independently.
local sidekick = Renderable.BaseModel(mesh)
sidekick:GetAnimatedTrack(walk):Play()
Names of every clip in the set, in FBX import order.
Pull a single named animation. Errors if the name doesn't
match. The returned Animation is shareable
— pass it to part:GetAnimatedTrack(animation)
on as many parts as you want; each call builds a fresh
playback track using the same source keyframes.
See :Free(). Drops the set from the
asset cache. Existing Animation handles
(which hold their own Arc on the keyframes)
keep working until they themselves are released.
Animation
A single named clip pulled out of an
AnimationSetAsset. Cheap to clone, safe
to share across parts. Bind with
part:GetAnimatedTrack(animation) to get a playable
AnimationTrack.
Length of the clip in seconds.
SoundData
Encoded byte length (NOT decoded sample count). Decoding happens on SFX.LoadSound.
See :Free(). Stops every Sound
backed by this data; subsequent :Play()
errors.
FontAsset
See :Free(). Drops every Text Primitive using this font and clears the cache.
Pass to GUI.Basic.Font(font) to create a Text primitive.
Glyphs are rasterised at runtime sized by the primitive's
.TextSize.
ShaderAsset
Opaque handle wrapping a vertex-stage WGSL program. Load with
Asset.GetAsset("Shader", path) and pass it to a shader-
accepting method like
part:AttachShader(shader) or
GUI.SetSkybox(shader).
Most projects never override the vertex stage; the engine's default handles transform / projection / interpolation. See Shaders for the WGSL contract.
See :Free(). Detaches this shader from every Part / Primitive / scene hook and drops it from the cache. Detach-only, the consumers keep rendering with the engine default.
FragmentAsset
Opaque handle wrapping a fragment-stage WGSL program. Loaded via
Asset.GetAsset("Fragment", path) and passed to
part:AttachShader(asset),
primitive:AttachShader(asset),
GUI.SetSkybox(asset), or
GUI.SetPostEffect(asset).
See :Free(). Detach-only, like
ShaderAsset:Free(); clears any skybox /
post-effect slot it was bound to as well.
This is where ~all custom shader work lives, surface lighting, dissolve effects, stylised lighting, scrolling textures, post effects. See Shaders for the prelude, parameter declarations, and helpers.