A CLEAN SLATE
Originally posted on FidelumGames.wordpress.com
Since my last post, I let myself get a bit out of hand as far as staying disciplined goes.
I had made some decent progress, and had some cool things to show off, but most of what I was showing was only superficial progress. The things I’m talking about are environments, enemies (actual animated models, not just cubes) and some UI stuff.
The problem with these things is that they were all parts of incomplete systems.
In one of my earlier posts, I wrote:
“…I decided that, although the game is still in a super early state, I owed it to myself to add a bit of polish and replace my cubes with one of the assets I acquired.”
This was a mistake, and led to me falling back into my old habits of sitting down to a development session and asking myself, “what do I want to work on today? What would be cool?”
The result of this workflow was getting a given feature ‘good enough’, making it look cool, and then moving on to something else with the intention of returning to the feature I had just ‘completed’ in order to finalize and polish it.
The problem with this was that I would often go too long before returning to that feature (if I did at all), and forget the mental model required to finish the system properly. I basically wound up with a bunch of fragments that didn’t fit together.
This quickly became overwhelming and really sucked my motivation away.
What I really owe myself is to not add any polish until the game is ready for it.
Luckily, things have been slow at work lately (until today), and so I’ve been left mostly to my own devices. Without any project work to complete, they let me work on pretty much whatever (as long as it’s somewhat related to what we do there), and since Unity is part of the skill set I use on a regular basis at work, I was able to justify working on my own game in order to increase my proficiency with Unity.
God how I wish that was the norm. Working all day long on something I love really makes the time fly.
Anyhow, with all of this time to work on The Wayfarer, I decided it was the perfect time to throw away everything I’d done so far and start over, with a new mentality. This might seem like a bit of a waste, but I’ll be able to reuse some of what I’ve done, and consider the original work as a prototype.
The most important thing I’ve been doing is forcing myself to plan everything (except for the smallest tasks) before I even open Unity.
This prevents me from sitting down and asking that awful question “what do I want to work on today? What would be cool?”. Instead, I ask myself, “where did I leave off? What needs to be done today?”
My new workflow goes something like this:
Look at my task list (something I never had before, but to which I adhere strictly to now) and see where I left off. If a task is in progress, pick up where I left of. If not, move on to the next item, adding to the task list as required.
These tasks break down essentially into two categories: planning/design and implementation/development, and both groups take up about the same amount of time.
Before I write a single line of code, I establish my algorithms in my GDD in plain English (which feels strangely similar to coding).
Then, I draw an activity diagram based on the algorithm, identifying any logic flaws along the way and revising the algorithm as required.
Finally, after all of that is done, I move on to actual coding–simply translating the algorithm and diagram to code.
All of this might seem like overkill (and it does feel like a pain in the ass sometimes), but it’s all worth it in the end, and makes writing the code so much easier. Not only that, but my code is cleaner, more robust and more easily expanded upon.
Way fewer headaches now.
For those of you interested, here’s one of the plain English pseudo-code examples taken right from my GDD, and the corresponding activity diagram (these are for enemy AI):
Enemies will have the ability to make the following decisions:
There are 3 base states which an enemy can be in, which will determine available decisions:
If an enemy has not seen or been attacked by the player, it will be in an unalerted state and will always either Patrol or Wander, unless it has seen or been attacked by the player within x turns.
Patrolling will always occur if patrol points exist for the enemy, otherwise it will wander.
If the enemy has seen or been attacked by the player within x turns, it will be in an alerted state.
In the alerted state, regardless of whether the enemy can currently see the player, it will always ‘know’ the player’s position, unless x turns pass without it seeing the player, in which case it will return to an unalerted state and continue to either patrol or wander.
While in the alerted state, the enemy will first check if its health has dropped below the injury threshold. If it has, it will randomly decide whether to heal (if available), continue engaging, or transition to the injured state.
If the enemy is still engaging, it will check if it is within a certain range of the player (a range it will consider to be dangerously close to the player). If it is and it has any available defensive buff spells whose effects are not currently active, it will perform a random check as to whether it should cast the buff or perform another action.
If it decides to perform another action, it will check if it is facing the player and if it has a clear line of sight within a range of y, where y is the longest attack range of any of its usable (has enough mana) attacks or abilities.
If this case is met, but the distance between the player and enemy is less than y, the enemy will make a random decision to either move backward to increase distance (up to, but not beyond y), or to attack.
If the distance between the player and enemy is equal to y, the enemy will always attack.
If the distance between the player and enemy is greater than y, but the enemy has seen or been attacked by the player within x turns, the enemy will attempt to move closer to the player.
While moving along the calculated path to the player, the enemy will always favor movement over turning, if available. It will do this by first favoring forward movement.
To favor forward movement, the enemy will calculate a new path from the cell in front of it to the player. If this path is shorter, it will move forward. If this path is longer, or the same distance, it will follow the original path.
Enemies may have an option for sidestepping. They will always favor this strafing over turning if it has been enabled for a given enemy.
This process will continue until either x turns have passed without it being attacked or seeing the player, in which case it will return to an unalerted state, or until it is within range of the player again and can attack.
While in the injured state, the enemy will check if it is dangerously close to the player. If so, it will make a random decision to flee, or heal (if available). The enemy will remain in the injured state for at least x turns (unless it has healed itself beyond the injury threshold), but not more than y turns.
If it is not within a dangerously close range, the enemy will always heal if available. Otherwise, it will move away from the player.
When leaving the injured state, if it has seen or been attacked by the player within x turns, it will return to the alerted state—otherwise, it will return to the unalerted state.
I have to say that with all of the practice I’ve gotten in lately, and my new workflows, I feel like I’ve just come out of a learning plateau, and am reaching a new skill level with programming and game development.