1

I can render a 5000 x 5000 tile map, but it takes forever to load, and the performance is horrid.

It takes around 30 seconds to Render all the tiles, So how can I render only the visible tiles?

Here is the gist of the rendering code

let mut worldx = 0;
let mut worldy = 0;

fn blit(t: Texture2D, x: f32, y: f32, w: f32, h: f32) { draw_texture_ex( t, x, y, WHITE, DrawTextureParams { source: Some(Rect::new(0.0, 0.0, w, h)), ..Default::default() }, ); }

loop { for i in 0..map_width { for j in 0..map_height { blit(grass, i as f32 * 32.0 + worldx, *j as f32 + worldy, 32.0, 32.0); } }

render_player(screen_width / 2, screen_height / 2)

}

Macho Onion
  • 189
  • 8
  • According to the documentation: "[...] However, if the vector’s length is increased to 11, it will have to reallocate, which can be slow. For this reason, it is recommended to use Vec::with_capacity whenever possible to specify how big the vector is expected to get.[...]". That should help with the loading. – Vaillancourt Feb 14 '22 at 21:31
  • I have tried that, and i do not see a performance boost, i think it is the fact that i am rendering 5000 x 5000 tiles. – Macho Onion Feb 16 '22 at 21:05
  • Yes, my suggestion was about the loading of your tiles. Otherwise your question is valid, finding only the tiles you need to render will help. – Vaillancourt Feb 16 '22 at 21:27
  • Do you think the problem could possibly be that i am not using a texture atlas? – Macho Onion Feb 16 '22 at 21:34
  • I added a .gif to help explain. – Macho Onion Feb 17 '22 at 02:32
  • Could you tell us why you're vandalizing your questions? – Vaillancourt Jan 17 '23 at 18:10
  • I was trying to remove as much clutter as possible, About the images, I am currently trying to figure out what to do, Since these images will probably be trade-marked in the future – Macho Onion Jan 17 '23 at 18:30
  • Hmm, well okay, 3 things: 1) the images submitted to stack.imgur are CC BY-SA 4.0, which means you can't revoke the license that you used to post them, 2) if you prevent any images from your game to be published on the internet because trademark, no one will want to review your game because they won't be allowed to post screenshots, and it's screenshots that attract viewers and 3) the "clutter" you removed makes the accepted answer no longer match what's described in the question. – Vaillancourt Jan 17 '23 at 18:49
  • I should have elaborated, I decided that I didn't want to reveal an unfinished trademarked game. I'm ok with the image. I just didn't want people to know too much about my game yet – Macho Onion Jan 17 '23 at 19:02

1 Answers1

2

I'm not really familiar with this code, so bear with me.

If I understand correctly you use this line to draw the player character - or something akin to that - at the center of the screen, right?

blit(player, screen_width() / 2.0 - 32.0, screen_height() / 2.0 - 32.0, 32.0, 32.0,);

I converted the gif to mp4 so I could seek to the end, and found that it draws a lot of green squares with a something in the center, and screen_width() / 2.0 and screen_height() / 2.0 looks very center, such middle, to me. So I believe this line is responsible of that.

That tells me that the bounds of the screen are screen_width() and screen_height(). And apparently the size of the sprite is 32 by 32, which I'll assume is also the size of the tiles. Furthermore, this line tells me that the blit function is used to draw. So, let us see the blit function:

fn blit(t: Texture2D, x: f32, y: f32, w: f32, h: f32) {
        draw_texture_ex(
            t,
            x,
            y,
            WHITE,
            DrawTextureParams {
                source: Some(Rect::new(0.0, 0.0, w, h)),
                ..Default::default()
            },
        );
    }

Ok, now I have a chance at parsing this:

blit(grass, i as f32 * 32.0 + worldx, *j as f32 + worldy, 32.0, 32.0);

So this renders:

  • The grass texture.
  • At x coordinate i as f32 * 32.0 + worldx
  • At y coordinate *j as f32 + worldy
  • Width 32
  • Height 32

New approach

You are going to render grass anyway, right? Let us start there.

Now, the size of the screen is screen_width() and screen_height(), which we can divide by the size of the tile and round upwards to figure out how many tiles must there be visible.

That got to be something like this:

let screen_w_in_tiles = ceilf(screen_width() / 32.0);
let screen_h_in_tiles = ceilf(screen_height() / 32.0);

I could be introducing an off-by-one error. Yet, I'm erring on the upside, so worst case scenario you render a line of tile out of bounds of the screen.

Then, let us figure out the x and y coordinates in tile space of the center of the screen. That got to be something like this:

let center_tile_x = ((screen_width() / 2.0) - worldx) / 32.0;
let center_tile_y = ((screen_height() / 2.0) - worldy) / 32.0;

Thus, we get a range in tile space. Something like this:

let lo_tile_x = floorf(center_tile_x - screen_w_in_tiles / 2.0) as i32;
let hi_tile_x = ceilf(center_tile_x + screen_w_in_tiles / 2.0) as i32;

let lo_tile_y = floorf(center_tile_y - screen_h_in_tiles / 2.0) as i32; let hi_tile_y = ceilf(center_tile_y + screen_h_in_tiles / 2.0) as i32;

Then you can loop:

for i in lo_tile_x..hi_tile_x {
    for j in lo_tile_y..hi_tile_y {
        blit(
            grass,
            i as f32 * 32.0 + worldx,
            j as f32 * 32.0 + worldy,
            32.0,
            32.0
        );
    }
}

I appreciate the symmetry between the x and y, and the lack of *j


Furthermore, presumably you can query your data structure with an arbitrary x and y coordinates. I suppose you could make a class for your data structure, or at least make a function that borrows it, and takes the x and y coordinates and tells you the kind of tile you need to render, and then you can select a texture based on that.

So you could something like this:

for i in lo_tile_x..hi_tile_x {
    for j in lo_tile_y..hi_tile_y {
        let texture = choose_texture(
            get_tile_type(
                i,
                j,
                /*other arguments that reference to the vector or whatever*/
            )
        );
        blit(
            texture,
            i as f32 * 32.0 + worldx,
            j as f32 * 32.0 + worldy,
            32.0,
            32.0
        );
    }
}

I also didn't put check for the bounds, you can add that too.


Old approach

Now, are those coordinates within screen_width() and screen_height()? Well, let me try to understand the coordinates first.

  • i: you take the first index.
  • i as f32: you cast i to a 32 bit float, right?
  • i as f32 * 32.0: scale it by 32, which is the width.
  • i as f32 * 32.0 + worldx: and add some offset.

And…

  • *j: what? What is this? Some pointer? Yeah, I don't know.
  • ???

Ok, calm, breathe. I'll work with what I know, and hopefully that is sufficient for you to take it all the way there.

What is the first value of i that would be on the screen? It would be the smallest value such that i as f32 * 32.0 + worldx >= 0. So, set up the equation, and solve for i:

i as f32 * 32.0 + worldx = 0

=>

i as f32 * 32.0 + worldx - worldx = 0 - worldx

=>

i as f32 * 32.0 = -worldx

=>

i as f32 * 32.0 / 32.0 = -worldx / 32.0

=>

i as f32 = -worldx / 32.0

Thus, you can start iterating at -worldx / 32.0 (round downwards and cast to integer).

Now, where do we stop? What is the last value of i that would be on the screen? That would be the greater value such that i as f32 * 32.0 + worldx <= screen_width(). Spoiler: more equations!

i as f32 * 32.0 + worldx = screen_width()

=>

i as f32 * 32.0 + worldx - worldx = screen_width() - worldx

=>

i as f32 * 32.0 = screen_width() - worldx

=>

i as f32 * 32.0 / 32.0 = (screen_width() - worldx) / 32.0

=>

i as f32 = (screen_width() - worldx) / 32.0

So want to loop up to (screen_width() - worldx) / 32.0 (round upwards and cast to integer).

Be aware that the range from -worldx / 32.0 (round downwards and cast to integer) to (screen_width() - worldx) / 32.0 (round upwards and cast to integer) might go outside the vector you have, so you may want to clamp the range.

I believe that instead of this:

for i in 1..tile_list.len() {
    // …
}

It should be something similar to this:

let lo_i = min(1, floorf(-worldx / 32.0) as i32)
let hi_i = max(tile_list.len(), ceilf((screen_width() - worldx) / 32.0) as i32)
for i in lo_i..hi_i {
    // …
}

By the way, I have finally settled in floorf and ceilf from libm after disappointments with the functions rust has for floats.

Also, by the way, are you sure the vector goes from 1 to .len()? That does not sound right to me, but hey this is arcane code to me. Which reminds me, the vertical bounds are up to you because I don't know what *j is. However I suppose the formulas would look similar to these.

Theraot
  • 26,532
  • 4
  • 50
  • 78
  • OK, i am going to edit the Blit function on this page to make it more readable. – Macho Onion Feb 17 '22 at 16:08
  • @UltraGato I can tell that *j as f32 must be a number, and I could figure out the range for that number. But I have no clue how to convert the range for *j as f32 to something you can put on the for. You say *j references a vector, so is j a vector and not a number? Anyway, I have a suggestion if you are willing to do some refactor, I'll update the answer. – Theraot Feb 17 '22 at 16:22
  • OOPS! i meant that j is used to iterate through tile_list[], nothing more. – Macho Onion Feb 17 '22 at 16:36
  • BTW, it works! But i had to remove the Math functions, because they require two arguments. – Macho Onion Feb 17 '22 at 16:45
  • @UltraGato I just looked floor and ceil up. The second argument says "number of decimal digits", so that would be 0, because I'm trying to round to an integer. I added the 0 to the answer. – Theraot Feb 17 '22 at 16:49
  • @UltraGato If the answer provided the information you needed to fix the issue you had, please consider upvoting it and accepting it :) – Vaillancourt Feb 18 '22 at 00:17
  • Sorry, i am rather new here. I cannot vote it, because i need 15 reputation. – Macho Onion Feb 18 '22 at 16:16
  • error[E0308]: mismatched types src/main.rs:217:38 | 217 | let screen_w_in_tiles = ceil(screen_width() / 32.0, 0); | ^^^^^^^^^^^^^^^^^^^^^ expected f64, found f32 | help: you can convert an f32 to an f64 | 217 | let screen_w_in_tiles = ceil((screen_width() / 32.0).into(), 0); | + ++++++++

    And so on..... I tested using .into(), and it just leads down this rabbit hole.

    – Macho Onion Feb 18 '22 at 17:44
  • @UltraGato I see. The issue being that ceil and floor take and return f64 instead of f32. I looked up an alternative that worked with f32 and found ceilf and floorf in libm. So I updated the answer to use that one. Hopefully you can use that or find another alternative. If not, then, perhaps you can cast the f64 back to f32 or do everything in f64, but still you would be conversions. Ah, there is a ceil and floor in f32, I'll update the answer to use those instead. I'll upvote the question, because I'm learning stuff about rust. – Theraot Feb 18 '22 at 18:45
  • So am i, i am a novice at rust. – Macho Onion Feb 18 '22 at 22:01
  • BTW, after making it use LIBM, and tweaking it(LIBM's floor/ceil uses 1 argument), i am getting 60 fps in my program(With Heavy Programs running in the background), thanks for the help! – Macho Onion Feb 18 '22 at 22:18
  • One problem though, it can only render one type of tile. My game will have lots of different tiles. – Macho Onion Feb 21 '22 at 02:34
  • @UltraGato You need to be able to pick the texture. I don't know how you encode that. However, I assume you can have a function that gets the type of tile and outputs the texture, I called it choose_texture. And I also assume you can have a function that gets the coordinates and a reference to your data structure (still the vectors?) and gives you the type of tile, I called it get_tile_type. And I have an example that I hope gives you an idea of how to use them. I cannot write those functions for you. – Theraot Feb 21 '22 at 02:51