The goal of this document is to provide the maximum amount of important content in the minimum amount of words. It can be useful to give this to your language model to give it an up to date source of information about Bevy.
The full text of this document in markdown can be found on the Bevy starter
Bevy is an archetype Entity-Component-System (ECS) game engine built in Rust. It emphasizes modularity, performance, and ease of use.
Entity and Components
An Entity
on its own holds no data or behavior. The actual Entity
is just an identifier to find associated components where the real data is stored.
Each Entity
can only have a single Component
of each type. These components can be added and removed dynamically over the course of the entity's lifetime. Everything is stored inside a World
and everything is managed by the App
.
A good mental model to use is that entities represent a row in an in-memory database, while components are our columns.
- Entities An identifier for a row
- Components A column in a row
- Systems All the behavior
We define components by deriving the Component
trait:
#[derive(Component)]
struct Player;
#[derive(Component)]
enum Ship {
Destroyer,
Cruiser,
Battleship,
}
#[derive(Component)]
struct Health(f32);
#[derive(Component)]
#[component(on_add = on_position_added)]
struct Position {
x: i32,
y: i32,
}
Components have 5 different life-cycle hooks we can use to handle side effects that need to happen:
#[component(on_add = on_add_function)]
#[component(on_insert = on_insert_function)]
#[component(on_replace = on_replace_function)]
#[component(on_remove = on_remove_function)]
#[component(on_despawn = on_despawn_function)]
Components can also be required by other components
#[derive(Component)]
#[require(Position, Ship)]
struct Player;
fn spawn_player_with_required_components(
mut commands: Commands
) {
commands.spawn(Player);
}
When a component is spawned, if it has any required components, it will automatically add them unless we override them. The only requirement is that each required component implements the Default
trait.
All these required calls are recursive. If a component you require has required components, they will also be added.
A Resource
is a special kind of component that has no Entity
. They have more convenient accessors for systems since there is only ever one of them.
#[derive(Resource)]
struct Score(usize);
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.init_resource::<Score>()
.run();
}
Systems
Systems are where we trigger side effects that change our game's state.
In Bevy, systems are simple rust functions with one rule: They can only have parameters that implement SystemParam
.
fn spawn_player(mut commands: Commands) {
// Spawns a single entity with multiple components
commands.spawn((
Player,
Ship::Destroyer,
Health(100.0),
Position { x: 1, y: 2 }
));
}
Commands
are what we use to change the state of our World
in a way that is more performant than letting each system mutate the world directly.
When you use the system parameter Commands
you are enqueuing your commands to the CommandQueue
which runs when we transition to the next Schedule
.
Apps
Everything is coordinated through an App
which schedules our systems to run at certain points in the game's loop:
use bevy::prelude::*;
fn main() {
App::new()
.add_systems(Startup, setup_everything)
.add_systems(Update, process_input)
.add_systems(FixedUpdate, move_player)
.run();
}
You will mostly be adding your logic to the three main schedule labels:
Update
runs once every loopFixedUpdate
runs once every fixed amount of timeStartup
runs once at startup
Additionally there are other built-in schedule labels for more specific use:
PreStartup
Startup
PostStartup
First
PreUpdate
StateTransition
RunFixedUpdateLoop
which runsFixedUpdate
conditionallyUpdate
PostUpdate
Last
These types are each a ScheduleLabel
. Labels are used to identify a Schedule
which contains the metadata and executor needed to run them under certain conditions.
Bevy will try and run all systems in parallel as long as there are no mutable data access conflicts. Archetypes are used as a performance optimization for this process.
An App
can be given a state enum to manage different modes of operation:
#[derive(Debug, Clone, Eq, PartialEq, Hash, Default, States)]
enum AppState {
#[default]
MainMenu,
InGame,
Paused,
}
fn main() {
App::new()
// Add our state to our app definition
.init_state::<AppState>()
// We can add systems to trigger during transitions
.add_systems(OnEnter(AppState::MainMenu), spawn_menu)
// Or we can use run conditions
.add_systems(Update, play_game.run_if(in_state(AppState::InGame)))
.run();
}
If we wanted to create explicit transitions we could implement the logic on our state:
impl AppState {
fn next(&self) -> Self {
match *self {
AppState::MainMenu => AppState::InGame,
AppState::InGame => AppState::Paused,
AppState::Paused => AppState::InGame,
}
}
}
Plugins
Almost every app will include the DefaultPlugins
plugin which groups together all the default functionality needed for a game.
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.run();
}
DefaultPlugins
includes the following
Plugin | Description |
---|---|
DiagnosticsPlugin | Adds core diagnostics |
DlssInitPlugin | Initializes DLSS support if available |
FrameCountPlugin | Adds frame counting functionality |
HierarchyPlugin | Handles Parent and Children components |
InputPlugin | Adds keyboard and mouse input |
PanicHandlerPlugin | Adds sensible panic handling |
ScheduleRunnerPlugin | Configures an App to run its Schedule according to a given RunMode |
TaskPoolPlugin | Setup of default task pools for multithreading |
TimePlugin | Adds time functionality |
TransformPlugin | Handles Transform components |
Then additionally, depending on the features you enable, it will include:
Plugin | Feature | Description |
---|---|---|
AccessibilityPlugin | bevy_window | Adds non-GUI accessibility functionality |
AnimationPlugin | bevy_animation | Adds animation support |
AntiAliasPlugin | bevy_anti_alias | Adds multi-sample anti-aliasing (MSAA) |
AssetPlugin | bevy_asset | Adds asset server and resources to load assets |
AudioPlugin | bevy_audio | Adds support for using sound assets |
DefaultPickingPlugins | bevy_picking | Adds picking functionality |
DevToolsPlugin | bevy_dev_tools | Enables developer tools in an App |
CameraPlugin | bevy_camera | Adds 2D and 3D camera components and systems |
CiTestingPlugin | bevy_ci_testing | Helps instrument continuous integration |
CorePipelinePlugin | bevy_core_pipeline | The core rendering pipeline |
GltfPlugin | bevy_gltf | Adds support for loading gltf models |
GilrsPlugin | bevy_gilrs | Adds support for gamepad inputs |
GizmoPlugin | bevy_gizmos | Provides an immediate mode drawing api for visual debugging |
HotPatchPlugin | hotpatching | Enables hot-patching of assets |
ImagePlugin | bevy_render | Adds the Image asset and prepares them to render on your GPU |
LightPlugin | bevy_light | Adds light components and systems |
LogPlugin | bevy_log | Adds logging to apps |
MeshPlugin | bevy_mesh | Adds the Mesh asset and prepares them to render on the GPU |
PbrPlugin | bevy_pbr | Adds physical based rendering with StandardMaterial etc |
PostProcessingPlugin | bevy_post_process | Adds post processing effects |
PipelinedRenderingPlugin | bevy_render | Adds pipelined rendering |
RenderPlugin | bevy_render | Sets up rendering backend powered by wgpu crate |
ScenePlugin | bevy_scene | Loading and saving collections of entities and components to files |
SpritePlugin | bevy_sprite | Handling of sprites (images on our entities) |
StatesPlugin | bevy_state | Adds state management for Apps |
TerminalCtrlCHandlerPlugin | std | Handles Ctrl-C signals in terminal applications |
TextPlugin | bevy_text | Supports loading fonts and rendering text |
UiPlugin | bevy_ui | Adds support for UI layouts (flex, grid, etc) |
UiRenderPlugin | bevy_ui_render | Adds support for sending UI nodes to renderer |
WindowPlugin | bevy_window | Provides an interface to create and manage Window components |
WinitPlugin | bevy_winit | Interface to create operating system windows (to actually display our game) |
Plugins are a way to group related functionality together. They receive a mutable reference to the App
and can add systems, resources, and other plugins. Plugins are run in the order they are added to the App
.
fn plugin(app: &mut App) {
app.add_system(some_plugin_system);
}
fn main() {
App::new().add_plugins(plugin);
}
If we need to manage the life-cycle of a plugin we can implement the Plugin
trait and hook into it.
pub struct CameraPlugin;
impl Plugin for CameraPlugin {
fn cleanup(&self, _app: &App) -> bool {
info!("Time to clean up")
true
}
fn build(&self, app: &mut App) {
app.add_systems(Startup, initialize_camera);
}
}
fn initialize_camera(mut commands: Commands) {
commands.spawn(Camera2d);
}
Querying
To access the components of an entity inside our systems we can use the Query<D, F>
system parameter:
fn fetch_players(query: Query<&Player>) {
for player in &query {
info!("Player: {:?}", player);
}
}
The Query
system parameter lets us specify the data we want from each entity using the two generic parameters:
// ------- the `QueryData`
// | ---- the `QueryFilter`
// v v
Query<D, F>
// --------- Give us read-only access to all the `Transform` components
// | ---- Which have a `Player` component on the same entity
// v v
Query<&Ball, With<Player>>
// --- NOTE: Each parameter can be a tuple as well
// |
// v
Query<&mut Transform, (With<Player>, With<Living>)>
When one of the generic parameters is a tuple then all the types in that tuple must be satisfied by that query.
There are convenient types that make expressing more complicated queries easier:
parameter | description |
---|---|
Option<T> | a component but only if it exists, otherwise None |
AnyOf<T> | fetches entities with any of the components in type T |
Ref<T> | shared borrow of an entity's component T with access to change detection |
Entity | returns the entity |
In addition to the Query
system parameter there are other sibling system parameters that also perform queries:
System parameter | Description |
---|---|
Single<D, F> | Matches exactly one query item. Skips the system if more or none. |
Option<Single<D, F>> | Matches zero or one query item. Skips the system if more. |
Populated<D, F> | matches at least one or more. Skips the system if none. |
Single
can be useful to reduce boilerplate when you know there is only ever a single entity with a particular component:
fn move_the_only_player(mut transform: Single<&mut Transform, With<Player>>) {
transform.translation.x += 1.
}
The second argument in your Query<D, F>
is the QueryFilter
. These filters are wrapped by a condition type:
method | description |
---|---|
With<T> | only items with a T component |
Without<T> | only items without a T component |
Or<F> | checks if all filters in the tuple F apply |
Changed<T> | only components of type T that were changed this tick |
Added<T> | only components of type T that were added this tick |
To retrieve components from our ECS storage our Query
system parameter provides several methods:
method | description |
---|---|
iter | returns an iterator over all items |
for_each | runs the given function in parallel for each item |
iter_many | runs a given function for each item matching a list of entities |
iter_combinations | returns an iterator over all combinations of a specified number of items |
par_iter | returns a parallel iterator |
get | returns a query item for a given entity |
get_component<T> | returns the component for a given entity |
many | returns a query item for a given list of entities |
get_single | the safe version of single which returns a Result<T> |
single | returns the query item while panicking if there are others |
is_empty | returns true if the query is empty |
contains | returns true if query contains a given entity |
Each method also has a corresponding *_mut
variant which will return the components with mutable ownership. This lets us change their data, instead of just reading it.
In situations where we have a particular Entity
(which is basically an ID), we can use get
or get_mut
.
#[derive(Resource)]
struct PlayerRef(Entity);
fn move_player_by_component(
mut query: Query<&mut Transform>,
player: Res<PlayerRef>
) {
if let Ok(mut transform) = query.get_mut(player.0) {
transform.translation.x += 1.;
}
}
In cases where we have a list of Entity
and we want to iterate over only those entity components we can use iter_many
.
#[derive(Component)]
struct Health(pub f32);
#[derive(Resource)]
struct Selection {
enemies: Vec<Entity>
}
const ATTACK_DAMAGE: f32 = 10.;
fn attack_selected_enemies(
mut query: Query<&mut Health>,
selected: Res<Selection>
) {
let mut iter = query.iter_many_mut(&selected.enemies);
while let Some(mut health) = iter.fetch_next() {
health.0 -= ATTACK_DAMAGE;
}
}
Assets
To load assets we use the AssetServer
which manages asynchronous loading assets from a particular AssetSource
, usually the filesystem.
All assets follow the same general process:
- We register a new
Asset<T>
type if its custom - We Register an
AssetLoader
for that asset if its custom - We add the asset to our
assets
folder - Then we call
AssetServer::load
to get aHandle<T>
to the asset
fn load_images(asset_server: Res<AssetServer>, mut commands: Commands) {
// This will not block, the asset will be loaded in the background
let image_handle: Handle<Image> = asset_server.load("images/bevy.png");
commands.spawn(Sprite {
image: image_handle,
..default()
});
}
By default it will expect our assets to be inside the assets
folder inside the root directory of our application controlled by the BEVY_ASSET_ROOT
environment variable.
Assets can be tracked one of two ways:
- Through events like
AssetEvent::LoadedWithDependencies
- Or by querying the asset server with
AssetServer::get_load_state
.
Messages and events
There are two kind of events in Bevy:
Message
for communication between systemsEvent
andEntityEvent
for observers that trigger immediate behavior
Messages<T>
is a collection that acts as a double buffered queue. This is done to ensure each system has an opportunity to see each message. It is helping systems not have to care about the exact ordering within a frame.
Messages are defined by deriving the Message
trait:
// With a marker message
#[derive(Message)]
struct PlayerKilled;
// With a unit type
#[derive(Message)]
struct PlayerDetected(Entity);
// With fields
#[derive(Message)]
struct PlayerDamaged {
entity: Entity,
damage: f32,
}
fn main() {
App::new()
.add_message::<PlayerKilled>();
.add_message::<PlayerDetected>();
.add_message::<PlayerDamaged>();
}
If your messages are not consumed by 2 frames from now then they will be cleaned up and dropped silently.
To write messages to a stream we use a MessageWriter<T>
:
fn detect_player(
mut messages: MessageWriter<PlayerDetected>,
players: Query<(Entity, &Transform), With<Player>>,
) {
for (entity, transform) in players {
messages.write(PlayerDetected(entity));
}
}
We can read messages from our systems with an MessageReader<T>
that consumes messages from our buffers:
fn react_to_detection(mut messages: MessageReader<PlayerDetected>) {
for message in messages.read() {
// Do something with each event here
}
}
Events are the immediate version of messages. They come in two types:
Event
for global events defined with aGlobalTrigger
EntityEvent
for entity specific events defined with anEntityTrigger
These events are consumed by an Observer
which is a callback system that takes an On
system parameter:
fn on_respawn(
event: On<Add, Enemy>,
query: Query<(&Enemy, &Position)>,
) {
let (enemy, position) = query.get(event.entity).unwrap();
println!("Enemy was respawned at {:?}", position);
}
Observers can be global by adding them to the App
definition:
fn main() {
App::new().add_plugins(DefaultPlugins).add_observer(on_respawn);
}
Or they can be local and only triggered for particular entities:
fn spawn_boss(mut commands: Commands) {
let entity = commands.spawn((Enemy, Boss)).observe(on_boss_spawned).id();
// Later, or potentially in another system
commands.trigger(BossSpawned { entity });
}
These entity events will bubble up a hierarchy of ChildOf
attached components.
This table summarizes the differences between events and messages:
Events | Messages | |
---|---|---|
Optimal event frequency | Infrequent | Frequent |
Handler | Only handles a single event | Can handle many messages together |
Latency | Immediate | Up to 1 frame |
Event propagation | Bubbling | None |
Scope | World or Entity | World |
Ordering | No explicit order | Ordered |
Coupling | High | Low |
Relationships
Bevy has a built-in relationship it provides for parent/child relationships that is made up of two components:
ChildOf
: TheRelationship
we attach to other entitiesChildren
: TheRelationshipTarget
that is kept in sync
These will propagate Transform
and GlobalTransform
of the parent to its children to keep them in sync.
When you despawn the parent (the entity holding the Children
) then all the ChildOf
components are removed automatically.
fn spawn_ship(mut commands: Commands) {
let fleet = commands.spawn(Fleet).id();
commands.spawn(Ship, ChildOf(fleet));
}
We can spawn children from a parent with the with_children
method:
fn spawn_fleet(mut commands: Commands) {
commands
.spawn(Fleet)
.with_children(|parent| {
parent.spawn((Ship, Name::new("Ship 1")));
parent.spawn((Ship, Name::new("Ship 2")));
});
}
Instead of the closure we can pass a bundle of children to the children!
macro.
fn spawn_fleet_with_sugar(mut commands: Commands) {
commands.spawn((
Fleet,
children![
(Ship, Name::new("Ship 3")),
(Ship, Name::new("Ship 4")),
]
));
}
The source of truth is the Relationship
component. This is the component we will be adding to other entities to specify the relationship. It must contain a reference to the entity we will be attaching ourselves to.
#[derive(Component)]
#[relationship(relationship_target = ShipAttachments)]
struct AttachedToShip(pub Entity);
The RelationshipTarget
is the component that will automatically be kept in sync with all our AttachedToShip
components. It must contain a list of entities to store them.
#[derive(Component)]
#[relationship_target(relationship = AttachedToShip, linked_spawn)]
struct ShipAttachments(Vec<Entity>);
The linked_spawn
will allow us to remove the ShipAttachments
and Bevy will automatically despawn any AttachedToShip
components on our other entities.
To create the relationship we can then spawn this Relationship
on other entities.
fn spawn_ship(mut commands: Commands) {
// Spawn the parent Ship
let ship = commands.spawn((Ship, Name::new("Ship"))).id();
// Spawn a GunTurret and attach it to the Ship using the new Relationship
// component
commands.spawn((GunTurret, AttachedToShip(ship), Name::new("GunTurret 1")));
commands.spawn((GunTurret, AttachedToShip(ship), Name::new("GunTurret 2")));
}
This can be shortened by using the related!
macro to specify the relationships from the parent entity instead:
fn build_ship(mut commands: Commands) {
// Spawn a Ship entity
commands.spawn((
Ship,
Name::new("Ship A"),
related!(ShipAttachments[
// Attach GunTurrets to the Ship using the relationship
(GunTurret, Name::new("GunTurret 1")),
(GunTurret, Name::new("GunTurret 2")),
]),
));
}
Relationships are stored as components so we can query them:
fn log_ship_report(
ships: Query<(&Name, &ShipAttachments), With<Ship>>,
turrents: Query<&Name, With<GunTurret>>,
) {
for (ship_name, attachments) in &ships {
info!("{} has the following attachments:", ship_name.as_str());
for &attachment in &attachments.0 {
if let Ok(child_name) = turrents.get(attachment) {
info!(" - {}", child_name.as_str());
}
}
}
}
You can iterate the association from either side of the relationship:
fn iterate_from_turrets_to_ships(
ships: Query<Entity, With<Ship>>,
turrets: Query<Entity, With<AttachedToShip>>,
attachments: Query<&AttachedToShip>,
) {
for turret in &turrets {
for attached in attachments.iter_ancestors(turret) {
let ship = ships.get(attached);
info!("Turret {:?} is attached to Ship {:?}", turret, ship);
}
}
}
fn iterate_from_ships_to_turrets(
ships: Query<Entity, With<Ship>>,
turrets: Query<Entity, With<GunTurret>>,
ship_attachments: Query<&ShipAttachments>,
) {
for ship in &ships {
for attachment in ship_attachments.iter_descendants(ship) {
let turret_entity = turrets.get(attachment);
info!("Ship {:?} has Turret {:?}", ship, turret_entity);
}
}
}
You must be careful not to use this if your relationships contain loops as this will run infinitely.
Bevy does not currently have a native way of representing many-to-many relationships. ChildOf
can only point to a single entity.
Input
There are two ways to handle input in Bevy:
- Reacting to the events emitted automatically by Bevy's input systems
- Querying a resource like
ButtonInput
,Axis
,Touches
orGamepads
Bevy has a different resource for each type of input:
Resource | Description |
---|---|
Axis | stores the position data from certain input devices |
ButtonInput | a "press-able" input |
GamepadAxis | An axis of a gamepad |
GamepadButton | represents a single button of a gamepad just like a keyboard |
Gamepads | represents a collection of connected game controllers |
TouchInput | represents touch based input events |
Touches | a collection of Touch es that have happened |
For example, to handle keyboard input we use the ButtonInput<T>
resource which has a set of convenient methods we can use to trigger behavior:
Method | Description |
---|---|
pressed | will return true between a press and release event |
just_pressed | will return true for one frame after a press event |
just_released | will return true for one frame after a release event |
We can read these events in general by listening to KeyboardInput
events:
/// Track keyboard inputs — useful for debugging or keybinding tools
fn log_keyboard_input(mut keyboard_events: EventReader<KeyboardInput>) {
for event in keyboard_events.read() {
println!(
"Key pressed: {:?}, logical key: {:?}",
event.key_code, event.logical_key
);
}
}
Or we can use the resource to check for a more specific state:
/// Handle player jump
fn jump_input_system(input: Res<ButtonInput<KeyCode>>) {
if input.just_pressed(KeyCode::Space) {
info!("Jump!");
}
}
fn combo_key_system(input: Res<ButtonInput<KeyCode>>) {
let shift = input.any_pressed([KeyCode::ShiftLeft, KeyCode::ShiftRight]);
let ctrl = input.any_pressed([KeyCode::ControlLeft, KeyCode::ControlRight]);
if ctrl && shift && input.just_pressed(KeyCode::KeyA) {
info!("Special ability activated! (Ctrl + Shift + A)");
}
}
When you place your mouse on the screen it would two positions:
- On-screen coordinates (the position of the pixel on a screen)
- World coordinates (the position of the mouse projected onto our game)
The RelativeCursorPosition
component stores the cursor position relative to our node. If it is within the range of (-0.5, -0.5)
to (0.5, 0.5)
then the cursor is currently over the node, with (0., 0.)
being center. You can use the cursor_over: bool
field to figure this out.
If the cursor position is unknown (e.g we are alt+tabbed out of our game) then the position will be None
.
use bevy::ui::RelativeCursorPosition;
fn relative_cursor_position(cursor_query: Query<&RelativeCursorPosition>) {
if let Ok(cursor) = cursor_query.single() {
if let Some(cursor) = cursor.normalized {
info!("({:.1}, {:.1})", cursor.x, cursor.y)
}
}
}
Cameras
Each Camera
is responsible for 3 main things:
- The render target which is the region of the screen to draw something
- The projection which determines how to transform 3D into 2D (our screen)
- The position of the view in our scene to capture and transform
Each frame, Bevy will start by drawing the ClearColor
over the camera's viewport and then draw things from scratch on the screen.
The coordinate system in Bevy is right handed so:
- X increases going to the right
- Y increases going up
- Z increases coming towards the screen
- The default center of the screen is (0, 0)
When we spawn a camera we use Camera2d
or Camera3d
depending on our game.
// Useful for marking the "main" camera if we have many
#[derive(Component)]
#[require(Camera2d)]
pub struct MainCamera;
fn initialize_camera(mut commands: Commands) {
commands.spawn(MainCamera);
}
fn move_camera(
mut camera: Single<&mut Transform, With<MainCamera>>,
player: Single<&Transform, With<Player>>,
time: Res<Time>,
) {
let direction = Vec3::new(
player.translation.x,
player.translation.y,
camera.translation.z,
);
camera.translation =
camera.translation.lerp(direction, time.delta_secs() * 2.);
}
fn rotate_camera_to_mouse(
time: Res<Time>,
mut mouse_motion: MessageReader<MouseMotion>,
mut transform: Single<&mut Transform, With<Camera>>,
) {
let dt = time.delta_secs();
// The factors are just arbitrary mouse sensitivity values.
// It's often nicer to have a faster horizontal sensitivity than vertical.
let mouse_sensitivity = Vec2::new(0.12, 0.10);
for motion in mouse_motion.read() {
let delta_yaw = -motion.delta.x * dt * mouse_sensitivity.x;
let delta_pitch = -motion.delta.y * dt * mouse_sensitivity.y;
// Add yaw which is turning left/right (global)
transform.rotate_y(delta_yaw);
// Add pitch which is looking up/down (local)
const PITCH_LIMIT: f32 = std::f32::consts::FRAC_PI_2 - 0.01;
let (yaw, pitch, roll) = transform.rotation.to_euler(EulerRot::YXZ);
let pitch = (pitch + delta_pitch).clamp(-PITCH_LIMIT, PITCH_LIMIT);
// Apply the rotation
transform.rotation = Quat::from_euler(EulerRot::YXZ, yaw, pitch, roll);
}
}
UI
Bevy's UI system is also done through its ECS.
A Node
is a component that holds the layout and style properties. Nodes are laid out with either a flexbox or CSS grid layout.
This is what a Node
looks like:
impl Node {
pub const DEFAULT: Self = Self {
display: Display::DEFAULT,
box_sizing: BoxSizing::DEFAULT,
position_type: PositionType::DEFAULT,
left: Val::Auto,
right: Val::Auto,
top: Val::Auto,
bottom: Val::Auto,
flex_direction: FlexDirection::DEFAULT,
flex_wrap: FlexWrap::DEFAULT,
align_items: AlignItems::DEFAULT,
justify_items: JustifyItems::DEFAULT,
align_self: AlignSelf::DEFAULT,
justify_self: JustifySelf::DEFAULT,
align_content: AlignContent::DEFAULT,
justify_content: JustifyContent::DEFAULT,
margin: UiRect::DEFAULT,
padding: UiRect::DEFAULT,
border: UiRect::DEFAULT,
flex_grow: 0.0,
flex_shrink: 1.0,
flex_basis: Val::Auto,
width: Val::Auto,
height: Val::Auto,
min_width: Val::Auto,
min_height: Val::Auto,
max_width: Val::Auto,
max_height: Val::Auto,
aspect_ratio: None,
overflow: Overflow::DEFAULT,
overflow_clip_margin: OverflowClipMargin::DEFAULT,
row_gap: Val::ZERO,
column_gap: Val::ZERO,
grid_auto_flow: GridAutoFlow::DEFAULT,
grid_template_rows: Vec::new(),
grid_template_columns: Vec::new(),
grid_auto_rows: Vec::new(),
grid_auto_columns: Vec::new(),
grid_column: GridPlacement::DEFAULT,
grid_row: GridPlacement::DEFAULT,
};
}
Which we can use to spawn a simple UI box centered on the screen:
fn spawn_box(mut commands: Commands) {
let container = Node {
width: percent(100.0),
height: percent(100.0),
justify_content: JustifyContent::Center,
..default()
};
let square = (
BackgroundColor(Color::srgb(0.65, 0.65, 0.65)),
Node {
width: px(200.),
border: UiRect::all(px(2.)),
..default()
},
);
commands.spawn((container, children![(square)]));
}
All Children
of a node will set their position to be relative to their parent, so the Node
we spawned as a child will be placed in the center of its parent.
Text can be rendered in two separate ways:
- As part of our game with
Text2d
- As part of our UI with
Text
fn spawn_text_in_ui(mut commands: Commands, assets: Res<AssetServer>) {
commands.spawn((
Node {
position_type: PositionType::Absolute,
bottom: px(5.0),
right: px(5.0),
..default()
},
Text::new("Here is some text"),
TextColor(Color::BLACK),
Text