Files
Sprites, sounds, fonts and covers
A sketch can ship images, audio and fonts alongside its source. One function resolves a name to wherever those files ended up.
import { assetUrl, useTexture, audio } from "oj"
const glow = useTexture("glow.png") // a texture, or null while loading
const pop = await audio.load("pop.wav")
<Image src={assetUrl("logo.png")} />assetUrl(name) |
The address of one of your files, as a string |
loadTexture(name) |
Loads an image and resolves to a texture |
useTexture(name) |
The hook form. null until it arrives |
loadSheet(name), useFlipbook(ref, name) |
A sprite sheet, and playing one on an element |
Use the name as it appears in the sidebar
"glow.png" for a file at the top, "sprites/hero.png" for one in a folder. Never "assets/glow.png", and never an absolute path.
That is the whole rule, and it is what lets the same source run in two places that store files differently:
| Where | assetUrl("sprites/hero.png") resolves to |
|---|---|
| On play.onejs.com | /assets/sprites/hero.png on the sketch's own origin |
| After an eject | The project's own assets folder, keeping the same folders |
Folders are part of the name rather than something you create: putting a file at sprites/hero.png is what makes sprites exist, and it is gone when the last file leaves it.
A leading ./ is accepted and stripped, and so is a leading assets/. That is why assets is the one folder name your files may not use at the top level. Anything that already resolves, like a full URL, is handed back untouched, so passing a remote image through assetUrl is harmless.
loadTexture, useTexture and audio.load all take the bare name too.
What you can ship
Images (png, jpg, webp, gif), audio (ogg, wav, mp3) and fonts (ttf, otf, woff, woff2).
| One file | 16 MB |
| All your files | 64 MB |
| How many | 200 |
Files are stored apart from your source and served from the sketch's own origin. A save re-uploads your code and leaves the art alone, which is what you want given art changes far less often than code.
A file you add is uploaded straight away, so pressing Run loads it immediately. Deleting one removes it straight away too. That is the one place files differ from source: your code lives in the editor until it saves, and your files are stored the moment you add them.
Loading takes a frame or two
useTexture returns null until the file arrives, so render a placeholder rather than assuming:
function Sparks() {
const ref = useRef(null)
const glow = useTexture("glow.png")
useParticles(ref, {
emitters: [{ rate: 40, texture: glow, glow: 1 }],
})
return <View ref={ref} style={{ flexGrow: 1 }} />
}Textures are cached by URL, so a second component asking for the same file does not pay for the bytes again, and a component that unmounts mid-load does not cancel it.
Covers
Your sketch's card in the catalog is a picture. There are three ways to decide it, and the first is doing nothing.
| You do | The card shows |
|---|---|
| Nothing | A graphic the site draws |
Put cover.png or cover.jpg at the top of your sketch |
That picture |
Put pictures in .oj/covers/ |
The first one at rest, the rest as you hover it |
Doing nothing is a supported choice, not a gap. Most sketches want the second row.
If you have both, .oj/covers/ wins and the publish says your root picture was ignored.
The Cover button
Press Cover, next to the Private and Public setting, and whatever the preview is showing lands in your files as cover.png. That is the whole workflow: play your sketch until it looks right, then press the button.
It never overwrites. A second press writes cover_1.png, a third cover_2.png. Only cover.png is the card, so the numbered ones are drafts sitting in your files: keep the one you like by renaming it over cover.png, and delete the rest.
The picture is the canvas at its current size, as a png, so it is as large as your preview pane. Crop or shrink it yourself if you want it smaller. It lands in your tree like any file you uploaded, goes out with the next save, and counts against your storage.
A set to cycle on hover
Pictures for the hover preview go in .oj/covers/, and arranging them is yours to do. They are ordered by file name, so zero-pad them:
index.tsx
oj.json
.oj/
covers/
01.jpg
02.jpg
03.jpgThe first is what rests on the card and the rest cycle as a visitor hovers it.
| How many | 8 |
| One picture | 2 MB |
| All of them together | 6 MB |
Past those limits nothing is refused; the extra is ignored and the publish says so. Read what comes back after a save, because that note is the only sign a ninth picture did nothing.
A file in there that is not a png or jpg is ignored, and the publish says that too. A webp dropped in the covers folder shows nothing and changes nothing. They count against the same 64 MB your other files do.
Embedding
A published sketch can go on another page. Press the embed button, the angle brackets beside Fork, and copy the tag:
<iframe src="https://play.onejs.com/g/abc123/embed" title="Tuner on OJPlay"
width="600" height="760"
style="width:100%;max-width:600px;aspect-ratio:600/760;height:auto;border:0;border-radius:6px"
allow="fullscreen; gamepad; clipboard-write" loading="lazy"></iframe>Three things about it are worth keeping:
- The allow list is not decoration. A permission only reaches a sketch if every frame on the way delegates it, so a tag without it gives the sketch no fullscreen and no controller, and nothing reports why.
- The size is carried twice on purpose. The attributes are what a page that strips inline styles keeps; the style is what a page that keeps it uses, shrinking the frame to its column while holding the stage's aspect.
- An embed waits to be asked, because every reader who scrolled past would otherwise download the runtime. Add
?autoplay=1when the sketch is the point of the page.
Pasting a sketch's link on its own line is enough on sites that support oEmbed. A private sketch cannot be embedded; its embed address answers 404 to everyone, its owner included.
After an eject
Your files come down with the sketch, under the same assets/ folder they were served from and in the same folders you put them in, so the source that ran on the site runs unchanged.
.oj/ does not come with it. It holds how your sketch looks in the catalog, which a Unity project has no use for. A git clone keeps it, because a clone is the repository.
This is the reason to write the name the sidebar shows you: it is the one form that means the same thing on both sides.