Coding with Cleanliness: Devlog #3
Hello Again
This week and the following are going to have slightly shorter posts, as I am currently in a game jam. However, while this topic and the next won't necessarily warrant full-size posts, I still think they are extremely important. For this essay, I want to look into the practice of programming effectively through the lens of a game developer, something you could probably use a lecture on. If you cannot write your scripts in a manageable, readable way, you end up with this:
So, lets talk about the strategies I use to clean up my code. I use GDScript to program in the Godot engine, but these concepts should be widely applicable to anyone.
What makes Clean Code?
From my perspective, "clean" code has to meet two criteria: 1) Clean code has to be readable to the extent such that people can understand what a function or script is doing without the context of other functions or scripts. 2) Clean code has to be organized in such a way that it can be corrected and/or modified easily down the line. That's it. Now, there is a difference between clean code and optimized code. However, for the purposes of most indie games, it would seem as if the main roadblock most developers are faced with is the readability and malleability of their code, not optimization. Notice, also, that these criteria are purposefully very vague. They don't mention any specific concepts within programming, and they don't advocate for/against any particular strategies for achieving this criterion. That is because, ultimately, no one strategy can guarantee that your code is clean, and even the use of seemingly amateur practices can be advisable in the right context. The strategies listed below that I use work for me, but understand that context is important to solving problems, and there usually isn't a one-size-fits-all solution to things like this.
Readability
Function and variable names are of the utmost importance to the readability of scripts. I sometimes have trouble representing what my functions are actually accomplishing just through a couple words. What helps me do this, however, is making sure that every function only serves one purpose, and simply composing functions that require the use of many different processes. The "big" function with all of the composed functions inside of it has an extremely vague, all-encompassing name. The names of those "small" functions within it can provide a more detailed-idea of what the "big" function is actually doing on a deeper level. For instance, in Enchanterland, my _jump function has a lot of things going on under the hood. Every time you jump, the game has to play the animation, put the player in jumpsquat (the very brief immobile state after the button is pressed but before the character actually jumps), cut the player's ability to move left and right, play a sound, and then let the player actually jump. That's a lot for an English sentence, let alone a GDScript function. The main part that would have caused clutter is the use of the timer that indicates duration of the jumpsquat. However, I made a separate function, _run_action_timer, that takes the needed wait time as a parameter and handles all of the intricacies of running that timer. Now the _jump function doesn't have to worry about doing that, and I can assume that the _run_action_timer function is handling everything directly related to the timer. This looks a lot better than, lets say, writing out this entire process within the _jump function itself. Even though it's pretty easy to understand what the _jump function does just from the name, the need to look at all of the underlying processes of that function without the composition I used would have made it unreadable.
All of this also applies to classes. About a month into the project, I noticed my "Character" class for the main player had a lot of different properties, and that a lot of those properties could be used by other classes as well, such as the enemies. This is where I tend to use inheritance . I made an "Entity" class that contains the properties that apply to both "Character" and "Enemy", then made both classes an extension of "Entity". The names here are important: they allow me to infer that a character is an entity, and that an enemy is an entity. This form of inheritance not only divides my script into more manageable pieces, but also allows me to form easy, intuitive mental connections between the two classes. Composing nodes or using resources/interfaces can also sometimes accomplish something similar. For instance, my "Character" and my "Entity" classes both have health values, so eventually, I'd want to put a "Health Manager" node onto any scenes for characters or enemies. As a rule of thumb, if something is something else, I use inheritance. If something has something else, I use composition and resources/interfaces.
Comments can occasionally be helpful, but I think it's usually worth considering whether you actually need comments to accomplish readability. Functions, classes, and variables have English names for a reason. If "_jump" doesn't tell you enough about a function, then consider using composition or inheritance before using comments.
Malleability
You might also be able to see how many of the strategies could help my code be more malleable. When my code is divided into "pieces", with each piece being named according to their functionality, it becomes extremely easy for me to debug. This style allows me to consider every piece individually instead of taking a birds-eye view of all the crazy crap this computer is miraculously accomplishing, which helps me identify problems at a good pace. That is the primary reason I tend to take a divisive approach to programming, and after all this time, it is what has shown to be the most effective approach for me. Even if I'm not debugging, dividing my code into chunks like this also allows me to "swap out" different quantities, functions, and even classes between the different parts of my system. The Malleability of my code is one of the main things I consider when writing my programs, especially since I tend to change my mind a lot. It's a lot cheaper and easier to replace a cog than it is to replace a machine.
A big thing that stunts malleability is when people write scripts that depend on each other. Ask yourself: if you get an error in one script, do you get an error in other scripts as well? It's very easy to make scripts more readable by making them dependent on one-another, but it can make it hard to modify your program as a whole if there is too much dependence. If you have problems with this, look up tutorials for your engine/language on Events and Singletons, or even just generalized tutorials for preventing unnecessary dependence.
Thousand Millimeter Climb is what happens when you only have four days to finish a game as a solo programmer and you fail to divide anything into any smaller pieces whatsoever. The original submitted version of the game was... extremely broken. I was able to fix some of the many problems after the jam ended, but even then, I couldn't iron out all the wrinkles. Looking back at the code as a somewhat more experienced programmer now............... scares me. There is no reliable way to modify the parts of that project without deleting a bunch of scripts and functions entirely. If only I knew how to write clean code!
Wait, That's it?
Yep. I'm glad I was able to finish anything for this week in the first place, as I've been hard at work on a local game jam project. Of course, I'll get back to working on Enchanterland when the jam ends. I'll also have to hold back on releasing that short playable demo for another couple weeks, since I don't have enough time to work on two projects at once. I'll be here next week to post another short devlog before the deadline for this jam. Until then!
Get Enchanterland
Enchanterland
Status | In development |
Author | Everett Rees |
Genre | Platformer |
More posts
- Level Design and Player Progress: Devlog #105 days ago
- Strategy and Autonomy: Devlog #912 days ago
- Audience and Accessibility: Devlog #819 days ago
- Reflection on Progress: Devlog #726 days ago
- First Demo Uploaded29 days ago
- Hooks and Player Engagement: Devlog #633 days ago
- Enemies Worth Fighting: Devlog #540 days ago
- Scope-Creep and the Minimalist's Question: Devlog #447 days ago
- Skipping the Post This Week.54 days ago
Comments
Log in with itch.io to leave a comment.
Heck yeah!