HiveBrain v1.2.0
Get Started
← Back to all entries
patternMinor

Chunk Loader for 2D Mining Game

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
chunkloadermininggamefor

Problem

I've spent a couple days on my latest project, and I believe that I have achieved a sufficient level of efficiency to post it here for review.

The idea of the game is to mine out an "infinite" world of blocks by tapping on the screen. Objective-C (and Sprite Kit specifically) do not allow you to process a large number of sprites without extreme slowdown. Even though Sprite Kit is supposedly truncating the nodes to only those that are visible on screen, if thousands of nodes are present in the world, the slow down is completely obvious.

To overcome this problem I have devised a simple chunk loader that will only load the visible nodes into the world before rendering. It is working very well, and it includes dynamically expanding the view when zooming in or out.

Edit: I have made some major changes to the code that improved performance dramatically. I have edited the code in this question to include these changes. Next time I will make sure to wait a day before posting a question like this so that I do not miss such obvious performance increases. Initially, I was re-rendering the entire scene on each update. Now I have created a system that identifies which parts of the scene need to be updated, and only updates those.

I will only include the relevant parts of the scene:

DWScene.h

```
//this runs each update loop in a fixed time step
#pragma mark - Update
-(void) updateWithWorldBounds:(CGRect)worldBounds {
[_game.gameWorld loadChunksForBounds:worldBounds];
[self loadNewChunks];
[self removeChunks];
[self updateBlocks];

}
-(void) loadNewChunks {
for (DWChunk *chunk in _game.gameWorld.loadedChunks) {
if (![_visibleChunks childNodeWithName:chunk.chunkName]) {

SKNode *chunkNode = [[SKNode alloc]init];
chunkNode.name = chunk.chunkName;
[_visibleChunks addChild:chunkNode];

for (DWBlock *block in chunk.blocks.allValues) {
[chunkNode addChild:[self blockSpriteForBlock

Solution

When I first read this question, my first thought is... how come this is all run on a single thread?

I realize that part of this reason is because at the end, we're drawing UI and that all has to be done on the main thread. But that doesn't mean everything has to be done on the main thread.

However, it should be noted that I really don't like this term "load" or "loading" in your title, methods names, etc. What we're actually doing here is rendering.

To me, "load" says we're moving things into memory. This can either mean we're either reading from a file and instantiate objects to represent the contents of that file, or we're reading from some sort of webserver, to do the same thing, or we're instantiating from scratch based off preset constants, random values, or user inputs. The point here is, to me, "load" is the step from which we go to having no instantiate objects in RAM to represent whatever we're "loading" to the point of having those objects in RAM.

Meanwhile, "render", to me, says we're taking instantiated objects in RAM and representing them graphically on the screen. In practice, a lot of times, this may mean instantiating a new object... but this is different from loading. We're instantiating a UI object only to represent the graphics of the model object we "loaded" in the loading step. In reality, a lot of times, we're actually "re-rendering." Consider how a UITableView works. We only render (instantiate a new UI object) if there isn't already one of that time in the table view. Otherwise, we rerender (recycle) the one that has just scrolled off the screen. We don't instantiate a new cell, we simply grab the old one and change how it looks a bit. If we rendered a new one each time, we'd run into one of two problems. Either a) we'd run out of memory as we stuck a ton of cells into RAM or b) we'd scroll very slowly as we'd be taking the time to deallocate, allocate, and initialize a new cell every time one scrolled off and a new one scrolled on.

I know this seems like a long rant just to point out a seemingly inconsequential difference in word choice... but it's not actually inconsequential.

At the end of the day, you've written this code because keeping all the rendered chunks in memory was too much, so instead, now, this code just keeps the models of the chunks in memory and then renders them just-in-time to appear on the screen. That's fine (though, I still recommend multithreading the rendering).

But how big can the world get? What's the memory footprint of the chunks? Is is possible that the player's world is too large to keep all the models of the chunks in memory at once?

We might have to do something similar to this to actually load chunk models into memory from file (at a farther distance than we render them) and then save them to file from memory (then deallocate) as they move farther away. Even if the current game limits wouldn't be too much to keep the entire world in memory, making this change would allow your worlds to have an unlimited size. (Well, less limited anyway... there's still a limit on how much you can write to file... but you could even save it on the cloud, etc).

Context

StackExchange Code Review Q#65129, answer score: 5

Revisions (0)

No revisions yet.