Christopher Angelico

Coding to Scale

Tuesday, March 28, 2017

A recent freelance project taught me a valuable lesson in writing code to scale. It’s easy to fall into the trap of writing code for the situation you have and not the one you may ultimately have later. I won’t get into too much detail of the project itself, what I will say is that my focus was on character customization, it was planned to have online capabilities where people can see each others character, and would possibly be mobile. The characters were formed using overlapping layers of sprite sheets. The base layer was the character’s skin while the character features such as hair, shirt, etc. were layered on top. Initially as a MVP I had all sprite sheets loaded into individual arrays for each layer and allowed the user to control the index of each. The class would then take the appropriate sprite sheets, draw one on top of another to form the character, and crop the sheet to just the forward facing position.

This implementation was incredibly inefficient and had a second or two delay whenever you changed one of the character’s layers. I was able to greatly reduce the loading and drawing time by loading only the first column of the sprite sheet instead of the entire sprite sheet. Since the first column contained all eight standing angles, it allowed me to add a rotation feature to the character creation menu. I was eventually given more assets to add to the customizer and it was at this point I learned my first scaling mistake. Loading in every asset into memory was not a viable option, especially if the project would be adding more assets in the future and might be mobile at some point.

My solution was to load them dynamically as needed instead of all together at the beginning of runtime. I created a class called character dictionary that stores a list of strings for each layer of the character and each list contains the paths to assets for that layer. The indexes of each layer that the GUI displays for the user to see and change are stored in an array. Since the array’s size is the number of layers of the character and the dictionary has a list for each layer I used the layer’s index as the key for the character dictionary so it would be easy to iterate through the layers and load the appropriate sprite sheets to create the character sprite sheet.


Character Dictionary
Key Value
0 List<string> Hairs
1 List<string> Shirts
List<string> Hairs
Index String
0 "…./Assets/Male/Hair/Hair01.png"
1 "…./Assets/Male/Hair/Hair02.png"
Index Array
Index Layer Index
0 1
1 4
Example: characterDictionary.getValueAt(0)[indexArray[0]] would result in loading Hair02.png


By loading assets dynamically I made it so that during character creation only the sprite sheets that are needed are loaded into memory. As I am writing the finishing touches on saving and loading their character for the rest of the game when I realize I’ve hit another scaling problem. The players will end up saving a sprite sheet of themselves on their system for the game to reference, but what about everyone else they see in the game? Everyone will need their sprite sheet loaded onto the players system as well. This could translate into dozens if not more sprite sheets cached onto the player’s system and some only really needed for a few seconds depending on how long they are in the player’s line of sight. Now I already knew it would be silly to try to download sprite sheets of each player. Instead, similar to how the character creator does it, they would be generated by the player’s system using a string of numbers received from the server that represent layer indexes that make up other player’s characters. This still doesn’t solve the problem of having to generate dozens or more sprite sheets for each visible player.

The solution was to draw the sprite sheets individually to display the character instead of merging them together and saving it as a sprite sheet. At first this would seem like its more resource intensive because you now have ten or so sprite sheets loaded into memory for a character instead of just one. The truth is that not only is this better, but it fixed another issue that I hadn’t thought of before which is what if two people have the same character with only one difference. With how I originally handled characters that would mean there are two sprite sheets in memory that are near identical except for one feature. With the new way the game already has most of the pieces loaded into memory and only needs to load in one new sprite sheet. This means fewer assets are loaded for characters that share features.

This experience gave me a great lesson on how to better plan my coding in the future, making sure my solutions consider the end goal as best as possible.


No comments :