Build your first screen
You’ll turn the starter’s message and button into a click counter. We’ll add state and an action in C#, display them in the UI, then make the same screen work in a browser without Unity.
Before you start
Open the project from Install and run Loom and make
sure Play mode is stopped. We’ll edit Assets/Scripts/UIBridge.cs and the UI
files in the UI/ folder beside Assets/.
Define a bridge
The screen needs a title and a count to display. We’ll store both on the bridge
and add a Click() action that increases the count whenever the user presses
the button.
Replace the contents of Assets/Scripts/UIBridge.cs with:
using Loom;
[Bridge]
public partial class UIBridge : LoomBridgeBase
{
public string Title { get; set; } = "Main Menu";
public int Clicks { get; set; }
[BridgeAction]
public void Click()
{
Clicks += 1;
}
}
Like Message in the starter, Title and Clicks become values the UI can
read. The count starts at zero. When the UI calls Click(), the change to Clicks is sent back to the screen.
Save the file and wait for Unity to compile. Loom updates the declarations in UI/generated/ automatically. In TypeScript, the names start with a lowercase
letter: title, clicks, and click.
Render the screen
Now let’s show those values. Replace the contents of UI/src/App.tsx with:
import { useBridge } from '@loomgui/bridge';
export default function MainMenu() {
const bridge = useBridge();
return (
<main class="loom-app">
<h1>{bridge.title}</h1>
<button onClick={() => void bridge.click()}>
Clicked {bridge.clicks} times
</button>
</main>
);
}
useBridge() gives the component access to the bridge provided by the starter
in UI/src/index.tsx. The expressions in braces read its current values, and
Solid updates the heading and button text when those values change. Keep the
reads as bridge.title and bridge.clicks inside the JSX so Solid can track them.
Clicking the button calls bridge.click(). That action runs Click() in C#;
the resulting change to Clicks then updates the button text.
Update the browser mocks
The screen now expects title, clicks, and click, so we need to replace the
starter’s message and ping configuration too. Open UI/src/bridge.ts and
replace its contents with:
import { bootBridge } from '@loomgui/bridge';
export const bridge = bootBridge({
initial: { title: 'Main Menu', clicks: 0 },
mockActions: ({ state }) => ({
click: () => { state.clicks += 1; },
}),
});
The initial values give the UI something to display at startup. Once it
connects to Unity, the game supplies the values. When you preview the UI on
its own, it starts with these values and uses the mock click action to
increase the count instead.
Keep UI/src/index.tsx as it is. It already passes this bridge to BridgeProvider and renders the app inside it.
Verify the screen
From a terminal in UI/, run:
npm run typecheck
This checks that the UI and mocks use fields and actions from the generated
bridge. If it reports message or ping, check that both UI files have been
updated. If it doesn’t recognize title, clicks, or click, check the Unity
Console for C# compilation errors.
Enter Play in the scene you configured during installation. You’ll see Main Menu and Clicked 0 times. Click the button twice: the count becomes 1, then 2. Unity is running the action and supplying the new count.
Now stop Play. In the terminal, start the browser preview:
npm run dev
Open the URL printed in the terminal. The count starts at 0 again, and the button increases it using the mock action. You’re using the same screen with a separate set of values, so these clicks don’t change the game.
Keep going
Continue with Development workflow to move between the Game view and browser while you work. Then Build a pause menu connects a larger UI to game time, gameplay input, and keyboard and gamepad navigation.