Refactoring and Managing Code
Hello everyone! This time I’ll be talking about properly refactoring and managing code for development. Not counting empty new lines, brackets, or comments, I’ve written 17859 lines of code for Streets of Peril so far, and I’ve learned a lot of techniques since the first line I wrote for it. For those unfamiliar with the topic, Code Refactoring refers to changing the internal structure of the game without changing its external behavior. Now before I start, I’ll claim that I’m not an expert at coding yet, but hopefully my tips will come in handy. As a heads-up, I’ll be showing C# code; if there’s any syntax I won’t expect you to know, I’ll explain it. I’ll assume that you know basic programming concepts such as inheritance and the like. With that said, feel free to leave a comment if I still didn’t make something clear enough for you.
One of the most important things I learned when making Streets of Peril was the concept of inheritance. If you can subtype something to make it easier for yourself, by all means do it. This means having more classes, but think of the benefits: isn’t it great when you don’t need to take so many little factors into account and let the subtypes handle themselves? I’ll provide an example:
In the Normal mode for one of the challenge levels for Streets of Peril, Survival, you’re in an underwater level with an Oxygen Tank. This differs from normal Oxygen Tanks in that every hit you take will also hurt the Oxygen Tank, leaving you with 5 hits maximum before you lose. I initially tried so many different things to change my general collision class to take this into account. From tracking the player’s health before damage to passing in a delegate (function pointer) to the collision class, everything seemed lackluster.
Finally, I came up with the following: I created a new subclass that inherited from the character used in that challenge, Graham, and named it SurvivalNGraham. This class had only one change, which occurred when taking damage:
public override void TakeDamage(float activeTime, bool FacingLeft, int damage, Status enemystat, Hitbox enembox, TileEngine TileEngine, List<Collideable> Solids)
base.TakeDamage(activeTime, FacingLeft, damage, enemystat, enembox, TileEngine, Solids);
//Make the oxygen tank take damage whenever Graham does
if (OxygenTank != null) OxygenTank.Break(activeTime, 1, enembox, TileEngine);
(My apologies for not knowing how to properly format code in WordPress yet)
As you can see from the code, all I did was make the Oxygen Tank lose health right when Graham took damage, regardless of how much damage was done. This tiny block of code solved EVERY problem I was having with this game mode, and it was all because I subtyped properly!
If you took programming courses at all, you’ve probably heard that you haven’t coded something as efficiently as you can. Then you begin to worry about everything else you coded. I have news for you: in this day and age, you don’t need to be as efficient with managing your memory in general. Obsessing over whether you should use a 16-bit integer or a 32-bit integer will only hurt you more than it helps you.
With that said, there are still some benefits to refactoring your algorithms, which are by far the deciding factor on how your game runs. I will show an example:
In Streets of Peril, whenever an Item Container took damage, it would check if the health was 5 less than the sprite changing point and change the container’s sprite if so; in other words, every 5 health taken from an Item Container would change its sprite to a more “broken” one. I had to consider that if a large amount of damage was done to the Item Container at once, it would still switch to the appropriate sprite. The algorithm went like this:
while (Health < (BreakHealth – 5))
if ((BrokenCounter + 1) > Sprite.MaxFrame) return;
BreakHealth -= 5;
(Note that I have comments here in the full code segment, but I took them out because they were directed at another piece of code that I’m still not sure about including or not)
Now let’s see here. It will repeatedly go through this process until either the container’s (BreakHealth – 5) no longer equaled or exceeded the container’s Health. This certainly works, but can it be done better? Of course!
Basically, what I did to improve this algorithm was to turn it into a calculation. I won’t post all the code since it’s a bit longer, but I checked the current health of the container and subtracted that value from the BreakHealth, then divided by the constant factor determining when the sprite will change (it’s no longer just 5), now called “SpriteChangeHealth.” This gives me the number of times I need to change the sprite, and best of all it only needs to be done once! I even changed another algorithm I had into a calculation with some help from a friend, as that calculation involved computing a series decreasing at a constant rate!
There’s another part to efficiency that I want to bring up, and it has some to do with subtyping. What if you need to access a field that is in some of your subtypes but not all of them? In an AI system like mine, this is often the case.
In C#, you can use Reflection to get a field, or you can have the fields be public in the subtype, cast your base reference to the subtype, then access the fields there. The problem with these approaches are they tend to be pretty messy. Here’s a line of code I used to have in the Uppercut action of my enemy AI system:
System.Reflection.FieldInfo info = Enemy.GetType().GetField(“Uppercut”);
To tell an enemy if it’s performing an uppercut move, I had to set a boolean called Uppercut to true or false. Since I didn’t know if the enemy in question had this field (granted, they all should if they’re going to have the action, but the extra error handling really helps if something slips by), I used Reflection to grab it based on the type of the enemy. If the enemy didn’t have it, I wouldn’t do anything, otherwise I would set it to true at the start of the action and set it back to false at the end.
What did I end up doing instead? I created a very general SimpleAttack action that allows me to pass in an animation and hitbox for the attack. This not only allows me to use SimpleAttack for virtually any one-hit move, but it made my whole life much easier by allowing me to use the proper hitbox and animation for the action and enemy I wanted! This also means that I no longer needed the “Uppercut” variable again!
Using Reflection in C# to get fields is unchecked by the compiler, since it doesn’t know the type of the enemy until runtime. This makes it impossible to know if the code will run or not before compiling and causes runtime errors if something goes wrong. This coupled with the fact that the GetField operation is a slower one compared to the ones the compiler can and does completely approve of makes the method I chose much more preferable.
Oh, and while I’m wrapping up Refactoring I’ll say that variable runtime is bad. What is variable runtime? An example can be choosing a random number and hoping it wasn’t already chosen with a while loop. Instead, create a list of the possible number choices if you can and choose a random index from that list instead. This way, when the numbers are chosen, you can remove them from the list and narrow your options until you have none left. It’s much more reliable and has no potential to take longer than it should.
Refactoring: Naming Conventions and Commenting
This section will be pretty short, but I can’t emphasize how much you should comment your code. It seriously helps both you and anyone else that may be on your team when there’s a glitch somewhere and you can’t spot it quickly because you have to read through the code again to figure out what it does. In fact, I’m on a team right now for my senior project, and I’m the only one on the team that regularly comments!
Now, it’s understandable if you just want to get something done and leave the commenting for later. I do that A LOT. The problem arises when you don’t return to it and comment it while it’s still fresh in your mind. I prefer longer, more detailed comments, but others are fine with short, quick comments. Here are some examples of what I consider good comments:
//Makes the player character lose health – called when a hitbox collides with the player’s hurtbox
public virtual void GetHit(int damage)
/*A Bonus Status Meter to be referenced by players; when the meter gets to 100, the player will gain a positive status based on the status of the enemies killed*/
public class BonusStatus
There’s also naming conventions. I know that the game industry has standards in place for naming things like classes and temporary variables, but I have my own standard that I maintain across Streets of Peril. I name all classes and important class variables in Camel Case (LikeThis), and start timers with the name “Prev,” so I’d have something like “PrevJump” which dictates how often you can jump.
Method names are equally important, since you should know what they’re doing without having to look at the implementation. If you’re having trouble naming something, which I often do (it’s been almost 2 years and I haven’t settled on a name for Streets of Peril yet), at least make it up with a comment above it describing what it’s for or what it does (which I always do for classes and methods). Also if you’ve ever done any programming, you’d know not to name fields or methods arbitrary things like “q” unless it’s relevant in some way (Ex. “X” for the X position of a game object).
Well that about wraps things up this time! Next time I will talk more about Streets of Peril itself, I promise!