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.gitOr clone directly into your Assets folder:
cd YourProject/Assets
git clone https://github.com/Singtaa/OneJS.gitOr just ask your AI coding agent to install OneJS for you.
Set Up a Project#
- Create a new GameObject and add the JSRunner component
- Click Initialize Project in the inspector
- 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 markerWrite 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:
- Add the JSPad component to a GameObject
- Write TSX directly in the inspector
- 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?#
| V2 | V3 |
|---|---|
| ScriptEngine + Runner + Bundler | Single JSRunner component |
Project-root App/ folder | Scene Folder convention |
| Shared PanelSettings | Per-instance (auto-created) |
npm run setup to initialize | "Initialize Project" button |
| Preact | React 19 |
| Separate Tailwind watcher | Built-in (import "onejs:tailwind") |
Next Steps#
- C# Interop: calling C# from JavaScript
- Styling: USS, CSS Modules, and Tailwind
- Components: all available UI components