A tilemap is just a more efficient way to render a grid of (generally square) sprites. Technically you could just spawn a bunch of sprites next to each other and you'd get the same visual result but performance will scale horribly. Tilemaps are a more controlled way to do that. Currently what we have in bevy is a way to render chunks but eventually we want an abstraction over this so users don't have to think about chunks and they only need to give bevy their entire map and don't need to bother splitting it in chunks.
#Tilemaps
1426 messages · Page 2 of 2 (latest)
I see
It would be nice to be able to specify which way the tiles will be layed out
I have built in chunks integrated into my tile library that does infinite tilemaps and all the fancy querying I want in bevy core, but for each insertion I have to do this:
tile_i = (chunk_size - tile_i / chunk_size - 1) * chunk_size + tile_i % chunk_size;
to recalculate the chunk index.
For most other things, it makes sense to just layout the chunk similar to world coordinates, but that doesn't work with these chunks
https://github.com/bevyengine/bevy/pull/21681
Here's a PR to do just that
Err, transform calc is wrong, gotta fix that
I left a comment that basically amounts to: why should these be options? I don't see similar options in say, godot's tilemap implementation and we don't allow configuring axis in 2d/3d/ui either
I'm only making it an option since some people are already using the current way, changing to my preferred default would be breaking
so which do you think should be the preferred default and why?
Bottom left to top right to match world coords
currently it's top left to bottom right because of default uv direction in shader?
So, I haven't looked at the PR yet but the original goal of the TilemapChunk was to be a rendering primitive and most users shouldn't be using this directly. There should be another layer sitting on top of it that makes it more user friendly and that's probably where that kind of thing should be handled
I have my own crate that is a layer on top of this and handling it directly means special casing coordinate and index calculations for the built in chunk renderer
Also it looks like I'm not the only one to run into this
my general opinion is that it would not be great from a maintainability and general use perspective to have options that nobody is using. If there's a better default I think we should switch to it rather than adding options.
the implementation in the pr for the options looks fine
if we introduce options, then everyone who uses this needs to care that there are four ways to do it
Yeah, and even if there are edge cases like "some tools export things in different orientations", that feels like an asset-processing problem, which should be fixed by a "add a setting to the tilemaps" solution
It's okay to make breaking changes, really 🙂 Just clearly document why you're doing so and how to migrate
Changing the default is certainly easier if we're okay breaking things
https://github.com/bevyengine/bevy/pull/21684
Simpler pull request
Closing the other one
what do you mean by (5, 6) in world coords => (5, 9) in chunk coords?
Aren't world coords in 2d pixels? or are these "tile coordinates"
Idk how to word it, but in world coordinates you count right 5 than up 6
5 tiles spaces right, then 6 tile spaces up?
To get that same position in a chunk on the screen you'd have to count right 5 then down 9 spaces
Yes
so you could mod the tile position and get the index of a tile in a tile chunk correctly, that's pretty cool tbh
Because y 15 in the chunk is actually the bottom edge of the chunk, where as in world coordinates that would be y 0
Yes
It makes it much easier and more intuitive to calculate things
yeah, we might want to point people to rem_euclid in the migration guide
since -1 would be chunk_length-1 tile pos
ran it, it works as expected. I gave an approval but I'm expecting IceSentry to also take a look at some point when they have time since they have the vision for the chunk abstraction in their head.
Needs a clippy fix to pass CI
I confirmed ldtk levels are top-left 0,0, and tiled seems to also have this layout in the editor as well, fwiw. Didn't check unity/godot
Godot is going to be the same, unity bottom left is 0,0 iirc
But godots whole coordinate system is -y up
I think the engines tilemap feature should match it's coordinate system
Yeah, sorry, I should have mentioned that earlier. My point was mostly that making it configurable should be higher level but a more useful default orientation makes sense to me.
I actually haven't thought about that part that much other than knowing I want the rendering primitives and high level end user api separated. I'm mostly hoping the community can build that part and I can simply help guide/review/move it along. I don't really have time to spend a lot of time on it unfortunately.
yeah that's fine by me, that's not a "we shouldn't merge" because of this, but rather a sharing of "this is the impact we're having with this"
I have a high level (but bad) impl of auto chunking and handling arbitrary tile types if you feel like looking and giving feedback
Right now I think the biggest unsolved question is whether or not we want to make each tile an entity. I'm partial to doing it this way for the official implementation since I believe it aligns a bit better with bevy's goal of using ECS for everything but it's also a bit of a controversial api.
But at this point I might be willing to accept anything that actually works and is well written
I think we shouldn't have each tile be an entity
But
I think we should easily enable that on top of the chunk abstraction
That's exactly why I want to make sure the rendering and end user api are separate. I know not everyone wants entity-per-tile, but I'm pretty sure most people want some way to render a tilemap efficiently
well, you know where I stand on that so. 😆
Basically, in my mind the Chunk is just an entity with Vec<T: TileComponent> components stored on it, so if a user wants a tile per entity, we'd have a chunk with Vec<Option<Entity>> stored on it
I'm more than happy to accept various utility functions that can be used to build both approaches though
I don't think having a non-entity api is bad, but having entity as a default for people that aren't doing 14million tile single chunk tilemaps is far more useful
I mean the entity count can add up really quick when you factor in layers and such
Yeah, I mean, what I have in mind is that a high level api would end up filling those vecs in the backend
https://github.com/jamescarterbell/bevy_tiles/tree/bevy_0_17/crates/bevy_tiles_ecs
Here's the branch I'm working on that does it
I mean, this really implies that all entities are always live for all tiles though, doesn't it?
What do you mean?
even ones 3 hundred thousand tiles off-screen
(I'm picking arbitrary numbers, obviously)
but like, the entity count mattering really requires having live entities that aren't really involved in the game being spawned
Bevy should be able to handle really high entity count if most of these entities don't change every frame and you aren't iterating the full list every frame
There used to be an issue caused by the render world that would slow things down horribly when hitting a high amount of entities but that has been resolved
If you're not iterating the full list every frame and not changing them very often why do they need to be entities?
And what about accidentally iterating them every frame or proposing hierarchy changes on accident via transform?
because entities are fundamentally a useful tool for attaching data and behavior to
The chunk rendering kinda shows that for the rendering portion, what really matters is the chunk, and I think that's true for a lot of tile use cases, if you want full ecs it's better to opt in
Sure, and they are a good tool to opt into imo
they are bevy's default though, so they would be opt-out
Technically bevy today has tiles now without entity per tile
In the future do you want what currently exists to require an entity per tile?
I never said I wanted to require it
I think its the right default behavior
and like I was saying before, chunking usually involves despawning/respawning entities. For example: 3d terrain
For tiles, yes, but for almost anything else bevy uses entities as the core api
so why would a massive tilemap have every entity be always-live if it was chunked?
You can spawn and despawn the chunks just as vecs
I can definitely envision edge cases (dwarf fortress maybe?), but that kind of scale isn't the default
It's actually easier
Yes, and that's exactly why I wanted it to be abstracted independently. For rendering it matters, but for gameplay it's a different story where both solution are valid
You have to opt in to an entity per tile by spawning an entity with the tile component
Imo
The opt out is using some other api that lets you work with chunks more directly
But in my mind that's not having an entity per tile be the default
It's the default in the sense that it would be the default end user api that is being shown/advertised/used in examples.
(also just to be clear, its fine to differ in opinion on this. "both apis" are going to exist)
And people not using that end user api would be opting out of that. Part of the idea there is also that we could make changes to the rendering side without needing to impact the end users. So people not using this api would essentially opt out of that stability. (tbf, we don't have any stability guarantess right now but that's the general idea)
And yeah, I'm pretty sure it's possible to make the current chunk api easier to use and build the entity per tile on top of that. That way most people should have what they need.
So the problem I have in my mind is generating a physics mesh per chunk.
If entity per tile is the default, I have to do a lot of entity subqueries per map to get the entities per chunk so I know which physics shapes I need to add up.
If we have some arbitrary chunk data store, I can just query for the Chunk<CollisionInfo>(Vec<Option<CollisionInfo>>) type on each chunk pretty straightforwardwardly
And if it's a static level, I get no benefit from having an entity per tile, so I'd rather be able to completely side step that
And I don't think this is a particularly advanced use case
I don't see why those colliders wouldn't be pre-computed if it was static
this is a pretty common conversation with avian even for 3d applications
For the same reason you don't precompute the mesh and texture for each chunk in a mostly static tilemap, it's kinda annoying
I think you're misconstruing "default" with "will never be able to avoid"
you won't have to use it if you don't want to
I'm worried about entity per tile having to be "avoided"
why?
Because I think for the vast majority of use cases it's unnecessary
that's not really a reason for it not to exist, and you're allowed to have that opinion
I'm not saying it shouldnt exist, I think it absolutely should, it's a sick feature and use case
But I don't think it should be used to supercede other options when a not ecs integrated solution would make more sense, and I don't think it should be pushed as the default way of using tilemaps.
For instance, I don't think importing an ldtk map should automatically spawn an entity per tile
its fine to have that opinion, but the most widely used ldtk integration does exactly that, and its not being pushed at all
That's also one of the first and only maintained tilemap libraries and doesn't have any other mode of operation
to reframe it: Bevy has already made the choice to be entity-forward in contrast to other engines. It makes sense to use entities as the default API because everything else revolves around them (observers, etc). Not using entities is opting-out of the rest of bevy's offering.
Then the chunk api as today should be changed, the chunk entity should be a render world construct
I think it would be less useful that way, but it would be entity forward
there's a draft pr that I've already linked to you in the past that makes these changes
the current implementation is a foothold implementation to land something in the 0.17 cycle
And it doesn't need entity per tile
and the render chunk api isn't "going anywhere", other apis will be built on top
Just like importing an ldtk map doesn't need an entity per tile
I don't feel like this conversation is going anywhere so I'm going to step away. To be clear: nobody is going to force you to use entities, but that is the primary bevy api so makes sense as the default high-level interface.
I've looked at the PR and it has the issue I'm talking about, it's entirely focused on Entity per tile, it doesn't build out an API that Entity per tile can be built on, it makes Entity per tile the only way of doing things outside of rendering a chunk
So I can avoid using it, but then I have to write my own bespoke chunking impl
Also just to be clear since I know we were talking past each other, I do understand that everyone here wants the best user experience and engine possible, I don't mean to be rude if/when I am.
we're good in my mind. 
I integrated TilemapChunk into my game. I see that you recently inverted the y axis to match Bevy's other coordinate systems. I agree with this change. It was unexpected that 0,0 in the chunk was referencing the top-left tile.
Playing around with tilemaps between meetings
If we have a tilemap component on entities that enables so automatical chunk spawning/handling, what would be an elegant way of handling an insane user who decides to reinsert the map with a new chunk size?
https://github.com/bevyengine/bevy/pull/21756
Here's the vague start to what I'm thinking of, for tile entities they'd use TileStorage<Entity>
Yep, that's what was just mentioned above 😆
What do you mean by reinsert the map with a new chunk size?
So I imagine the size of a chunk will be uniform in a tilemap, so the best place to store the chunk size would be on a tilemap component that the user can instantiate
If we automatically spawn new chunks when we insert a tile, we will use that components chunk size to calculate the chunks tile storage size
However, since we're letting the user instantiate the tilemap component, and are letting them choose the chunk size, they can insert a new tilemap component on an entity that already has one
In that case, all the existing chunks in that tilemap would be the incorrect size, and we'd have to update the sizes and rearrange tiles between chunks
It would be a mega pain to write and a pretty slow/bad thing for the user to do, but idk if we could technically stop them
I think that's a valid assumption but probably only limited per layer. I could imagine someone wanting different size per layer but that's probably not an issue here?
In my mind a different layer is just a different tilemap, but yes, within a layer the chunk size is uniform
Yeah, me too, but just wanted to make sure
Maybe if they change chunk size at runtime we just panic!("Please stop being cringe")
Anyway, just a thought I had while working on this
I would prefer not using that kind of language and if possible avoid panics and just return an error, but yes, I'm fine with that kind of scenario being considered an error
I think my draft PR handles this unless I’m misunderstanding
Handles automatic chunk creation on tile insertion I mean
I also think we want TileStorage to store entities probably? I’m not sure about making it generic
It's about what happens after a chunk has been created
So if someone set 4x4 as the chunk size on the top level map and then created a chunk, it would be a Vec with size 16
But then if they reinsert the map component on the map entity with chunk size 6x6, what happens to the 4x4 chunk?
I'm gonna work on it more this week hopefully, but the purpose of making it generic is that you don't need to store tile entities if you're not opting into tile entities
I think the decision was already made that tile entities are opt-out, not opt-in. I think what that means in practice is that anything intuitively named lke Tilemap or TilemapLayer should by default have entity-based tile storage. But then maybe we can have a second set of components that opt out of entities.
Ah I stand corrected -- [cart before had mentioned](#1367261228459884604 message) that opt-in is worth considering. So it's more just what the final API looks like and how comfortable people are with it.
So I guess no explicit decision, however I think we were sort of headed in the "opt-out" direction generally.
I think the opt-in opt-out discussion is not getting at the proper issue
You always have to opt-in to entity per tilemap because you have to spawn a tile
Now when you spawn that tile, it can create the TileStorage<Entity> component on the entity chunk
If you don't want a tile per entity, you just don't spawn tile entities
You work with TileStorage<T> directly, or some other api's that work on the specific storages
You get tile per entity if you want it, and you don't have to sacrifice any other use case while they all use common components and functionality
^
The miscommunication here seems to be that @coral harbor seems to think that "the default APIs" will mean "everyone has to use entities" which is not the case. Users will be able to not use entities, and both use cases will be supported.
Correct
cool, I think we're all on the same page then now.
https://github.com/bevyengine/bevy/pull/21756/commits/620817d9643225996da60d4f2bf9351cdfc32f3a
Used lunch to write a quick example showing tile entities built off generic storages
It's pretty straightforward imo, the only issue I see is batching large number of spawns
Basically: component hook on a TileCoord component automatically puts the tiles entity into a chunks tile storage
In the example, it looks like you're spawning the tile entities in update. Would you instead want to spawn in setup and then just update the TileRenderData component?
I was just copying my non-entity example
But that would also work
(I'd need to change the component hook but this is poc level code)
Is there a way to remove a component without triggering the component hook?
No
Hrmm... How opposed are we to having that behind unsafe?
I'm investigating but I think it's necessary for automatic tile cleanup
Basically, if I remove a TileCoord component from an entity, I need to do some cleanup in the tile storage.
If I place a tile on top of another tile, I remove the old tile from storage, then remove the TileCoord from the replaced entity, but that will trigger the same cleanup code as above, even though the cleanup is done.
I think this may be why ecs_tilemap stores the previous position as well? Correct me if I'm wrong @coral wolf
actually maybe ecs_tilemap doesn't handle this case
if this is about a TileStorage-like (in the ecs_tilemap sense) use case, then no. ecs_tilemap doesn't automatically manage that index storage for you (there's multiple ways to manage indexes)
hooks are meant for this kind of index upkeep though, so I'm not sure why you'd need unsafe or to avoid triggering a hook
You'd want to avoid triggering the hook to avoid repeated work and wrong behavior:
I have a tile at (0,0), a place a new tile at (0,0), this removes the old tile and I remove the TileCoord component from it.
Removing the TileCoord component would also be a way to remove the tile from the map, so now since we removed the component from the replaced tile, we go in and remove that tile from storage... But we already did that when we placed the old tile, so really we're just removing the new tile
sounds like a problem that stems from trying to manage the index manually without the components
No, it's just a direct result of having to manage indices through hooks:
world.spawn(TileCoord(0,0))
What should happen if you call that twice?
if you have an entity with a TileCoord or whatnot, and that gets despawned or removed, then that on_remove/on_insert, etc fires, which is all the proper ordering for it right?
probably the despawn of the first entity or possibly a warning depending on your viewpoint
That's what I think also
So we spawn the first entity and insert it into some chunk as the entity at (0,0)
Then we go to spawn the second entity, it sees there's an entity at (0,0) and replaces it in the chunk then goes to despawn it
Now when despawning the first entity, we remove TileCoord
I guess the thought there is to enable sparse TileCoord entities?
So when you remove TileCoord from an entity, do we think that should do some cleanup for said entity in the Tile storage?
tile storage here is TilemapChunkTileData? or something else?
For the sake of argument yes, it's the think that lets a chunk know an entity is in the chunk
so for the sake of argument, there's no entity->tiledata sort of index to use?
We have a Vec of entities on a chunk and we have entities with TileCoord components
seems like an error to spawn TileCoord components if one already exists
if we can't track the entity responsible for the tiledata
It doesn't seem like an error to me, it seems like something totally normal to do
it feels like it is, almost like these are getting treated as Resources instead
I can't think of another example of a second entity getting spawned with the same components that then fully replaces the first
is there an example I'm not thinking of?
I think replacing the first would be optional
(Resources basically having the API of being entities with component data, if you haven't been following that discussion)
But that's not the important part
A tile is just something that has a relationship to a chunk indexed by coordinates, something else should be able to take over that relationship without being an error
Putting something into a hash map that is already there isn't an error, you just replace the old thing and get the old thing back, that seems normal for tilemaps
so yeah I see what you're getting at since there won't be a hook to fire for the existing entity, if a new one is spawned in without it knowing
but that really feels like the original one should be replaced, not a new entity inserted
I mean the original one would be replaced, but then the original one will have things trigger that can remove the new one
Or there's just a bunch of weird ordering things to figure out
Not going to even start talking about the nightmare of multiple tile storages on a chunk
Which I do think is correct but requires more book keeping
Ecs compatible tilemaps are not straightforward
I think the "two entities should be able to exist for the same TileCoord" is the weird part here
I don't think they should
I think one of them should lose their TileCoord component or be despawned
yeah which is why I was referencing the Resource use case earlier, and its special set of apis for enabling that behavior
if you .insert_resource, for example, that results in the Resource getting replaced
I see the argument for using .spawn to do this, in that its familiar to users, but it is not a behavior I can think of any other spawn currently having
Sure, but in the event the user wants to keep the old tile entity, we wouldn't want to replace it in the world, just the tile storage, we'd only want to remove the TileCoord component
well this would be solved by not automating it too, afaict
Not automating the TileCoord removal?
a custom command would potentially solve it, since that would have world access
but that's a departure from .spawn
Yeah
yeah, basically not trying to be smart about it and saying instead "if you've spawned a tile at this coord, then you have a tile at this coord, don't spawn another"
My issue with that is it gives the user a whole lot of book keeping to do when using this feature
And you don't have to do that book keeping in any other engine imo
secondary question: would the first entity despawn or just have the component removed?
both feel valid
I was thinking there'd be a DontDespawnOnRemoved component or vice versa
So you could have both
eh, I don't love that 😅
Since I think both are valid
I mean it's not the cleanest
We could also hide the flag in the InMap component
Imma just keep working on this PR and making big sweeping decisions so there's something to look at
In my bevy_tiles crates I did things via custom commands and it gets pretty ugly
But idk maybe that's the right way
trying to modify other entities in hooks also feels really weird
It comes with the territory of some entities being psuedo storages for other entities
I think it comes with not having an index afaict
Like secondary entity indices?
just any index at all. ie: trying to compress into just a Vec<WhateverData> without entities vs having entity keys or values around to check
.spawn could also be disallowed, which also solves the problem and allows hooks to only operate on the current component
(and I do think enabling .spawn to overwrite other data is new and strange behavior compared to what exists right now)
not that its Wrong or anything, I just can't think of anything else that does that
I mean don't relations have to use hooks to write to other entities?
they insert themselves into a collection, yeah. They don't despawn other entities.
like to remove an entity from Children you'd remove the ChildOf on that entity. Which would translate to removing TileCoord.
This would be that but one step further and it is a bit strange
Was just fiddling around and finally figured out where the rounding error I've been running into is. In the shader the local_uv isn't using a clamped value, so it gets the wrong value when in.uv is >= 1.
And even with vertex UVs only in the range [0, 1] you can still get fragments with an input uv > 1 depending on camera position, so you get flickering seams between chunks.
I fixed it by doing this:
let tile_uv = clamp(in.uv, vec2<f32>(0.0), vec2<f32>(0.9999999)) * vec2<f32>(chunk_size);
var tile_coord = vec2<u32>(floor(tile_uv));
but it feels a little janky
Okay, one parental medical episode, one grandparental episode, one dog broken leg, and one family dog passing later, I have added automatic tile removal to my PoC
I'm so tired gang 🥲
Anyway, here's the problem: If you have an Tile Entity or a Non-Entity Tile, when you Despawn it, everything keeping track of the Tile will want to hear about it somehow to cleanup whatever they are tracking.
Since my approach to this problem consists of a chunk entity that stores tile data that we don't want living on an entity for whatever reason (perf opts for things like collision mesh generation for example) but still supports tiles as entities, it means that when a tile entity gets respawned, we probably want to remove that tile from each collection on the chunk, for example:
CHUNK: TILE_SPRITES [ .. ] | TILE_COLLIDER_SHAPES [ .. ] | TILE_HEALTH [ .. ] | TILE_ENTITIES [ .. ]
So say I despawn a tile entity, I also need to remove that tiles sprite, collider, and health from these collections. The issue is... well that's kind of hard to do dynamically since tiles can have arbitrary information added, but I figured it out.
Basically, the chunk now keeps track of what tile storages it has added to it, when a tile storage gets added, we also register the function for removing data at a specific index, then in the tile removal/despawn code we have the following:
for (tile_storage, tile_removal) in tile_storages.removals {
let Ok(storage) = tile_storage_entity.get_mut_by_id(tile_storage) else {
continue;
};
tile_removal(storage, tile_relative_position);
}
To automatically go and clear out the data for that tile.
I think this is the final basic functionality that is needed for spawning/despawning adding/removing, and I think it's pretty intuitive. Next up is the specialized query type for spatial info.
Innnnteresting, I don't supposed you could grab a clip of the flickering? I haven't noticed it yet
Quality isn't great, but you can kinda see it here
Oh discord can't convert that
webm maybe?
there we go
Uploaded a little demo here: https://github.com/grind086/bevy_flickering_tiles/tree/main
Arrows or WASD to move, F3 to toggle tile (white) and chunk (magenta) debug grid
someone filed a similar issue today: https://github.com/bevyengine/bevy/issues/22250
Does the fix I came up with look okay? I can open a PR if so
the 0.999999 looks suspicious tbh, but I wouldn't dissuade you from a PR. At least the issue/feedback/etc would be documented on github then
Yeah that's my main concern. It's 1.0 - f32::EPSILON so it's not completely arbitrary, but it might cause issues with big chunks.
Going to push another change tonight that includes a TileQuery:
fn set_tile(
mut commands: Commands,
mut clicks: MessageReader<Pointer<Click>>,
camera_query: Single<(&Camera, &GlobalTransform)>,
map: Single<Entity, With<Tilemap>>,
mut tiles: TilemapQuery<&mut TileRenderData>,
) {
let (camera, camera_transform) = *camera_query;
let map = *map;
let mut tiles = tiles.get_map_mut(map).unwrap();
for click in clicks.read() {
if let Ok(tile_coord) =
camera.viewport_to_world_2d(camera_transform, click.pointer_location.position)
{
let tile_coord = tiles.map.get_tile_coord(tile_coord);
info!("{}", tile_coord);
if let Some(tile) = tiles.get_at_mut(tile_coord) {
tile.tileset_index = (tile.tileset_index + 1) % 4;
} else {
commands.set_tile(
map,
tile_coord,
Some(TileRenderData {
tileset_index: 0,
..Default::default()
}),
);
}
}
}
}
Check the 2d/tilemap example to try it, it basically lets you do positional lookups into the map via a system param, I'll do the Entity compat version tomorrow, that will let you query Entity tiles via their tile index and is built ontop of the TilemapQuery
Going to do one more push then I'm hoping I can get some eyes on this before during the spatial iterators:
Added TileEntity queries, this is an entity query for entities that have a TileCoord component and handles all the logic of indexing, so you can just look up a tile entity by it's coordinate, I also added a usage example to the breakout example:
fn on_bomb_hit(
hit: On<BrickHit>,
map: Single<Entity, With<Tilemap>>,
bombs: Query<(Entity, &TileCoord), With<Bomb>>,
bricks: TilemapEntityQuery<Entity, With<Brick>>,
mut score: ResMut<Score>,
mut commands: Commands,
) {
let Ok((bomb_id, bomb_coord)) = bombs.get(hit.0) else {
return;
};
commands.entity(bomb_id).despawn();
**score += 1;
let Some(bricks) = bricks.get_map(*map) else {
return;
};
for coord in bomb_coord.adjacent() {
if let Some(brick) = bricks.get_at(*coord) {
commands.entity(brick).trigger(BrickHit);
}
}
}
It makes it pretty straightforward to start doing spatial based stuff in the grid
https://github.com/bevyengine/bevy/pull/21756
I'm really thinking this is viable as a bridge for tile entitites and non tile entities
@coral wolf ^ when you have a chance
Updated the PR with a big ol' wall of text and example videos
So a friend is trying out bevy, and their project involves a tilemap. (After running into perf issues caused by entity-per-tile of bevy_ecs_tilemap) They're trying the built-in bevy tilemap.
First they ran into https://github.com/bevyengine/bevy/issues/21501, which would've been difficult to debug, but luckily I knew of the issue.
Then they had issues with finding the documentation of the coordinate systems used, but that needed just some trial and error.
Now they're dealing with these one-pixel-thick line artifacts which, as far as I can tell, are caused by texture samplers sampling into neighbouring tiles of the tilemap.
I feel like this is a bug in the renderer?
And is there a workaround? (Usually I'd add padding around the sprites in the texture, but I don't see how to make the renderer skip over the margins.)
Oh sorry, now I found the already existing issue for it. But still, if anyone knows a workaround, that would be salient.
I think disabling MSAA should work here?
MSAA? I'm not sure how MSAA would cause these, but we can try it.
do you know what the "perf issues" were with bevy_ecs_tilemap? There's a benchmark example that renders 1.5 million tiles at > 60fps
Spawning the tiles (which were in the hundreds of thousands by my rough estimate) took about 250ms, then there was also some "entity sync" lag?
I can show you the screenshot of the tracy profile I saved:
Some of the perfs bottlenecks are due to user code
that's cool of you, thanks 🙂
But I believe entity_sync and some of those extract schedules are out of direct control. (And by themselves are too long to fit in a frame)
yeah "entity spawn speed" similar to "transform propagation speed" is an upstream issue generally
Yeah, we tried also using spawn_batch instead of individual spawns, but that made only a few ms of improvement, not saving a 1.7s frame (honestly it was within measurement error)
For reasons, MSAA can cause texture bleeding where it ends up reading data just outside of the bounds of a tile
Interesting. We'll try disabling it then
MSAA is generally a good default to have except for pixel art where it's not great
Okay so apparently disabling MSAA (by inserting Msaa::Off) just, broke rendering entirely? (Currently trying to troubleshoot it)
@thorny spruce curious, is entity per tile something they need?
Nope, they just picked bevy_ecs_tilemap initially because it had tile flipping support.
So removing the entities is a feature in this case.
Updates from tilemap adventures:
Moving from bevy_ecs_tilemap to the bevy builtin greatly improved perf:
The game still chugs, but this time it's 95% user systems, so it's not a bevy problem
This was a big annoyance
we ended up patching the renderer
oof
what was the patch?
So are you running MSAA right now?
I'm still confused by the part where disabling msaa broke things
Yeah Msaa is still running, I honestly have no clue what happened with the msaa, but turning it off made the screen blank (camera clear colour)
That's very strange. No error messages?
If you can make a repro that would be really nice
Yup, it's very simple to repro, turns out
Nvm, I am confused now, I'll investigate tomorrow
I must've made a mistake in the repro and tested the wrong thing
Here's some feedback from the tilemap user (as of 0.17.3, maybe you've already patched some of those things):
The tilemap works pretty great, but:
Getting the texture 2Darray format on the image seems needlessly complicated, and there was no way for a newbie to figure out the bug from the wgpu errors behind the crash.
The texture bleeding is difficult to deal with, and it probably should've just worked out of the box.
They would've liked support for flipping the tiles (in all 8 possible symmetries of the square, so 2 axis flips and a diagonal one) (I've been thinking bevy_math maybe should have a symmetry type for this.)
Coordinate systems (indexing, anchor, position in local space) would warrant better docs.
Def agree on all of the above
My biggest issue is the tight coupling with TilemapMaterial. I think things would be a lot easier to tweak if we could put the tile data somewhere else. Or make TilemapMaterial extensible without messing with the tile data upload.
I want to see how the bevy_material split turns out before trying to implement something though. There's functionality in 3d that might be useful if 2d/3d materials can be merged.
Hey, do y'all have any GSoC project ideas for #1460367944193282089? This working group feels like it's prime for "high value low urgency technically crunchy" work
Very late but not that I can think of
It really is just, "get some base tilemaps feature" merged in
Then iterate and expand on it
Speaking of, I'd love to work more on that PR if it seems like a reasonable direction for tilemaps to go 😳
can you link that PR for those of us who are not following super closely?
also, what needs to happen on it? Do you need maintainer guidance on direction?
Basically, if there seems to be positive vibes I'll work on it more, clean it up and add more features, otherwise I'll close it
I just wanted enough code to show the idea and see if it's usable before making it a fully fledged feature
My vibes are very positive
This is very much the sort of thing I want as a user
And seems nice to hook into
@high otter may have opinions too
haven’t clicked yet or dug in - is this meant to be an alternative for bevy_ecs_tilemap?
If so, I would echo @rugged sapphire’s positive vibes for bevy_map_editor
also, nothing against bevy_ecs_tilemap either - but nice to get first party support for tilemaps
Yep 🙂
Yes but not bevy_map_editor, this is just adding tilemaps with auto chunking and tile entity support to bevy
No auto tiling, no editors, no levels, just the basic map and utilities for working with it
I think auto tiling and such can be built on top, just like this is built on the chunk renderer
For background and shameless plugging, I’m the creator of bevy_map_editor which does this 🙂
We use bevy_ecs_tilemap quite heavily
And it would be nice to have tilemap support without tiles being forced as entities
Do you care if the tilemap + chunks are entities?
I see, I assumed it was doing all the tile stuff also, but that makes sense
I wanted to avoid writing it from scratch - but we have an autotiling algorithm based on Tiled
Not necessarily really, just less overhead
Could be nice for infinite maps, but we don’t support infinite maps yet 🙂
My overall goal for this working group is to keep the entity per tile part only as a user level api for those that like that but the rendering shouldn't have to care about that at all and users that don't like it shouldn't be forced into it
having chunks and tilemap metadata as entities seems pretty low overhead to me?
It is, I think he's saying it's fine
Yes exsctly
Ah, yeah, I misinterpreted that 😅
Tbh, overhead is only really important for infinite maps which we don’t really support yet, and I haven’t really performance tested hard the limits of having tiles as entities
I generally only work in 50x50 or 100x100 right now - but we obviously want to support much larger tilemaps
1000x1000 did seem ok in bevy_map_editor though
I would say that I’m happy to help test this heavily though whenever you need
I'm pretty sure the current approach with chunks as entities will work with infinite world. No game is actually infinite and the amount of chunks you would need for the ECS part to be an issue would be absolutely giant
@coral harbor just a question on the example snippet:
bricks: TilemapEntityQuery<Entity, With<Brick>>,
Rather than a specific Query type for Tilemaps, could we improve the ergonomics a bit to just use Query<Tilemap, With<Brick>> because presumably the Tilemap itself is an entity? Or have I misunderstood that
The breakout example can mostly be ignored, it's a bit of a cumbersome addition to an already fine example
The Tilemap itself is an entity, but the bricks are tiles in the tilemap
Tile entities specifically, so we look up the map, then can index which brick we want
The TilemapEntityQuery and TilemapQuery's are designed for looking up items in tilemaps and are basically nested queries with functionality for doing positional look ups
Yes that makes sense to me tbh
I’m probably not well placed to give a proper review of your implementation, but i can certainly help test it!
I think I may have just found a fun issue.
I was getting this on WASM while trying to load a tilemap with a texture that happened to contain 12 tiles
adding an empty 13th tile to the texture fixed it
Oh wow, that is supremely annoying
hm, thought that was fixed in https://github.com/StarArawn/bevy_ecs_tilemap/pull/557
this is for the built-in tilemap afaik, so probably doesn't have that fix
oh oops, wrong thread
but yeah, seems like yall will need the same janky workaround
I had fixed this at some point, but maybe that was part of my unmerged PR.. let me take a look
well, the "fix" here is to say don't do it x.x
There was some reason we didn't want to just increment the layer count at the time, but I don't remember why..
ah right, we discussed it on the original PR: https://github.com/bevyengine/bevy/pull/18866#issuecomment-2853112066
My original thinking is that we'd have a tileset asset processor and it'd be fixed there.
Tilemaps
It might be worth logging a warning in ImageLoader or something. I could see this being a problem for sprite animations too if someone uses an array texture instead of the built-in atlases.
I've just implemented a (bad) tilemap for my needs. Which is rendering maps (actual factual maps, so each tile is ever only used once and when moving the map new tiles need to be inserted constantly). I do also need all of the data in a single texture/texture array so I can do wacky projections and such.
-> Should I expect to be able to use Bevy's tilemaps in the future? Or am I firmly in the niche category?
Can you explain your use case a bit more? What do you mean by factual maps? Also, what data do you need in the texture-- the tile indices and such?
by actual maps I mean that I'm drawing real world maps, so getting tiles from OpenStreetMap and similar. so unlike in the usual videogame scenario no single tile is ever rendered multiple times on the same tilemap. but i do need all of the tiles on a single texture so i can reproject them before presenting #showcase message
I'm guessing I'm in the niche category but it would be nice to be able to use bevy's implementation for my usecase too
Oh yeah that's an interesting use case. I guess the fact that no tile is ever reused means it doesn't fit very well. You would have to create a massive tileset, or dynamically swap tilesets out I guess?
@coral harbor I finally added an initial review to your tilemap pr -- great work on it: https://github.com/bevyengine/bevy/pull/21756#pullrequestreview-3835107176
and I'm doing both at the moment, I have the NxN tilemap as a texture array and just update singular tiles on gpu when they're loaded, and when camera moves enough the whole tilemap needs to be updated, it works really well actually, even the naive "just build a single large 4/8/16k texture on CPU and upload to GPU whenever it changes" worked much better than I'd expected
Sick, I'll take a look this weekend!
so I did some thinking about tilemaps
the basic point i want to make: https://gist.github.com/mrkybe/6b6060923ede6229cd5bf6122221504c
what a tilemap in game engines could/wants to be: https://gist.github.com/mrkybe/bd9ec3fb8d73d4116899304eb0ff0f8a
written mostly by Claude
I'm reading through this and there's some interesting points. I would say that as long as you confirmed what was written and you're standing behind it, I don't care who/what wrote it. You did mention not confirming the engine breakdown table -- it'd be nice if you were able to confirm what's there.
Also you admit at the end it's ai slop x.x -- I don't know if that was in jest, but please don't send ai slop.
These are just my opinions btw, I know Bevy has a stricter stance on ai contributed code, but I'm not really sure where it lands on ai ideation/prototyping stuff.
I think a baseline of "the ideas are yours and not generated by ai" and "I checked the data" is a reasonable bar to even start to have other people engage with it tbh. Otherwise they might as well be linking to "let me google that for you"
I don't even think "let me google that for you" is a bad thing in some cases. Like if the person sending it knew a specific thing to query google the target wasn't aware of (like some data structure or something). I think in this case it's pretty clear @fast osprey had to write some pretty specific prompts and do some iteration on the AI response to get it to this state, so I also think it's a bit more effort than just googling something obvious.
I'm going to walk away from this since we seem to have different ideas of what is acceptable here. This is self-described unchecked ai-slop, and is exactly the kind of thing that takes more time to evaluate than it does to generate. It doesn't get much clearer than the author stating that in the post.
My tilemap PR directly addresses your concern, it is mainly a gridded look up system for data or entities that has an integration with the tilemap chunk renderer
But also I don't think you needed AI to make this point, I've made the argument a few times
The asymmetry of effort is real... Dealing with managers opening 10x PRs than ever before (they are all incorrect so far :))
I did read through it about dozen times and tweak it. wrt, the Engine breakdown table, I can speak to Unity being correct bc that was my engine of choice for the last decade. I think the main point I wanted to make is 'don't build a tilemap just because everyone else has a thing called tilemap, it ends up being a very leaky abstraction in practice, and there's a lot of tradeoffs involved.' ie: Unity's 'TileMap', which lets you attach gameobjects to tiles, create tileprefabs, etc, can be an immediate foot-gun for noobs trying to make a big world ("oh ill make a tile and attach my trees to it"). (and also the size of the tilemap gets serialized and doesn't update when you delete tiles in editor, so if you make some tilemap that is 100x100, delete it until its 10x10, you will have so much fun when some other part of your game wants to know the width/height of a tilemap. you just have to hit that edge case, struggle, and find out you need to call recalculatebounds sometimes)
and Unity's terrain system... is why there's multiple asset store assets selling terrain systems. And like, its also a basically a kind of tilemap.
What I'm saying is, instead of having a 'TileMap', have different versions 'grid-aligned-data-rendering-system' that are optimized for the different use-cases that come up in practice, so that where the shared APIs/features between them are, the re-use happens in a sensible way. cuz there's a lot of commonality between '2d TileMap' and '3d height field TerrainMap'.
https://github.com/bevyengine/bevy/issues/23171
There's some very actionable testing / debugging in here about how we're handling colors. It's above my rendering paygrade though, so I would appreciate some other eyes
@rose bear you might have ideas about this ^
oh , while I might have ideas what the bug is... this might be unrelated to what I was fixing/doing since that was alpha-compositing in nonlinear color spaces. this seems like there is a final display transform / gamma curve applied to the texture. probably texture format inconsistency between the render graph internals and what is presented on screen later. very similar to the egui blit challenges you face
the incorrect gradient comes from srgb being treated as linear
Yeah, I wouldn't be surprised it's something similar going on
I feel like our render graph needs a good way to tag passes as srgb or linear - both the inputs and outputs from shaders. that way tracking down bugs like this would be trivial.
I think now mostly everything assumes it's linear.
I did some brainstorming about this topic. I need to translate it to English first and type it because it's handwritten, but I'm happy to share it
maybe I put too much effort to cross validate my ideas about the tile map system, and in my research process I found this document
https://hackmd.io/@bevy/Hkum_o-xll
Is this still relevant ?
Because it looks like, I wrote more or less the same thing, but with some extra or advanced features
He wrote the current chunk renderer
I will check the review of the tilemap PR this week
Packed bit of life recently
No worries, I will write down my ideas as an extension of this document/pr
I've been working on figuring out how to allow for tilemaps to use a grid arranged tileset rather than a vertically stacked one. I gathered some notes and dropped them in https://github.com/bevyengine/bevy/issues/23492#issuecomment-4188125763 Does this seem like a good plan or am I approaching this completely wrong?
Sorry for being late with this, here is my brainstorming notes to extending the tilemap renderer implementation.
Thanks for the writeup! TileEntityMode per-chunk is interesting, but I wonder if the same could be achieved with multiple tilemap layers. Definitely take a look at @coral harbor's PR (https://github.com/bevyengine/bevy/pull/21756) and my draft PR (https://github.com/bevyengine/bevy/pull/20205) to see some ECS api approaches.
The idea behind this is to prevent the entity explosion by setting up some sort of limit (in reality, it's more like a QoL feature for newcomers and devs who don't want to implement all the necessary systems).
Advanced users will still be able to use the Custom option when they need to implement the related systems and the brakes on their creations.
Using multiple layers is still possible with this.
Just a quick skim this basically looks like what my PR does
There's just no automatic turning of data tiles into entity tiles, but that wouldn't be hard for someone to build on top
I was not aware of you PR until Conner shared with me and honestly I was lazy to read through.
But I'm happy for the similarities
Check it out and give it a review, I'll eventually address comments 🫣
Who's the SME assigned to this working group?
This is legacy and does not have one
Sure, any time
let me know if there's anything I can do to help get this PR merged!
Should we try to get more reviews on it so you can batch review?
That would be good
I'm trying to find a window for review in one batch; I started yesterday, but it's difficult to manage, besides my daily work (Chaos Driven Development 4 life)
@rugged sapphire do you know what the motivaiton for this is?
I'll lay out the case clearly in the pr discussion
I don't think the reasons to revert it are sound. Shared my thoughts. https://github.com/bevyengine/bevy/pull/24275#issuecomment-4473347647