bevyengine / bevyengine/bevy

Modular, Reactive Gizmos

Open
#9,498 7 comments 0 reactions 0 assignees View on GitHub
A-Gizmos C-Feature
Dominant language
Rust
Stars
48.2k
Forks
4.8k
Avg merge
3d 22h
Merged PRs (30d)
161

Description

**Caveat**: the ideas presented here are fairly wild / brainstormy and I don't expect them to be accepted or implemented - but I do want to write them down here because they may inspire discussion.

I'm in the process of porting my game engine and editor from three.js to Bevy. Three.js has about a dozen Gizmos (which are called "Helpers" in three.js terminology), but I don't find them to be particularly useful for my needs. Instead, I've created my own Gizmo framework which has gone through several iterations. I don't use these in the game per se, but they are very helpful in the editor for things like:

* Terrain editing
* Physics colliders
* Musical and sound effects cues
* NPC pathfinding waypoints
* Navigation meshes
* Portal apertures
* Water current direction
* Use markers (named markers that tell the actor where to stand when interacting with a scenery element).

...and many others. There are about two dozen custom gizmo types that I use, all of which are based on this common framework.

Note: in the following sections, I'm going to talk about JSX. However, I am **not** proposing that JSX support be added to Bevy.

A typical gizmo in my three.js system looks like this:

```tsx
/** Component which displays aspects of fixtures which are normally invisible. */
export const FixtureOutlinesOverlay: VoidComponent = props => {
const toolState = useToolState();
const fixtures = createMemo(() => props.structure?.instances.list().filter(isFixture) ?? []);
const loader = new TextureLoader();
const mapPin = loader.load(mapPinImg);

const spriteColor = (config: IRegionAppearance, selected: boolean) =>
colord(config.color ?? '#ffdd88')
.darken(selected ? -0.2 : 0.2)
.toHex() as ColorRepresentation;

return (


{fix => (
<>

{config => (
<>

fix.position}
scale={[0.25, 0.4, 0.25]}
color={spriteColor(config, fix === toolState.selectedInstance)}
center={mapPinCenter}
opacity={1}
/>

)}


{config => (
<>

fix.position}
radius={fix.ensureProperties().radius}
color={config.color as ColorRepresentation}
opacity={fix === toolState.selectedInstance ? 1 : 0.2}
/>
fix.position}
scale={[0.25, 0.4, 0.25]}
color={spriteColor(config, fix === toolState.selectedInstance)}
center={mapPinCenter}
opacity={1}
/>

)}

().waypoints.length > 1}>
()
.waypoints.map(wp => wp.position)}
dashLength={0.2}
lineWidth={0.09}
opacity={fix === toolState.selectedInstance ? 0.7 : 0.3}
occlusionFactor={0.6}
/>



)}


);
};
```

And here's a screenshot of what that looks like:

![use-marks](https://github.com/bevyengine/bevy/assets/1166135/d1256347-c6fd-47c7-88ce-ef9a43e8610f)

Things I want to point out about this code:

* Gizmos are composed of multiple translucent primitives.
* Primitives can include **meshes**, **lines**, **sprites** and **floating text**.
* Gizmos can also contain reusable "components" such as FlatCircle and DashedPolyline, which are themselves Gizmos.
* Most primitives are 2D meshes like circles and rectangles.
* The attributes of the primitives are reactive: that is, when the game state changes, the Gizmo vertex buffers and material properties are automatically re-calculated. So for example in the above code, the line `opacity={fix === toolState.selectedInstance ? 1 : 0.2}` means that the opacity of the selected fixture is higher than fixtures that are not selected.
* In my TypeScript code, I use JSX syntax to declaratively define Gizmos. The system is similar in concept to react-three-fiber, however this system is based on Solid.js rather than React.
* Iteration and Conditions are supported via the standard Solid.js components `For` and `Show`. So if I want to display a bounding box for every physics collider, it's a simple matter to iterate over the list of colliders.

For implementing 2D primitives such as circles and rectangles, I have a 2D drawing library which generates meshes. So for example, the `DashedPolyLine` component calls `drawShapes.strokePolyLine()`, which fills in a vertex buffer and index buffer. This library also accepts a transform, so for example the Gizmo that draws portal apertures can align the generated rectangle with the portal.

Now, I know that Bevy is not a reactive framework like Leptos. However, it does have the ability to trigger behaviors on change events. So something like what I am describing is not entirely unfeasible, although it would no doubt look very different than the code that I have shown.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.