Weekly Update – January 20, 2023

  • Map generation optimization. I knew as I wrote Map Generator 2.0 that some of the code was horribly slow and wasteful and would need to be optimized later. Map generation had ballooned to 10-15 seconds and .5 GB of memory. It’s now down to 3-5 seconds and 200 MB of memory, and there’s much more room for improvement. The optimization techniques were converting LINQ statements to for loops and reducing use of temporary lists. One optimization example involved how connections between rooms are stored. I started with one list that stored all original connections between rooms. Then I created a new list for connections that constructed loops and another new list for connections that joined sections. I used separate lists instead of the original list because I needed to do different things with the items in these lists, and it was more expedient to create new lists (though a little voice inside my head was telling me to slow down and do it the right way). I added a fourth list when I realized I needed to track each connection in each room that used the connection (as opposed to only the room that originated the connection). Because it was sometimes necessary to get all of the connections, I created a property that combined all four lists into one new list. Yikes. The allocations… The solution was to combine the lists into one and add an attribute indicating the type of connection. This caused way more rework, and troubleshooting issues caused by the rework, than I anticipated. At least the rework made the code simpler and easier to understand, which is always beneficial.
  • Movement optimization. Enabling actor actions to be displayed simultaneously exposed a problem: the movement code took a long time to run, causing actors to instantly move to the next cell rather than moving incrementally over multiple frames. Linear interpolation is used to calculate how far an actor moves each frame, with the actor’s movement speed and elapsed time since the last update as inputs. I ran the Unity profiler and identified the main causes: dynamic lighting and excessive Unity log calls. The log calls are easy enough to deal with; they won’t be in production releases. Dynamic lighting, which uses the Smart Lighting 2D asset, is a dilemma. I want to keep it in the game but I’m not sure how much I can optimize it. Temporarily disabling the lighting and logging fixed movement between two cells, but there was still an issue when moving across multiple cells. Actors momentarily stopped at each new cell before moving again. I had seen this before and knew the source: the state logic in the Update method caused some frames to skip movement. For example, an Update call would determine that all actions had finished in the previous Update and it would update the turn state. Movement wouldn’t resume until the next Update. With nested state logic (there are turn phases, actions phases, and action step phases), several frames passed before movement resumed. This was resolved by modifying the state logic to process state changes in the same update when applicable. For example, when an action step finishes, the logic will start the next action step in the same update.
  • Displaying actor actions simultaneously. I reverted the changes I made last week to present actor actions simultaneously. It became clear that an enormous amount of rework was needed to separate action logic and presentation. Fortunately, a much simpler solution had been right in front of me the whole time: asynchronous actions. Instead of waiting for each action to finish being presented, I’d simply start off each action at the same time. I didn’t consider this initially because one actor’s actions can affect another; I believed that all actors’ actions had to be resolved before any of them could be presented. For example, if the player hits an enemy, and that enemy dies, the enemy shouldn’t be able to move. I still had to make some modifications to get this working, such as checking that an actor is still alive, and tracking cells that actors are moving to before they reach the cell (so that other actors don’t attempt to move into the same cell).
  • Pathfinding improvement. Over time, I’ve changed my mind on how actors interact with other actors and objects that are diagonally adjacent. I may change my mind again, but what’s certain is that there needs to be a way to allow interactions with adjacent objects in ordinal or cardinal directions, depending on the object. Currently, a melee attack can be performed from an adjacent diagonal cell, but opening a door cannot. Until this week, the latter was not possible because of a limitation in the pathfinding code – since actors can move diagonally, and the pathfinding code finds the shortest route, paths end at a cell diagonal to the cell containing the object being interacted with. The fix for this was to change the path destination based on the interaction range of the object. An object with a range of 1 can only be interacted with if the actor is adjacent to the object in a cardinal direction. 
  • Better debugging statements. It just occurred to me that I’ve written a lot of bad debugging statements. I typically add debugging statements while troubleshooting a particular issue. They make sense when working within the context of an issue, but not on their own. Without context, they do more harm than good because they increase cognitive load, which is already high from being in troubleshooting mode. I improved these statements by adding more relevant state information to them. I also rearranged the statement in some cases so that the subject of the statement (actor, item, etc.) was at the beginning of the statement. This made it easier to skim through the debug log.
  • Inspector improvements for ScriptableObjects using Odin. To reap the full benefit of Odin, I added Odin attributes to all classes inheriting from ScriptableObject. These objects are now easier to view and edit in the Unity Inspector.
  • Duplicate door bug fix. Doors recently stopped opening when they were clicked. Actually, most doors didn’t open but a few did. I reviewed the pertinent code but couldn’t find a problem. I started a game and right-clicked on a door to open the Inspect Panel, which shows everything in the cell. Nothing appeared to be out of the ordinary, and the door opened when I clicked it. Then I clicked another door. This one didn’t open. I opened the Inspect Panel and found the problem: there were two doors on the cell. It turns out that the recent change to track connections between rooms in both rooms caused most doors to be added twice. The fix was trivial; I just had to exclude door creation on the duplicate connections.

Next week, I’ll further optimize map generation. Possibly, I’ll start coding the procedural history generation, which I’ve been slowly designing over the past month.

2022 Year in Review

Legend is now entering its fourth year of development. It took the first two years to build the foundation. Systems were stood up, then overhauled once, twice, or even three times in some cases. In 2022, rework dropped significantly (evidence that systems were stabilizing and meeting design needs), enabling me to focus on refinement and content in preparation for a public release. The release didn’t happen due to the unplanned Map Generation 2.0 work, but the game is much, much closer to this goal.

UI/UX Improvements

With the focus on getting a playable version out to the public, UI/UX received a lot of attention. 

  • Quick equipment switching. Melee weapons, ranged weapons, ranged weapon ammo, and light sources can be quickly changed using slots next to the hotbar.
  • New inventory screen with separate equipment slots, stats, and inventory items.
  • Visual indicators for the cell the mouse is hovering over and the default action that will be performed by clicking on the cell.
  • Improved inspect panel now displays each tile and entity in the cell and actions that can be performed on those.  
  • Status effect icons shown for player and enemies.
  • Improved Select Class screen.
  • Stamina and magic bars.
  • Added a loading screen.
  • Changed the main font to Merriweather. Not sure if I’ll keep it, but it’s preferable to the low resolution, fixed-width font I was previously using because it’s more compact and looks less retro.
Updated UI
Loading screen
Status effect icons
Select Class screen

Visibility

Using the Smart Lighting 2d Unity asset, dynamic lighting, shadows, ambient lighting, and entity light sources were added. In addition to giving dungeons more ambience, the new lighting creates some interesting gameplay. Players should proceed with caution because who knows what could be lurking in the shadows… Lighting example.

One of the trickier features implemented this year was partial wall hiding. This was needed because walls between rooms occupy a single cell. The player shouldn’t be able to see the wall in the other room. Partial wall hiding solves this problem by only drawing the portion of a wall cell that is visible to the player. Partial wall hiding example.

New Content

The slowdown in systems development, and maturation of those systems, made it possible to create a lot of new content. Dozens of new enemies, items, and objects were added. Abilities were finally added to the game as well.

Examples:

Crystals | Torches | Vampire | Eggs | Resurrect Ability | Mass Fear Ability | Summon Giant Rat Swarm Ability | Destroy Undead Ability | Warding Glyph Ability | Kill Touch and Charm Animals Abilities

Map Generation 2.0

At the beginning of November I realized that my map generator was too limited to achieve the game’s vision and that it needed to be replaced. Map Generation 2.0 had four objectives:

  1. New structuring methodology – layout of walls and floors in rooms, corridors, caverns, and other shapes
  2. Sections – map partitioned into separate areas with discrete structures, themes, and content 
  3. Data-driven stocking – replace the existing hardcoded dungeon stocking with a data-driven implementation
  4. Node pattern-based stocking – identify the best locations on the map to place specific types of content using node patterns on the map graph

All four objectives were completed. Much of the code from original map generation was still usable, but had to be repackaged. Some code, such as the BSP code, was scrapped. The new generator is much cleaner and, most importantly, is capable of producing the kinds of maps I originally envisioned (with some more fine-tuning).

New multi-section map structure and map graph visualization improvements

Unity Assets

I acquired some fantastic Unity assets in 2022:

  • Odin Inspector and Serializer – I can’t recommend this asset enough; it’s a must-have for Unity developers. It can greatly increase your productivity when using the Unity inspector. It’s very easy to learn and start using.
  • History Inspector – super handy. It lists recently viewed assets and allows you to go to those assets with a single mouse click. I was spending a lot of time going back and forth between assets and finding assets before I got this.
  • All in 1 Sprite Shader – I used this to make grayscale and frozen versions of sprites. 
  • 3552 RPG Icons Pixel Art – using for ability icons. I doubt I’ll keep these in the final version because I want all the art to be custom.
  • Pro Sound Collection and Ultimate Sound FX Bundle – added to my stock sound effects collection.

I didn’t end up using:

  • Recently Used Assets – this wasn’t useful because it just tracked assets that changed. History Inspector (see above) is what I was actually looking for.
  • Behavior Designer – this seems like a great tool, but after I bought it I realized I was trying to solve the wrong problem. Legend’s AI meets present requirements, and doesn’t require the sophistication of behavior trees. But, if more complex AI is required in the future I will reconsider this asset.

Ergonomics

I replaced my keyboard with a Logitech MX Keys Mini keyboard and mouse with a Logitech MX Vertical mouse after experiencing pain in my forearms, wrists, and hands. I haven’t had any pain since!

Time Tracking

I wanted to understand how many hours I was working on Legend and what that time was spent doing. In April, I started tracking my time using Clockify.me. Since Clockify integrates with Trello, the tool I use to track my work, the overhead added by time tracking was negligible. 

In 2022, I spent 538 hours working on Legend, averaging 10.3 hours per week. 

Hours per week from April – December

Over half of this time was spent on enhancements (new features, feature improvements). 25% of my time went to testing and bug fixing. While I didn’t track time in prior years, I suspect the refactoring time was much higher in those years compare to the 8.7% in 2022 because I was doing a lot more rework.

Time spent by category

2023 Outlook

I’m confident that more people than me and my kids will play this game in 2023. There’s some work to do to get a public release out, and it will be far from finished (and will very likely still contain the Oryx stock art rather than original art). That work includes:

Improved Dungeon Stocking 

Dungeon stocking using Map Elements is currently done on a per-room basis. I want to add multi-room and multi-level Map Elements to provide more cohesion across levels and the dungeon as a whole. An example is placing a locked door and key in different map locations.

Improved Performance

Playing the game doesn’t feel great currently. It’s clunky and unresponsive at times, especially when there are multiple enemies on the screen. A big issue is the way turns are handled. Actor actions are animated sequentially, so the player has to wait for every other visible actor to move before moving again.

Content Creation

Much more content is needed, primarily objects, Map Elements, and map section types, to ensure that maps are varied enough. Some more enemies and items are needed too.

Balancing

Balancing improved in 2022 but still has a ways to go.

Once these tasks are completed, I’ll distribute the game to a small group of people who are interested in trying out Legend and providing feedback. After incorporating that feedback, I’ll create a Steam page and publish an early access version.

Thanks for reading and Happy New Year everyone!.

Weekly Update – January 6, 2023

  • Node pattern-based stocking. This identifies the best locations on the map to place specific types of content using node patterns on the map graph. To my pleasant surprise, this was easy to implement. The difficult pieces – constructing the map graph and identifying various patterns – were already in place. I just need to tie it all together and make it configurable through the Unity inspector. All four of the Map Generation 2.0 objectives are now complete. A stretch goal, story-driven map content placement, is still out there. 
  • Symmetry and alignment in room / corridor generation. A preference for symmetry and alignment is now built into the generation, resulting in maps that look more human-made.  
  • Priority randomizer. This is a new randomizer that randomly selects items from a prioritized list. In practice, the priorities are used as categories rather than a stat-ranking (the latter wouldn’t be random). This randomizer is used to randomly select a list of items in order of priority, forcing preferred options to be selected first.
  • 9-slice sprite support. Unity has 9-slice support built in, but it didn’t apply to my use case. Now 9-slice objects such as rugs and pools can be created in the editor.
  • Map Generation 2.0 code clean-up. Some of the cell locator classes, which identify specific cells to place content, were combined, reducing the number of classes from seven to three. Two different sets of classes had emerged for specifying Map Element placement criteria. One set of these classes was removed. 

Next week’s goals are:

  1. Multi-area Map Elements. This allows Map Elements involving multiple map areas, such as a locked door and key, to be added.
  2. Simultaneous actor animations. Each turn, all actors’ actions should be shown at the same time, instead of sequentially.

Weekly Update – December 17, 2022

  • Map generation refinements. Map sections now connect to each other (most of the time; smarter connecting corridors are still needed). The starting section locations are better spread out across the map. This helps balance the section sizes, but doesn’t eliminate a frequent issue I’m seeing in which a particular section grows disproportionately and blocks other sections from growing. The solution I’m considering is to check the size of each section as the map is being generated and dynamically adjust the growth of sections that are too large or small relative to the other sections.
  • Catacombs and Crypt themes. Map sections can now be themed as catacombs or crypts, in addition to the existing bastion and cavern themes. It’s just a tile swap at the moment, but future updates will alter structure and content.
  • Refactoring. Some enums have been converted to classes that inherit from ScriptableObject so that they can be referenced in the Unity inspector. MapElements, which are responsible for stocking the dungeon, are also in the process of being converted to ScriptableObjects. This will accelerate creating new MapElements, which will soon be needed because the upcoming section-based stocking requires a wide variety of room types.
  • Started using Odin Inspector. On multiple occasions I’ve run into the limitations of the out-of-the-box functionality of the Unity inspector. While it is possible to customize the inspector, a relatively high level of effort is required to do so (coding). I originally heard about Odin from a Jason Weimann video. In the video, Jason said that if you can only get one Unity asset, make it Odin. That set my expectations pretty high, but so far Odin is living up to those expectations. It’s extremely easy to use and is very well documented. In a nutshell, I’m using it to make the Unity inspector more useful, refining the controls and presentation.

I’m on vacation for the rest of December and looking forward to making huge progress with all that time. Map generation refinement will be the main focus. If time allows I will also tackle speeding up game turns by enabling all actors to move at the same time.

Weekly Update – August 26, 2022

After literally years of research, brainstorming, analysis, and procrastination, the types of resources consumed by abilities have been determined. This decision has been drawn out by many dependent questions regarding Legend’s underlying resource management design: are resources finite or infinite? Is there a hunger clock? Do health, magic, etc. regenerate? Not all of the questions have been answered. I aspired to find the perfect solution, but the model is too complex to assemble, and I no longer believe a perfect solution exists anyway. I increasingly aim for solutions that are good enough, solutions that don’t offer complete flexibility, but are flexible enough. I embrace the resulting constraints, for they shrink the answer space, and foster creativity. And thus, at the end of the ability resource design journey, I arrived not at revelation but convention, with the health/stamina/magic trinity, a staple of the PC RPG.

  • Ability resource consumption design. Abilities may consume stamina, magic, health, and/or items. Magic fuels spells, stamina powers physical feats, and items are needed for specific skills such as picking locks. Like health, stamina and magic are represented in points. The quantities of resources consumed by each ability will be fine-tuned during balancing.
  • Player magic and stamina meters. Stamina and magic meters now join the Health meter. I reduced the dimensions of the meters so that they cover up a smaller screen area.
Redesigned health/stamina/magic meters
  • New Action Step: Consume Resources. Luckily, I didn’t have to implement a new system to handle resource consumption; it fits nicely into the Action Step framework, becoming another building block for constructing actions. This was the most logical solution, since abilities essentially just invoke actions.    
  • Added Action Source to Actions. Actions are defined using the following structure: [Actor] do [Action Type] with [Item(s)] on [Target] in [Context]. For the Consume Resources Action Step to work, it needs the source of the action (the ability that invoked the action), so that it knows which resources, and how much of each resource, to consume. The action source has been added to the actor structure, making the structure now: [Actor] do [Action Type] from [Source] with [Items] on [Target] in [Context]. This required a lot of changes in the code, but was done quickly using global search and replace (which is surely an architecture smell).
  • New items
    • Magic and Stamina Potions. Self explanatory.
  • New abilities
    • Health to Magic. Recover magic at the cost of health. No class with healing abilities will be able to use this, since it would be possible to have unlimited health and magic.
    • Magic to Stamina. Recover stamina at the cost of magic.
  • Visual effects. While looking for a way to modify sprite colors at runtime (I need to display grayscale versions of sprites and make sprites appear frozen when they are hit with an Ice spell), I came across a Unity Asset, All in 1 Sprite Shader. It does far more than I need (at least currently), but it was very popular and highly rated (and on sale) so I bought it. Of all the Unity assets I’ve installed, this asset is probably the easiest to learn; simply add the component to a gameobject and, using the component’s UI, select the visual effects you want to apply. However, I don’t think I’m using it in the intended way. I didn’t want to add the component to the dozens of actor and item prefabs because I didn’t want the visual effect to be always on, and I didn’t want to maintain an extra component for actors and items. So, I just used it to make materials, and I’m applying the effects at runtime by changing the sprite material.
  • Improved random Map Element selection. Previously, all Map Elements were assigned a rarity level, which served as the weight in a weighted randomizer. The problem with this is that the rarity distribution changes depending on the number of Map Elements of each rarity level. I’ve now split the selection into two steps. The first step selects the rarity level using a fixed set of probabilities. The second step selects a Map Element from the list of Map Elements of the selected rarity level. This ensures that Map Elements are correctly chosen based on the rarity distribution.
  • Bug fixes
    • Quick Switch and Hotbar slots still interactive after the player dies
    • The action indicator cursor still appears after the player dies
    • Enemies stop moving when the player moves out of sight
  • Minor improvements
    • The Escape key now cancels the Select Cell prompt. My goal is for the game to be completely playable using a keyboard and/or mouse.
    • Ability resource consumption support
      • Preventing abilities from being used if there are insufficient resources
      • Dimming slots containing abilities that can’t be used due to lack of resources
      • Tooltips indicate why ability slots are dimmed

Next week, the major task is reassigning sound effects. I’ve purchased several collections from the Unity asset store, but I recently pulled the files out of the Unity project because they were making project snapshots huge. Now, I need to bring only the sound effects that the game is using back into the project. Beyond that, there will be more play-testing and tightening things up. 

Weekly Update – June 3, 2022

In my relentless pursuit of increasing software development productivity, I started the week off pondering what is slowing me down the most. I kept coming back to aspects of object-oriented programming – encapsulation, abstraction, inheritance/composition, polymorphism. OOP has always been a double-edged sword for me, providing both solutions and problems. Certainly some of my issues are the result of my shortcomings as a developer, but I believe there are inherent shortcomings in OOP as well. A frequent challenge is determining where things belong, and a frequent source of bugs is putting things in the wrong place. I began questioning whether data and functionality belonged together in the same class (I was quite deep into the rabbit hole at this point) and if I could reduce complexity by separating the two. I also considered making data and functionality, once separated, completely public (I know, OOP heresy) and using either immutable or versioned data. I googled these ideas to see what already existed and found something very close: Data-Oriented Programming (DOP). Now, it would be impractical to go back and rewrite 2+ years of code using a DOP paradigm. But, I’m going to experiment with it for some of the new code I’m writing (see the AI example below). 

  • AI Overhaul part 2. I thought I was done with AI rework after last week, but I put even more time into it this week. To make the new composition-based AI configurable in the Unity editor, I added AIType classes (implementing the Type Object pattern). inheriting from ScriptableObject, I also made the pluggable components of AIType, such as the observation and action deciders, ScriptableObjects. The legacy AI classes were gutted and consolidated. AI state data was moved into a separate generic data structure (see below) and AI functionality was moved into the AIType classes. I added general AI behaviors such as offense and flee, and mapped actions to the behaviors. This simplifies the action decider code because only the behavior has to be specified; the behavior class will return all of the applicable actions to the action decider. With these improvements, I can assemble AI’s in the Unity editor, provided that the pluggable components have been written. I may need to move to data-driven behavior trees if the AI logic becomes too complicated, but for now I’ll stick with conditional statements.
  • Generic Data Structure. To support my data-oriented programming experiment, I created a class to act as a general-purpose data container. It’s essentially a map data structure, but contains three dictionaries to store values of different types (bools, ints, and objects). It’s not sophisticated but it works. I’m now using this to store AI state data, which varies by AI type. The syntax for accessing data within the structure is more cumbersome than individually defined variables, but that drawback is outweighed by flexibility and ease of serialization/deserialization. I also like that the syntax makes it obvious which variables are part of the state.

Next week’s goals are the same as last week’s goals: add the vampire and 1-2 more enemies to test the new AI, and add a few new abilities.

2021 Year in Review

2021 Retrospective

Legend has been in development for 2.3 years. It’s hard to believe that that much time has passed since I started working on the game. I don’t know if anyone else has experienced this, but how I felt at the two-year mark was in stark contrast to my feelings at the one-year mark. After the first year of development, I was thrilled with how much I had accomplished and excited for the future. At two years, panic set in. How could I possibly finish at the rate I was going? Was I wasting my time? Was this game even any good? 

Ultimately, the mid-gamedev crisis was a good thing. This was my brain telling me to reassess and correct course. That’s exactly what I did in the latter part of the year. The reality was that, at the rate Legend was progressing, it would need at least several more years to release. I don’t want to wait that long (I have more games to do!). I cut a large chunk of planned features while preserving the original vision. I forced myself to make decisions. I have a tendency to postpone decisions as long as possible to avoid limiting possibilities. It’s been a sure-fire way of keeping completion in the distant future.

Development thus far has consisted primarily of building the game system framework, and rebuilding many facets of the framework as my Unity knowledge increased and my ideas crystalized. Heading into 2022, the framework is done and development shifts to using the framework to flesh out the actual game.

What I said I was going to do in 2021:

Replacing the stock art

Completion: 0%

I’m still using Oryx. I still have some uncertainty about the exact 2D perspective that will be used. I also feel that the art doesn’t need to be replaced until the game is ready for a public release.

More content 

Completion: 5%

A handful of new enemies, items, and objects were added. Every piece of existing content was reworked in some way, for example extracting a parent class for actors and items, and moving from room-based to element-based map generation.

More polish

Completion: 30%

At the beginning of 2021, I never would have expected to accomplish as much as I did in this area. I considered polish something you do at the end of development. That largely is the case, but I’m using “polish” loosely here to refer to any visual and audio effects beyond the bare minimum, and refinement of any sort, such as fine tuning procedural generation and balancing combat. The game looked bad and felt dull whenever I did testing. Even though I knew I’d improve the look and feel before launch, I was still getting discouraged. For my own psychological benefit more than anything else, I added some game juice, including:

  • Particle effects
  • Better combat and movement animations 
  • Screen shake
  • Environmental impact: corpses, particle effect residue
  • Sound effects

Visuals | Sound Effects

This did the trick; I could finally envision other people playing the game.

Community building

Completion: 5%

I posted a weekly dev update on the website and Sharing Saturday on r/roguelikedev. I posted the link to each Sharing Saturday update on Twitter. I occasionally posted videos on Youtube. I have tiny followings on those channels. I continued to spend a minimal amount of time on community in favor of game development.

Early access release (aspirational goal)

Completion: 10%

This was nowhere close to happening, but there was movement toward this goal due to focusing on it at the end of the year. I also created a mind map visualizing features by release and a roadmap.

What else happened in 2021:

The new map generator, started in 2020, was finished in January. I built some dev tools for procedural generation analysis, tuning, and troubleshooting: an interactive map generation visualizer and a map graph visualizer. These proved to be very handy.

A map graph

I made another major change to map generation later in the year with the addition of Map Elements. These are the basic building blocks for populating a map with content after the map structure’s been created. They’re hierarchical and modular, enabling rooms to be constructed from layers of interchangeable components and reuse common components. For example, many room types can include an Abandoned Map Element that adds cobwebs and debris to give the appearance that the room is abandoned.

Maps became both more varied and more playable. Map configuration parameters, which dictate a map’s structure (number of rooms, room sizes, room distances, room themes, etc.) are now randomized. This additional layer of procedural generation increased the variety of maps while maintaining consistency within a map. Map configuration parameter ranges were fine-tuned to avoid problematic maps. Room type probability changed from a linear distribution to a weighted distribution based on rarity to give maps a more logical composition of rooms.

Significant UI work was done. New screens were added, a hotbar was added, and the main game UI was refined. 

Class selection screen
Inventory and hotbar

In action:

AI was expanded. Now, actors can have their own AI controllers and behave differently than other actors. AI controllers can be reused across multiple actor types. Actors can now track any number of other actors, enabling them to do much more than charging the player. They can attack, defend, and interact in other ways with tracked actors as dictated by their AI controller. Actors now gain awareness of events based on their vision and hearing ranges and act based on the type of event occurring. Each actor has its own inventory and the ability to pick up items and use them. When an actor dies, other actors can acquire the items it was carrying.

In light of my time constraints and the mountain of work remaining, I made a deliberate effort throughout the year to increase my productivity. The ways in which I did this include:

  • Reducing and simplifying code.
  • Moving logic from code to configuration (physical material interactions, game events).
  • Adding unit testing.
  • Adding an in-game console for spawning objects to accelerate playtesting. 
  • Adding more granular logging so that I have more information to troubleshoot.
  • Consolidating test settings into a single design-time editable object.
  • Switching my IDE from Visual Studio to Rider.
  • Reorganizing my Unity editor layout.
Reorganized Unity Editor layout

Finally, code rework occurred throughout the year. Most of it was necessary to keep the code maintainable, but I also know that I am overly eager to rework code and sometimes create more problems for myself than I solve. As the year progressed, the rework did slow down. There are two reasons for this: 1) the framework reached maturity and 2) I became more selective with when I reworked code. Before I make changes or add new features, I now consider what compromises I can make and what I can do within the existing framework to avoid rework. 

2022 Outlook

This year’s goals are essentially the same as last year’s. However, the priorities have changed. An early access release is now the primary goal. Original art, a requirement for the release, is also a top priority. Effort will be concentrated on what is absolutely necessary for public release, including tightening the game loop, balancing, and clearing the bug list. 

Weekly Update – December 31, 2021

It was a great finish to the year. The main gameplay loop is coming together, levels are more balanced, changes have gotten easier.

  • Dungeon level-based equipment drops. Each dungeon level now contains equipment appropriate to the player’s power curve. Prior to this change, dropped equipment was random. A player on level one could find the best armor in the game, and a player on level twenty could find a copy of the short sword that they started with. Now the player will typically get equipment on the power curve, occasionally equipment one tier above or below the curve, and rarely equipment two tiers above the curve.
  • Map element placement overhaul. Previously, map elements were added by calculating the number of elements to place based on the number of map nodes and then randomly selecting a node and an element for each iteration. This was quick to implement but it unevenly populated the map with elements. I changed this to iterating over each map node to guarantee that it received one and only one map element. Additionally, I moved enemy placement responsibility from the map generator to the map elements themselves because the available enemies varies based on the map element. I also added a way to control the relative frequency of map elements, so that some occur more or less often that others.
  • Map element hierarchy. After creating many map elements it became evident that there’s a natural hierarchy – some elements define an entire map node while other elements perform a subordinate function such as decoration. I organized the folder structure and added inheritance into the classes. This also helped identify which map elements should be used directly by the map generator and which ones should be used by parent map elements.
  • Most destructible objects are now destroyed with one hit. Walking into a room full of barrels and crates and having to hit each one a few times to destroy it was realistic but tedious. I made these and similar objects destructible in one hit. Some objects that are not routinely destroyed and should require some effort, such as wooden doors, still take multiple hits.
  • Combat modifiers for standing on corpses. Standing on a corpse now causes a slight combat disadvantage, the thinking being that an actor can’t maneuver as well. More importantly, it adds another tactical option for combat.
  • Enabled Unity’s Universal Render Pipeline. I honestly didn’t know about this (embarrassed to say that with two years of Unity experience under my belt now) and stumbled upon it while researching lighting in Unity. To use some of the lighting features, the URP was required. It wasn’t too difficult to enable. I had some issues with materials in a couple of third party assets. I’m still fuzzy on the URP but it seems like the right choice given that I have an aspirational goal of releasing on multiple platforms and the game is 2D. Ironically, I ended up using a lighting asset from the Unity Asset Store rather than the built-in lighting because the latter didn’t have all the functionality I needed.
  • Added lighting, then removed it (temporarily). I set up basic lighting (darkened dungeon, player visibility, torch light) using Unity out-of-the-box lighting and it looked pretty good, but I ran into some limitations. I bought a Unity asset, Smart Lighting 2D, to get more functionality. I got it partially working, but I’m still learning how to use it. I had to put it on hold because I had some more important things to do this week. I’ll pick it back up in January.
  • Fixed some bugs that had been hiding for a long time. I discovered a few bugs that causing problems I didn’t even know the game had! For example, one of the methods for fetching cells in a pattern was returning all of the cells in a map node rather than the room inside the map node. This explained the occasional mysterious placement of objects outside of a room. I also realized that enemy AI was executing some of the code for handling player clicks. This was ultimately benign, but still totally wrong so I corrected it.
  • Increased the frequency of enemies and items for the standard room. Most rooms use a generic map element that sometimes adds enemies and items. Sometimes wasn’t enough; there were too many empty rooms, and maps often felt dull. Simply by increasing the frequency of enemies and random items, the game was funner to play. 

Next week, I’m making some more minor tweaks to gameplay and map generation with the end goal of making level one challenging and fun. 

Weekly Update – December 24, 2021

Happy Holidays everyone! With a few days off from work this week, I got a lot done! Many of the changes were visual, which always feels more productive. I primarily worked on combat game juice.

Combat game juice
  • Corpses. Slain enemies now leave corpses behind. This increases the player’s impact on the environment and will be used for a variety of mechanics including reanimating corpses, enemies reacting to corpses, and combat modifiers when fighting on a cell containing a corpse. I was pleasantly surprised to find that this feature didn’t require any code changes. It was implemented entirely in the Unity Editor using the custom trigger/effect framework developed earlier in this year.
  • Particle effect residue. Particle effects can now leave permanent residue, such as blood stains and bone fragments. Along with corpses, this is intended to increase the player’s feeling of leaving a mark on the environment. You can very clearly tell where battles took place and the size of those battles.
  • New walk and attack animations. After getting frustrated a couple of weeks ago by not being able to make a decent walk animation, I took some time to learn more about classic animation techniques. I applied the squash and stretch technique to walk and attack animations. I also added a slight pause to the beginning of attacks for anticipation. I got much better results this time around, due primarily to a more precise application of squash and stretch.
  • Replaced animation implementation. In researching Unity animation techniques I discovered Unity’s new 2D Animation package. This package provides a way to reuse animations with different sprites. Previously, I was using what I believe is the traditional methodology – for each animated game object prefab, create an animation override controller for the animation state machine, duplicate the animations referenced in the animation controller, and replace the sprites in the animations. This technique has major drawbacks: 1) it’s tedious 2) it generates a lot of redundancy. If I ever needed to change a common animation such as walking, I’d have to change every copy of the animation. The 2D Animation package provides a way to define an animation only once and swap out sprites based on the game object using the animation. This is done by adding a sprite library and sprite resolver component to each animated actor and configuring animations to reference the spriter renderer properties rather than the sprite directly.
  • Added a parent to all animated actor prefabs. This was suggested by /u/Notnasiul as a way to use relative positioning in animations and as a general good practice. It took around an hour to go through the existing actor prefabs, add a parent game object, and move the Sprite Renderer component to a child game object. And… it worked great!
  • Directional attack animations. With the capability to control relative game object position in the Unity animator, and trigger events in animations, I was able to remove the code that was previously responsible for this. This code was complicated. It updated the game object position on each Update() based on an easing function and managed time-based state changes for different points in the animation where events had to occur. I was glad to see it go.
  • Reconfigured my Unity Editor layout. Up until now I’ve been using the default Unity layout. This layout has been workable but not optimal. The project asset hierarchy panel isn’t large enough, the game object hierarchy panel is too large, and some tabs that need to be viewed concurrently (such as Animation and Scene) are by default in the same panel. I reorganized the panels to better suit the needs of this project. This is the end result.

Next week’s goal is to give level 1 the right amount of challenge. To do this, I’m going to tweak stats and drop frequencies for items and enemies. I want to finish the year with at least one good level. 🙂

Weekly Update – December 17, 2021

Minor progress this week. I have some time off from work over the next two weeks and look forward to getting more done.

  • Added screen shake on hits. Continuing down the game juice road, every combat hit results in a screen shake now. I think it looks good, but wonder if some people may find it excessive or unnecessary. In line with the “sword and sorcery” theme, I want combat to feel exciting, and somewhat over the top. I’ll leave it in, but may dial down the effect. As with the blood particle effects, I want to vary the magnitude according to the amount of damage done. 
  • Distinguished missed hits from zero-damage hits. Whether the attacker hits or misses is based on the attacker’s chance to hit and the defender’s chance to evade. When a miss occurs, the text “Missed” appears instead of a damage amount. The damage from a successful hit is based on the attacker’s attack damage and the defense’s damage absorption. The amount of damage done is displayed when a successful hit is performed. A zero-damage hit occurs when the defender fully absorbs the hit. Previously, a zero-damage hit was being displayed as a miss, simply because this was easier to code. But, this didn’t accurately reflect what was happening. I needed to distinguish misses and zero-damage hits to play the correct sound effect (a whoosh vs a weapon hitting armor), whether to display damage and blood particle effects, and to degrade the attacker’s weapon and defender’s armor. I also wanted to communicate to the player whether no damage was being taken because of evasion or damage absorption.
  • Moved player gold count back to the main screen. The player’s gold count was initially displayed on the main screen. I moved it to the inventory screen at some point to simplify the main screen UI. However, acquiring gold is so closely tied to the gameplay and the theme (treasure is a primary motivation for exploring the dungeon) that I moved the count back to the main screen.
  • Researching Unity animation with relative positions. I want to make combat animations more complex. To do that, I want to move combat animations out of code and into Unity animations so that I can leverage existing engine/editor functionality. There doesn’t seem to be a straightforward way to move a game object relative to its current position in a Unity animation. A common recommendation is to create a parent object so that the child can move relative to the parent. It’s not an ideal solution, but it’s the best one I’ve found.

Next week, I’ll continue adding game juice to combat. This will likely be the focus for the remainder of December.