Cats update 2 (One Game a Month, March)

Published on 2018-03-23.

The Project

It’s getting close to the end for OGAM March, and it’s time to talk more about game engines and Cats.

Things have taken a different direction since the last update. I spent a lot of time drawing objects for the main house scene, to give things a cozier feel. I also spent a lot of time making my workflow and ng more stable.

New Graphics

Here’s the new look:


My drawing skills have always been poor, and I’m making an effort to improve. I discovered Pixel Dailies a few days ago and have been participating each day, even if what I make isn’t good enough to submit. I also placed some constraints on the art, which make it easier to get pieces finished (less decision-making):

  • 3/4 top down (JRPG) perspective.

  • 16x16 base tile size.

  • 16 color palette (DINOKNIGHT 16).

Here’s the full diff for the Cats plan file since the last post:

 doc/plan/plan.rst | 36 +++++++++++++++++++++++-------------
 1 file changed, 23 insertions(+), 13 deletions(-)

diff --git a/doc/plan/plan.rst b/doc/plan/plan.rst
index 1e48dfc..19821ef 100644
--- a/doc/plan/plan.rst
+++ b/doc/plan/plan.rst
@@ -21,32 +21,42 @@ To do

-- Communicate changes to user with a popup or scrolling marquee.
 - Cat is more/less likely to eat food based on preference.
 - Food degrades/spoils over time?
 - Some food is turned into waste and cats poop.
+- Add pause functionality.

-2018-03-28 milestone
+OGAM deadline

 - Support metric units.
-2018-03-21 milestone
-- Add save/load game state functionality.
+- Implement hand as cursor.
 - Add breeds.
+- Make cats spawn in a random (and unoccupied) area in the house.
+- Feeding places food in bowl for cat to eat.
+- Add save/load game state functionality.
 - Bug: death animations are visible from other scenes.
 - View legacy of passed cats.

-2018-03-14 milestone
-- Add pause functionality.

+- Fix lockup.
+- Fix highlighting in select box so user can see selected item.
+- Support multilayer tiled maps.
+- Hovering over cat shows its statistics in floating gadget.
+- Bug: Feeding window does not show all food inventory.

Multilayer Maps

Support for multilayer maps allows us to put objects on top of the floor and walls. It makes a big visual difference!

Gameplay and Interface Changes

I removed most of the cats’ attributes from the user interface to focus on the feeding mechanic.

New Asset Pipeline

I implemented a basic asset pipeline. I was previously using a single image (texture atlas) as the image source for both Tiled and the game config. This became a bit inconvenient as I rearranged tiles on the image; I had to update game config as well as manually redraw the map in Tiled each time. I found a thread where the Tiled author recommends using single images (one per tile) as the source for the tileset. There is now a simple asset pipeline for tile images that works like this:

  • Create a new image in Aseprite and save in .ase format.

  • From each .ase image, create a .png image for use in Tiled.

  • From all .ase images, generate a .png texture atlas for use in ng.

All steps after the first are automated using GNU make.

The Tech

Here’s the ng diff:

 doc/plan/plan.rst | 24 ++++++++++++++++++++++++
 1 file changed, 24 insertions(+)

diff --git a/doc/plan/plan.rst b/doc/plan/plan.rst
index cc3b252..4e23e3e 100644
--- a/doc/plan/plan.rst
+++ b/doc/plan/plan.rst
@@ -1,6 +1,8 @@
 To do

+- Fix pick functionality when device-independent resolution doesn't
+  match actual window size.
 - Reusable logging configuration for each module.
 - Support multiplicative color modification of entities.
 - Add collision info to event system.
@@ -14,6 +16,28 @@ To do

+- Support bold font in textarea gadget.
+- Support getting currently pressed keys.
+- Support setting device-independent resolution for renderer.
+- Add debug bounding box highlight.
+- Use Aseprite JSON file to unpack texture atlas coordinates.
+- On-demand reloading of changed texture resources from filesystem.

I added some features I know I’ll need for an upcoming project. Also, some of what I’ve been doing has been catching up with me, so I added several features to make things easier to use.

Logical Resolution

Also known as device-independent resolution, this will make it more convenient to work with smaller pixel dimensions in the future.

As I mentioned earlier, I’m currently using a 16x16 base tile size for Cats. The resulting graphics are tiny in a 1280x1024 window. To make things visible, a 4x scaling factor is applied in both directions, so each texture pixel becomes 16 real pixels at runtime. This works, but is inconvenient since I have to apply the scaling factor to every entity individually in game config.

Normally, it’s desirable to apply the same scale to all graphics, so the ability to set a global logical resolution will be convenient. It is done like this: ng.resolution(640, 480)

Because of the way SDL’s GetMouseState routine works, this feature currently breaks mouse coordinates and picking. I won’t convert Cats to use it until that is fixed.

Texture Reloading

Texture reloading supports reloading changed texture files into the running game without a restart. The in-game entities using the textures are updated instantly with the changes. This is done by calling ng.resources_reload(), which I bind to the R key in the game.


You know, animation testing

This feature is useful when adjusting the tile graphics or doing animation testing. For example, an artist might change a few pixels in an animation frame, save a new version of the image, and reload the resources to see the changes to the animation immediately without restarting the program.

Later, support can be extended to handle changes to all resource types, including configuration!

Bounding Box Highlight

Bounding box highlight draws each entity’s bounding box, using alpha transparency to show overlapping boxes. This is toggled with the boolean ng.debug['bounding_box'], which I bind to the B key in the game. This is useful for troubleshooting collision detection issues.


Debug bounding boxes

Texture Atlas Support

I’ve been manually updating the atlas coordinates of individual textures in game config, which becomes tedious-to-unfeasible as the number of textures grows and they are rearranged on the atlas image by the packing algorithm. Aseprite writes a JSON file with texture coordinates when it exports an atlas. ng can now use this file to find map tile coordinates in the atlas image. It’s not working yet for entity textures in general; I’ll work on that at the same time as I fix up some rough edges around the animation system in preparation for the April OGAM.


For a few days, I tolerated a bug that caused Cats to lock up after a few minutes. I first noticed the problem around the same time I had some other strange issues with one of my video card’s ports. Fearing the first, like an OS driver issue or even a hardware issue, I avoided investigating for awhile. Digging in on Wednesday, I reproduced the issue on my laptop, suggesting a local software problem instead.

I tested a new skeleton pysdl rendering program, which didn’t show the problem. I then added back ng’s calls until I found the issue. nui, ng’s user interface API, created new textures every frame for the text that displays the program time and performance information. It didn’t remove the previous frame’s textures, so it eventually consumed all available memory. This made the program unresponsive and caused the system to perform poorly until the program finished and the OS reclaimed the resources. Correctly freeing the old textures each frame resolved the issue.

I told M. this story and he asked me what I learned. At first I said “not much”; the cause of the bug was directly along the lines I suspected and it “only” took an hour or so to fix (hubris).

As we talked about that, I thought about it more and decided I had learned something that now seems obvious. When writing an interactive software with a render loop, it’s important to start long-running testing as early as possible and do it frequently. I’ve been writing example programs to exercise ng’s features, but have been running them quickly to verify functionality and stopping them. I can improve these by making them repeat and leaving them running, verifying ng’s stability over time and noticing regressions sooner.

Secondly, the conditions under which a recurring bug is discovered shouldn’t bias the programmer’s attitude toward it. I shouldn’t have feared a driver or OS issue before investigating. I have been programming for several years and still make this kind of mistake more than I’d like. The human brain is great at establishing patterns, which can lead to counterproductive assumptions sometimes.

Finally, I have to admit the cost to fix this was more than the hour I measured after I finally decided to focus on it. The issue was a little distracting over the last several days and stopped me from doing long-running testing. What’s the real cost of that?

We must not fear or tolerate user-facing bugs.


Thanks for reading. Next week, we’ll do a retrospective on Cats and do some planning for April.