Categories
Game Design Game Development Geek / Technical

Freshly Squeezed Progress Report: Dungeon’s Too Dark, Can’t Proceed

Last time, I reported that I was replacing my test dungeon with the first actual locations for The Dungeon Under My House, my second Freshly Squeezed Entertainment project.

I set out to allow the player to interact with the new environment.

Sprint 2024-6: Pre-production and initialization

Planned and complete:

  • Prevent player navigation if dungeon area is pitch black

Unplanned and incomplete:

  • Party member can carry items

Last week started out quite productive, and I was able to knock out a feature that impacts the player’s navigation through the dungeon. If you are trying to move into a grid cell that is too dark, the game stops you from proceeding.

The way it does so is by treating the darkness as an obstacle, much a wall or closed doorway that prevents movement and tells you why.

The Dungeon Under My House - too dark to proceed

And in this case, it hints at the next set of features I need to implement. You need a flashlight to proceed, and that means the characters need to be able to carry items, which means the game needs an inventory system.

I created an inventory system that allowed for two different types of items: individual items that take up an entire slot, and items that represent quantities.

So a Flashlight exists as an individual item. If you get a second Flashlight, it takes up a second slot.

But a Pickle Jar represents not just a jar but also the contents of the jar. Maybe you eat some of the pickles but not all of them at once. The original Legend of Zelda game had two kinds of potions, and one of them was basically “you can use it twice.”

I like the idea that using up all of the pickles results in an Empty Pickle Jar, which can come in handy.

But that can come later.

I spent a little bit of time trying to figure out how complex adding items to the player’s inventory should be. What if you have get a second jar of pickles? Does it add to the quantity of the original jar, or do you just get two jars, each with a set quantity? Most games do the former, but practically speaking, who wants to merge pickle jars together? Ew.

But I decided that such design decisions can come later when I need to decide them. For now, I created a simple Inventory which I can assign to entities in the game. So I did, and my next job was to ensure that inventories can get persisted when the player saves.

Which did not work, and it was incredibly bizarre because the code looked like it should. Yet running the tests was telling me that it wasn’t.

So I ended the week by losing productivity because I had accidentally introduced a bug that was manifesting in weird ways. I had one unit test that kept insisting that as soon as I added an item to an inventory that the inventory was still empty, and other seemingly unrelated tests would inconsistently take up a ton of memory on the system, seeming to lock everything up for minutes at a time.

Ultimately what was happening was that I was initializing my inventory’s internal std::vector and the internal integer that initializes its size in the wrong order, which meant, well, most likely it is undefined what happens, but it includes in the realm of possibilities initializing the size of the vector of Items with a very, very large value.

Once I figured it out, partially due to lots of logging statements and mostly thanks to the amazing tool valgrind highlighting the exact problematic code (and thanks to my Past Self for having high standards so that there wasn’t a lot of other noisy output due to other problems to wade through), I corrected the mistake, but by that point, it was a few precious hours of development later.

Never code tired.

Anyway, this coming week I anticipate finally adding the ability for the player to inspect the inventory of a particular party member as well as find items in other parts of the game, such as the bedroom where the flashlight will be.

And once the player can acquire a flashlight, being able to light the way in the darkest areas of the dungeon comes next.

Thanks for reading!

Want to learn when I release The Dungeon Under My House, or about future Freshly Squeezed games I am creating? Sign up for the GBGames Curiosities newsletter, and download the full color Player’s Guides to my existing and future games for free!

Categories
Game Design Game Development Geek / Technical

Freshly Squeezed Video Progress Report: The Real Dungeon Appears

Here’s the companion video for Monday’s Freshly Squeezed Progress Report: The Real Dungeon Appears:

Enjoy! And let me know what you think by replying below!

Categories
Game Design Game Development Geek / Technical

Freshly Squeezed Progress Report: The Real Dungeon Appears

In my last report, I continued optimizing the dungeon rendering code for The Dungeon Under My House, my second Freshly Squeezed Entertainment project.

I decided to finish it up and move on to more interesting work for this past week.

Sprint 2024-5: Pre-production and initialization

Planned and complete:

  • Render dungeon based on light levels

Unplanned and incomplete:

  • Prevent player navigation if dungeon area is pitch black

So, right away, I took a lot of my optimizations for drawCeilings() and applied them to drawFloors().

I even applied a new optimization. When I apply lighting data to a given pixel, I multiply the light level by a value to indicate gradations in lighting, then multiply that value by the light color’s red, green, and blue values.

Well, since lighting data is basically precalculated anyway, why not precalculate that math, too?

So now, in my code that gets called for each rendered pixel, I went from this:

    // Apply lighting 
    Color color = lighting.lightColor;
    color.redness *= lighting.lightLevel*.1;
    color.greenness *= lighting.lightLevel*.1;
    color.blueness *= lighting.lightLevel*.1;
    convertedPixelColor.tintInline(color);

to this:

   convertedPixelColor.tintInline(lighting.processedColor);     

It saves not only the creation of a Color object and the various multiplications but also on any subtle data casting/conversion that might be going on between doubles and ints.

While drawCeilings()/drawFloors() are still the major bottlenecks in my dungeon rendering code overall, the major bottleneck in those functions is no longer the code that applies color changes.

It’s funny. Every time I think I’ve hit a limit with optimizing this code, I keep discovering new ways to either entirely eliminate unnecessary calls or moving them so they aren’t called as frequently.

And I know that each time I do, it makes the game less taxing on player hardware and conserves the player’s battery life on laptops and mobile devices.

But, for now, let’s say that optimization is “done enough” and move on to the work of making the actual game!

Since I started working on dungeon-related code, I have had a test dungeon in one form or another. For a long time, it was two rooms, one with the dungeon entrance in the form of a ladder, with a short hallway connecting to a door that opens into the larger room which features a small closet and a column.

The Dungeon Under My House - test dungeon layout

It allowed me to experience navigating the dungeon, opening and closing doors, and most recently see how lighting works.

Well, that test dungeon is now gone.

This past week, I set out to design the opening layout that you’ll actually see in the finished game. While I anticipate iteration and tweaks, I envisioned the ladder from the basement of the house descending into a long, dark hallway.

The Dungeon Under My House - early dungeon space

The only light source is the opening in the ceiling at the entrance itself, which means that as you look down the hallway, it gets darker and impossible to see what is beyond a certain point.

The Dungeon Under My House - early dungeon space

What’s at the other end of the hallway? You can’t find out yet…

Well, technically at the moment you can.

Currently, the game doesn’t actually prevent navigation into the darkness yet, because I had spent much of my time finally making it easier for me to actually create the dungeon spaces. Until now, each cell in the dungeon was individually defined in code. Each wall tile, floor tile, ceiling tile, and door or ladder or light source was coded to make the test dungeon.

So I wrote some helper functions in my dungeon creation code. Now I can define entire arbitrarily-sized rooms at once, as well as defining columns, which are like inside-out rooms, with walls defined in the adjacent cells to enclose a space.

To create my test dungeon, I was able to remove over 30 lines of code and replace them with 7 lines.

The entire time, I was also envisioning the creation of a tool to let me use a GUI to create the dungeon so I wasn’t writing it in code. Even with these helpers to reduce the code needed to generate all of the grid cells, it still gets hard to picture exactly where on my graph paper a particular cell will end up. Plus, it would be nice to have a tool that can generate walls, ceilings, and floors with variations in textures automatically that I can then tweak manually.

I’ve looked briefly into existing tools such as Tiled, and I should dig a bit more to see how I can leverage them rather than spend time on creating my own editor. Either way, having such a tool will pay dividends as I continue designing and creating spaces in the dungeon throughout the remainder of this project’s development.

Thanks for reading!

Want to learn when I release The Dungeon Under My House, or about future Freshly Squeezed games I am creating? Sign up for the GBGames Curiosities newsletter, and download the full color Player’s Guides to my existing and future games for free!

Categories
Game Design Game Development Geek / Technical

Freshly Squeezed Video Progress Report: Faster Dungeon Rendering At Last?

Here’s the companion video for Monday’s Freshly Squeezed Progress Report: Faster Dungeon Rendering At Last?:

Enjoy! And let me know what you think by replying below!

Categories
Game Design Game Development Geek / Technical

Freshly Squeezed Progress Report: Faster Dungeon Rendering At Last?

Last week, I reported I was optimizing the dungeon rendering code using a profiler for The Dungeon Under My House, my second Freshly Squeezed Entertainment project.

I continued optimizing, with an eye toward reducing the number of calculations that needed to occur when rendering the floors and ceilings, my main bottlenecks.

Sprint 2024-4: Pre-production and initialization

Planned and incomplete:

  • Render dungeon based on light levels

Two weeks ago, I reported that while I could light up the dungeon, it was too slow, but then I fixed a silly architectural representation issue that was slowing things down needlessly and it was much faster.

In the next week, I continued optimizing because while it was faster and rendering much more efficiently (and so uses fewer hardware resources and costs less electricity and saves battery usage on mobile devices), I wasn’t satisfied and wanted even more performance improvement.

The Dungeon Under My House - profiler output

Thanks to the use of an actual profiler, I could see the bottlenecks were specifically drawing the floors and ceilings, but I could also see that there wasn’t any one thing within those functions that was the bottleneck. There were a lot of different things that contributed to the slowness, and the main culprit was that so many pixels needed to be drawn each frame, each of which uses all of that processing capacity.

This past week, I got a late start on my game development efforts, but I managed to make some changes to draw pixels only when needed, which helped a lot, plus some other improvements.

At first, I thought I was going to need to draw the walls and doors and such to a buffer, then scan through the buffer to find pixels that weren’t drawn to yet, then draw the ceilings and doors for only those pixels.

But as I was working with SDL2’s textures, and textures by and large are not optimized to be read from, I was worried that this approach was going to be too slow even if I did cut down on the number of pixels I needed to process.

Instead, it turns out that I already had the information I needed when I drew the walls, so there was no need to read each pixel.

See, when I raycast walls, I keep track of the depth at each column. If I hit a wall, I know how far away from the camera it is.

This depth information was helpful for drawing things like doors and ladders around the existing walls, and it can now be helpful to draw the ceilings, which used to be drawn first and flood filled the background.

When I attempt to draw a ceiling pixel, I can check the projected distance associated with that pixel, compare it to the known depth of the wall, then draw only if the depth of the pixel is closer.

I was making many of my optimizations just for the drawCeilings() function, mainly so I can compare it to the drawFloors() function. Before optimization efforts started, those two functions looked very similar, so comparing their performance tells me if I am making actual improvements.

The Dungeon Under My House - profiler output

And it was an improvement, but it wasn’t as nice of an improvement as I was hoping for, mainly because the only calculations I skip are related to grabbing the texture and lighting data.

Many of the optimizations I did earlier were to move as much of the calculation out of the inner loop as I could, and I suppose now that the inner loop might be processed fewer times that it might make sense to move those calculations back, but I suspect the inner loop still does too much work and things will slow down again. After all, a smaller percentage of a lot of pixels is still a lot of pixels, relatively.

However, the profiler helped me to find that I was needlessly creating and deleting objects, so I did more inline calculations at the bottlenecks, and it helped get the performance of drawCeilings() significantly more efficient compared to drawFloors().

When I animate transitions normally, it feels very smooth, even without making the same kind of improvements to drawFloors() yet. However, if I increase the length of time to animate transitions, I can tell that my mouse cursor doesn’t render as often as when the screen is stationary.

It’s tolerable, I suppose, especially for this game which does not rely on real-time twitch muscles to play, and the mouse cursor isn’t relevant when playing on a mobile device, but I hate that it feels noticeably laggy at times.

That said, I should finish up my optimization work soon and move on to more game content work, such as creating a real dungeon level to replace the test dungeon I have come to know and love. The rest of the game needs attention, and I think I could spend forever on optimization code if I allow myself to.

Thanks for reading!

Want to learn when I release The Dungeon Under My House, or about future Freshly Squeezed games I am creating? Sign up for the GBGames Curiosities newsletter, and download the full color Player’s Guides to my existing and future games for free!

Categories
Game Design Game Development Geek / Technical

Freshly Squeezed Video Progress Report: Profilin’ and Optimizin’

Here’s the companion video for Monday’s Freshly Squeezed Progress Report: Profilin’ and Optimizin’:

Enjoy! And let me know what you think by replying below!

Categories
Game Design Game Development Geek / Technical

Freshly Squeezed Progress Report: Profilin’ and Optimizin’

In my previous report, I got into the details of how I fixed the silly way I was storing data in my game so that it was more efficient and allowed the dungeon rendering to be faster in The Dungeon Under My House, my second Freshly Squeezed Entertainment project.

This past week, I continued optimizing my dungeon rendering code, with a goal of getting the animations to be 60 frames per second, plus figuring out why one door in the dungeon is so well lit compared to what it should be.

Sprint 2024-3: Pre-production and initialization

Unplanned and complete:

  • Defect: Door visual floats in middle of view when open and seen from the side; should appear to block view to right instead

Planned and incomplete:

  • Render dungeon based on light levels

In my last report, I said I was done with the rendering of the dungeon based on light levels, but I wasn’t satisfied with the performance, even after my changes from last week, so I decided to continue optimizing.

Even though the game isn’t meant to be played in real-time, I think the game feels a lot better when it runs at 60 FPS, and the slowness of the dungeon rendering was getting in the way of making the entire game feel crisp and smooth.

But first, this door was getting on my nerves.

The Dungeon Under My House - lighting the dungeon walls

That image was from a few weeks ago, when I hadn’t started working on the floor and ceiling rendering code yet. I was wandering through my small test dungeon, checking how the walls got darker the farther away you were from the light source, when I discovered this very bright door and doorway.

What was going on there?

I double-checked, and I didn’t set any ambient light on that cell, and in fact, the walls next to it were darker. It was just the door and its associated doorway that was rendering so bright despite the fact that the cell it belonged to was rendering much darker.

So I kept digging and logging, and eventually I decided to set the default color of a cell to something extreme. And there was my my first clue: apparently the door wasn’t taking its color from the cell it belongs to but from an adjacent cell, and in this case, one that was out of bounds and so wasn’t paying attention to the lighting code.

After digging into the code, I discovered that the way I wrote the code and the way the door is represented means that the wrong cell’s lighting data was being used. Basically, the hinge edge and the far edge of the door is stored, and I used the hinge edge’s location to identify the cell’s position. In some orientations, this worked fine, but in others, it grabbed the next cell.

And for things like the doorway, well, those could be arbitrary dimensions. How do I generically identify which cell it should belong to?

Well, I decided that I would just store the door’s current cell as data in the door itself, so instead of trying to derive the cell based on the door’s data, I could just get the cell itself.

Anyway, I also had a different problem with doorways. If the door is open, and you are facing the entryway, the door will be seen on the side.

The Dungeon Under My House - open doorway

But if you are facing away from the entryway (that is, the entryway is behind you), the door should be on the other side.

And it is…except it kind of floats out in the middle of the viewport.

The Dungeon Under My House - open doorway

This is ugly, and I wanted to fix it, too. If the door is to the side, it should block your view from that side, right?

Since this is raycasted code, the best idea I had for what was going on was that the rays were going around it.

The Dungeon Under My House - Why Doors Look Wrong

So I think intuitively the rays would hit the door behind the camera plane, too, and should show that area as blocked, but the point of the camera plane is to not draw behind it, and the point of the rays being projected out was to avoid fisheye lens effects.

So, what could I do? I tried changing the field of view. I did find that if I change the FOV from 1.0 to 0.5 that the door seemed to block the far side of the view as I wanted, but the downside was that the player’s view seemed too constrained. I wanted the player to be able to stand next to a hallway and see that it exists, but too small a FOV prevents that.

But anything larger would show a floating door in certain orientations.

Now I loved my door and entryway, especially how it was centered in the tile, but I decided that the most straightforward thing to do is to make the door larger so that the hinge is at the far end of the tile rather than in the center.

The Dungeon Under My House - larger door

This way, when the player is facing out of the entryway, the door is seen at the far edge of the viewport rather than have the door protrude out of the middle of the camera.

The Dungeon Under My House - larger door

The Dungeon Under My House - larger door

I’m not happy that these kinds of doors now take up the entire cell, but it seemed the least bad option. And on the plus side, I think I found that having a FOV of 0.66667 looks great.

Anyway, back to optimizing. Now that walls, doors, ladders, ceilings, and floors of the dungeon are rendered based on lighting, I found that there was a definite difference between how smoothly the game seems to be responding when stationary versus when turning or moving forward or backward.

If I remove the ceiling and floor rendering code, the walls and “things” render super quickly. The game feels very smooth.

But if I remove the walls and “things” rendering code, the ceilings and floors render just a little too slowly. It’s fast enough in terms of functionality, but it feels jarring and off.

I had changed my game’s dungeon representation from an std::map to an std::vector, using fast indexing math to quickly access the data I need in constant time, but it was still slow.

And using the “poor man’s profiling” technique of interrupting the debugger and seeing what code it is sitting in most often got me pretty far before, I decided to try using a real profiler to see what I can learn.

So I love valgrind, and I used the command “valgrind –tool=callgrind” on my game’s binary file. While the dungeon rendering was much, much slower when running with a profiler, I was able to collect some good data.

Then I used kcachegrind to pull up some fun visualizations.

The Dungeon Under My House - profiler output

What they told me was that my drawCeilings() and drawFloors() functions weren’t slow due to any one particular thing. There were lots of different things going on, each of which was taking up a chunk of the processing.

What this means is that while I could tackle any number of optimization opportunities that I see here, and I did gain small improvements when I did do so, there was something bigger happening.

What’s very interesting is that the drawWalls() and drawThings() functions take up so little processing compared to drawCeilings/drawFloors(). My main bottleneck with the latter wasn’t the various functions it called or objects it created. It was that it was working pixel by pixel across half a million plus pixels.

Basically, no matter how fast I get the code to render a pixel, it still has to draw a lot of pixels, and the main way I am going to speed things up is to draw fewer pixels.

I didn’t get the chance to try it before the end of the week, but my next major optimization attempt will be to draw the walls and “things” first, then scan pixel by pixel to find what hasn’t been drawn yet and draw the ceilings and floors at those points.

It should cut down significantly on pixels drawn, especially since most of those pixels were getting drawn on top of anyway with my current approach, so I anticipate that this new approach will mean most of the pixels won’t be so expensive to render.

But we’ll see.

Thanks for reading!

Want to learn when I release The Dungeon Under My House, or about future Freshly Squeezed games I am creating? Sign up for the GBGames Curiosities newsletter, and download the full color Player’s Guides to my existing and future games for free!

Categories
Game Design Game Development Geek / Technical

Freshly Squeezed Video Progress Report: More Dungeon Lighting Work

Here’s the companion video for Monday’s Freshly Squeezed Progress Report: More Dungeon Lighting Work:

Enjoy! And let me know what you think by replying below!

Categories
Game Design Game Development Geek / Technical

Freshly Squeezed Progress Report: More Dungeon Lighting Work

Last week, I reported that I had started working on adding lighting to the dungeon walls and doors in The Dungeon Under My House, my second Freshly Squeezed Entertainment project.

Since then, I’ve been working on adding lighting to the floors and ceilings.

Sprint 2024-2: Pre-production and initialization

Planned and complete:

  • Render dungeon based on light levels

Changing the brightness of the walls and doors was relatively easy: I just changed the tint when rendering each column in my raycasting code based on how dark the current cell is.

But I couldn’t do the same for the floors and ceilings, as I said last time:

While the walls draw columns of textures and can be tinted, the ceilings and floors draw individual pixels of textures, each of which represents the actual pixel color from the source texture as a Uint32 value.

Now, when I usually deal with color, I am dealing with RGBA values, with each of R, G, B, and A being a value from 0 to 255, or even using hexadecimal values if I want to match what I might see in HTML color codes. So 0xff would be equivalent to 255.

Drawing walls, the only color I deal with is my tint value, which is the dungeon light level’s color multiplied by its brightness percentage.

But drawing the ceiling, I need to directly modify the color value of the pixel in question, and I am still figuring out exactly how. The Uint32 value should correspond to RGBA-like values, and in this case, I believe it is ABGR8888 in SDL2’s representation.

I imagined that I would need to change the hex values that represented the pixel, and I didn’t know if the math was going to be complicated or straightforward.

But my colleague Joel Davis, who has helped me before with his insight and experience on this project, suggested that the way to handle it (which I later found supporting posts online that say the same) is to first change to RGBA, then change the color as usual, the change back to the Uin32 hex values.

And wouldn’t you know, it worked as expected!

The Dungeon Under My House - applying lighting to the ceilings and floors

I had some strange artifacts in terms of white lines at the edges of the walls, though. When I stopped drawing walls to see what was going on, I could see that my default brightness was applying anywhere that there wasn’t an actual cell to draw:

The Dungeon Under My House - applying lighting to the ceilings and floors

This seemed straightforward to solve: just change the default color to something darker.

But there were more pressing concerns. Namely, now that I was rendering the floor and ceiling with lighting data from the current cell, suddenly the game was rendering incredibly slowly, which was noticeable during transitions, such as turning or moving forward and backward:

The Dungeon Under My House - rendering is too slow

It isn’t smooth at all.

I was struggling with how to optimize this code. According to my poor man’s profiling (interrupt the program in the debugger, and see what function/line it seems to be processing most often), the code was spending most of its time looking up the dungeon cell’s lighting data.

WARNING: it gets a bit technical again

Now, as a reminder, I’m doing raycasting to simulate a 1st-person 3D dungeon. My game’s viewport is 960×576 pixels, and while drawing walls is for the most part 960 columns of processing, drawing the ceilings and floors is more of a pixel by pixel situation.

Most raycasting tutorials have a viewport that mimics what computers were capable of back when raycasting was the only real technique for faking 3D aside from drawing sprites that just happened to look 3D. Those viewports were something on the order of 320×200 pixels. When you don’t have hardware acceleration and only a slow processor, having only 320×200 = 64000 pixels to draw is quite fast, especially if it is just a tutorial and leaves a lot of extra things as homework for the developer.

960×576 pixels is over half a million pixels to process. That sounds like a lot, but without processing lighting, and hardcoding the texture used, it was quite fast before.

But now that I am handling lighting, and I eventually want different textures based on the dungeon cell being drawn, I can’t really see a way around it processing them pixel by pixel.

My game isn’t meant to be a real-time one, as it is a turn-based party-based RPG, but I did have visions of using this same raycasting code in future games that might be real-time, and I was starting to worry that it wasn’t going to be possible.

I was also worried that this game was just going to feel slow and sluggish when players tried it.

So what was slowing things down? Well, my dungeon was being represented as a std::map, and when I created my lighting code, I mimicked it by creating a similar map of std::map (see last week’s report). My current test dungeon is about 30 cells, and with an arbitrary shape (it doesn’t need to be a square grid), it only takes about 30 entries in the map to represent it. Seemed efficient to me.

For each pixel, a projection was made to figure out what cell was being drawn, then I would search the map for the lighting for that cell, and I would process it to modify the color so that the pixel would be darker or lighter.

The code that I used to find the lighting data originally looked like:

gameData.dungeonLighting()[cell]

This code worked for the walls just fine: the cell always existed in the map.

But this code did not work for the ceiling and floor because the projected cell would sometimes represent a tile very, very far away near the horizon, and that cell was unlikely to be defined in my dungeon.

So here’s the fun part: accessing a map with the [] operator using a key that doesn’t exist in the map will ADD AN ENTRY TO THE MAP!

I knew this fact about std::maps, but I was careless with my coding. It was functionally correct in that rendering was, in fact, happening as expected. It was just a very suboptimal way to go about it.

According to my debugging, my map went from about 30 entries to about 10,000+ entries!

The Dungeon Under My House - accidentally adding too many entries to my data collection

Which meant for each of those 960×576 = 552,960 pixels, I was searching through potentially 10,000+ entries in a map for a cell to get lighting data from. That’s a lot of accidental extra computation! Whoops!

So my first fix was to prevent additions to the code:

DungeonLight light;
std::map<Point, DungeonLight>::const_iterator iter = lightLevels.find(cell);
if (iter != lightLevels.end()) 
{
   light = iter->second;
}
else
{
   continue;
}

std::map’s find() doesn’t add entries to the code, and it returns the map’s end() iterator if it can’t find the entry.

Instead of needing to search through potentially 10,000+ entries, it was only 30ish entries, but that’s still a lot of computation time spent looking for data, especially if most of the cells being searched for are not in the map, so it is almost always worst-case scenario.

Even with std::map’s indexing/find operations running at logarithmic time (so, better than iterating through each entry), it is still too slow.

My initial attempts to solve this problem were to draw fewer pixels. I stopped drawing about 100 pixels before getting to the horizon, and I figured that I could always draw a single hazy fog sprite at the horizon to cover up the fact that nothing is getting drawn there. While it helped, it still felt jittery and slow. There is still too much computation happening, and I am still only drawing the ceilings and floors. What happens when I add back in the walls and doors, and when I start adding more things like other characters populating the dungeon?

Luckily, Joel Davis pointed me in the right direction. Why was I using the inherently slow map to represent my dungeon and lighting data when a 1-dimensional collection works much faster?

You know why, Joel (and anyone reading this who is also wondering)? Because until this project, I have never had to worry about how fast my rendering code was.

That isn’t to say that I was purposefully careless. I am not one of those people who say, “Eh, well, computers are fast enough today to not worry about it.” I think there are plenty of good reasons to still be respectful and efficient of someone else’s computer hardware’s and/or battery usage, and I try to be efficient in general.

But this is the first project that required me to worry about trying to do too much in one frame of rendering. Most of my past projects, the bottlenecks in my code were not related to rendering at all, but in pathfinding or other calculation-heavy systems.

Anyway, what Davis suggested is typical in games. Instead of a map or a 2-dimensional collection to represent a 2-dimensional tile-based map, use a 1-dimensional array/vector. Some very simple and quick math gets you the index you need into that collection, and lookups are no longer logarithmic but constant time.

Now, I will need more space to store the dungeon. Instead of a map of 30-ish entries, I need to accommodate entries in my vector for cells that might not exist. But I think this trade-off of speed vs memory is fine, as the extra memory used isn’t too great anyway.

So I hacked in a quick std::vector of DungeonLighting, piggybacking on my previous lighting code to populate it, and when I went to draw the ceiling and floor, I calculated the index of cell by:

index = cell.X() + cell.Y() * DUNGEON_DIMENSIONS;

Now, I can quickly check if the index is within the bounds of my vector. If it is, I then get the lighting data and process the pixel. Otherwise, I stop processing the pixel and move on to the next.

Mathematically, this means that my viewport needs only 960×576 = 552,960 potential lookups to process the ceiling and floor combined, which is a substantial improvement!

Visibly, IT IS SO MUCH FASTER, even after I put the wall and door drawing code back in:

The Dungeon Under My House - rendering is much faster!

And I wasn’t done yet! There was still room for improvement in my dungeon rendering code in general. Drawing my walls and my doors still used the old map to get lighting data, so switching to the vector version there resulted in even more improvement, and in fact, I believe that my future real-time projects are likely to be a possibility!

There is still more optimization to do. Lighting data wasn’t the only thing I was looking up in a map, as the current texture to use for walls and eventually ceilings and floors is also something I searched for. Switching to a vector representation of the dungeon data should result in a much, much smoother experience for the player.

End of technical details

Anyway, I’ve been very happy to get the game to render the dungeon so much more quickly and efficiently. My previous optimization efforts prevented the rendering from occurring in the first place when it wasn’t needed, such as when the player is standing still. There’s no need to re-render the dungeon if it won’t change, after all, and I loved how putting my phone down meant that the battery life didn’t drain due to extra, unnecessary processing.

But now I can also be assured that the game will feel snappy AND avoid draining batteries even more.

I look forward to finishing up the optimization and finally solving the problem of the door that resists being rendered according to the cell’s lighting data. So far, my troubleshooting shows me that it is grabbing lighting data from a cell outside of the dungeon’s boundaries, so clearly my math is wrong somewhere when I determine which cell to use for lighting. But I haven’t figured out how it is wrong and what would be correct yet.

Thanks for reading!

Want to learn when I release The Dungeon Under My House, or about future Freshly Squeezed games I am creating? Sign up for the GBGames Curiosities newsletter, and download the full color Player’s Guides to my existing and future games for free!

Categories
Game Design Game Development Geek / Technical

Freshly Squeezed Video Progress Report: Dungeon Lighting

Here’s the companion video for Monday’s Freshly Squeezed Progress Report: Dungeon Lighting:

Enjoy! And let me know what you think by replying below!