use bevy::prelude::*;
use bevy::render::pass::ClearColor;
use ldtk_rust::{EntityInstance, Project, TileInstance, IntGridValueInstance};
use std::collections::HashMap;
const LDTK_FILE_PATH: &str = "assets/test_game.ldtk";
const TILE_SCALE: f32 = 2.5;
struct Map {
ldtk_file: Project,
redraw: bool,
current_level: usize,
}
#[derive(Clone)]
struct VisualAssets {
int_grid_materials: HashMap<i32, Vec<Handle<ColorMaterial>>>,
spritesheets: HashMap<i32, Handle<TextureAtlas>>,
entity_materials: HashMap<i32, Handle<ColorMaterial>>,
}
#[derive(Clone, Copy)]
struct LayerInfo {
grid_width: i32,
_grid_height: i32,
grid_cell_size: i32,
z_index: i32,
px_width: f32,
px_height: f32,
}
#[derive(Copy, Clone)]
struct ExtraEntDefs {
__tile_id: i32,
__width: i32,
__height: i32,
__scale: f32,
}
impl ExtraEntDefs {
fn new() -> Self {
Self {
__tile_id: 0,
__width: 0,
__height: 0,
__scale: 1.0,
}
}
}
fn main() {
App::build()
.add_resource(WindowDescriptor {
title: "title".to_string(),
width: 1024.0,
height: 768.0,
..Default::default()
})
.add_plugins(DefaultPlugins)
.add_startup_system(setup.system())
.add_system(update.system())
.run();
}
fn setup(
commands: &mut Commands,
asset_server: Res<AssetServer>,
mut materials: ResMut<Assets<ColorMaterial>>,
mut texture_atlases: ResMut<Assets<TextureAtlas>>,
) {
let map = Map {
ldtk_file: Project::new(LDTK_FILE_PATH.to_string()),
redraw: true,
current_level: 0,
};
let mut visual_assets = VisualAssets {
int_grid_materials: HashMap::new(),
spritesheets: HashMap::new(),
entity_materials: HashMap::new(),
};
for tileset in map.ldtk_file.defs.as_ref().unwrap().tilesets.iter() {
let texture_handle = asset_server.load(&tileset.rel_path[..]);
let texture_atlas = TextureAtlas::from_grid(
texture_handle,
Vec2::new(tileset.tile_grid_size as f32, tileset.tile_grid_size as f32),
(tileset.px_wid / tileset.tile_grid_size) as usize,
(tileset.px_hei / tileset.tile_grid_size) as usize,
);
let texture_atlas_handle = texture_atlases.add(texture_atlas);
visual_assets
.spritesheets
.insert(tileset.uid as i32, texture_atlas_handle);
}
for layer in
map.ldtk_file
.defs
.as_ref().unwrap()
.layers
.iter()
.filter(|f| match f.purple_type.as_ref().unwrap() {
ldtk_rust::Type::IntGrid => true,
_ => false,
})
{
let mut colors = Vec::new();
for i in layer.int_grid_values.iter() {
let clr = match Color::hex(&i.color[1..]){
Ok(t) => t,
Err(e) => {
println!("Error: {:?}", e);
Color::BLUE
}
};
let col_mat = materials.add(ColorMaterial::from(clr));
colors.push(col_mat);
}
visual_assets
.int_grid_materials
.insert(layer.uid as i32, colors);
}
for ent in map.ldtk_file.defs.as_ref().unwrap().entities.iter() {
let clr = match Color::hex(&ent.color.clone()[1..]) {
Ok(t) => t,
Err(e) => {
println!("Error: {:?}", e);
Color::BLUE
}
};
let col_mat = materials.add(ColorMaterial::from(clr));
visual_assets
.entity_materials
.insert(ent.uid as i32, col_mat);
}
commands
.insert_resource(map)
.insert_resource(visual_assets)
.spawn(Camera2dBundle::default());
}
fn update(commands: &mut Commands, mut map: ResMut<Map>, visual_assets: Res<VisualAssets>) {
if !map.redraw {
return;
}
commands.insert_resource(ClearColor(
Color::hex(&map.ldtk_file.levels[0].bg_color[1..]).unwrap(),
));
for (idx, layer) in map.ldtk_file.levels[map.current_level]
.layer_instances
.as_ref()
.unwrap()
.iter()
.enumerate()
.rev()
{
let tileset_uid = layer.tileset_def_uid.unwrap_or(-1) as i32;
let layer_uid = layer.layer_def_uid as i32;
let layer_info = LayerInfo {
grid_width: layer.c_wid as i32,
_grid_height: layer.c_hei as i32,
grid_cell_size: layer.grid_size as i32,
z_index: 50 - idx as i32,
px_width: layer.c_wid as f32 * (layer.grid_size as f32 * TILE_SCALE),
px_height: layer.c_hei as f32 * (layer.grid_size as f32 * TILE_SCALE),
};
match &layer.layer_instance_type[..] {
"Tiles" => {
println!("Generating Tile Layer: {}", layer.identifier);
for tile in layer.grid_tiles.iter() {
display_tile(
layer_info,
tile,
commands,
visual_assets.spritesheets[&tileset_uid].clone(),
);
}
}
"AutoLayer" => {
println!("Generating AutoTile Layer: {}", layer.identifier);
for tile in layer.auto_layer_tiles.iter() {
display_tile(
layer_info,
tile,
commands,
visual_assets.spritesheets[&tileset_uid].clone(),
);
}
}
"IntGrid" => {
match layer.tileset_def_uid {
Some(i) => {
println!("Generating IntGrid Layer w/ Tiles: {}", layer.identifier);
let i = i as i32;
for tile in layer.auto_layer_tiles.iter() {
display_tile(
layer_info,
tile,
commands,
visual_assets.spritesheets[&i].clone(),
);
}
}
None => {
println!(
"Generating IntGrid Layer w/ Color Materials: {}",
layer.identifier
);
for tile in layer.int_grid.iter() {
display_color(
layer_info,
tile,
commands,
visual_assets.int_grid_materials[&layer_uid][tile.v as usize]
.clone(),
)
}
}
}
}
"Entities" => {
println!("Generating Entities Layer: {}", layer.identifier);
for entity in layer.entity_instances.iter() {
let mut extra_ent_defs = ExtraEntDefs::new();
for ent in map.ldtk_file.defs.as_ref().unwrap().entities.iter() {
if ent.uid == entity.def_uid {
extra_ent_defs.__tile_id = 0;
extra_ent_defs.__width = ent.width as i32;
extra_ent_defs.__height = ent.height as i32;
}
match ent.render_mode.as_ref().unwrap() {
ldtk_rust::RenderMode::Tile => {
extra_ent_defs.__tile_id = ent.tile_id.unwrap() as i32;
for ts in map.ldtk_file.defs.as_ref().unwrap().tilesets.iter() {
if ts.uid == ent.tileset_id.unwrap() {
extra_ent_defs.__scale =
ent.width as f32 / ts.tile_grid_size as f32;
}
}
}
_ => (),
}
}
display_entity(
layer_info,
entity,
commands,
visual_assets.clone(),
&extra_ent_defs,
);
}
}
_ => {
println!("Not Implemented: {}", layer.identifier);
}
}
}
map.redraw = false;
}
fn display_tile(
layer_info: LayerInfo,
tile: &TileInstance,
commands: &mut Commands,
handle: Handle<TextureAtlas>,
) {
let mut flip_x = false;
let mut flip_y = false;
match tile.f {
1 => flip_x = true,
2 => flip_y = true,
3 => {
flip_x = true;
flip_y = true
}
_ => (),
}
commands.spawn(SpriteSheetBundle {
transform: Transform {
translation: convert_to_world(
layer_info.px_width,
layer_info.px_height,
layer_info.grid_cell_size,
TILE_SCALE,
tile.px[0] as i32,
tile.px[1] as i32,
layer_info.z_index,
),
rotation: flip(flip_x, flip_y),
scale: Vec3::splat(TILE_SCALE),
},
sprite: TextureAtlasSprite::new(tile.t as u32),
texture_atlas: handle,
..Default::default()
});
}
fn display_entity(
layer_info: LayerInfo,
entity: &EntityInstance,
commands: &mut Commands,
visual_assets: VisualAssets,
extra_ent_defs: &ExtraEntDefs,
) {
match &entity.tile {
Some(t) => {
let tileset_uid = t.tileset_uid as i32;
let handle: Handle<TextureAtlas> = visual_assets.spritesheets[&tileset_uid].clone();
commands.spawn(SpriteSheetBundle {
transform: Transform {
translation: convert_to_world(
layer_info.px_width,
layer_info.px_height,
extra_ent_defs.__height,
TILE_SCALE,
entity.grid[0] as i32 * layer_info.grid_cell_size,
entity.grid[1] as i32 * layer_info.grid_cell_size,
layer_info.z_index,
),
scale: Vec3::splat(extra_ent_defs.__scale * TILE_SCALE),
..Default::default()
},
sprite: TextureAtlasSprite::new(extra_ent_defs.__tile_id as u32),
texture_atlas: handle,
..Default::default()
});
}
None => {
let handle: Handle<ColorMaterial> =
visual_assets.entity_materials[&(entity.def_uid as i32)].clone();
commands.spawn(SpriteBundle {
material: handle,
sprite: Sprite::new(Vec2::new(
extra_ent_defs.__width as f32,
extra_ent_defs.__height as f32,
)),
transform: Transform {
translation: convert_to_world(
layer_info.px_width,
layer_info.px_height,
extra_ent_defs.__height,
TILE_SCALE,
entity.grid[0] as i32 * layer_info.grid_cell_size,
entity.grid[1] as i32 * layer_info.grid_cell_size,
layer_info.z_index,
),
scale: Vec3::splat(TILE_SCALE),
..Default::default()
},
..Default::default()
});
}
}
}
fn display_color(
layer_info: LayerInfo,
tile: &IntGridValueInstance,
commands: &mut Commands,
handle: Handle<ColorMaterial>,
) {
let x = tile.coord_id as i32 % layer_info.grid_width;
let y = tile.coord_id as i32 / layer_info.grid_width;
commands.spawn(SpriteBundle {
material: handle,
sprite: Sprite::new(Vec2::new(
layer_info.grid_cell_size as f32,
layer_info.grid_cell_size as f32,
)),
transform: Transform {
translation: convert_to_world(
layer_info.px_width,
layer_info.px_height,
layer_info.grid_cell_size,
TILE_SCALE,
x * layer_info.grid_cell_size,
y as i32 * layer_info.grid_cell_size,
layer_info.z_index,
),
scale: Vec3::splat(TILE_SCALE),
..Default::default()
},
..Default::default()
});
}
fn convert_to_world(
width: f32,
height: f32,
grid_size: i32,
scale: f32,
x: i32,
y: i32,
z: i32,
) -> Vec3 {
let world_x = (x as f32 * scale) + (grid_size as f32 * scale / 2.) - (width / 2.);
let world_y = -(y as f32 * scale) - (grid_size as f32 * scale / 2.) + (height / 2.);
let world_z = z as f32;
Vec3::new(world_x, world_y, world_z)
}
fn flip(x: bool, y: bool) -> Quat {
let mut q1 = Quat::default();
let mut q2 = Quat::default();
if x {
q1 = Quat::from_rotation_y(std::f32::consts::PI);
}
if y {
q2 = Quat::from_rotation_x(std::f32::consts::PI);
}
q1 * q2
}