Game Engine Issues May 14, 2026

Bevy 0.17 Asset Hot Reload Silent - PNG Watcher Not Triggering After 0.16 Upgrade - How to Fix

Fix Bevy 0.17 asset hot reload not triggering when PNG files change. Enable watch_for_changes_override, Cargo file_watcher feature, correct assets/ paths, and work around Aseprite atomic rename on Windows.

By GamineAI Team

Bevy 0.17 Asset Hot Reload Silent - PNG Watcher Not Triggering After 0.16 Upgrade - How to Fix

Problem: You upgraded from Bevy 0.16 to 0.17, enabled asset hot reload in AssetPlugin, and run with cargo run. Saving assets/player.png in Aseprite, Krita, or Photoshop does not update the sprite on screen. No panic, no log spam — the watcher is simply silent.

Who is affected now: Windows developers using art tools that save via temp-file-then-rename (common in Aseprite), teams that assumed hot reload was on because DefaultPlugins changed defaults in 0.17, and repos that forgot the file_watcher Cargo feature after copying a minimal Cargo.toml from a WASM template.

Fastest safe fix: Set watch_for_changes_override: Some(true) explicitly, add features = ["file_watcher"] on the bevy dependency, confirm the PNG path is under assets/ (not embedded), and verify with touch assets/player.png — if touch works but Aseprite save does not, apply the atomic-rename workaround below.

Direct answer

Bevy 0.17 can hot-reload assets, but only when three gates pass: the file_watcher feature is compiled in, AssetPlugin has watch_for_changes_override: Some(true), and the OS file watcher sees the save event. Many 0.16 → 0.17 upgrades satisfy none of the three because templates differ and art tools on Windows do not emit modify events for atomic renames. Turning the override on and enabling the feature fixes the majority of reports; the remainder need a save-path or polling fallback.

Why this issue spikes in 2026

  1. Bevy 0.16/0.17 marketed default-friendly hot reload — teams enabled it mentally but not in Cargo.toml.
  2. Mid-2026 Discord volume clusters on Windows + Aseprite atomic saves after indie teams adopted Bevy for 2D platformer jams.
  3. Guide and blog content shows hot reload in snippets without repeating the file_watcher feature line every time.

Pair this fix with the Bevy 0.17 Required Components, Observers, and Asset Hot Reload guide chapter for the full modernization path.

Symptoms and phrases to match

  • Texture unchanged after Ctrl+S in Aseprite; cargo run restart shows new art.
  • watch_for_changes_override commented out or missing after merge from 0.16 template.
  • Works on macOS for one teammate, silent on Windows for another (atomic rename).
  • Image loaded via include_bytes! or Asset::embedded — watcher never applies.
  • Saving to art/source/player.png while the game loads assets/player.png.

Root causes (check in this order)

  1. file_watcher feature not enabled in Cargo.toml.
  2. watch_for_changes_override left None — 0.17 still requires explicit Some(true) in many project templates.
  3. Wrong folder — file saved outside assets/ root the AssetServer watches.
  4. Embedded asset — no filesystem path to watch.
  5. Atomic rename save on Windows (Aseprite default) — notify may not fire Modify on the final path.
  6. WASM build target — hot reload is intentionally absent; verify you are not testing wasm32 while reading desktop docs.

Fastest safe fix path

Step 1 - Enable the Cargo feature

In Cargo.toml:

[dependencies]
bevy = { version = "0.17", features = ["file_watcher"] }

Run cargo clean once after adding the feature so the watcher backend links in.

Step 2 - Set AssetPlugin override explicitly

use bevy::asset::AssetPlugin;
use bevy::prelude::*;

fn main() {
    App::new()
        .add_plugins(DefaultPlugins.set(AssetPlugin {
            watch_for_changes_override: Some(true),
            ..default()
        }))
        .run();
}

Do not rely on “defaults changed in 0.17” without this line in your app entry.

Step 3 - Confirm the asset path

Your load call should reference a project path:

// Good — watched file
commands.spawn(Sprite::from_image(asset_server.load("player.png")));

// Not watched — embedded
// let handle: Handle<Image> = asset_server.load_embedded_asset!(...);

The file on disk must be assets/player.png (or a subfolder under assets/).

Step 4 - Baseline test with touch

With the game running:

# macOS / Linux
touch assets/player.png

# Windows PowerShell
(Get-Item assets/player.png).LastWriteTime = Get-Date

If the sprite updates within ~250 ms, the watcher works — your art tool save path is the remaining issue.

Step 5 - Aseprite atomic rename workaround (Windows)

If touch works but Aseprite does not:

  1. In Aseprite, use File > Save As occasionally to force an in-place write, or
  2. Enable File > Preferences > Files > Export files with "Save As" format only for export — for working files, save to the assets/ path directly, or
  3. Add a dev-only R key that calls asset_server.reload(&handle) for the active hero texture during art sprints.

Alternative fix paths

A) Dev-only hot reload plugin (desktop only)

#[cfg(not(target_arch = "wasm32"))]
fn hot_reload_plugin(app: &mut App) {
    app.add_plugins(DefaultPlugins.set(AssetPlugin {
        watch_for_changes_override: Some(true),
        ..default()
    }));
}

Gate WASM builds separately — they should not enable file_watcher.

B) Polling fallback for jam builds

For game-jam scope only, a system that reloads a handle every N seconds in cfg(debug_assertions) is acceptable. Remove before ship — it masks production asset bugs.

C) Copy-through save script

Save Aseprite sources in art/, then run a watch script that cp into assets/ (copy triggers modify events reliably on Windows).

Verification checklist

  • [ ] cargo tree -i bevy shows file_watcher feature enabled.
  • [ ] watch_for_changes_override: Some(true) present in main.rs (or shared plugin).
  • [ ] touch assets/player.png updates the sprite in under 1 second.
  • [ ] Aseprite Save updates the sprite after workaround, or documented reload key works.
  • [ ] No load_embedded_asset! on the texture under test.

Prevention

  • Pin Bevy to a minor version in Cargo.toml (0.17.*) and document hot-reload requirements in README.md.
  • Add a CI smoke job that runs touch on a golden PNG in a headless App test (where supported).
  • Keep art sources and runtime assets paths distinct; only assets/ is watched.
  • Ship the 15 Free Bevy 0.17 Indie Iteration Resources link bundle in onboarding docs.

Troubleshooting

Symptom Likely cause Fix
Nothing ever reloads Missing file_watcher feature Step 1
Reload on Linux only Windows atomic rename Step 5
Reload once then stops Handle replaced / entity despawned Reload same Handle, same entity
Panic on reload Image size changed Keep export dimensions fixed per handle
Works in Editor tool, not game Wrong assets/ root Align path with AssetPlugin folder

Frequently asked questions

Q: Does hot reload recompile Rust?

A: No. Only assets on disk (PNG, RON, WGSL, etc.). Code changes still need cargo run.

Q: Should I enable this in release builds?

A: Usually no. Gate with #[cfg(debug_assertions)] or a dev feature flag.

Q: Bevy 0.16 worked without file_watcher — why?

A: Older templates and docs differed; 0.17 documents hot reload as ergonomic but still feature-gated in Cargo.

Related help articles and guides

Bookmark this article before your next 0.16 → 0.17 upgrade sprint so art iteration does not stall on silent watchers.