Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add SFML example #133

Merged
merged 18 commits into from
Feb 11, 2022
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Rust
name: Library

on:
push:
Expand All @@ -15,11 +15,10 @@ jobs:

steps:
- uses: actions/checkout@v2

- uses: Swatinem/rust-cache@v1

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

- name: Run tests
run: cargo test --verbose
run: cargo test --verbose --tests --example example
28 changes: 28 additions & 0 deletions .github/workflows/sfml.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: SFML

on:
push:
branches: [ master ]
paths:
- 'examples/sfml/**'
pull_request:
branches: [ master ]
paths:
- 'examples/sfml/**'

env:
CARGO_TERM_COLOR: always

jobs:
build:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- uses: Swatinem/rust-cache@v1

- name: Install dependencies
run: sudo apt-get install -y libsfml-dev libcsfml-dev

- name: Build example
run: cargo test --verbose --example sfml
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
8 changes: 8 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,16 @@ path = "src/lib.rs"
name = "example"
path = "examples/main.rs"

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

[dependencies]
base64 = "0.10"
xml-rs = "0.8"
libflate = "0.1.18"
zstd = { version = "0.9", optional = true }

[dev-dependencies.sfml]
version = "0.16"
features = ["graphics"]
170 changes: 170 additions & 0 deletions examples/sfml/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
//! rs-tiled demo with SFML
//! Displays a map, use WASD keys to move the camera around

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::{path::Path, time::Duration};
use tiled::{LayerData, LayerTile, Map};
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 width = map.width as usize;
let tilesheet = {
let tileset = map.tilesets[0].clone();
Tilesheet::from_tileset(tileset)
};
let tile_size = map.tile_width as f32;

let layers = map
.layers
.iter()
.map(|layer| match &layer.tiles {
LayerData::Finite(x) => generate_mesh(&x, &tilesheet, width),
_ => panic!("Infinite maps not supported"),
})
.collect();

Self {
tilesheet,
layers,
tile_size,
}
}
}

/// Generates a vertex mesh from this tile layer for rendering.
fn generate_mesh(tiles: &Vec<LayerTile>, tilesheet: &Tilesheet, width: usize) -> QuadMesh {
let height = tiles.len() / width;
let mut mesh = QuadMesh::with_capacity(width * height);
for x in 0..width {
for y in 0..height {
let tile = tiles[x + y * width];
if tile.gid != 0 {
let uv = tilesheet.tile_rect(tile.gid);
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 map = Map::parse_file(Path::new(MAP_PATH)).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 vertices.
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 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: Tileset,
}

impl Tilesheet {
/// Create a tilesheet from a Tiled tileset, loading its texture along the way.
pub fn from_tileset<'p>(tileset: 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, gid: u32) -> FloatRect {
let id = gid - self.tileset.first_gid;

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,
}
}
}