• Hey Guest! Ever feel like entering a Game Jam, but the time limit is always too much pressure? We get it... You lead a hectic life and dedicating 3 whole days to make a game just doesn't work for you! So, why not enter the GMC SLOW JAM? Take your time! Kick back and make your game over 4 months! Interested? Then just click here!

Legacy GM Optimize drawing things into an endless runner

J

JoltJab

Guest
TLDR: Endless runner with individually saved rooms bring up new rooms of tiles as you run through them. Is there a better way to draw the environment art so it can play on the phone?




Ok so first, thanks for taking the time to read this. It’s appreciated!

So here’s my problem. My framerate is droppin way too low and I’m wanting to optimize how I’m drawing the art on screen. Currently this is how things work:

Step 1: create a new room and populate it with how the level will look.
Step 2: save the position and object type of every instance within the room to a file (also do the same thing with all other rooms and add them to this file)
Step 3: in game as you run through a room, randomly choose a room from the file and start drawing tiles from that room.

I save all instances as 32 x 32 tiles, and if an instance is bigger than that, I just cut it up into multiple tiles.

Along with that, I only create tiles for what’s specifically on screen and delete tiles when they’re off screen. (ignore the framerate in the gif. This is an old gif and doesn’t include backgrounds, enemies, collision, etc. in the calculation)

on the computer it stays around 300 fps, but it isn’t as nice on the phone. Falling down to 20 at times, and I still have more things I need to add.

I don’t know if this is enough info to help explain the problem or not. If you want to know more just ask! Otherwise, would you know of a better way to do this?
 
Are you destroying the instances, or just deactivating them? If you are destroying them, what happens if you deactivate them instead? Does your FPS stay current?
 

TheouAegis

Member
1) Use binary files. When you compile the room data to generate the files, first create a buffer and write all the object data to the buffer with a strict adherence to structure. For example, if your room width is a 2-byte value, make sure all x coordinates are written with 2 bytes. If you want your game compatible with low-memory phones, you can manipulate the bits to save on file size, but that should be unnecessary nowadays. Anyway, save the buffer to a file. Then in-game when the random file needs to be opened, load it into a buffer then close the file.

But i'd consider just loading every file into its own buffer at start, memory constraints permitting.

2) How are you deciding if a tile is out of the view? That could probably be optimized.
 
J

JoltJab

Guest
@Michael McMullen , I'm destroying them. I've tried leaving them deactivated but it builds up super fast and the whole game slows to a crawl.

@TheouAegis

1) I think I'm already doing that? I am saving all the stuff to a buffer, then saving the buffer to a file. In game, I'm then pulling the room info from the file and reading it from a buffer. What do you mean by "manipulate the bits to save on file size"?

2) it's based on the position of the player. When their X or Y is in a new tile, it deletes tiles from one side and adds tiles to the other. I once tried moving tiles from one side to the other and just changing how they looked, but it didn't work well and required a max number of tiles. See there's 6 different layers for tiles. 1 background (the red walls), 3 decorations (wooden beams, chains, skulls, etc.), and 2 foreground (some edges overlap in order to look right). If I were to try moving tiles instead of deleting and re-creating them, I'd need to always have 6 tiles at every grid position. Most of those tiles would be empty and just slow down the game more than deleting and re-creating does.
 

TheouAegis

Member
1) Don't worry about manipulating the bits. You want speed right now more than memory management.

2) So you have a buffer around the view of 1 tile each direction? You are actually using a view-camera, right? Or how are you moving everything relative to the player, if not using a view-camera?
And you're updating the tiles during the End Step event? Or during Pre-draw? Those are the only events I'd personally use.
Have you actually speed-tested using 6 tiles of ID 0 per cell on mobile? I bet it's still pretty slow, but just wondering if you've tested it vs. just spouting rhetoric (it happens a lot around here, lol).

Does it drop to 20 fps as soon as you move to another tile (and thus update the tiles around the view) or only after a matter of time?
Are you closing the files as soon as you have copied them to the buffer?
Are you using just one (or 2 or 3) buffer(s) over and over (using buffer_create() only at the beginning of the game and not every time the file is read)? Or are you using a new buffer every time (calling buffer_create() every time a file is read), and if so, are you destroying each buffer?

What's your buffer writing code (when generating rooms)? What's your buffer reading code (when updating tiles)?
 
J

JoltJab

Guest
dang it! I didn't mean to post that! Also I don't know how to delete messages so I'm just gonna erase it all and write this instead. Please ignore this message. I'm almost done writing the other message.
 
J

JoltJab

Guest
@TheouAegis
Sorry it took so long to respond.

I am using views. I honestly don't know how to show stuff on screen without using views. But I don't really know what you mean by, "So you have a buffer around the view of 1 tile each direction?". Currently the way it works is like this. Each "world chunk" (the endless runner game is split up into pre-created chunks you run through) has a buffer. That buffer has info about each 32x32 px square on the game's grid. That info is; collision type, back wall tile type, decoration tiles (wooden beams, chains, skulls, etc.) (there can be up to 3 different decorations on a single grid square position), midground tiles (the ground and ceiling tiles your character runs on) (up to two of these tiles can overlap on a grid square position). Earlier I said there were 6 different types of tiles, but I forgot to say that the collision is also included in the same buffer. So there's 7 different pieces of info per grid square, but only 6 are of tiles.

The way I'm moving is with the view-camera. The camera follows the player as they run forward. the gifs I showed earlier didn't have a moving camera, so here's a more up to date gif below. Also I update the tiles in the End Step event.

(keep in mind this is frame rates on the computer not the phone) (also don't mind the placeholders)


"Have you actually speed-tested using 6 tiles of ID 0 per cell on mobile?" I have no clue what that means. As far as rhetoric goes, I'm not trying to spout it. I'm not too good with jargon so if I use the wrong word for something that's my bad. I've been going back through this message to make sure it all makes sense and I'm using the right words in the right spot. So with that, my earlier messages where I said rooms, I meant "world chunks". And where I said tiles, I was mixing up tiles and 32x32 px grid squares. And the framerate isn't 20 but actually much higher. I was going off memory and was inaccurate. Sorry about that. On my little test, it can average anywhere from 80 to 60. But I'll get back to that more at a later part. The gameplay all takes place in a single room. If what I say somewhere doesn't make sense just let me know and I'll try to re-phrase it. That's also why it's been taking me a little bit to respond. Sorry about that. I was actually going off memory when asking this question as I've been putting this problem off for a little while. I was going back through all my old code to fully remember what I did and how I did it.


So the part earlier about 80-60 frames on average. You see the numbers above the main character's head in that gif? Well the average fps (I think it's like... the average of 60 frames?) reads 80 - 60. And although those sound like good numbers, you can feel any time it dips below 60. It my average at 60 but if any of those frames dip below, you notice. And it certainly does. It really seems to be based on the number of tiles on the screen. Anything under 300 and there seems to be no problem. But once you hit 400 or more tiles on screen you start to notice the game slow down. And at times the number of tiles can go over 800! Staying still doesn't seem to make a huge difference in framerate though. Though it's just a little tough to tell with this being an auto-runner. (also I forgot to say the game runs at 60 fps)

After going through it, I don't think I am closing files after putting them into a buffer! How do I do that?

I believe it's 3 buffers for current world chunks (the current chunk I'm in, the one behind me, and the one in front), and 1 buffer that holds all the chunks. And I'm re-using the 3 buffers. I clear them and add new tile and collision info from the allChunks buffer. And if you're wondering, "why not just have 1 buffer you pull from instead of copying chunks into yet 3 more buffers?" well that's because when I create a new world chunk, some items in that chunk are randomized. That way, when "Chunk 7" appears again, it looks slightly different! They'd all look the same if I overrode the allChunks buffer. I make copies so I can change them.



The code for creating the buffers for each world chunk kind of works like this:

--Create Event--

-copy file of allChunks.jab to a buffer named allChunks
-create a list of 3 world chunks
-create 1 buffer of the current chunk you'll start in
-create 2 empty buffers


--step event--
-if your character is entering a new world chunk,
-look at the list to figure out which world chunk you'll be making next
-resize the buffer to match the new world chunk you'll be bringing in soon
-copy the chunk from the allChunks buffer over to this new buffer.
-over the course of several frames, randomize certain values in the buffer


The code for creating/ updating the tiles on screen

--end step event--
-if the character's position is within a new 32x32 grid square then the one she was in the previous frame
-check the 8 cardinal directions to figure out which new grid square she's in.
-if she moved right into the next grid square,
-delete all tiles in the left most column
-check which chunk the right most column of tiles is coming from and add tiles from the buffer belonging to the very right column
-if she moved left, up, down, or a combination of left down, right up, etc.. then do very similar things to what's written above


If you want the actual code.... there's a LOT of it. Hundreds and hundreds of lines. I'm still a huge noob at coding games, so I make it all up as I go. It isn't always very clear or clean.
 

TheouAegis

Member
I'm walking into work and can't read the whole thread just yet, but ask for closing files...

Quick question, are you using buffer_load() to copy the file contents into the buffer? I'm going to test something when I get home.

Did you check the Windows task Manager while your game was running overtime to verify you don't have any memory leaks? if your rooms are all the same size more or less, then your game should typically be using up the same amount of memory after loading three rooms as it does after loading five rooms. If the memory load is going up, then there is a possibility that what is causing it to go up is causing issues on Android.

But other than that, nothing concrete is coming to my mind just yet.
 
J

JoltJab

Guest
closing file? What's that?

And yeah I'm using buffer_load().

Oh! But I am noticed a very slight increase over time on the graph. This looks like it could be a separate problem though, as the increase is very gradual and wouldn't cause the problems that show up right when you start up the game. Or at least, I don't think it would?

It seems like the biggest problem I have though, is the number of tiles and not how they're being created.
 

TheouAegis

Member
Sorry for the tardiness, July 4th week is super busy at work.

So when exactly does it lag? Is it consistently low even before you start moving? Or does it only really drop once you move essentially 1 tile?
-if the character's position is within a new 32x32 grid square then the one she was in the previous frame
How are you calculating when this occurs?
-check the 8 cardinal directions to figure out which new grid square she's in.
-if she moved left, up, down, or a combination of left down, right up, etc.. then do very similar things to what's written above
You have 3 buffers, so that implies to me you only move horizontally across chunks. If that's the case, you don't need to check 8 directions.

Is your game scrolling like a normal, modern game where it can scroll 1 pixel at a time, or is it like the MSX where it scrolls 1 tile at a time?

When you refer to tiles, are you actually talking about instances of objects, or do you mean actual tiles which exist strictly in the background layers?

How are you tracking which tiles are on the far left column or on the far right column?

Personally, I think the view might be making things tougher for the system to handle.

Tracking the tile count issue is easy enough -- have your code still run through all 6 tile layers, but only create/modify the first 2 or 3. If your game speeds up significantly (200fps with no slowdown), then it's the tile count. If your game hardly changes at all (120fps with or without slowdown), then it's probably your code. (The fps values I mentioned are just rough estimates, assuming your game can even run 200fps on your phone).

Also, what are the specs of your phone?
 

Yal

🐧 *penguin noises*
GMC Elder
If you can put the stuff in an array instead of files, you get much faster loading (since it's always in memory)... you could combine this (external storage + fast access) by loading all the files into a multidimensional array on game start. I'd recommend an approach where you save [x,y,object] tuples instead of a grid where you have object or "nothing", to save space (because most cells will typically be empty)... this can be done in several ways, one of which is to have a 2D array, where the X axis is which "room", the Y axis is data for objects in this room. Y = 0 would be the total number of objects, Y= 1, 2, 3 would be the first object's x, y, object ID... 4, 5, 6 the next object's, and so on.
 
J

JoltJab

Guest
Holy crap this is a big post!



@TheouAegis

Hey man, you're doing me a favor by responding at all! You don't gotta apologize for not responding sooner! Also, I hope your 4th of July went well.

So when exactly does it lag? Is it consistently low even before you start moving? Or does it only really drop once you move essentially 1 tile?
moving doesn't seem to make a huge difference. The thing that seems to slow it down is how many tiles are on screen at any given time.

How are you calculating when this occurs?
So I said, "based on the character's movements" but I kinda goofed that. It's actually the camera's movements. But it's still the same idea. In my code I called each grid square a "cell".

Code:
ccxPrev=ccx;            //the previous camera cell x position
ccyPrev=ccy;            //the previous camera cell y position
ccx=cam.x div 32;     //the camera cell x position
ccy=cam.y div 32;     //the camera cell y position

result=(sign(ccx-ccxPrev)+1)*3+sign(ccy-ccyPrev)+1;      //0-8 result number based on which direction you move (or don't move) within a grid cell
these lines of code above are used to compare the previous position and the current position to see if there's any difference. And that comparison is then put into a variable I called "result". the number it spits out then goes into a switch case to add and remove tiles to line up with the new position on the screen. Also the result number corresponds to a direction like this:

0 3 6
1 4 7
2 5 8

"4" is no movement, "0" is up and left, "7" is right, etc. etc.


You have 3 buffers, so that implies to me you only move horizontally across chunks. If that's the case, you don't need to check 8 directions.
Part of this is correct. I do move horizontally across chunks. No chunks connect above or below the current chunk. But you can still move up and down within the chunk. So there can be tiles above or below what you can see, that don't yet exist. If I were to create all tiles above and below what's visible, that would be super wasteful as a lot of tiles (hundreds) wouldn't even be visible! So I do need to check 8 directions.


Is your game scrolling like a normal, modern game where it can scroll 1 pixel at a time, or is it like the MSX where it scrolls 1 tile at a time?
you saw the gif images I posted in the previous messages right? If you press the "spoiler" button it shows the gifs. It scrolls like a modern game 1 pixel at a time. But the tiles update like the MSX that scrolls 1 tile at a time (I assume? I don't actually know what an MSX is). Take a look at these 2 gifs from previous posts to see what I mean.
(you see how the player [green square] updates per pixel, but the tiles update per cell? Imagine the camera following the player, and that's how it works.)

(actually you don't have to imagine! You can see that working right here.)


When you refer to tiles, are you actually talking about instances of objects, or do you mean actual tiles which exist strictly in the background layers?
I mean actual tiles. Are all tiles part of a background layer? If so then yes? Even though some of them are in front of the player, which is a sprite. I use tile_add and tile_delete to create and remove tiles in game.


How are you tracking which tiles are on the far left column or on the far right column?
oooohh.... That's one of the things that get super complicated (at least for me). I'll do my best to explain, but this part gets super hakey.

Also if there's a name for doing this, I don't know it. So I apologize if I over explain a bunch of stuff, I just don't know of another way to clearly explain what I'm doing. But just in case I'll include a TLDR at the bottom. I suggest reading that first, and if it doesn't make sense try looking at the longer bit. Otherwise feel free to ask questions!


So every tile that's on screen is in a ds_list, within a ds_list. It's a list of lists! Big columns all in one big box. It's kind of like a 2D array. The picture above can help explain.


Here we can see columns with a different number of tiles within each of them. they're all in 1 big square. As well as an arrow at the top.

I start with a variable called "listEdge" and that variable starts at zero. listEdge is the arrow. Imagine the arrow in the picture all the way to the left of the first column. When listEdge is at zero, that's where the arrow would be. Each time you add a number to listEdge, the arrow moves to the right. And if you subtract a number, the arrow moves left. It also loops around if it's less than zero, or equal to or greater than the total number of lists. So that means that within the image, listEdge is equal to seven.

So each time we move one cell to the right, we add one to listEdge, and the arrow moves. Making sense to far?

So in game, because the game window size is always the same size, we never need more than a certain number of columns (for this example, to keep things simpler, we don't change the game window size). If it takes 10 columns to fill the screen, then that's all we need correct? Correct! So if we move to the right, and the column of tiles on the very left is no longer on screen, and we have an empty space exactly 1 column big that needs to be filled with tiles! We can re-use the column that's off screen! We can clear out all the tiles that are off screen, and fill the column back up with tiles that would be on screen!

So here's where things get a bit complicated. The order of the columns don't actually matter. You could organize the columns in any way you wanted and it wouldn't effect how the game looks at all. But how right? Wouldn't the tiles look like they're all over the place, all cut up and weird? Well actually no. You see, these columns are actually just for organization of the tiles. The columns make it easy to find which tile I want to interact with. Where a tile is within a column, or what column that tile is in makes no difference to where the tile is placed within the game. When you create a tile, you decide it's x and y position within the word (something I'm guessing you know). So it doesn't matter if that tile is in a ds_list or a ds_grid or an array or anything like that, because that doesn't change the tile's x and y position.

So then why do all this, right? Sounds like a lot of work to make all these extra lists and stuff. Well, in putting all these tiles into ds_lists makes it really easy to delete them! So if I take all tiles with the same x position and put them in 1 list, then when I move right and all those tiles are off screen, I can look at the one ds_list and delete everything with in it. Other wise I gotta check the hundreds and hundreds of tiles currently existing and delete the ones with a certain x position value.

So if the order of columns doesn't change where tiles go, then how do you know where tiles at the edge of the screen are? Well that's where listEdge comes back into play! Now again, the reason we're putting tiles into the ds_lists is for the sake of organization. So listEdge is very helpful. It helps tell where the left most tiles are, as well as the right ones. So let's pretend listEdge, the arrow in the image, is at the very left of the columns. The first column on the very left of the image are all the the left most tiles. And the column all the way on the right in the image holds all the right most tiles. If you imagine the columns looping as you move through them, the column to the left of the arrow is the right most tiles, and the column to the right of the arrow is the left most tiles! Now this rule always needs to be true. The arrow (listEdge) is used to say where the left and right columns loop back. So as you move right and erase and re-fill tiles in columns, you move the arrow to the right tooYou can now use listEdge to find where the left most and right most tiles are.

So that's how you find the tiles on the left and right of the screen, but what about top and bottom? Well, I've organized the tiles in the ds_lists/columns to help with that too. See the tiles in one column are organized by their y value. Tiles at the top of the column (beginning of the ds_list) have the lowest y values, and high y values go to the bottom of the column (end of the ds_list). So if you move upwards, you then have a "bottom most y value" and you start deleting tiles at the bottom of the column that are over that y value. Those are tiles that are no longer on screen. And now we add tiles to the top, but remember to check which chunk we're in first as to grab the right tiles. And now do that with each column. That's how you add and remove tiles when moving up. And just do the reverse (remove tiles from the top, add them to the bottom) when going down. Doing it like this allows you to put any number of tiles into the column you want. As well as keeping all the different tiles in a single list instead if 6 of them.


--TLDR--
For going left and right:
a ds_list holding ds_lists. the 2nd dimension of ds_lists I visualize as columns. In the columns we put tiles based on their x position. The columns loop, but we have a variable called "listEdge" (the arrow in the picture) that helps show where the left most and right most tiles are. The column starting at this point (where the arrow is) is the left most column of tiles on screen. subtract 1 (and remember to loop based on the number of columns) and that is the column of tiles on the farthest right of the screen. Ever time you move right, you erase all tiles in the column under the arrow (which are the tiles all the way to the left of the screen), fill that column up with tiles (check which chunk you're pulling tiles from first) that go on the right most side of the screen, and move the arrow forward by adding one to listEdge. Do the reverse when going left.

For going up and down:
in the columns, the tiles of the same x position are grouped together. But they're organized by their y position. The lowest y position is at the start of the column (the top) and the highest is at the end of the column (the bottom). If you move up, go through 1 column and delete the tile at the end. Check the new end tile and see if it's off screen. If it is delete and repeat this cycle until you find a tile that isn't off screen. Then go through the buffer of tiles and add tiles (check which chunk you're pulling tiles from first) to the beginning of this column based on the x and y position you're trying to fill. Repeat this process of adding and deleting tiles for all columns. If you go down instead, delete the beginning tiles, and add tiles to the end instead.


Personally, I think the view might be making things tougher for the system to handle.
what do you mean? Care to elaborate? Is there a better way to do this?


Tracking the tile count issue is easy enough -- have your code still run through all 6 tile layers, but only create/modify the first 2 or 3. If your game speeds up significantly (200fps with no slowdown), then it's the tile count. If your game hardly changes at all (120fps with or without slowdown), then it's probably your code. (The fps values I mentioned are just rough estimates, assuming your game can even run 200fps on your phone).
I'm pretty sure it's the number of tiles. There's actually chunks within the game that are stripped way down. They only have 2 of the 6 tile types and in those chunks the frame rate shoots way up. But when you're in chunks showing all 6 tile types on screen, and filling the screen with those tiles, the frame rate really starts to crawl.


Also, what are the specs of your phone?
oh jeez! I got some cheap crappy phone I got in 2015. I'm not sure what info is helpful, but I found this wikipedia page that shares a bunch of stuff in case the info I show here isn't what you want to know. https://en.wikipedia.org/wiki/Moto_E_(1st_generation)

phone type: Moto E
operating system: 4.4.4 kitkat
Total Space: 2.21GB


@Yal

I save the chunks in a file because I build the chunks in a different project. Exporting the files over is a lot easier, plus in game I just move them all out of the file and into a buffer for reading. I only go through the file once, and it's right at the beginning of the game. So if I understand you correctly, I'm already moving all info from the file on gamestart. But I'm putting it into a buffer instead of an array. That's faster to read from, right? And I think I understand the rest of what you're saying. But if I don't use the position within the 1D buffer as a way of knowing the tile's coordinates, how do I find the exact tile I need when going through the buffer? Wouldn't it take longer, and thus lower the framerate, to have to search for the tiles I need instead of searching a specific position within a buffer? You are 100% correct in there being empty spaces within the buffer, but isn't that the better option in this situation? I mean, I could be totally wrong. And if I am, I'd love to hear why. But right now I'm not sure if having a big list of tiles like that would work... unless you're talking about how to create the tiles on screen, in which case I think I'm already doing something like what you're suggesting but with ds_lists.

American or not, I hope your 4th of July went well too Yal.
 
Last edited by a moderator:
Top