Jump to content


Photo

Dealing with hundreds of terrain objects


  • Please log in to reply
7 replies to this topic

#1 Killpill

Killpill

    Extreme Halo Fan

  • New Member
  • 294 posts

Posted 08 May 2012 - 05:05 AM

My project's other programmer started having lag issues after tiling a map he made. He quickly jumped to blaming Game Maker's tile system, saying it can't handle a large amount of tiles and such. That struck me as silly because we have had larger maps with more tiles in the past.

Anyways, in looking for a solution to his lag (I can't even get the game to lag on my machine), I saw this map has around 1,500 32x32 terrain block objects. In our engine, like many engines, these blocks are not visible and have no events. We've always had a large number of objects like this so I figured I should try, again, to fix it.

I decided this time to compress all the blocks into two objects: a block and a slope.

This is my code:
//Create surfaces
_blocks = surface_create(room_width, room_height);
_slopes = surface_create(room_width, room_height);

//Go through the terrain objects
with objSolid{

  if (object_index = objBlock){ //normal 32x32 block
    surface_set_target(other._blocks); //set drawing target to the block surface
    draw_sprite(sprite_index, 0, x, y); //draw 32x32 block
    instance_destroy(); //destroy unneeded block object
  }
  
  if (object_index = objSlope_left || object_index = objSlope_right){  //slopes
    surface_set_target(other._slopes);
    draw_sprite(sprite_index, 0, x, y);
    instance_destroy();
  }
}

surface_reset_target(); //set normal drawing mode

//create a sprite for the new block object
_spr = sprite_create_from_surface(_blocks, 0, 0, room_width, room_height, 0, 0, 0, 0);
//create the block and set the sprite
(instance_create(0, 0, objBlock)).sprite_index = _spr;

//same for slopes
_spr = sprite_create_from_surface(_slopes, 0, 0, room_width, room_height, 0, 0, 0, 0);
(instance_create(0, 0, objSlope)).sprite_index = _spr;


//free the unneeded surfaces
surface_free(_blocks);
surface_free(_slopes);

Which reduces the object count by around 1,500 with a memory impact (two 5000x5000 sprites). I know I can trim a few thousand pixels from the sprites but I'll wait for the other coder to see how his PC handles this script before I go into it further.

Memory*:
18.993152mb, before
19.857408mb, after

*Note: cleanmem dll is run after this script

So, this reduces the object count by 1,500 but I don't know how well this code will run on other computers. Any thoughts on the method I'm using?

Edited by Killpill, 08 May 2012 - 05:09 AM.

  • 0

#2 chaz13

chaz13

    GMC Member

  • GMC Member
  • 4144 posts
  • Version:Unknown

Posted 08 May 2012 - 09:37 PM

that's a valid method but you may not want to use surfaces any larger than the size of the screen, or the GPU might dislike it.

Alternatively I'd suggest not using objects at all and using data structures.
  • 0

#3 Gamer3D

Gamer3D

    Human* me = this;

  • GMC Member
  • 1815 posts
  • Version:GM:Studio

Posted 08 May 2012 - 10:41 PM

The performance of the render-to-surface method depends on the size of the surface (Too large and computers with low graphics memory can't allocate it)

Answer the following questions, either in a post or a PM, and I'll suggest a solution based on it:
  • Are your terrain objects/tiles static? (Looks like yes)
  • Are all your terrain objects snapped to some grid? (Is there some pair of real numbers <x,y> such that all object positions are a product <n * x, m * x> for some integers n and m)
  • How big is the room?
  • How big is the view?

If the view is sufficiently small, I'd recommend building a slightly-more-than room-sized surface, and editing it one row/column at a time as the view moves. If your tiles are snapped to a grid, you can access exactly the tiles you need using arrays. I'll see if I can whip up a small example.

EDIT: Here is an example. Note how it uses only slightly more memory than required to hold the screen and only re-draws tiles when they begin to become visible. You can use a larger buffer to reduce flickering a bit.

I may make this into a full improved tiling system.

Edited by Gamer3D, 08 May 2012 - 11:58 PM.

  • 0

#4 Killpill

Killpill

    Extreme Halo Fan

  • New Member
  • 294 posts

Posted 09 May 2012 - 08:04 AM

1. Yes.
2. 32x32 usually but there are some things that require a block be placed on a 16x16 grid. I can't trust the other guy to stick to a grid, limits his design.
3. All sizes but this one is 5000 by 5000 (the real map is really like 5000x2000).
4. 640x400

Tiles were mentioned only so someone could say so if the tile system of Game Maker may be the problem. From what I know, it works well and doesn't draw outside the view. Am I wrong in this assumption?

The terrain objects are not visible and are only used as triggers. If I had made the physics system things would be different (two dimensional array) but alas... this is what my friend understands. We found out that when I make the engine he can't work in it so he made the engine. Moving from move_contact_solid to real math would alienate him from the physics system... so I'm trying to keep the solid objects without having hundreds of them.

The other coder crashed with the first script I posted so I made it work over a step event, dealing with 15 blocks of terrain at a time. After all the blocks are dealt with the surface-to-sprite code is run. He can handle the creation of large surfaces just fine but the game freezes (does not crash) at converting the surfaces to sprites.

The problem with the game is so many objects interact with the terrain that I can't just make the area around the player the solid place. Plus with hitscan bullets... the entire level remaining solid is a must.

I guess my next step is to start with one block, call it the anchor. It will get the nearest block. If that block is close enough, it will draw the block to a surface and delete the block. Rinse and repeat until the nearest block is too far away. Then it draws itself, turns the surface into a sprite, creates a new block at its position and sets the block's sprite to the new sprite then deactivates the object (so the script will not run on it). Then destroys itself. Then we repeat with the block that was too far away and stop when there are none left. Of course, I would also only save the part of the surface that was drawn to this time.

I don't know what size the surfaces should be yet. 960x960 seem reasonable?

If I get this all working I'll create a simple file to upload and show everyone... so you know what this all was for.

Edited by Killpill, 09 May 2012 - 08:20 AM.

  • 0

#5 Gamer3D

Gamer3D

    Human* me = this;

  • GMC Member
  • 1815 posts
  • Version:GM:Studio

Posted 09 May 2012 - 09:31 AM

2. 32x32 usually but there are some things that require a block be placed on a 16x16 grid. I can't trust the other guy to stick to a grid, limits his design.

That'll make tile-drawing a bit slower, but it's not too much more complex. Just make each cell hold all tiles that touch it.

3. All sizes but this one is 5000 by 5000 (the real map is really like 5000x2000).

That could be held in memory by most modern graphics cards (Need 72 MB of ram or more), but it's not a good use of graphics memory, especially when you could get almost the same benefit from 1.5 MB.

Tiles were mentioned only so someone could say so if the tile system of Game Maker may be the problem. From what I know, it works well and doesn't draw outside the view. Am I wrong in this assumption?

GM has a generalized system (For an example of this generalization, you can place tiles outside the bounds of a grid). This (and, I assume, a desire not to over-use graphics memory) prevents them from using some of the optimizations that are possible in a more rigid environment (In the worst case, GM may check each tile to determine if it is in the view. For your example (5000 on a side, 32x32 tiles), that's around 24000 checks. We can do it in 250 or less using grids). The example I created uses graphics memory to reduce CPU use (Tiles are pre-rendered, then copied to the screen buffer by the GPU (A faster operation than copying all tiles individually)

The terrain objects are not visible and are only used as triggers. If I had made the physics system things would be different (two dimensional array) but alas... this is what my friend understands. We found out that when I make the engine he can't work in it so he made the engine. Moving from move_contact_solid to real math would alienate him from the physics system... so I'm trying to keep the solid objects without having hundreds of them.

The problem with the game is so many objects interact with the terrain that I can't just make the area around the player the solid place. Plus with hitscan bullets... the entire level remaining solid is a must.

If your current engine is slow and you can fix it, it's probably time to put your coding skills to work and improve it. If you provide your friend with a simple interface (Find out what he'll be doing and what he'll need in order to do that) for your faster engine, then you will almost certainly have a better program.

If nothing else, have him work on something he's good at. Is he your graphics guy? Have him work on graphics. Does he write the story? Give him a means of building dialogue. My point is that you shouldn't give control of the programming to the worst programmer, just as you shouldn't give control of the art to the person with the worst taste. If you're a good enough programmer, you probably can provide both a good engine and the simple interface he needs.

I guess my next step is to start with one block, call it the anchor. It will get the nearest block. If that block is close enough, it will draw the block to a surface and delete the block. Rinse and repeat until the nearest block is too far away. Then it draws itself, turns the surface into a sprite, creates a new block at its position and sets the block's sprite to the new sprite then deactivates the object (so the script will not run on it). Then destroys itself. Then we repeat with the block that was too far away and stop when there are none left. Of course, I would also only save the part of the surface that was drawn to this time.

That sounds like a really bad idea for several reasons.
  • "Near enough" will result in a circle. That's a poor fit on a rectangular surface.
  • instance_nearest is O(n). That is, it runs through every object to find the nearest one. It's better to run through every instance once and figure out how to draw it then.
  • I wouldn't recommend drawing everything to a surface. Draw what you need from an efficient spatial index as you move (see previous example).

I don't know what size the surfaces should be yet. 960x960 seem reasonable?

Always make textures (that includes surfaces) a power of 2. If you use my method, it'll be 1024x512. For your proposed "Draw everything at once" method, go with 512x512 or 1024x1024. If you use 512x512, you'll need to draw up to 9 at a time, but if you choose to save and load them dynamically, it'll be 4 times as fast to load each one.
  • 0

#6 Killpill

Killpill

    Extreme Halo Fan

  • New Member
  • 294 posts

Posted 09 May 2012 - 10:03 AM

He is a lot of things. His skill is graphics. He is a spriter/designer but also a decent programmer.

Him and I are the only ones who work on the project's code. He handles everything he can and calls on me when things go over his head. I do things like the website, the game's online functionality (statistic tracking for example), bug fixing, and whatever is asked of me. I seem to be more of a jack of all trades, doing whatever needs to be done to further the group's goals. I solve problems. I don't seem to have mastery over anything but I get things to work.

Converting a surface to a sprite takes forever (will making the surface a power of two change this?). Doing a bunch of small ones will only make this worse. I guess I will scrap most of this code and use what I have to build a grid then define custom triggers that will act as the new collision events. This way, he can build the levels the same way and the user will only have a short loading screen while the grid is generated and the blocks deleted. I don't want to rewrite the physics system on him so I'll try to keep it as similar as I can by hiding the real code behind triggers and scripts.

Thanks for all your help by the way. Never knew about the texture thing. I will drill this fact into the mind of the other, since he controls all textures.

Edited by Killpill, 09 May 2012 - 10:04 AM.

  • 0

#7 Gamer3D

Gamer3D

    Human* me = this;

  • GMC Member
  • 1815 posts
  • Version:GM:Studio

Posted 09 May 2012 - 06:12 PM

Converting a surface to a sprite takes forever (will making the surface a power of two change this?). Doing a bunch of small ones will only make this worse.

Depends on the bottleneck. If the problem is reading the surface pixels (For large surfaces, it probably is), then a bunch of small ones won't change much.

One thing I'm wondering is why you insist on making sprites from the tile surfaces. Surfaces can be drawn from directly, so it's not a graphical thing. Are you using tiles for collisions?

Thanks for all your help by the way. Never knew about the texture thing. I will drill this fact into the mind of the other, since he controls all textures.

A good idea. Non-power-of-2 textures (Note: They don't have to be square) display as expected on some graphics cards, but other cards do not support them; this leads to confused people wondering why their computer runs their game perfectly while their friend's computer does not look right.
  • 0

#8 Killpill

Killpill

    Extreme Halo Fan

  • New Member
  • 294 posts

Posted 09 May 2012 - 07:01 PM

Oh, the point isn't tiles. Sorry. I would be using the surface for collision detection of the entire map (these blocks are objects used as triggers for physics and nothing else). I mentioned the tile "problem" because that may very well be a problem we need to solve, even if I don't think so. I believe we need to optimize the game, since tiles are making him lag, while he believes he has to remake the map to be smaller so the "tiles" don't lag him. Making the map smaller does many things. I think the tiles are just pushing that limit that causes lag to be visible but our code has always had underlining flaws.



I made the terrain write itself to an array and made the detection work.

This creates the array:
//Get the width and height of the array
_width = ceil(room_width/32);
_height = ceil(room_height/32);

//Loop through the array and set the default value to false
for (_i = 0; _i < _width; _i+=1){
  for (_ii = 0; _ii < _height; _ii+=1){
    global.map[_i,_ii] = false;
  }
}

//Loop through the terrain objects and delete all the ones on the 32x32 grid
with objSolid {
  if (x mod 32 = 0 && y mod 32 = 0){ //if there is a remainder the object is not on the grid
    global.map[x/32,y/32] = object_index; //write object type to the array at object position
    instance_destroy();                   //destroy object
  }
}


Edit:

Physics are finished for bipeds. It was kind of a hack job but it works and getting rid of all the blocks solved his lagging problem. I've got to add this for the rest of the objects but it seems all is well.

Edited by Killpill, 09 May 2012 - 11:39 PM.

  • 0




0 user(s) are reading this topic

0 members, 0 guests, 0 anonymous users