rust-sokoban/src/systems/rendering_system.rs

123 lines
4.5 KiB
Rust

use ggez::graphics::Image;
use ggez::graphics::{Color, DrawParam};
use ggez::{nalgebra as na, timer};
use ggez::{graphics, Context};
use ggez::graphics::spritebatch::SpriteBatch;
use itertools::Itertools;
use specs::{Join, Read, ReadStorage, System};
use std::collections::HashMap;
use std::time::Duration;
use crate::components::*;
use crate::constants::TILE_WIDTH;
use crate::resources::*;
pub struct RenderingSystem<'a> {
pub context: &'a mut Context,
}
impl RenderingSystem<'_> {
pub fn draw_text(&mut self, text_string: &str, x: f32, y: f32) {
let text = graphics::Text::new(text_string);
let destination = na::Point2::new(x, y);
let color = Some(Color::new(0.0, 0.0, 0.0, 1.0));
let dimensions = na::Point2::new(0.0, 20.0);
graphics::queue_text(self.context, &text, dimensions, color);
graphics::draw_queued_text(
self.context,
graphics::DrawParam::new().dest(destination),
None,
graphics::FilterMode::Linear,
)
.expect("expected drawing queued text");
}
pub fn get_image(&mut self, renderable: &Renderable, delta: Duration) -> String {
let path_index = match renderable.kind() {
RenderableKind::Static => {
// We only have one image, so we just return that
0
}
RenderableKind::Animated => {
// If we have multiple, we want to select the right one based on the delta time.
// First we get the delta in milliseconds, we % by 1000 to get the seconds only
// and finally we divide by 250 to get a number between 0 and 4. If it's 4
// we technically are on the next iteration of the loop (or on 0), but we will let
// the renderable handle this logic of wrapping frames.
((delta.as_millis() % 1000) / 250) as usize
}
};
renderable.path(path_index)
}
}
impl<'a> System<'a> for RenderingSystem<'a> {
type SystemData = (
Read<'a, Gameplay>,
Read<'a, Time>,
ReadStorage<'a, Position>,
ReadStorage<'a, Renderable>,
);
fn run(&mut self, data: Self::SystemData) {
let (gameplay, time, positions, renderables) = data;
// Clear the screen/set the background
graphics::clear(self.context, graphics::Color::new(0.95, 0.95, 0.95, 1.0));
// Get all the renderables with their positions
let rendering_data = (&positions, &renderables).join().collect::<Vec<_>>();
let mut rendering_batches: HashMap<u8, HashMap<String, Vec<DrawParam>>> = HashMap::new();
// Iterate through all paris of positions & renderables, load the image
// and draw it at the specified position.
for (position, renderable) in rendering_data.iter() {
// Load the image
let image_path = self.get_image(renderable, time.delta);
let x = position.x as f32 * TILE_WIDTH;
let y = position.y as f32 * TILE_WIDTH;
let z = position.z;
// Add to rendering batches
let draw_param = DrawParam::new().dest(na::Point2::new(x, y));
rendering_batches
.entry(z)
.or_default()
.entry(image_path)
.or_default()
.push(draw_param);
}
// Iterate spritebatches ordered by z and actually render each of them
for (_z, group) in rendering_batches
.iter()
.sorted_by(|a, b| Ord::cmp(&a.0, &b.0)) {
for (image_path, draw_params) in group {
let image = Image::new(self.context, image_path).expect("expected image");
let mut sprite_batch = SpriteBatch::new(image);
for draw_param in draw_params.iter() {
sprite_batch.add(*draw_param);
}
graphics::draw(self.context, &sprite_batch, graphics::DrawParam::new())
.expect("expected render");
}
}
// Render any text
self.draw_text(&gameplay.state.to_string(), 525.0, 80.0);
self.draw_text(&gameplay.moves_count.to_string(), 525.0, 100.0);
let fps = format!("FPS: {:.0}", timer::fps(self.context));
self.draw_text(&fps, 525.0, 120.0);
// Finally, present the context, this will actually display everything
// on the screen.
graphics::present(self.context).expect("expected to present");
}
}