Skip to content
Snippets Groups Projects
main.rs 5.58 KiB
Newer Older
//! ## rs-tiled demo with SFML
//! --------------------------
//! Displays a map, use WASD keys to move the camera around.
//! Only draws its tile layers and nothing else.

mod mesh;
mod tilesheet;

use mesh::QuadMesh;
use sfml::{
    graphics::{BlendMode, Color, Drawable, RenderStates, RenderTarget, RenderWindow, Transform},
    system::{Vector2f, Vector2u},
    window::{ContextSettings, Key, Style},
};
use std::{env, path::PathBuf, time::Duration};
use tiled::{FilesystemResourceCache, Map, TileLayer};
use tilesheet::Tilesheet;

/// A path to the map to display.
const MAP_PATH: &'static str = "assets/tiled_base64_external.tmx";

/// A [Map] wrapper which also contains graphical information such as the tileset texture or the layer meshes.
///
/// Wrappers like these are generally recommended to use instead of using the crate structures (e.g. [LayerData]) as you have more freedom
/// with what you can do with them, they won't change between crate versions and they are more specific to your needs.
///
/// [Map]: tiled::map::Map
pub struct Level {
    layers: Vec<QuadMesh>,
    /// Unique tilesheet related to the level, which contains the Tiled tileset + Its only texture.
    tilesheet: Tilesheet,
    tile_size: f32,
}

impl Level {
    /// Create a new level from a Tiled map.
    pub fn from_map(map: Map) -> Self {
        let tilesheet = {
            let tileset = map.tilesets()[0].clone();
            Tilesheet::from_tileset(tileset)
        };
        let tile_size = map.tile_width as f32;

        let layers = map
            .layers()
            .filter_map(|layer| match &layer.layer_type() {
                tiled::LayerType::TileLayer(l) => Some(generate_mesh(l, &tilesheet)),
                _ => None,
            })
            .collect();

        Self {
            tilesheet,
            layers,
            tile_size,
        }
    }
}

/// Generates a vertex mesh from a tile layer for rendering.
fn generate_mesh(layer: &TileLayer, tilesheet: &Tilesheet) -> QuadMesh {
    let finite = match layer.data() {
        tiled::TileLayerData::Finite(f) => f,
        tiled::TileLayerData::Infinite(_) => panic!("Infinite maps not supported"),
    };
    let (width, height) = (finite.width() as usize, finite.height() as usize);
    let mut mesh = QuadMesh::with_capacity(width * height);
    for x in 0..width {
        for y in 0..height {
            // TODO: `FiniteTileLayer` for getting tiles directly from finite tile layers?
            if let Some(tile) = layer.get_tile(x, y) {
                let uv = tilesheet.tile_rect(tile.id);
                mesh.add_quad(Vector2f::new(x as f32, y as f32), 1., uv);
            }
        }
    }

    mesh
}

impl Drawable for Level {
    fn draw<'a: 'shader, 'texture, 'shader, 'shader_texture>(
        &'a self,
        target: &mut dyn RenderTarget,
        states: &sfml::graphics::RenderStates<'texture, 'shader, 'shader_texture>,
    ) {
        let mut states = states.clone();
        states.set_texture(Some(&self.tilesheet.texture()));
        for mesh in self.layers.iter() {
            target.draw_with_renderstates(mesh, &states);
        }
    }
}

fn main() {
    let mut cache = FilesystemResourceCache::new();

    let map = Map::parse_file(
        PathBuf::from(
            env::var("CARGO_MANIFEST_DIR")
                .expect("To run the example, use `cargo run --example sfml`"),
        )
        .join(MAP_PATH),
        &mut cache,
    )
    .unwrap();
    let level = Level::from_map(map);

    let mut window = create_window();
    let mut camera_position = Vector2f::default();
    let mut last_frame_time = std::time::Instant::now();

    loop {
        while let Some(event) = window.poll_event() {
            use sfml::window::Event;
            match event {
                Event::Closed => return,
                _ => (),
            }
        }

        let this_frame_time = std::time::Instant::now();
        let delta_time = this_frame_time - last_frame_time;

        handle_input(&mut camera_position, delta_time);

        let camera_transform = camera_transform(window.size(), camera_position, level.tile_size);
        let render_states = RenderStates::new(BlendMode::ALPHA, camera_transform, None, None);

        window.clear(Color::BLACK);
        window.draw_with_renderstates(&level, &render_states);
        window.display();

        last_frame_time = this_frame_time;
    }
}

/// Creates the window of the application
fn create_window() -> RenderWindow {
    let mut context_settings = ContextSettings::default();
    context_settings.set_antialiasing_level(2);
    let mut window = RenderWindow::new(
        (1080, 720),
        "rs-tiled demo",
        Style::CLOSE,
        &context_settings,
    );
    window.set_vertical_sync_enabled(true);

    window
}

fn handle_input(camera_position: &mut Vector2f, delta_time: Duration) {
    let mut movement = Vector2f::default();

    const SPEED: f32 = 5.;
    if Key::W.is_pressed() {
        movement.y -= 1.;
    }
    if Key::A.is_pressed() {
        movement.x -= 1.;
    }
    if Key::S.is_pressed() {
        movement.y += 1.;
    }
    if Key::D.is_pressed() {
        movement.x += 1.;
    }

    *camera_position += movement * delta_time.as_secs_f32() * SPEED;
}

fn camera_transform(window_size: Vector2u, camera_position: Vector2f, tile_size: f32) -> Transform {
    let window_size = Vector2f::new(window_size.x as f32, window_size.y as f32);

    let mut x = Transform::IDENTITY;
    x.translate(window_size.x / 2., window_size.y / 2.);
    x.translate(
        -camera_position.x * tile_size,
        -camera_position.y * tile_size,
    );
    x.scale_with_center(tile_size, tile_size, 0f32, 0f32);
    x
}