⚠️ 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 pybevy.prelude import *
 
 
@component
class Cube(Component):
    pass
 
 
@component
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
    transform.translation.x = (t * 2.0).cos() * 10.0
    transform.translation.z = (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 OR | | ~ | 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()