bevy_map_scatter

Crates.iobevy_map_scatter
lib.rsbevy_map_scatter
version0.4.1
created_at2025-08-28 18:23:11.067688+00
updated_at2026-01-17 20:18:21.758058+00
descriptionBevy plugin that integrates the `map_scatter` core crate for object scattering with field-graph evaluation and sampling
homepage
repositoryhttps://github.com/morgenthum/map_scatter
max_upload_size
id1814426
size1,243,984
Mario Morgenthum (morgenthum)

documentation

https://docs.rs/bevy_map_scatter

README

bevy_map_scatter

License: MIT or Apache 2.0 Docs Crate Build Status

Bevy plugin for rule-based scattering with asset loading, async execution, and ECS-friendly results.

logo

Features

Asset-driven scattering for Bevy with async execution and ECS-friendly results:

  • Asset-based authoring of scatter plans (RON): load *.scatter files via AssetServer.
  • Texture integration: snapshot Bevy Images to CPU textures with configurable domain mapping.
  • Asynchronous execution: runs scatter jobs on AsyncComputeTaskPool.
  • ECS-friendly: placements become entities; components can be attached for rendering, gameplay, or tooling.
  • Streaming helper: manage chunked scatter around moving anchors (optional plugin).
  • Diagnostics: forward core events as Bevy messages (ScatterMessage, ScatterFinished) with configurable filtering.

Use cases

Use cases: decorative dressing, resource distribution, or any placement driven by textures and rules.

Workflow notes:

  • Plans are assets and can hot-reload
  • Tweak textures, thresholds, or layer order and rerun
  • Deterministic seeds by default; vary them per run when needed

Examples:

  1. Stylized forest: trees sparse, mushrooms where tree probability is low, grass fills gaps.
  2. Town dressing: benches in plaza masks, lamp posts along roads with spacing, clutter where nothing else landed.
  3. Dungeon encounters: camps in large rooms, enemies avoid camp influence, rare loot in dead ends with minimum spacing.

Integration details:

  • Assets: *.scatter plans loaded by AssetServer
  • ECS: placements become entities you can tag and decorate
  • Async: jobs run on AsyncComputeTaskPool
  • Determinism: same seed + plan + textures = identical placements
  • Events: listen to ScatterFinished or ScatterMessage to drive gameplay or tooling

Examples

See the example crate for curated demos you can run locally.

Quick start

Add the crates to a Bevy application:

# Cargo.toml
[dependencies]
bevy = "0.18"
bevy_map_scatter = "0.3"
map_scatter = "0.3"

Create a scatter plan in assets/simple.scatter:

(
  layers: [
    (
      id: "dots",
      kinds: [
        (
          id: "dots",
          spec: (
            nodes: {
              "probability": Constant(
                params: ConstantParams(value: 1.0),
              ),
            },
            semantics: {
              "probability": Probability,
            },
          ),
        ),
      ],
      sampling: JitterGrid(
        jitter: 1.0,
        cell_size: 1.0,
      ),
      selection_strategy: WeightedRandom,
    ),
  ],
)

Trigger a single scatter run once the asset is ready:

use bevy::prelude::*;
use bevy_map_scatter::prelude::*;

#[derive(Resource, Default)]
struct PlanHandle(Handle<ScatterPlanAsset>);

fn main() {
    App::new()
        .init_resource::<PlanHandle>()
        .add_plugins(DefaultPlugins)
        .add_plugins(MapScatterPlugin)
        .add_systems(Startup, load_plan)
        .add_systems(Update, trigger_request)
        .add_observer(log_finished)
        .run();
}

/// Loads the scatter plan asset on startup.
fn load_plan(mut handle: ResMut<PlanHandle>, assets: Res<AssetServer>) {
    handle.0 = assets.load("simple.scatter");
}

/// Triggers a scatter request once the plan asset is loaded.
fn trigger_request(
    mut commands: Commands,
    mut once: Local<bool>,
    handle: Res<PlanHandle>,
    assets: Res<Assets<ScatterPlanAsset>>,
) {
    // Only run once.
    if *once {
        return;
    }
    // Wait until the asset is loaded.
    if assets.get(&handle.0).is_none() {
        return;
    }

    // The domain size for scattering.
    let domain = Vec2::new(10.0, 10.0);

    // Create run configuration and seed for (deterministic) randomness.
    let config = RunConfig::new(domain)
        .with_chunk_extent(domain.x)
        .with_raster_cell_size(1.0);

    // Spawn an entity to track the request (attach your own components if needed).
    let entity = commands.spawn_empty().id();

    // Trigger the scatter run.
    commands.trigger(ScatterRequest::new(entity, handle.0.clone(), config, 42));

    // Mark as done.
    *once = true;
}

/// Observes the `EntityEvent` when a scatter run has finished.
fn log_finished(finished: On<ScatterFinished>, mut commands: Commands) {
    info!(
        "Scatter run {} finished: placements={} evaluated={} rejected={}",
        finished.entity,
        finished.result.placements.len(),
        finished.result.positions_evaluated,
        finished.result.positions_rejected
    );

    // Clean up the entity used for the request (keep it if you need it later).
    commands.entity(finished.entity).despawn();
}

Run the application with cargo run. After the scatter job completes, a summary appears in the log; continue with placement logic as needed.

Streaming (optional)

For moving worlds or endless maps, use MapScatterStreamingPlugin and attach ScatterStreamSettings to an anchor entity. The plugin will spawn/despawn chunks around the anchor and emit placement entities tagged with ScatterStreamPlacement.

Alternatives

  • bevy_feronia: more opinionated, art-focused scattering with built-in wind/material/LOD workflows; likely a better fit if you want an end-to-end visual pipeline rather than a low-level, data-driven scatter core.

Compatibility

bevy_map_scatter map_scatter bevy
0.4 0.4 0.18
0.3 0.3 0.17
0.2 0.2 0.17
Commit count: 31

cargo fmt