Stage and Mount#
This is the part of oj that differs from the OneJS model rather than wrapping it. Everything else in the package is a filtered re-export or a thin helper.
mount()#
import { mount, View, Text } from "oj"
function Game() {
return (
<View style={{ flexGrow: 1 }}>
<Text text="Hello" />
</View>
)
}
mount(<Game />)mount(element, options?) renders your tree into the window. Its one option, theme, applies the default control theme before the first render; set it to false if you are supplying your own.
Underneath, mount calls render against a root it sets up itself. That is the whole relationship between the two: render is the primitive, mount is render plus a stage and a runtime. In a Unity project mount starts that runtime itself, which is why the same source runs unchanged whether it came from play.onejs.com or from your own scene folder.
You still need a JSRunner and its PanelSettings in the scene. mount decides what happens inside the panel, not whether there is one.
The stage#
The stage is the window, in logical pixels (one logical pixel is one CSS pixel on the web). There is no fixed design size and no fit mode: a game lays itself out against the window and reflows when it changes, the way a page does. A game that wants a fixed board, such as an arcade screen or a card table, builds one as a fixed-size View with scale set to fit the window.
Reading the layout#
useStage() returns the window size as { width, height } and re-renders when it changes.
import { useStage } from "oj"
function Hud() {
const stage = useStage()
return <Text text={`${stage.width} x ${stage.height}`} />
}Pointer x/y and localX/localY are already in the stage's space, so there is nothing to convert.
The frame loop#
useFrame(callback, deps?) runs a callback once per frame with the delta in seconds.
import { useRef } from "react"
import { useFrame, View } from "oj"
let y = 0
const velocity = 120
function Ball() {
const ref = useRef(null)
useFrame((dt) => {
y += velocity * dt
ref.current.style.top = y
})
return <View ref={ref} />
}deps works like useEffect's, except that leaving it out subscribes once rather than every render. The callback that runs is always the one from the latest render, so it can read current state and props. Drive motion from dt rather than assuming a frame rate, since the same code runs on machines that do not agree about what a frame costs.
useFrame is onejs-react's own, so the same component works in a OneJS app. In a cart it runs on the Play runtime's frames, so it stops when the cart does and sees each frame's input; elsewhere it runs on requestAnimationFrame. A drawing that moves every frame is useDrawing(ref, draw, "frame"), on the same clock.
Write to element styles or refs from a frame callback, not to React state. Sixty state updates a second is sixty renders a second, and the reason useFrame exists at all is to give you a place to move things without paying for that.