UnivisEditor is a node-based visual editor built on top of the Bevy Engine. It follows the philosophy that "everything is a node" - similar to Unreal Engine's Blueprint system or Blender's Node Editor.
The codebase is now organized as a Cargo workspace:
univis_editor_coreunivis_editor_runtimeunivis_editor_uiunivis_editor_persistenceunivis_editor_nodes_builtinunivis_editor_game_editor(optional)univis_editor_app(facade + prelude)
- π¨ Visual Node Editor - Create complex logic by connecting nodes visually
- π Extensible Node System - Easy to create custom nodes with a clean trait-based API
- π Built on Bevy 0.17 - Leverages the latest Bevy ECS architecture
- π¦ World-Space UI - Custom UI system (
univis_ui) for 3D world-space interactions - π Search & Filter - Quickly find nodes with built-in search functionality
- π Auto-scrolling Menu - Context menu with scrolling support for large node libraries
- Rust 1.75 or later
- Bevy 0.17.3 compatible system
Add to your Cargo.toml:
[dependencies]
univis_editor_app = { path = "path/to/univis_editor/crates/univis_editor_app" }
bevy = "0.17.3"use bevy::prelude::*;
use univis_editor_app::prelude::*;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_plugins(NodeGraphPlugin) // Add the editor plugin
.run();
}UnivisEditor uses a trait-based system for defining nodes. Simply implement NodeDefinition:
use univis_editor_core::node_definition::{
NodeDefinition, NodeId, NodeCategory, PortDefinition,
ProcessContext, ProcessResult
};
use univis_editor_core::value::NodeValue;
use bevy::prelude::*;
pub struct MyCustomNode;
impl NodeDefinition for MyCustomNode {
// === Required Methods ===
fn id(&self) -> NodeId {
NodeId::new("custom/my_node")
}
fn display_name(&self) -> &str {
"My Custom Node"
}
fn inputs(&self) -> Vec<PortDefinition> {
vec![
PortDefinition::input_float("Input A")
.with_default(NodeValue::float(0.0)),
PortDefinition::input_float("Input B")
.with_default(NodeValue::float(0.0)),
]
}
fn outputs(&self) -> Vec<PortDefinition> {
vec![PortDefinition::output_float("Result")]
}
fn process(&self, ctx: &mut ProcessContext) -> ProcessResult {
let a = ctx.get_float_or(0, 0.0);
let b = ctx.get_float_or(1, 0.0);
ctx.set_float(0, a + b); // Simple addition
ProcessResult::Success
}
// === Optional Methods ===
fn category(&self) -> NodeCategory {
NodeCategory::new("Custom")
}
fn description(&self) -> Option<&str> {
Some("Adds two numbers together")
}
fn color(&self) -> Color {
Color::srgb(0.3, 0.5, 0.7)
}
fn icon(&self) -> Option<&str> {
Some("+")
}
fn keywords(&self) -> Vec<&str> {
vec!["add", "sum", "plus"]
}
fn menu_order(&self) -> i32 {
1 // Display order in menu
}
}use univis_editor_core::node_registry::NodeRegistry;
fn register_my_nodes(registry: &mut NodeRegistry) {
registry.register(MyCustomNode);
// Add more nodes...
}The ProcessContext provides convenient methods for reading and writing values:
// Reading values
let value = ctx.get_float(0); // Option<f64>
let value = ctx.get_float_or(0, 0.0); // f64 with default
let value = ctx.get_int(0);
let value = ctx.get_bool(0);
let value = ctx.get_vec2(0);
let value = ctx.get_vec3(0);
let value = ctx.get_string(0);
// Writing values
ctx.set_float(0, 42.0);
ctx.set_int(0, 42);
ctx.set_bool(0, true);
ctx.set_vec2(0, Vec2::new(1.0, 2.0));
ctx.set_vec3(0, Vec3::new(1.0, 2.0, 3.0));
ctx.set_string(0, "Hello");PortDefinition::input_float("Value")
.with_description("A float input")
.with_default(NodeValue::float(1.0))
.with_color(Color::srgb(1.0, 0.5, 0.0));
PortDefinition::output_float("Result");
PortDefinition::output_any("Pass Through");| Method | Required | Description |
|---|---|---|
id() |
β | Unique identifier (e.g., "math/add") |
display_name() |
β | Human-readable name |
inputs() |
β | Input port definitions |
outputs() |
β | Output port definitions |
process() |
β | Processing logic |
category() |
β | Node category for menu |
description() |
β | Tooltip/description |
color() |
β | Node header color |
icon() |
β | Unicode icon |
show_in_menu() |
β | Show in context menu (default: true) |
menu_order() |
β | Sort order in menu |
keywords() |
β | Search keywords |
has_custom_body() |
β | Custom UI body |
build_body() |
β | Build custom UI |
| Node | Description |
|---|---|
| Add | Addition of two numbers |
| Subtract | Subtraction |
| Multiply | Multiplication |
| Divide | Division (with zero check) |
| Clamp | Clamp value to range |
| Lerp | Linear interpolation |
| Min / Max | Minimum / Maximum |
| Abs | Absolute value |
| Sin / Cos | Trigonometric functions |
| Node | Description |
|---|---|
| Number | Float value input |
| Integer | Integer value input |
| Boolean | True/false toggle |
| Text | String input |
| Vector2 | 2D vector |
| Vector3 | 3D vector |
| Color | RGBA color |
| Node | Description |
|---|---|
| Compare | Compare two values |
| Branch | If/else conditional |
| And / Or / Not | Boolean operations |
| Node | Description |
|---|---|
| View | Display value in node |
| Watch | Named value display |
| Debug | Print to console |
src/
βββ core/
β βββ node_definition.rs # Node trait and types
β βββ node_registry.rs # Node registration system
β βββ value.rs # Dynamic value types
β βββ menu.rs # Context menu system
β βββ wire.rs # Wire/connection logic
β βββ interaction.rs # User interaction
βββ nodes/
β βββ math.rs # Math nodes
β βββ input.rs # Input nodes
β βββ output.rs # Output nodes
β βββ logic.rs # Logic nodes
βββ data/
β βββ node_spawn.rs # Node spawning system
βββ widgets/
β βββ infinity_grid.rs # Background grid
βββ editor/
β βββ editor.rs # Editor camera & controls
βββ lib.rs # Main plugin
Run the test suite:
# Run all tests
cargo test
# Run specific test file
cargo test --test math_node_tests
# Run with verbose output
cargo test -- --nocapturevalue_tests.rs- NodeValue type testsprocess_context_tests.rs- Context helper testsport_definition_tests.rs- Port builder testsmath_node_tests.rs- Math node functionalityinput_node_tests.rs- Input node testsoutput_node_tests.rs- Output node testslogic_node_tests.rs- Logic node testsintegration_tests.rs- End-to-end tests
βββββββββββββββ βββββββββββββββ βββββββββββββββ
β Input Node ββββββΆβ Math Node ββββββΆβ Output Node β
β (Value) β β (Process) β β (View) β
βββββββββββββββ βββββββββββββββ βββββββββββββββ
β β β
βββββββββββββββββββββ΄ββββββββββββββββββββ
β
Topological Sort
(Kahn's Algorithm)
- Initialize - Set default values for new nodes
- Sort - Topological ordering for correct execution
- Propagate - Transfer values through connections
- Process - Execute each node's logic
- Output - Results available for next frame
Contributions are welcome! Please feel free to submit issues and pull requests.
# Clone the repository
git clone https://github.com/univiseditor/univis_editor
cd univis_editor
# Run tests
cargo test
# Run the example
cargo run --example simple_editorThis project is licensed under the MIT License - see the LICENSE file for details.
- Bevy Engine - The game engine this is built on
- Blender - Inspiration for the node system architecture
- Unreal Engine Blueprints - Design philosophy reference
Built with β€οΈ using Bevy Engine