Skip to content

Commit

Permalink
Add SFML example (#133)
Browse files Browse the repository at this point in the history
* Add SFML example
Adds a rough demo using SFML for rendering.

* Simplifications to SFML example

* Rely on sfml 0.16 rather than a git revision
* Avoid allocating temporary Path
* Take into account margin and spacing
* Directly produce a FloatRect, since it is what SFML needs
* Renamed VertexMesh to QuadMesh
* Use Vertex::with_pos_coords since we always passed Color::WHITE
* Removed TileLayer wrapper, since it added no value
* Removed handling of gid == 0 in Tilesheet::tile_rect

* Update contributors list

* Apply formatting

* Avoid the need to inverse the camera matrix

* De-hardcode the tile size

Also fixes the example a bit, because the map was drawn too small.

* Update workflow

* Try fixing CI issue by removing rust-cache

* Fix CI

* Fix map asset path

* Merge SFML and Rust workflow

* Misc changes

* Merge upstream/master

* Update example to master

* Fix line endings

* Fixed comment

Co-authored-by: Thorbjørn Lindeijer <bjorn@lindeijer.nl>
  • Loading branch information
aleokdev and bjorn authored Feb 11, 2022
1 parent dfe8458 commit 187f1fb
Show file tree
Hide file tree
Showing 6 changed files with 295 additions and 6 deletions.
8 changes: 5 additions & 3 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@ jobs:

steps:
- uses: actions/checkout@v2

- uses: Swatinem/rust-cache@v1

- name: Build
run: cargo build --verbose
- name: Install dependencies
run: sudo apt-get install -y libsfml-dev libcsfml-dev

- name: Build library
run: cargo build --lib --verbose

- name: Run tests
run: cargo test --verbose
3 changes: 2 additions & 1 deletion CONTRIBUTORS.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
* Matthew Hall
* Kevin Balz
* Thorbjørn Lindeijer
* Thorbjørn Lindeijer
* Alejandro Perea
12 changes: 10 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,16 @@ path = "src/lib.rs"
name = "example"
path = "examples/main.rs"

[[example]]
name = "sfml"
path = "examples/sfml/main.rs"

[dependencies]
base64 = "0.13.0"
xml-rs = "0.8.4"
base64 = "0.13.0"
xml-rs = "0.8.4"
libflate = "1.1.2"
zstd = { version = "0.10.0", optional = true }

[dev-dependencies.sfml]
version = "0.16"
features = ["graphics"]
184 changes: 184 additions & 0 deletions examples/sfml/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
//! ## 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
}
43 changes: 43 additions & 0 deletions examples/sfml/mesh.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
use sfml::{
graphics::{Drawable, FloatRect, PrimitiveType, Vertex},
system::Vector2f,
};

pub struct QuadMesh(Vec<Vertex>);

impl QuadMesh {
/// Create a new mesh with capacity for the given amount of quads.
pub fn with_capacity(quads: usize) -> Self {
Self(Vec::with_capacity(quads * 4))
}

/// Add a quad made up of vertices to the mesh.
pub fn add_quad(&mut self, position: Vector2f, size: f32, uv: FloatRect) {
self.0.push(Vertex::with_pos_coords(
position,
Vector2f::new(uv.left, uv.top),
));
self.0.push(Vertex::with_pos_coords(
position + Vector2f::new(size, 0f32),
Vector2f::new(uv.left + uv.width, uv.top),
));
self.0.push(Vertex::with_pos_coords(
position + Vector2f::new(size, size),
Vector2f::new(uv.left + uv.width, uv.top + uv.height),
));
self.0.push(Vertex::with_pos_coords(
position + Vector2f::new(0f32, size),
Vector2f::new(uv.left, uv.top + uv.height),
));
}
}

impl Drawable for QuadMesh {
fn draw<'a: 'shader, 'texture, 'shader, 'shader_texture>(
&'a self,
target: &mut dyn sfml::graphics::RenderTarget,
states: &sfml::graphics::RenderStates<'texture, 'shader, 'shader_texture>,
) {
target.draw_primitives(&self.0, PrimitiveType::QUADS, states);
}
}
51 changes: 51 additions & 0 deletions examples/sfml/tilesheet.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
use std::rc::Rc;

use sfml::{
graphics::{FloatRect, Texture},
SfBox,
};
use tiled::Tileset;

/// A container for a tileset and the texture it references.
pub struct Tilesheet {
texture: SfBox<Texture>,
tileset: Rc<Tileset>,
}

impl Tilesheet {
/// Create a tilesheet from a Tiled tileset, loading its texture along the way.
pub fn from_tileset<'p>(tileset: Rc<Tileset>) -> Self {
let tileset_image = tileset.image.as_ref().unwrap();

let texture = {
let texture_path = &tileset_image
.source
.to_str()
.expect("obtaining valid UTF-8 path");
Texture::from_file(texture_path).unwrap()
};

Tilesheet { texture, tileset }
}

pub fn texture(&self) -> &Texture {
&self.texture
}

pub fn tile_rect(&self, id: u32) -> FloatRect {
let tile_width = self.tileset.tile_width;
let tile_height = self.tileset.tile_height;
let spacing = self.tileset.spacing;
let margin = self.tileset.margin;
let tiles_per_row = (self.texture.size().x - margin + spacing) / (tile_width + spacing);
let x = id % tiles_per_row * tile_width;
let y = id / tiles_per_row * tile_height;

FloatRect {
left: x as f32,
top: y as f32,
width: tile_width as f32,
height: tile_height as f32,
}
}
}

0 comments on commit 187f1fb

Please sign in to comment.