⚠️ Beta State

PyBevy is in an early and experimental stage. The API is incomplete, subject to breaking changes without notice, and you should expect bugs. Many features are still under development.

View API In Depth

Complete guide to PyBevy's high-performance batch processing View API.

Introduction

The View API is PyBevy's batch processing system for entity components. It uses a bytecode VM to execute expressions in parallel across all entities, providing NumPy-like performance with pure Python syntax.

Instead of iterating over entities one at a time with Query, the View API compiles your Python expressions into bytecode and runs them across all matching entities simultaneously.

System Signature

To use the View API, declare a View parameter in your system function. The type parameters specify which components to access.

from dataclasses import dataclass
from pybevy.prelude import *
 
 
@component
class Cube(Component):
    pass
 
 
# When a component has type-annotated fields, add @dataclass below @component.
# Without annotations (like Cube above), @dataclass is not needed.
@component
@dataclass
class Velocity(Component):
    x: float = 0.0
    y: float = 0.0
    z: float = 0.0

Column Access

Use column_mut() for mutable access (read and write) or column() for read-only:

def basic_view_system(view: View[Mut[Transform], With[Cube]]) -> None:
    # Mutable access — can read and write
    transform = view.column_mut(Transform)
    transform.translation.y = 10.0

Access nested fields naturally:

transform.translation.x = 5.0
transform.translation.y = 10.0
transform.scale.x = 2.0
transform.rotation.w = 1.0

Tier 1: Arithmetic Operations

The View API supports all standard arithmetic and compound assignments.

Operator Description Example
+ Addition x + 5.0
- Subtraction x - 2.0
* Multiplication x * 3.0
/ Division x / 4.0
** Power x ** 2.0
+= Add-assign x += 5.0
-= Sub-assign x -= 2.0
*= Mul-assign x *= 3.0
/= Div-assign x /= 4.0
def position_update(
    view: View[tuple[Mut[Transform], Velocity], With[Cube]],
    time: Res[Time],
) -> None:
    transform = view.column_mut(Transform)
    velocity = view.column(Velocity)
    dt = time.delta_secs()
 
    # Compound assignments — clean and fast
    transform.translation.x += velocity.x * dt
    transform.translation.y += velocity.y * dt
    transform.translation.z += velocity.z * dt

Tier 2: Math Functions

Trigonometric and numeric functions are called as methods on expressions.

Trigonometric Functions

Function Description
.sin() Sine
.cos() Cosine
.tan() Tangent
.asin() Arcsine
.acos() Arccosine
.atan() Arctangent

Numeric Functions

Function Description
.sqrt() Square root
.abs() Absolute value
.floor() Round down
.ceil() Round up
.round() Round to nearest
.min(val) Minimum
.max(val) Maximum
.clamp(min, max) Clamp to range
def circular_motion(view: View[Mut[Transform], With[Cube]], time: Res[Time]) -> None:
    transform = view.column_mut(Transform)
    t = time.elapsed_secs()
 
    # Circular orbit using trig functions
    # Note: trig methods are on View column expressions, so we include a column reference
    transform.translation.x = (transform.translation.x * 0.0 + t * 2.0).cos() * 10.0
    transform.translation.z = (transform.translation.z * 0.0 + t * 2.0).sin() * 10.0
 
    # Keep above ground
    transform.translation.y = transform.translation.y.max(0.0)

Tier 3: Logical Operations

Comparison Operators

Operator Description
== Equal
!= Not equal
< Less than
<= Less or equal
> Greater than
>= Greater or equal

Boolean Operators

Operator Description
& Logical AND
` `
~ Logical NOT

Important: Use &, |, ~ instead of Python's and, or, not keywords.

Conditional Expressions with where()

The where() function provides per-entity conditional logic:

from pybevy import where
 
# where(condition, true_value, false_value)
result = where(x > 0.0, 10.0, -10.0)

Example: Quadrant-Based Height

from pybevy import where
 
def classify_quadrants(view: View[Mut[Transform]]) -> None:
    transform = view.column_mut(Transform)
    x = transform.translation.x
    z = transform.translation.z
 
    # Different heights per quadrant
    height = where(
        x > 0.0,
        where(z > 0.0, 4.0, 1.0),
        where(z > 0.0, 3.0, 2.0),
    )
    transform.translation.y = height

Tier 4: Reduction Operations

Compute aggregate statistics across all entities in a single parallel operation.

Function Description Empty Result
view.reduce_sum(expr) Sum of all values 0.0
view.reduce_mean(expr) Average 0.0
view.reduce_max(expr) Maximum -inf
view.reduce_min(expr) Minimum +inf
view.reduce_count() Count entities 0
view.reduce_count(condition) Count matching 0

Example: Height Statistics

def compute_stats(view: View[Transform]) -> None:
    transform = view.column(Transform)
 
    max_height = view.reduce_max(transform.translation.y)
    avg_height = view.reduce_mean(transform.translation.y)
    above_ground = view.reduce_count(transform.translation.y > 0.0)
 
    print(f"Max: {max_height:.2f}, Avg: {avg_height:.2f}, Above ground: {above_ground}")

Vec3 Bulk Operations

Instead of updating x, y, z separately, perform Vec3-level operations:

transform = view.column_mut(Transform)
velocity = view.column(Velocity)
 
# Vec3 arithmetic
new_pos = transform.translation + velocity * dt
transform.translation.set(new_pos)
 
# Scalar multiplication
scaled = transform.scale * 2.0
transform.scale.set(scaled)

Supported: Vec3 + Vec3, Vec3 - Vec3, Vec3 * scalar, scalar * Vec3, Vec3 / scalar.

Complete Example: Wave Animation

Here's a complete runnable example that animates thousands of cubes using the View API.

def setup(
    commands: Commands,
    meshes: ResMut[Assets[Mesh]],
    materials: ResMut[Assets[StandardMaterial]],
) -> None:
    cube_mesh = meshes.add(Cuboid.from_length(0.15))
 
    # Spawn a grid of cubes
    grid_size = 50
    half = grid_size // 2
    for i in range(grid_size * grid_size):
        x = (i % grid_size - half) * 0.25
        z = (i // grid_size - half) * 0.25
        commands.spawn(
            Cube(),
            Mesh3d(cube_mesh),
            MeshMaterial3d(materials.add(Color.srgb(0.3, 0.7, 0.9))),
            Transform.from_xyz(x, 0.0, z),
        )
 
    commands.spawn(
        DirectionalLight(illuminance=5000.0),
        Transform.IDENTITY.looking_at(Vec3(-1.0, -1.0, -1.0), Vec3.Y),
    )
    commands.spawn(
        Camera3d(),
        Transform.from_xyz(-5.0, 10.0, 15.0).looking_at(Vec3.ZERO, Vec3.Y),
    )
 
 
def wave_animation(view: View[Mut[Transform], With[Cube]], time: Res[Time]) -> None:
    t = time.elapsed_secs()
    transform = view.column_mut(Transform)
 
    # Two overlapping sine waves
    x_wave = (transform.translation.x * 0.5 + t * 3.0).sin()
    z_wave = (transform.translation.z * 0.4 - t * 2.5).sin()
    transform.translation.y = (x_wave + z_wave) * 2.0

Running the Example

@entrypoint
def main(app: App) -> App:
    return (
        app
        .add_plugins(DefaultPlugins)
        .add_systems(Startup, setup)
        .add_systems(Update, wave_animation)
    )
 
if __name__ == "__main__":
    main().run()