• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Xevion / Pac-Man / 17310465987

28 Aug 2025 11:35PM UTC coverage: 48.731% (-4.0%) from 52.708%
17310465987

push

github

Xevion
fix: add expected MovementModifiers to spawn_test_player to fix movement tests

1114 of 2286 relevant lines covered (48.73%)

1148.25 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/systems/render.rs
1
use crate::constants::CANVAS_SIZE;
2
use crate::error::{GameError, TextureError};
3
use crate::map::builder::Map;
4
use crate::systems::{DeltaTime, DirectionalAnimated, Position, Renderable, ScoreResource, StartupSequence, Velocity};
5
use crate::texture::sprite::SpriteAtlas;
6
use crate::texture::text::TextTexture;
7
use bevy_ecs::component::Component;
8
use bevy_ecs::entity::Entity;
9
use bevy_ecs::event::EventWriter;
10
use bevy_ecs::query::{Changed, Or, Without};
11
use bevy_ecs::removal_detection::RemovedComponents;
12
use bevy_ecs::resource::Resource;
13
use bevy_ecs::system::{NonSendMut, Query, Res, ResMut};
14
use sdl2::pixels::Color;
15
use sdl2::rect::{Point, Rect};
16
use sdl2::render::{Canvas, Texture};
17
use sdl2::video::Window;
18

19
#[derive(Resource, Default)]
20
pub struct RenderDirty(pub bool);
21

22
#[derive(Component)]
×
23
pub struct Hidden;
24

25
#[allow(clippy::type_complexity)]
26
pub fn dirty_render_system(
×
27
    mut dirty: ResMut<RenderDirty>,
×
28
    changed_renderables: Query<(), Or<(Changed<Renderable>, Changed<Position>)>>,
×
29
    removed_renderables: RemovedComponents<Renderable>,
×
30
) {
×
31
    if !changed_renderables.is_empty() || !removed_renderables.is_empty() {
×
32
        dirty.0 = true;
×
33
    }
×
34
}
×
35

36
/// Updates the directional animated texture of an entity.
37
///
38
/// 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.
39
pub fn directional_render_system(
×
40
    dt: Res<DeltaTime>,
×
41
    mut renderables: Query<(&Position, &Velocity, &mut DirectionalAnimated, &mut Renderable)>,
×
42
    mut errors: EventWriter<GameError>,
×
43
) {
×
44
    for (position, velocity, mut texture, mut renderable) in renderables.iter_mut() {
×
45
        let stopped = matches!(position, Position::Stopped { .. });
×
46
        let current_direction = velocity.direction;
×
47

48
        let texture = if stopped {
×
49
            texture.stopped_textures[current_direction.as_usize()].as_mut()
×
50
        } else {
51
            texture.textures[current_direction.as_usize()].as_mut()
×
52
        };
53

54
        if let Some(texture) = texture {
×
55
            if !stopped {
×
56
                texture.tick(dt.0);
×
57
            }
×
58
            let new_tile = *texture.current_tile();
×
59
            if renderable.sprite != new_tile {
×
60
                renderable.sprite = new_tile;
×
61
            }
×
62
        } else {
63
            errors.write(TextureError::RenderFailed("Entity has no texture".to_string()).into());
×
64
            continue;
×
65
        }
66
    }
67
}
×
68

69
/// 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.
70
pub struct MapTextureResource(pub Texture<'static>);
71

72
/// 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.
73
pub struct BackbufferResource(pub Texture<'static>);
74

75
/// Renders the HUD (score, lives, etc.) on top of the game.
76
pub fn hud_render_system(
×
77
    mut canvas: NonSendMut<&mut Canvas<Window>>,
×
78
    mut atlas: NonSendMut<SpriteAtlas>,
×
79
    score: Res<ScoreResource>,
×
80
    startup: Res<StartupSequence>,
×
81
    mut errors: EventWriter<GameError>,
×
82
) {
×
83
    let mut text_renderer = TextTexture::new(1.0);
×
84

×
85
    // Render lives and high score text in white
×
86
    let lives = 3; // TODO: Get from actual lives resource
×
87
    let lives_text = format!("{lives}UP   HIGH SCORE   ");
×
88
    let lives_position = glam::UVec2::new(4 + 8 * 3, 2); // x_offset + lives_offset * 8, y_offset
×
89

90
    if let Err(e) = text_renderer.render(&mut canvas, &mut atlas, &lives_text, lives_position) {
×
91
        errors.write(TextureError::RenderFailed(format!("Failed to render lives text: {}", e)).into());
×
92
    }
×
93

94
    // Render score text in yellow (Pac-Man's color)
95
    let score_text = format!("{:02}", score.0);
×
96
    let score_offset = 7 - (score_text.len() as i32);
×
97
    let score_position = glam::UVec2::new(4 + 8 * score_offset as u32, 10); // x_offset + score_offset * 8, 8 + y_offset
×
98

99
    if let Err(e) = text_renderer.render(&mut canvas, &mut atlas, &score_text, score_position) {
×
100
        errors.write(TextureError::RenderFailed(format!("Failed to render score text: {}", e)).into());
×
101
    }
×
102

103
    // Render text based on StartupSequence stage
104
    if matches!(
×
105
        *startup,
×
106
        StartupSequence::TextOnly { .. } | StartupSequence::CharactersVisible { .. }
107
    ) {
108
        let ready_text = "READY!";
×
109
        let ready_width = text_renderer.text_width(ready_text);
×
110
        let ready_position = glam::UVec2::new((CANVAS_SIZE.x - ready_width) / 2, 160);
×
111
        if let Err(e) = text_renderer.render_with_color(&mut canvas, &mut atlas, ready_text, ready_position, Color::YELLOW) {
×
112
            errors.write(TextureError::RenderFailed(format!("Failed to render READY text: {}", e)).into());
×
113
        }
×
114

115
        if matches!(*startup, StartupSequence::TextOnly { .. }) {
×
116
            let player_one_text = "PLAYER ONE";
×
117
            let player_one_width = text_renderer.text_width(player_one_text);
×
118
            let player_one_position = glam::UVec2::new((CANVAS_SIZE.x - player_one_width) / 2, 113);
×
119

120
            if let Err(e) =
×
121
                text_renderer.render_with_color(&mut canvas, &mut atlas, player_one_text, player_one_position, Color::CYAN)
×
122
            {
×
123
                errors.write(TextureError::RenderFailed(format!("Failed to render PLAYER ONE text: {}", e)).into());
×
124
            }
×
125
        }
×
126
    }
×
127
}
×
128

129
#[allow(clippy::too_many_arguments)]
130
pub fn render_system(
×
131
    mut canvas: NonSendMut<&mut Canvas<Window>>,
×
132
    map_texture: NonSendMut<MapTextureResource>,
×
133
    mut backbuffer: NonSendMut<BackbufferResource>,
×
134
    mut atlas: NonSendMut<SpriteAtlas>,
×
135
    map: Res<Map>,
×
136
    dirty: Res<RenderDirty>,
×
137
    renderables: Query<(Entity, &Renderable, &Position), Without<Hidden>>,
×
138
    mut errors: EventWriter<GameError>,
×
139
) {
×
140
    if !dirty.0 {
×
141
        return;
×
142
    }
×
143
    // Render to backbuffer
×
144
    canvas
×
145
        .with_texture_canvas(&mut backbuffer.0, |backbuffer_canvas| {
×
146
            // Clear the backbuffer
×
147
            backbuffer_canvas.set_draw_color(sdl2::pixels::Color::BLACK);
×
148
            backbuffer_canvas.clear();
×
149

150
            // Copy the pre-rendered map texture to the backbuffer
151
            if let Err(e) = backbuffer_canvas.copy(&map_texture.0, None, None) {
×
152
                errors.write(TextureError::RenderFailed(e.to_string()).into());
×
153
            }
×
154

155
            // Render all entities to the backbuffer
156
            for (_, renderable, position) in renderables
×
157
                .iter()
×
158
                .sort_by_key::<(Entity, &Renderable, &Position), _>(|(_, renderable, _)| renderable.layer)
×
159
                .rev()
×
160
            {
161
                let pos = position.get_pixel_position(&map.graph);
×
162
                match pos {
×
163
                    Ok(pos) => {
×
164
                        let dest = Rect::from_center(
×
165
                            Point::from((pos.x as i32, pos.y as i32)),
×
166
                            renderable.sprite.size.x as u32,
×
167
                            renderable.sprite.size.y as u32,
×
168
                        );
×
169

×
170
                        renderable
×
171
                            .sprite
×
172
                            .render(backbuffer_canvas, &mut atlas, dest)
×
173
                            .err()
×
174
                            .map(|e| errors.write(TextureError::RenderFailed(e.to_string()).into()));
×
175
                    }
×
176
                    Err(e) => {
×
177
                        errors.write(e);
×
178
                    }
×
179
                }
180
            }
181
        })
×
182
        .err()
×
183
        .map(|e| errors.write(TextureError::RenderFailed(e.to_string()).into()));
×
184

×
185
    canvas.copy(&backbuffer.0, None, None).unwrap();
×
186
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc