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

glitches with tilemap graphics in some zoom levels #188

Open
martinlindhe opened this issue Apr 7, 2021 · 8 comments
Open

glitches with tilemap graphics in some zoom levels #188

martinlindhe opened this issue Apr 7, 2021 · 8 comments

Comments

@martinlindhe
Copy link
Contributor

martinlindhe commented Apr 7, 2021

I adjusted camera and zoom level from examples/platformer.rs and noticed a graphic glitch in the background tiles with some zoom levels, as can be seen in this video clip. The same issue is seen both on Windows and macOS.
The issue goes away at zoom level 2 and 3.

reprod_bug.2021-04-07.10-58-29.mp4

source

use macroquad::prelude::*;

use macroquad_tiled as tiled;

use physics_platformer::*;

enum Direction {
    Left,
    Right,
}

struct Player {
    collider: Actor,
    speed: Vec2,
    facing: Direction,
}

struct Platform {
    collider: Solid,
    speed: f32,
}

const MAP_WIDTH: f32  = 320.;
const MAP_HEIGHT: f32 = 152.;

#[macroquad::main("reprod_bug")]
async fn main() {

    let tileset = load_texture("examples/tileset.png").await;
    set_texture_filter(tileset, FilterMode::Nearest);

    let tiled_map_json = load_string("examples/map.json").await.unwrap();
    let tiled_map = tiled::load_map(&tiled_map_json, &[("tileset.png", tileset)], &[]).unwrap();

    let mut static_colliders = vec![];
    for (_x, _y, tile) in tiled_map.tiles("main layer", None) {
        static_colliders.push(tile.is_some());
    }

    let mut world = World::new();
    world.add_static_tiled_layer(static_colliders, 8., 8., 40, 1);

    let mut player = Player {
        collider: world.add_actor(vec2(200.0, 80.0), 8, 8),
        speed: vec2(0., 0.),
        facing: Direction::Right,
    };

    let mut platform = Platform {
        collider: world.add_solid(vec2(170.0, 130.0), 32, 8),
        speed: 50.,
    };

    let mut zoom = 1.;
    let mut camera = Camera2D::from_display_rect(Rect::new(0.0, 0.0, MAP_WIDTH, MAP_HEIGHT));

    loop {
        clear_background(DARKBLUE);

        let frame_time = get_frame_time();

        // camera follow player
        {
            let pos = world.actor_pos(player.collider);
            camera.target = vec2(pos.x, pos.y);
            camera.zoom = vec2(1. / MAP_WIDTH * zoom, -1. / MAP_HEIGHT * zoom);
            set_camera(camera);
        }

        tiled_map.draw_tiles("main layer", Rect::new(0.0, 0.0, MAP_WIDTH, MAP_HEIGHT), None);

        // draw platform
        {
            let pos = world.solid_pos(platform.collider);
            tiled_map.spr_ex(
                "tileset",
                Rect::new(6.0 * 8.0, 0.0, 32.0, 8.0),
                Rect::new(pos.x, pos.y, 32.0, 8.0),
            )
        }

        // draw player
        {
            // sprite id from tiled
            const PLAYER_SPRITE: u32 = 120;

            let pos = world.actor_pos(player.collider);
            match player.facing {
                Direction::Right => tiled_map.spr("tileset", PLAYER_SPRITE, Rect::new(pos.x, pos.y, 8.0, 8.0)),
                Direction::Left => tiled_map.spr("tileset", PLAYER_SPRITE, Rect::new(pos.x + 8.0, pos.y, -8.0, 8.0)),
            }
        }


        // platform movement
        {
            world.solid_move(platform.collider, platform.speed * frame_time, 0.0);
            let pos = world.solid_pos(platform.collider);
            if platform.speed > 1. && pos.x >= 220. {
                platform.speed *= -1.;
            }
            if platform.speed < -1. && pos.x <= 150. {
                platform.speed *= -1.;
            }
        }

        // player movement control
        {
            let pos = world.actor_pos(player.collider);
            let on_ground = world.collide_check(player.collider, pos + vec2(0., 1.));

            if on_ground == false {
                player.speed.y += 500. * frame_time;
            }

            if is_key_down(KeyCode::D) {
                player.speed.x = 100.0;
                player.facing = Direction::Right;
            } else if is_key_down(KeyCode::A) {
                player.speed.x = -100.0;
                player.facing = Direction::Left;
            } else {
                player.speed.x = 0.;
            }

            if is_key_pressed(KeyCode::Space) {
                if on_ground {
                    player.speed.y = -120.;
                }
            }

            world.move_h(player.collider, player.speed.x * frame_time);
            world.move_v(player.collider, player.speed.y * frame_time);
        }

        // zoom
        {
            let (_, mouse_wheel_y) = mouse_wheel();
            if mouse_wheel_y < 0. {
                zoom -= 0.5;
                if zoom < 0.5 {
                    zoom = 0.5;
                }
            }
            if mouse_wheel_y > 0. {
                zoom += 0.5;
                if zoom > 5. {
                    zoom = 5.;
                }
            }
        }

        // Back to screen space, render some text
        set_default_camera();

        draw_text(&format!("zoom {}", zoom), 10.0, 20.0, 30.0, WHITE);

        next_frame().await
    }
}
@Bytekeeper
Copy link

Could be caused by aliasing? All artifacts seem appear on the same corner tile.

@olefasting
Copy link

I have the same issue and I noticed that it can be fixed by tweaking the size of the source rectangle, as is done in macroquad tiled:

fn sprite_rect(&self, ix: u32) -> Rect {
    let sw = self.tilewidth as f32;
    let sh = self.tileheight as f32;
    let sx = (ix % self.columns) as f32 * (sw + self.spacing as f32) + self.margin as f32;
    let sy = (ix / self.columns) as f32 * (sh + self.spacing as f32) + self.margin as f32;

    // TODO: configure tiles margin
    Rect::new(sx + 1.1, sy + 1.1, sw - 2.2, sh - 2.2)
}

[...]

draw_texture_ex(
    tileset.texture,
    dest.x,
    dest.y,
    WHITE,
    DrawTextureParams {
        dest_size: Some(vec2(dest.w, dest.h)),
        source: Some(Rect::new(
            spr_rect.x - 1.0,
            spr_rect.y - 1.0,
            spr_rect.w + 2.0,
            spr_rect.h + 2.0,
        )),
        ..Default::default()
    },
);`` 

This will cut off ~one pixel around your tile, however, so creating tilesheets with padding around the tiles, might be an idea.

I believe it is due to float rounding and nearest neighbor filtering, but I am not sure....

@puppetmaster-
Copy link
Contributor

puppetmaster- commented Sep 1, 2021

my fix was
camera.target = vec2(pos.x.round(), pos.y.round());

so everything is drawn pixel accurate and there are no sub-pixel problems

@olefasting
Copy link

my fix was
camera.target = vec2(pos.x.round(), pos.y.round());

so everything is drawn pixel accurate and there are no sub-pixel problems

Beautifully simple. I will try this as well. Had some way more intricate solutions in mind, but simple is better.
Thanks for the tip!

@martinlindhe
Copy link
Contributor Author

martinlindhe commented Sep 2, 2021

my fix was
camera.target = vec2(pos.x.round(), pos.y.round());

so everything is drawn pixel accurate and there are no sub-pixel problems

Thanks, but applying this to the original snippet does not fix the issue :-(

image

@olefasting
Copy link

I posted another reply, based on some incorrect assumptions, so I deleted it. I would go through my code, step by step, and look for anything that might cause off-pixel positioning.

@martinlindhe
Copy link
Contributor Author

I posted another reply, based on some incorrect assumptions, so I deleted it. I would go through my code, step by step, and look for anything that might cause off-pixel positioning.

The complete code in order to reproduce this issue is in the first post.

@thefiredman
Copy link

This problem is happening with my own game as well. It appears the more zoomed in you are the more prevalent it is. Rounding it helps but causes camera stuttering and does not completely rid it of the issue. I am making an 8x8 pixel art game. Should I scale up the game? Should I put a gap between tiles in my atlas?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants