Quick Start#

Install OneJS and build a working UI component in your Unity project. The whole journey, from install to hot reload, in under a minute:

Install#

Open Window > Package Manager, click + > Add package from git URL, and enter:

https://github.com/Singtaa/OneJS.git

Or clone directly into your Assets folder:

cd YourProject/Assets
git clone https://github.com/Singtaa/OneJS.git

Or just ask your AI coding agent to install OneJS for you.

Set Up a Project#

  1. Create a new GameObject and add the JSRunner component
  2. Click Initialize Project in the inspector
  3. Enter Play Mode

JSRunner handles everything: installs dependencies, builds the bundle, and starts a file watcher. The first run takes a moment while packages install; once it's done, the starter UI appears in the Game view.

Your project folder lives next to your scene, named after the GameObject:

Assets/Scenes/YourScene/
└── App/                      # Named after your GameObject
    ├── ~/                    # Working directory (~ = Unity ignores)
    │   ├── index.tsx         # Entry point
    │   ├── package.json
    │   ├── tsconfig.json
    │   └── esbuild.config.mjs
    ├── app.js.txt            # Built bundle
    └── PanelSettings.asset   # Project marker

Write a Component#

Open ~/index.tsx and replace its contents:

import { render, View, Label, Button } from "onejs-react"
import { useState } from "react"

function Counter() {
    const [count, setCount] = useState(0)

    return (
        <View style={{ padding: 20, alignItems: "flex-start" }}>
            <Label text={`Count: ${count}`} style={{ fontSize: 24, marginBottom: 10 }} />
            <Button text="Increment" onClick={() => setCount(c => c + 1)} />
        </View>
    )
}

render(<Counter />, __root)

Save the file. JSRunner automatically watches for changes, rebuilds, and hot-reloads. The UI updates in the Game view without leaving the editor. This is standard React: useState, JSX, component composition. The difference is that <Button> creates a real UnityEngine.UIElements.Button rendered on the GPU.

Calling C##

Access any C# API through the CS global:

function DebugButton() {
    const handleClick = () => {
        CS.UnityEngine.Debug.Log("Hello from JS!")
        console.log(`Time: ${CS.UnityEngine.Time.time}`)
    }

    return <Button text="Debug" onClick={handleClick} />
}

This works with all Unity APIs, your own game code, and third-party libraries.

The Ball Drop#

The demo from the video above: one file that sets up a scene and wires a button to it. UI and game code, side by side.

import { render, Button } from "onejs-react"
import {
    Camera, Collider, Color, GameObject, MeshRenderer,
    Physics, PhysicsMaterial, PrimitiveType, Random, Rigidbody, Vector3
} from "UnityEngine"

Physics.gravity = new Vector3(0, -20, 0)
const bouncy = new PhysicsMaterial()
bouncy.bounciness = 0.8

const ground = GameObject.CreatePrimitive(PrimitiveType.Plane)
ground.GetComponent(MeshRenderer).material.color = Color.yellow
ground.GetComponent(Collider).material = bouncy

Camera.main.transform.position = new Vector3(9, 4, -8)
Camera.main.transform.LookAt(new Vector3(0, 1, 0))

function dropBall() {
    const ball = GameObject.CreatePrimitive(PrimitiveType.Sphere)
    ball.transform.position = new Vector3(Random.Range(-2, 2), 6, 0)
    ball.GetComponent(MeshRenderer).material.color = Color.red
    ball.GetComponent(Collider).material = bouncy
    ball.AddComponent(Rigidbody)
}

render(<Button text="Drop Ball" onClick={dropBall} />, __root)

Two notes. The UnityEngine import works because the built-in esbuild plugin turns ES6 imports of C# namespaces into CS.* references at build time. And physics only steps in Play Mode, so enter Play Mode before clicking the button.

Quick Prototyping with JSPad#

For quick experiments without file setup, use JSPad instead:

  1. Add the JSPad component to a GameObject
  2. Write TSX directly in the inspector
  3. Click Build, then enter Play Mode

JSPad is self-contained. The bundle lives in the scene file. Great for learning and quick tests. Use JSRunner for real projects.

Coming from V2?#

V2V3
ScriptEngine + Runner + BundlerSingle JSRunner component
Project-root App/ folderScene Folder convention
Shared PanelSettingsPer-instance (auto-created)
npm run setup to initialize"Initialize Project" button
PreactReact 19
Separate Tailwind watcherBuilt-in (import "onejs:tailwind")

Next Steps#