use crate::error::{GameError, TextureError}; use crate::map::builder::Map; use crate::systems::components::{DeltaTime, DirectionalAnimated, RenderDirty, Renderable}; use crate::systems::movement::{Position, Velocity}; use crate::texture::sprite::SpriteAtlas; use bevy_ecs::entity::Entity; use bevy_ecs::event::EventWriter; use bevy_ecs::prelude::{Changed, Or, RemovedComponents}; use bevy_ecs::system::{NonSendMut, Query, Res, ResMut}; use sdl2::rect::{Point, Rect}; use sdl2::render::{Canvas, Texture}; use sdl2::video::Window; #[allow(clippy::type_complexity)] pub fn dirty_render_system( mut dirty: ResMut, changed_renderables: Query<(), Or<(Changed, Changed)>>, removed_renderables: RemovedComponents, ) { if !changed_renderables.is_empty() || !removed_renderables.is_empty() { dirty.0 = true; } } /// Updates the directional animated texture of an entity. /// /// This runs before the render system so it can update the sprite based on the current direction of travel, as well as whether the entity is moving. pub fn directional_render_system( dt: Res, mut renderables: Query<(&Position, &Velocity, &mut DirectionalAnimated, &mut Renderable)>, mut errors: EventWriter, ) { for (position, velocity, mut texture, mut renderable) in renderables.iter_mut() { let stopped = matches!(position, Position::Stopped { .. }); let current_direction = velocity.direction; let texture = if stopped { texture.stopped_textures[current_direction.as_usize()].as_mut() } else { texture.textures[current_direction.as_usize()].as_mut() }; if let Some(texture) = texture { if !stopped { texture.tick(dt.0); } let new_tile = *texture.current_tile(); if renderable.sprite != new_tile { renderable.sprite = new_tile; } } else { errors.write(TextureError::RenderFailed("Entity has no texture".to_string()).into()); continue; } } } /// A non-send resource for the map texture. This just wraps the texture with a type so it can be differentiated when exposed as a resource. pub struct MapTextureResource(pub Texture<'static>); /// A non-send resource for the backbuffer texture. This just wraps the texture with a type so it can be differentiated when exposed as a resource. pub struct BackbufferResource(pub Texture<'static>); #[allow(clippy::too_many_arguments)] pub fn render_system( mut canvas: NonSendMut<&mut Canvas>, map_texture: NonSendMut, mut backbuffer: NonSendMut, mut atlas: NonSendMut, map: Res, dirty: Res, renderables: Query<(Entity, &Renderable, &Position)>, mut errors: EventWriter, ) { if !dirty.0 { return; } // Render to backbuffer canvas .with_texture_canvas(&mut backbuffer.0, |backbuffer_canvas| { // Clear the backbuffer backbuffer_canvas.set_draw_color(sdl2::pixels::Color::BLACK); backbuffer_canvas.clear(); // Copy the pre-rendered map texture to the backbuffer if let Err(e) = backbuffer_canvas.copy(&map_texture.0, None, None) { errors.write(TextureError::RenderFailed(e.to_string()).into()); } // Render all entities to the backbuffer for (_, renderable, position) in renderables .iter() .sort_by_key::<(Entity, &Renderable, &Position), _>(|(_, renderable, _)| renderable.layer) .rev() { if !renderable.visible { continue; } let pos = position.get_pixel_position(&map.graph); match pos { Ok(pos) => { let dest = Rect::from_center( Point::from((pos.x as i32, pos.y as i32)), renderable.sprite.size.x as u32, renderable.sprite.size.y as u32, ); renderable .sprite .render(backbuffer_canvas, &mut atlas, dest) .err() .map(|e| errors.write(TextureError::RenderFailed(e.to_string()).into())); } Err(e) => { errors.write(e); } } } }) .err() .map(|e| errors.write(TextureError::RenderFailed(e.to_string()).into())); }