Giter VIP home page Giter VIP logo

bevy-fast-tilemap's Introduction

Fast Tilemap for Bevy

Crates.io docs

Lightning fast tilemaps for bevy.

Features

  • Very high rendering performance (hundreds of fps, largely independent of map size).
  • Multiple layers can be achieved by multiple map instances or custom shader logic
  • Rectangular and axonometric (eg isometric) tile maps.
  • Coordinate conversion for eg computing map position of the mouse cursor.
  • Tiles can overlap either by "dominance" rule or by perspective. Perspective mode allows an orthographic camera like 3d look, that is, tiles don't need to be flat but can be isometric "objects" (see examples).
  • Optional custom mesh for which the map serves as a texture.
  • Color gradient for tinting the whole map.
  • Custom shader code that can apply per-tile effects such as tinting or animation.
  • Tiles may use textures bigger than a single tile. (see screenshot below).
  • Arbitrary boundary shapes through custom shader code.
  • Two kinds of "animation" are supported, you can
    • Update the tile indices regularly from a system (see Animation Example)
    • Inject some custom shader code that can animate a tile in whatever way you can express in WGSL.

Screenshots

iso_perspective

Isometric perspective rendering.

custom_mesh

Meshes don't have to be rectangles.

patterns

Tiles can use bigger textures and (through custom shader code) arbitrary boundary shapes.

Checkout screenshots/ for more.

How it works

The whole map is rendered as a single quad and a custom shader cares for rendering the correct tiles at the correct position.

This works by using a storage buffer with tile indices and a tilemap atlas with the tiles. With some clever sampling, tiles can overlap with correct perspective so a "tile" can actually be any isometric object. Bevy-fast-tilemap store the storage buffer in a special material which you can access and change (see examples/). The tilemap atlas should be provided by you (see assets/ for atlas examples).

As of this writing, this should be (much) faster than most other bevy tilemap implementations out there.

Limitations

  • Only tested on Windows/WSL, no WASM support
  • Currently no support for rotating or scaling the entity holding the map (it will not look like you'd expect). (You can of course still zoom/rotate the camera to achieve any such effect)
  • Currently no support for using maps in a 3d setting.

Related work

If you dont require all of bevy_fast_tilemaps performance and are looking for an approach that supports some more tile shapes and allows to treat each tile as a separate entity, take a look at bevy_ecs_tilemap which (among others) inspired this work.

Examples

Check out the examples/ folder to get an overview. You can run the examples like this:

cargo run --example bench
cargo run --example animation
cargo run --example iso_perspective
cargo run --example custom_shader_code
cargo run --example patterns
cargo run --example updates
...

Bevy Compatibility

bevy bevy_fast_tilemap
0.10 0.1.0 - 0.4.0
0.11 0.5.0
0.12 0.6.0
0.13 0.7.0 - 0.7.6

bevy-fast-tilemap's People

Contributors

dependabot[bot] avatar droggelbecher avatar msklosak avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

bevy-fast-tilemap's Issues

Consider a disclaimer or example for setting image sampler filter to Nearest to avoid sprite bleed

Consider adding an example that sets the image sampler to use nearest filtering to avoid sprite bleed. I didn't know linear filtering was set by default and it took quite a while to a) diagnose the issue and b) determine the best way to fix it. Along the way I tried padding which helped, but ultimately wasn't the real issue.
This is addressed generally in bevyengine/bevy#1289, so if you feel a full example isn't warranted with boilerplate to actually set the value, consider at least linking to this issue?

With bleed:
Screenshot 2024-05-05 at 8 32 04 PM

After padding but still border issues
Screenshot 2024-05-06 at 12 58 25 PM
Screenshot 2024-05-06 at 12 58 33 PM

After setting filter to Nearest
Screenshot 2024-05-06 at 1 33 42 PM

My code. Note this won't work out of the box because you don't have the required files on disk:

use bevy::{
    asset::{self, LoadState},
    diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
    ecs::schedule::ScheduleLabel,
    math::{uvec2, vec2},
    prelude::*,
    render::{render_resource::FilterMode, texture::ImageSampler},
    window::PresentMode,
};
use bevy_fast_tilemap::{map::DefaultUserData, FastTileMapPlugin, Map, MapBundleManaged};

mod mouse_controls_camera;
use mouse_controls_camera::MouseControlsCameraPlugin;

#[derive(Resource)]
struct TileTerrainIndices(Vec<u32>);
#[derive(Resource)]
struct TextureAtlasHandle(Handle<Image>);

fn main() {
    App::new()
        .add_plugins((
            DefaultPlugins.set(WindowPlugin {
                primary_window: Some(Window {
                    title: String::from("Fast Tilemap example"),
                    resolution: (1820., 420.).into(),
                    // disable vsync so we can see the raw FPS speed
                    present_mode: PresentMode::Immediate,
                    ..default()
                }),
                ..default()
            }),
            LogDiagnosticsPlugin::default(),
            // FrameTimeDiagnosticsPlugin::default(),
            MouseControlsCameraPlugin::default(),
            FastTileMapPlugin::default(),
        ))
        .add_systems(Startup, startup)
        .add_systems(Update, ensure_texture_filter)
        // .add_systems(FixedUpdate, update_map)
        // Performance-wise you can step this much faster but it'd require an epillepsy warning.
        .insert_resource(Time::<Fixed>::from_seconds(0.2))
        .run();
}

fn ensure_texture_filter(
    mut commands: Commands,
    asset_server: Res<AssetServer>,
    mut materials: ResMut<Assets<Map>>,
    mut images: ResMut<Assets<Image>>,
    mut done: Local<bool>,
    handle: Res<TextureAtlasHandle>,
    tile_terrain_indices: Res<TileTerrainIndices>,
) {
    if dbg!(*done) {
        return;
    }

    let handle = handle.0.clone();
    if dbg!(asset_server.load_state(&handle)) != LoadState::Loaded {
        return;
    }

    let atlas = images.get_mut(&handle).unwrap();
    atlas.sampler = ImageSampler::nearest();

    let map = Map::builder(uvec2(100, 100), handle, vec2(16., 16.))
        .with_padding(vec2(2., 2.), vec2(0., 0.), vec2(0., 0.))
        .build_and_initialize(|m| {
            // Initialize using a closure
            // Set all tiles in layer 0 to index 6
            for y in 0..m.size().y {
                for x in 0..m.size().y {
                    m.set(x, y, tile_terrain_indices.0[(x * 100 + y) as usize] - 1);
                }
            }
        });

    commands.spawn(MapBundleManaged {
        material: materials.add(map),
        ..default()
    });

    *done = true;
}

fn startup(mut commands: Commands, asset_server: Res<AssetServer>) {
    commands.spawn(Camera2dBundle::default());
    let handle = asset_server.load::<Image>("sprite_images/atlas-1.png");
    commands.insert_resource(TextureAtlasHandle(handle));

    let tiled_map_json = std::fs::read_to_string("./assets/SimpleMap.json").unwrap();
    let json = serde_json::from_str::<serde_json::Value>(&tiled_map_json).unwrap();
    let data = json
        .get("layers")
        .unwrap()
        .as_array()
        .unwrap()
        .get(0)
        .unwrap()
        .get("data")
        .unwrap()
        .as_array()
        .unwrap();
    let data = data
        .iter()
        .map(|v| serde_json::from_value::<u32>(v.clone()).unwrap())
        .collect::<Vec<_>>();
    commands.insert_resource(TileTerrainIndices(data));
}

Feature Request: Tile Colors (background and foreground)

You might have a bunch of tiles (a most obvious example is when your tiles are really cp-437 ascii in a roguelike) that you want to "tint". I see that you just added a feature for map attributes that sets a color, which it looks like applies more or less a color to the entire map? It would be nice to be able to apply colors to tiles independently.

I had a hand at a prototype by changing to store a struct in the storage buffer that contains the index and foreground and background colors. That could work .. but I also found it seemed to run things a bit slower. I am wondering what your thoughts are for including a feature like this in the library?

clearcolor not working

Hi,

I want to spawn a window using a viewport and camera on the tilemap but if i set clearcolor resource it will only set the background color of the transparant tiles and outside the map. I want to set the background color of my viewport but i see the map beneath it. How can i fix this?

Multiple Maps along the x and y axis do not Render correctly

I'm attempting to use this crate alongside a chunking implementation. To do so I wanted to make it so that each Chunk was a separate fast tilemap. However doing so causes all the other maps except for the first map to not be rendered correctly.

I copied the code straight from the examples for spawning my maps.

You can see based on the image that all the maps should be random like the first one however none of them actually are.

bevy_fast_tilemap_example_EP54SEQPqE

Code for spawning the tilemaps, used for each tilemap

// Create map with the given dimensions of our chunk
        let map = Map::builder(
            // Map size (tiles)
            uvec2(
                chunk.get_chunk_dimensions().x,
                chunk.get_chunk_dimensions().y,
            ),
            // Tile atlas
            asset_server.load("tiles_16.png"),
            // Tile size (pixels)
            vec2(16., 16.),
        )
        .build_and_set(&mut images, |pos| rng.gen_range(0..15));

        commands
            .entity(entity)
            .insert((
                SpatialBundle {
                    transform: Transform {
                        translation: Vec3::new(
                            chunk.chunk_pos.x() as f32
                                * chunk.get_chunk_dimensions().x as f32
                                * 16.0,
                            chunk.chunk_pos.y() as f32
                                * chunk.get_chunk_dimensions().y as f32
                                * 16.0,
                            1.0,
                        ),
                        ..default()
                    },
                    ..default()
                },
                ChunkMapSpawned,
            ))
            .with_children(|parent| {
                let mut map_bundle = MapBundle::new(map);
                map_bundle.transform.translation = Vec3::new(
                    chunk.chunk_pos.x() as f32 * chunk.get_chunk_dimensions().x as f32 * 16.0,
                    chunk.chunk_pos.y() as f32 * chunk.get_chunk_dimensions().y as f32 * 16.0,
                    1.0,
                );
                parent
                    .spawn(map_bundle)
                    .insert(Transform::from_translation(Vec3::new(1.0, 1.0, 1.0)))
                    // Have the map manage our mesh so it always has the right size
                    .insert(MeshManagedByMap)
                    .insert(FastTileMap);
            });

Layers

I've been playing with this locally and wadding through a-lot of learning and wanted to create an issue to figure out if I'm heading in the correct direction/wasting my time because you're already working on this.

The simplest surface change from the shader context, looks to be changing the map_size to a vec3u, and adding a 'layer_padding/layer_offset' vec2f.

isometric

Will donate this asset and my example I'm toying with, has some magical numbers.
https://gist.github.com/urothis/ba9eb3930dcc481c84fd10a6fc8d4e86

I assume the ideal would be to not over-complicate with additional logic for tile determinism and leave that up to build_and_initialize.

interacting with sprites / tiles

Hi,

I want to put an image that encompasses multiple tiles however now it highlights as single tile using your highlight example. What is a good approach for this?

Tile generation line visual bug

So I am using this plugin to create a pretty big tilemap and I seem to have run into a very interesting visual problem. I believe it might be due to how the chunks are loaded for tiles but if I move around sometimes I can subtly see black lines go accross my screen. At first I thought that it was no big deal, but then I was walking around and randomly stopped on a spot where those black lines were covering the screen and they were staying there the whole time.

Maybe you can look into it, but in the meantime I will just ignore it.

image_2024-03-13_154842477

mistake in animation.rs example

In the following code you build the map twice first with build_and_initialize for me this caused problems in the at function of map_indexer. So I remoeved the last let map definition and add map.clone() to the spawn command since it will complain of moved value. Now the at function does not always return zero.

    let map = Map::builder(
        uvec2(64, 64),
        asset_server.load("pixel_tiles_16.png"),
        vec2(16., 16.),
    )
    .build_and_initialize(|m| {
        // Initialize using a closure
        // Set all tiles in layer 0 to index 4
        for y in 0..m.size().y {
            for x in 0..m.size().y {
                m.set(x, y, ((x + y) % 4 + 1) as u32);
            }
        }
    });

    commands.spawn(MapBundleManaged {
        material: materials.add(map),
        ..default()
    });

    let map = Map::builder(
        uvec2(64, 64),
        asset_server.load("pixel_tiles_16.png"),
        vec2(16., 16.),
    )
    .build();

    let bundle = MapBundleManaged {
        material: materials.add(map),
        transform: Transform::default().with_translation(vec3(0., 0., 1.)),
        ..default()
    };

    commands.spawn(bundle).insert(AnimationLayer);

On Wayland, examples panic with `Requested present mode Immediate is not in the list of supported present modes: [Mailbox, Fifo]`

On Fedora Workstation, cargo run --release --example iso (or any of the other examples) gets me:

thread 'main' panicked at 'Error in Surface::configure: Validation Error

Caused by:
    Requested present mode Immediate is not in the list of supported present modes: [Mailbox, Fifo]
', /home/mattdm/.cargo/registry/src/index.crates.io-6f17d22bba15001f/wgpu-0.16.2/src/backend/direct.rs:734:18
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Encountered a panic in system `bevy_render::view::window::prepare_windows`!

If I use WINIT_UNIX_BACKEND=x11, they work fine.

Flipping tiles on an axis

Great library. I'm render a Tiled map and am unable to find any way to flip a tile on an axis. Does this a feature exist, or would implementing this feature be possible?

Does it support 3D?

Out of curiosity, does it support 3D camera orthographic projection? I'm struggling on how to display it in the world. Any tips will be appreciated!

By the way, this crate seems to be awesome!

overhang cuts of the top

Hi!

i was trying to use overhang (perspective) to have tiles visually appear bigger.
for reference, we have those tiles:
tiles

to see it easier here is the same thing with a black grid:
Tiles_for_export_and_grid

each tile is 40x20, i used an overhang of

            vec2(4. * TILE_SIZE.x, 4. * TILE_SIZE.y), // inner
            vec2(2. * TILE_SIZE.x, 4. * TILE_SIZE.y), // lefttop
            vec2(2. * TILE_SIZE.x, 0. * TILE_SIZE.y), // rightbottom

so basically

  • 4 tiles padding between each tile
  • 2 tiles padding from the left and right
  • 4 tiles padding from the top
  • 0 tiles padding from the bottom

this is what im seeing:
image

only 4 tiles (2 horizontal, 2 vertical) are shown of the palm.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.