-
Notifications
You must be signed in to change notification settings - Fork 102
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* 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
Showing
6 changed files
with
295 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
} | ||
} | ||
} |