#tilemap #2d #tetra #sprite #ggez #2d-game #spritebatch

stabilkon

基于纹理图集的瓦片地图网格构建器

1 个不稳定版本

0.1.0 2021年12月11日

#236 in 渲染

MIT许可证

43KB
889

基于纹理图集的瓦片地图网格构建器 最新版本

这个库可以帮助你创建用于绘制大量小型静态2D图像的网格。

Teaser

想象一下创建一个带有大范围可缩放的瓦片地图的2D游戏,就像Factorio。常规的精灵批处理针对动态精灵,其数据每帧都上传到GPU。使用它们来处理大量静态图像的大范围场景可能会太慢。所以,当你在牺牲GPU内存的代价下需要甜美的渲染速度时,就是你自己为地图的块创建网格的时候了。这正是这个库帮助你做到的。

简要指南

  1. features = [ "ggez" ]features = [ "tetra" ] 应当设置,如果你计划使用其中之一。
  2. 使用ggez、Tetra或你自己的自定义顶点类型(实现了From<PosUvColor>)创建网格构建器MeshFromQuads
  3. 使用构建器的set方法(如set_pos_color_source)以任何顺序将网格四边形设置为各种图像。
  4. 完成后,调用create_mesh或,如果你没有使用ggez和Tetra,调用into_vertices_and_indices
  5. 以您喜欢的方式将网格或顶点绘制到屏幕上,只需确保顶点按顺时针顺序排列即可。您甚至可以在 set 方法中控制 UV 翻转,并使用您想要的任何坐标系。默认情况下是针对 OpenGL 的从左到右、从下到上的系统,但在示例中我垂直翻转了 UV,因为 ggez 和 Tetra 都使用从上到下。

更详细的指南

查看 ggez 测试 (ggez.rs) 或 Tetra 测试 (tetra.rs)。像往常一样,在启动 Tetra 测试之前,请确保 SDL2 开发库可用。在 Windows 上,最快的方法是将它们直接放在 crate 根目录下。长话短说,这里是一个使用 Tetra 顶点类型创建瓦片地图的示例,ggez 几乎相同。

use stabilkon::*;
use tetra::{
    graphics::{
        mesh::{IndexBuffer, Mesh, Vertex, VertexBuffer},
        Color, Rectangle, Texture,
    },
    math::Vec2,
    Context, TetraError,
};
// Load texture atlas with tile images:
let tiles_texture_atlas = Texture::new(ctx, "./path/to/texture_atlas.png")?;
// We won't be using custom shaders and such - let the mesh builder fix UVs for us:
let use_half_pixel_offset = true;

// Single tile is 32×32:
let tile_size = 32.0_f32;
// Let's make test map 256×256:
let map_size = Vec2::from(256);
// Calculate required quad limit for the map:
let quad_count = map_size.x * map_size.y;
// Pick grass tile image from atlas; it is located at the very top-left of the texture atlas.
let grass_tile_source = [0.0, 0.0, 32.0, 32.0];
// Standard white color, means tile images will be drawn as-is.
let white_color = [1.0_f32, 1.0, 1.0, 1.0];
// When adding a quad to a mesh builder, you can control UV flipping with `UvFlip` parameter.
// By default the usual left-to-right, bottom-to-top system is used.
// But we decided to use left-to-right, top-to-bottom coordinate system in tile source rectangle above, so when
// adding quads using `grass_tile_source` a `UvFlip::Vertical` will be supplied.

// Create a mesh builder for an indexed mesh capable of holding entire map...
let mut terrain_mesh_builder: MeshFromQuads<Vertex> = MeshFromQuads::new(
    [
        tiles_texture_atlas.width() as f32,
        tiles_texture_atlas.height() as f32,
    ],
    use_half_pixel_offset,
    quad_count,
)?;
// ... and add a lot of quads with grass tile texture region:
let mut quad_index = 0_u32;
for y in 0..map_size.y {
    for x in 0..map_size.x {
        let position = [x as f32 * tile_size, y as f32 * tile_size];
        terrain_mesh_builder.set_pos_color_source(
            quad_index,
            position,
            white_color,
            grass_tile_source,
            UvFlip::Vertical,
        );
        quad_index += 1;
    }
}
// Finally, create a mesh consisting of quads covered with grass tile texture region:
let (terrain_mesh, terrain_vb) = terrain_mesh_builder.create_mesh(ctx, texture_atlas)?;
// All done, now you can use this mesh as usual!

在创建静态网格后更新四边形

让我们用 Tetra 来举四边形更新的例子。ggez 几乎相同,只是您直接在创建的网格上更改顶点缓冲区。

如您所注意到的,在先前的例子中,create_mesh 调用不仅返回一个网格,还返回其顶点缓冲区。要更改四边形顶点,您需要调用顶点缓冲区的 set_data 方法,并传递更改后的顶点和它们的偏移量。

更改单个四边形

// Assume we have created indexed mesh before, it is `terrain_vb` from the upper example.
let use_indices = true;
// ...and we want to change the eight quad we have added into a hole tile.
let new_quad_index = 7;
let hole_tile_source = [160.0, 0.0, 32.0, 32.0];
// Calculate its vertex offset:
let offset = new_quad_index * vertices_per_quad(use_indices);
// Get new quad vertices:
let new_quad_params =
    PosColorSource::new([512.0, 128.0], white_color, hole_tile_source, UvFlip::Vertical);
let new_quad_vertices = new_quad_params.to_vertices(texture_size, use_indices);
// Alright, now upload new vertices at the changed offset:
terrain_vb.set_data(ctx, &new_quad_vertices, offset as usize);

更改多个四边形

// Again, we will be changing `terrain_vb`, but this time we need to have access to entire vertex buffer data.
// Because we are changing random quads, we need to get proper vertices for all quads between the first
// (with the smallest index) and the last (with the biggest index) changed quads. 
// And for that we need to keep `terrain_mesh_builder` around.
// Alright, these are the quads we are going to change into hole tiles:
let changed_quads = [7, 17, 13, 11];
let hole_tile_source = [160.0, 0.0, 32.0, 32.0];
// Find the first quad and its vertex offset:
let first_changed_quad = changed_quads.iter().min().unwrap();
let first_changed_quad_vertex_offset =
    (first_changed_quad * terrain_mesh_builder.vertices_per_quad()) as usize;
// Find the last quad and its 'end' vertex offset:
let last_changed_quad = changed_quads.iter().max().unwrap();
let last_changed_quad_vertex_offset =
    (last_changed_quad * terrain_mesh_builder.vertices_per_quad()) as usize;
let after_last_changed_quad_vertex_offset =
    last_changed_quad_vertex_offset + terrain_mesh_builder.vertices_per_quad() as usize;
// Perform our changes:
for changed_quad_index in changed_quads {
    terrain_mesh_builder.set_pos_color_source(
        changed_quad_index,
        // Just extrude tiles diagonally from the map for demo purposes:
        [-(changed_quad_index as f32 * 32.0), -(changed_quad_index as f32 * 32.0)],
        white_color,
        hole_tile_source,
        UvFlip::Vertical,
    );
}
// Upload our changes to terrain vertex buffer:
let vertices_to_upload = &terrain_mesh_builder.vertices()
    [first_changed_quad_vertex_offset..after_last_changed_quad_vertex_offset];
terrain_vb.set_data(ctx, vertices_to_upload, first_changed_quad_vertex_offset);

局限性

这里有 3 件事您可能想要记住

  1. 静态图像构建器假设每个网格使用 1 个纹理,因此所有图像都应该打包到纹理图中。如果单个图集不足以,则创建多个网格并将它们叠加。
  2. 请确保您的顶点类型是纯文本的:它应该只包含值,不应该包含指针。
  3. 网格本身应该尽可能大,但不能太大以至于 GPU 无法处理。如果有疑问,以 32 MiB 块为目标。

依赖关系

~1–36MB
~555K SLoC