macroquad icon indicating copy to clipboard operation
macroquad copied to clipboard

glitches with tilemap graphics in some zoom levels

Open martinlindhe opened this issue 4 years ago • 8 comments

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.

https://user-images.githubusercontent.com/181531/113839861-5f024080-9790-11eb-972a-f35e880f5e53.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
    }
}

martinlindhe avatar Apr 07 '21 09:04 martinlindhe

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

Bytekeeper avatar Jun 20 '21 15:06 Bytekeeper

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....

olefasting avatar Aug 29 '21 07:08 olefasting

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

puppetmaster- avatar Sep 01 '21 06:09 puppetmaster-

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!

olefasting avatar Sep 01 '21 15:09 olefasting

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

martinlindhe avatar Sep 02 '21 08:09 martinlindhe

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.

olefasting avatar Sep 02 '21 13:09 olefasting

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.

martinlindhe avatar Sep 02 '21 13:09 martinlindhe

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?

thefiredman avatar Mar 19 '22 00:03 thefiredman