Devtober 2021 Postmortem
Background
I joined Devtober 2021 and it seemed like a great development jam to improve upon an existing game. This game jam encourages setting of one’s own goals and the writing of a post-mortem to examine if they were achieved and to what degree, as well as reflect on why it happened and what could have been done better. With the overarching goal to improve The Fat Hand, I opened 16 GitHub issues, each issue being a bug-fix or enhancement that I’d like to see in the game. Each of them require varying amounts of effort but the goal is to complete any of these as I see fit. There is no specification or rationale on whether or not it should be implemented; I just implement it because the game is my world, my art, so it’ll be implemented the same way I would paint a house on canvas. I can decide to keep it or erase it after. I managed to close 10 issues out of the 16 I’ve opened. These 10 issues shall be the subject of the postmortem.
Issues
-
Issue #1: Reorganize File Structure
Godot does not have any specific recommendations on conventions for project organization aside from simple guidelines, but it was getting pretty hard to navigate the game files. Amongst Java developers, we have a good design rule called Package by Feature and this is sort of what’s hinted in Godot’s guidelines as well, which is to separate files and classes into separate packages (folders) based on their functionality. For example, a player’s model, sounds, and scripts - anything tied to a player would be in a Player or Characters folder. However, if parts of the model or some of the sounds are also used in other areas, they would be in a different package, like a common folder. That’s a problem.
If I want to look for a sound that the player makes, I look into the player folder and don’t find it. Oh! It’s because it’s a common sound, so I have to look in the common folder. And vice versa. But if I didn’t know about that, I’d have to search for a sound file across multiple folders, which is absurd. Because this is not a pure software project, Package by Type makes more sense. However, it would be equally as confusing because there’s no information on what the sound is used for, and putting that into the filename is dubious. Therefore, I find the most useful structure to be a mixture of both such as this:
/images /ui /models /characters /props /scenes /characters /props /levels /ui /sounds /voices /footsteps /music
I now know the type of the files immediately from the top-level folder, and where to find said files because they are categorized by functionality which corresponds to other folders. For example, I know the .glb model file of the player is in
/models/characters
, but the .tscn scene file can be found in/scenes/characters
; absolutely no ambiguity there. For sounds, they are packed by their own relevant categories that makes it intuitive to find rather than being component-based.
-
Issue #2: Attribute all Creators in README.md
Although all third-party assets used in The Fat Hand are CC0 and no attribution is required, I make it my mission to attribute them anyway. The attribution of CC0 authors in my previous game project, Tetra Ho!, allowed an author for a piece of music that I used to find my game on Google and leave a comment in my game page. This is not only rewarding for me to see that they noticed my work (and vice versa), it also connected us briefly and we got to know a little more about each other’s work. I highly recommend crediting all authors fully, regardless of license as it helps everyone.
This type of mentality is also ties in to why the game is open source, and will be even in its eventual release as a full game. This does not necessarily mean that the game will be free on Steam; you can still sell open source games as per GPLv3; “free” in this context pertains to freedom. If a game can be pirated with extreme ease (and even encouraged among certain developers), there’s fairly weak reason not to share the source code for the purpose of education and progress. It gives us the opportunity to learn and improve from and upon each other. Libre software and copyright-free art like what you can find on opengameart.org is extremely important, it’s why I am even able to develop in the first place. You might think that people can just clone your game and sell it, but people have already been doing that to all kinds of digital media since its conception, yet it serves as a strong advertisement for when they will eventually buy your game to experience all of the features not present in a pirated copy (Steam achievements, sense of belonging, official integration, multiplayer, etc.) if they liked it. After all, owning a fake pair of Nike shoes is not the same as actually owning a genuine one even if they have indistinguishable build quality - it’s the brand, the authenticity of the experience that comes with it and we should aspire to build that. A poor Nike fan wouldn’t buy fake Nike shoes forever, they’ll eventually buy the real one when they’re ready.
-
Issue #3: Implement On/Off Lever Switch
Switches in this game are physically-operable, they’re not 2D clickable buttons, they must be controlled via a hand which is a physics object, and it must collide with the lever and make it move that way (no animations). I used a HingeJoint node which will fix the lever RigidBody onto a base StaticBody, so the lever will swing along a fixed axis relative to the base. However, even when specifying a limit to the rotation of the HingeJoint, it is possible for the player to punch the lever so hard that it folds into the ground before being able to correct itself to the limit. Once it was in the ground, it had a hard time returning to its correct position. To solve this, I had to extend the RigidBody class and modify
_integrate_forces()
which is Godot’s built-in function to directly modify the PhysicsState of RigidBodies. With this, I can ensure that the position of the body never exceeds a certain amount by overriding the engine’s physics calculations. I used a few if-checks to do that inLeverBody
. There are also checks to disable the RigidBody physics if the lever reaches its limit or reaches a tick (which is just a user-specified position within the range of the lever) so that it will come to a stop. It will only come to a stop if the position-correction portion of the code did not fire, so the lever wouldn’t be stopped mid-correction, because the lever would look out of place and slightly detached from its base if it happened.
-
Funnily, I actually started implementaton for the jump by reading this KidsCanCode 3D character guide even though I already have a working 3D character and I’m an experienced adult. I modified it for my own purposes but it goes to show that even for someone with a software engineer/academic background like me, I’d still like something to reassure that I’m on the right track.
-
I didn’t have any idea on how I wanted to actually implement the grabbing of objects. I know that I should set the position of the object to the hand, but do I want it to still be interactable with other physics objects? Can someone knock off the thing I’m carrying? Should the player be able to throw objects or just drop them? It’s yes to all, but I didn’t think about that when I first opened the issue, I only thought of grab, so I opened Issue #24 for another day. But what I did was also write a highlight system for generating highlights on any mesh capable of being grabbed using
create_outline()
.I also wrote automated tests to ensure all of this works, so when I modify its implementation in the future, the automated test should catch any regression bugs and ensure the highlight will always work as intended.
-
Issue #6: Retopologize and Recolor Player Character
This was actually the first task I did when Devtober started. I wanted to recolor the player because the dino looked too cliche. I also didn’t like the topology of the model which was generated by Blender’s ReMesh, the default remesher that turns sculpted models into lower fidelity blocky models ready for animation use. While it had no problems deforming, it was unintuitive to make changes to the model because there’s no symmetry in the topology. I manually retoplogized the model by creating a cube, subdividing and adding loop cuts to where I want them to be, then use the Shrinkwrap modifier in Blender to wrap the low-poly cube around the existing hi-poly model.
Certain parts, such as the head needed to be wrapped individually from the body because they were quite different in shape, so after wrapping, I joined the two meshes together in Edit Mode by hand by selecting the vertices and merging them. I thought it would be an absolutely tedious work, but Shrinkwrap is much more effective and produced cooler results than I expected, and I would certainly use it more often compared to ReMesh or doing the whole thing by hand.
Polycount was reduced from 2819 faces to 903. Fat Dino lost some fidelity, but he’s looking good for all intents and purposes of the game.
-
Issue #7: Setup Automated Tests and CI
I wanted the project to automatically export to Windows and Web every time I submit a pull request to merge a new feature that I made. It’s fairly simple to setup, and I’ve conveniently done so for my project, so any Godot user can do the same by putting the same /.github/workflows/godot-ci.yml file in their GitHub repository and editing the project name. This will create a GitHub Action that will automatically download the Godot docker container, checkout the project repository and export the project on pull request, as specified in the .yml file. I also added a job to run automated tests, which will actually boot up the game and test certain scripts and scenes to make sure they’re all working. This ensures that any change made to the code will not break any functionality or bugs fixed before (regression). However, this means that you will need the WAT addon by AlexDarigan for the GitHub Action to run properly, or, you can just remove the “Run Automated Tests” job and you’ll be fine.
I actually helped fix some bugs and submitted pull requests to the WAT project during the period of this game jam itself because it was affecting my testing, all in the spirit of open source. If there’s a problem, we fix it together, further supporting the point made in issue #2. Testing is another topic on its own which I can’t fully cover in this post, but feel free to check out the tests I have for my game in the
/tests
folder for a rough idea. As I said, it’s open source, use it!
-
Issue #10: Hand Position is Bugged
This problem looks tricky at first because of how the controls work in this game. You see, the player moves the arm of the dinosaur using their mouse, this movement is 2D, it goes side-to-side, front-and-back, and there’s no vertical movement. This arm movement is relative to the camera. So, if the player turns their body to the left or back, moving the mouse up should still point the arm forward relative to the camera regardless of body orientation. We can set the global position of the hand so that it’s not affected by the player’s local rotation (turning of the body), but we still need to rotate the arm appropriately because the arm is always attached to the right side of the dinosaur’s body. Hence, we still need to keep track of the right side of the dinosaur’s body (local) in order to properly calculate the rotation angle for the hand.
By treating the dinosaur as a XY cartesian axis graph, any point we plot on the graph is the hand’s position. We want to find out the new local coordinates of the hand position anytime the body rotates so that we can use atan2 to get the angle for the arm’s rotation (pictured in green below). Using the formula for rotation of axes, we can easily get it as such:
Thus, the whole solution to fix the arm’s rotation in regards to body rotation and hand position is only 4 lines as such in
hand_controller.gd@fix_yaw()
:var theta = rotation.y var x = translation.x * cos(theta) + -translation.z * sin(theta) var z = translation.x * sin(theta) + translation.z * cos(theta) rotation.x = atan2(x, z)
-
Issue #12: Fast RigidBody phases through walls
This is actually a bug in the Godot engine’s collision detection where fast-travelling objects can go past another object’s collision. There is workaround to increase physics FPS to insanely high levels so that it can detect the collision, but that’s not the way of a software engineer because of performance concerns and it changes how the physics work in certain cases. Instead, I made a script and assigned it to fast-moving objects, allowing it to have a reference to an Area which keeps it within the boundaries. So if this object escapes the Area, the Area will emit a signal to say that the body has escaped, and it will notify the body to reset its position to the boundary of the area where it exited. Almost no impact on performance, but the problem happens here. The boundary of the Area needs to be calculated, and it is not readily available because an Area can have a combination of many collision shapes that form into one Area - the Area is not necessarily a box, so to calculate each face, angle and normal that forms the boundary is unfeasible.
If we for example, simply draw a line (raycast) from the object to the center of an oblong area, and set its position to the boundary of the area, it will still re-enter the boundary in a weird way:
One solution is to not make overly oblong shapes like this, and break up the collision shapes into smaller, more uniform cubes. This way, there is less likelihood for a unnatural re-entry if the raycast checks each collision shape. However, a better way that has been implemented is to simply raycast to the player’s position, because the player is the one who sends objects out of bounds from their position. Therefore, it is very likely that the natural re-entry be towards the player’s position. In instances where this is not the case, it can fallback to the default behavior and the player wouldn’t know any better.
-
Issue #20: Add Controller Support
The mouse for example, has full freedom of 2D movement, so it can move anywhere within the space as such. The red square represents the position of the hand, it will correspond exactly to where the mouse moves. If the mouse stops moving, the square remains where it is, and it feels natural to continue or stop from there.
For the controller, if we follow the same movement, it makes perfect sense that if I move the analog stick up, the arm moves forward, and when I release the analog stick, the arm should stay there. Perfect. Now, because the analog stick has been released, it’s at the center. I now move the analog stick to the right, and the red square moves to the right, yet it maintains its frontal position, so now it’s 45 degrees to the right, but my analog stick is 90 degrees to the right. And there’s the problem, the arm should also be 90 degrees to the right.
It feels weird because the character’s actions don’t line up with my controls. I haven’t got the time to solve this yet because the jam’s deadline is up, but basically I learned that even a simple lateral movement may require different calculations or systems depending on the control input to feel natural. Though the solution should be easy: any one movement on a single axis simply needs to reduce the other, then it will gradually pull the hand towards the correct axis.
Conclusion
I’ve been taking my time on this project while still playing games and not having to worry about the deadline. I think precisely because #devtober doesn’t impose anything on you, it would be against the spirit of the jam to impose anything on oneself. They even said that we can ignore or tweak the rules to our liking. It is important to understand that the beauty and freedom of independent game development is something to be enjoyed at our own pace and at our own accord without the chains of results. It is this exploration and wonder that allowed me to think unique solutions for the mathematical problems in my game and contribute to the WAT repository. If I was still a stressed out software developer, I would just slap together a hacky duct-tape solution and I would never have had made a pull request to someone else’s GitHub repository because “I ain’t getting paid for this.” What a pathetic person I would have been if not for the indie and FOSS community.
10 issues were closed and this is excellent progress. With the main mechanics almost done, I can start to think about stories, characters and proper levels to add to this game. There are more mechanics to come such as swimming and rock climbing. You can play the devtober release on itch now, but they’re just minor updates to mechanics and the addition of controller support. Changelog in the GitHub release. For now, I’ll take a break. Until next time, on THE FAT HAND!
Files
Get The Fat Hand
The Fat Hand
Punch Everything
More posts
- The Fat Hand: Mini Jam ReleaseSep 07, 2021
Comments
Log in with itch.io to leave a comment.
I'm reading through all of this right now and just, wow! I remember playing this game back in the early September mini-jam, and you've really added a lot from the looks of this postmortem! If you know of any game jams like this one you've just joined, where you improve on an old project, please let me know! I want a good reason to go back to my own mini-jam game 😅
Thanks for reading, I was almost certain that nobody would read my long and boring postmortem 😂 Strangely enough, I actually read your latest devlog post and liked it yesterday. Sorry that I didn’t drop a comment then, I think it would’ve been better if I said something to show some acknowledgement like you did for me here. In fact, I even tried the USE demo and I’ve never seen a block-breaker type of game having a Bestiary, with Blue Brickz as the first entry if I might add, so I thought it was pretty cool. I’ll leave more of my thoughts on it in the game page later.
I’m also relatively new with game jams, so I don’t know of any jam similar to Devtober. I would be happy if you could enlighten me when you find another jam that encourages you to work on old games too. I hope to see more of your work as well!
I absolutely loved how deep you went into your postmortem. Thanks for checking my game out! Yeah, I've been trying to add some cool and unique twists to the genre, I thought a Bestiary would be a nice fun little thing to do for some random lore in a brick breaker 😆. Thanks, thoughts and feedback is always appreciated! :D
Will do!