-
I'm trying to figure out how to use tmx and tsx files from Tiled in my game, but the example I'm looking at, The Dungeon Puzzler's Lament, is frankly far too large and complex to be easily understandable. I've also looked at The Purple Night which is simpler, but mostly applicable to side-scrollers. If all I want is a static top-down level with no layers, objects, entities or Tiled extensions, what would my build.rs file need to output? |
Beta Was this translation helpful? Give feedback.
Replies: 3 comments 1 reply
-
Okay, I finally figured it out. No idea if this is the most performant solution, but it works for me. build.rs use std::env;
use std::fs::File;
use std::io::{BufWriter, Write};
use std::path::Path;
use quote::quote;
// Work-around to allow console output from build script.
macro_rules! p {
($($tokens: tt)*) => {
println!("cargo:warning={}", format!($($tokens)*))
}
}
fn main() {
p!("Running build script");
// This env var is set to the target folder by default
let out_dir = env::var("OUT_DIR").expect("OUT_DIR environment variable must be specified");
p!("Out dir is: {}", out_dir);
// Specify the name of your tile map file here (tmx file).
let house_map_filename = "map/house_map.tmx";
p!("cargo:rerun-if-changed={house_map_filename}");
let map = tiled::parse_file(Path::new(house_map_filename)).unwrap();
// The width and height for a GBA game will typically be 30x20, assuming 8px tiles.
let width = map.width;
let height = map.height;
// For simplicity's sake, we use a single-layer tile map.
let default_layer = &map.layers[0];
// Extract the tile ids. These will match the indexes of the tiles in the .tsx tile set file (or its backing image file).
let default_tiles = extract_tiles(&default_layer.tiles);
// Generate Rust code containing our tile map data. This will be a one-dimensional array of
// tile indexes positioned according to the tile map.
let output = quote! {
pub const DEFAULT_MAP: &[u16] = &[#(#default_tiles),*];
pub const WIDTH: i32 = #width as i32;
pub const HEIGHT: i32 = #height as i32;
};
// Print the above string to a file named tilemap.rs that can be imported from our main Rust program.
let output_file = File::create(format!("{out_dir}/tilemap.rs"))
.expect("failed to open tilemap.rs file for writing");
let mut writer = BufWriter::new(output_file);
write!(&mut writer, "{output}").unwrap();
p!("Done running build script");
}
fn extract_tiles(layer: &'_ tiled::LayerData) -> impl Iterator<Item = u16> + '_ {
match layer {
tiled::LayerData::Finite(tiles) => {
tiles.iter().flat_map(|row| row.iter().map(|tile| tile.gid))
}
_ => unimplemented!("cannot use infinite layer"),
}
.map(get_map_id)
}
fn get_map_id(tile_id: u32) -> u16 {
match tile_id {
0 => 0,
i => i as u16 - 1,
}
} main.rs #![no_std]
#![no_main]
#![cfg_attr(test, feature(custom_test_frameworks))] // This is required to allow writing tests
#![cfg_attr(test, reexport_test_harness_main = "test_main")]
#![cfg_attr(test, test_runner(agb::test_runner::test_runner))]
use agb::{syscall, println, display::{
tiled::{RegularBackgroundSize, TileFormat, TileSet, TileSetting, Tiled0, TiledMap, VRamManager},
Priority, object::{Graphics, TagMap},
}, input::{ButtonController, Button, Tri}};
// This statement imports the variables from the code generated by build.rs.
mod tilemap {
include!(concat!(env!("OUT_DIR"), "/tilemap.rs"));
}
#[agb::entry]
fn main(mut gba: agb::Gba) -> ! {
game_main(gba) // Use a separate function since agb::entry macro breaks rust-analyzer
}
fn game_main(mut gba: agb::Gba) -> ! {
// We need some metadata from the tiles.png file, so we import it here.
agb::include_background_gfx!(background_tiles, tiles => "map/tiles.png");
let vblank = agb::interrupt::VBlank::get();
// The tileset is needed to map tile indexes from the tmx file to tile graphics.
let tileset = background_tiles::tiles.tiles;
let (gfx, mut vram) = gba.display.video.tiled0();
// We use the palette from the tiles.png file as the background palette.
vram.set_background_palettes(background_tiles::PALETTES);
// Not sure what the 2nd and 3rd params mean, they are currently undocumented...
let mut bg = gfx.background(Priority::P0, RegularBackgroundSize::Background32x32, tileset.format());
// Iterate through the background one 8x8 tile at a time.
for y in 0..tilemap::HEIGHT as u16 { // 20 x 8px = 160px (see const display.HEIGHT)
for x in 0..tilemap::WIDTH as u16 { // 30 * 8px = 240px (see const display.WIDTH)
// Since the tilemap is a one-dimensional array, we need to multiply the y variable
// with the width to get to the next "row" of the tile map.
let tileId = *tilemap::DEFAULT_MAP.get((x + tilemap::WIDTH as u16 * y) as usize).unwrap_or(&0);
bg.set_tile(
&mut vram,
(x, y).into(), // x,y serves both as screen coordinates and tilemap array indexes.
&tileset, // this param tells bg which image to use for mapping tile ids to graphical tiles.
TileSetting::new(tileId, false, false, 0), // here we specify which tile to pick from the tileset, rotations and palette.
);
}
}
bg.commit(&mut vram);
bg.show();
loop {
vblank.wait_for_vblank();
// do stuff...
}
} house_map.tmx <?xml version="1.0" encoding="UTF-8"?>
<map version="1.10" tiledversion="1.10.2" orientation="orthogonal" renderorder="right-down" width="30" height="20" tilewidth="8" tileheight="8" infinite="0" nextlayerid="2" nextobjectid="1">
<tileset firstgid="1" source="house_tileset.tsx"/>
<layer id="1" name="Tile Layer 1" width="30" height="20">
<data encoding="base64">
BAAAAAoAAAAKAAAACgAAAAoAAAAKAAAACgAAAAoAAAAKAAAACgAAAAoAAAAKAAAACgAAAAoAAAAKAAAACgAAAAoAAAAKAAAACgAAAAoAAAAKAAAACgAAAAoAAAAKAAAACgAAAAoAAAAKAAAACgAAAAoAAAAFAAAADQAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAMAAAADQAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAMAAAADQAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAMAAAADQAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAkAAAAIAAAACAAAAAgAAAAMAAAADQAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAkAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAMAAAADQAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAMAAAADQAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAMAAAADQAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAJAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAMAAAADQAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAMAAAADQAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAMAAAADQAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAMAAAADQAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACQAAAAgAAAAIAAAACAAAAAgAAAAMAAAADQAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAMAAAADQAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAMAAAADQAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAJAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAMAAAADQAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAMAAAADQAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAMAAAADQAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAMAAAABgAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAAHAAAA
</data>
</layer>
</map> house_tileset.tsx <?xml version="1.0" encoding="UTF-8"?>
<tileset version="1.10" tiledversion="1.10.2" name="house_tileset" tilewidth="8" tileheight="8" tilecount="64" columns="8">
<image source="tiles.png" width="64" height="64"/>
</tileset> Gist link: https://gist.github.com/orende/93bd669d234110df8820d9ac5a430808 |
Beta Was this translation helpful? Give feedback.
-
You can see here GBA Rust Demo how i made mine. you will see how they prepare the data and then in main.rs with I've included all my files so you can see much more clearly what needs to be done. |
Beta Was this translation helpful? Give feedback.
-
This is game specific, we haven't come up with a pattern and instead do something different for each game. Once we've established a pattern we'll look at documenting it. |
Beta Was this translation helpful? Give feedback.
Okay, I finally figured it out. No idea if this is the most performant solution, but it works for me.
build.rs