This is a long one, and honestly it’s probably more for me than you. But if you’re into this kind of thing, soldier on!
I’ve been leisurely working towards developing this game in one way or another since June of 2018. Dreaming, brainstorming, attempting to assemble multiple teams, and finally deciding to do it myself have landed me here - in this blog post. Attempting to build an audience? Trying to feel a part of the game dev community? Looking for feedback in all the wrong places? Possibly. But no matter what, it’s happening right now so let’s talk about where the game is currently at, and where I see it going.
A Quick Trip Down Memory Lane
I’ve been able to grab some of the videos and gifs I’ve shared with friends from when I knew even less than I do now. The earliest gif I can find is showcasing my inability to get the camera to see the horse.
Thank God for Cinemachine. Originally I had intended for the game to be played on mobile. My inability to find decent mobile games fueled my desire to solve the problem, but the scope of the game made me realize it may be easier for my first attempt to be played on desktop. You’ll notice I’m still working in a mobile aspect ratio but that’s because I plan to add borders to the sides like other wide-screen top-down shooters do.
Early on (and honestly, sometimes still) I struggled with how to make objects move. I was using click-to-move when I was banking on mobile development, but was setting transform.position on update so I was just teleporting through objects and ignoring all physics. It literally took me about a week to figure out using rigidbody2d.movePosition was better and that movement via applying velocity actually enabled collisions. 🤯
Anyway, thanks for indulging me. Here’s a gallery of some other “woohoo!” moments including enemy waves, chests, items, buffs, and even a boss with multiple states swapping between shot patterns. The first video, however, is the current state as of posting.
The Horse Itself
I don’t really consider anything in the game “done” as I’m still uncovering interactions as the idea takes shape. But there are a lot of things which are functional. Let’s start with the Player.
Most things are pretty self explanatory like the Rigidbody for physics and Collider for… well, collisions. But some components are unique to the tools. “Ubh Shot Ctrl” is handling the enable/disable of my basic projectiles, “Poolable Info” is how Core GameKit’s Pool Boss module targets objects for pooling, “Flow Machines” (aka macros) are Bolt’s version of scripts, and I’m using “Sprite Outline” to toggle a colored outline on specific objects when the cursor hovers over them.
The player has two children: a “Pickup Radius” which is really just a trigger circle collider that interacts with collectibles and a “Ubh Shot” object which is targeted by the Player’s “Ubh Shot Ctrl” to determine what bullets get fired and how.
I’ve got player management broken into 4 scripts right now, but I’m not sure if there’s any performance benefit in combining them yet. Player Controller does a few things like telling Cinemachine to follow this object when its activated and getting directives from scriptable objects.
The Player Movement macro is setup to check for debuffs (which don’t yet exist) before the player can move. Update hits a branch which checks the canMove bool before it does anything. Then it’s setting rigidbody2d.velocity based on WASD input, but it’s passing through a debuffModifier unit first to prepare the way for some gnarly snares the enemies will be throwing at you.
Fire Abilities is specific to the fire mount and houses the projectile and ability functionality. Again, prepping for debuffs pressing the fire key (spacebar) down checks the canShoot bool before enabling the shot routine. Fire key up stops the routine. I’ll probably end up tying this to check for stun debuffs as well.
I’ve decided I want each mount to have one offensive and one defensive ability so with fire I want it to be pretty balanced as it’s your starter. It’s still a work in progress but right now Ability 1 is a freaking meteor which drops a nasty aoe after a brief delay and Ability 2 is set to be a short dash towards the cursor which makes you immune to shots for it’s duration and ends in a small explosion where you land. To follow suit, they’re both passing through a canUseAbility bool, checking to see if the player has enough mana, and are simply instantiating objects which control the rest of the ability’s effect.
Status and Death is where it gets fun. I’ve glossed over scriptable objects in this article but haven’t really talked about the power and pain of it. A few weeks ago I stumbled on this article which hits on the functionality and purpose of scriptable objects and links to videos by Richard Fine and Ryan Hipple about the practical uses of them in designing game architecture.
It absolutely wrecked me because so many of the pain points they addressed were actual kinks in my workflow. It was time to refactor. Again. But this time, it would take weeks. I’m actually still not done as I’m writing this.
Functionally, a scriptable object is a Game Object, without a Transform, which maintains a persistent state whenever its values are altered (in play or editor mode). At a more basic level, scriptable objects are data containers which can be referenced by other objects to increase modularity and flexibility in your code.
Rather than referencing your Player or a Scene Management object to update something many other objects rely on like the Player’s health, have them all reference the Player scriptable object. Now if you ever want to simply test the UI or some enemies in a test scene you don’t have to recreate your entire level structure! The object in question just looks to a scriptable object, which lives somewhere in your project folders, and knows exactly how to act.
There are so many other uses outside of that, like if you’re going to need the same kind of information into multiple places like having enemies appear in a level, but you also want their information listed in a bestiary - both of those scenes can reference the same object so if you ever have to update information, it updates in both places.
You can also group scriptable objects by type which is one major reason it was so appealing to me. I have a Buff type which houses information like affectedStat, duration, buffIcon, particleColor, and more which allow me to create one reusable macro for applying buffs and all it has to do it look at the correct scriptable object to work.
Those Who Would Oppose
Enemies are much more simple than the player at this point. I’ve found it’s much easier to create complex enemies than it is to make basic ones feel good. So I’ve tried to work with some slow moving, easy to dodge patterns that could really throw a wrench in the works when they gang up on you.
The piece of the enemy puzzle that I wish I could find a better solution for (or get some affirmation that this way is ok) is how I’m calculating damage. I’ve yet to figure out how to relate a bullet fired using UBH to the object it was fired from which means enemies can’t have an “attack” stat.
Bullets spawn inside of an object pool with no apparent connection to the controller which initiated the shot routine. Because of this I’ve opted for using a base enemy damage amount for the scene and having bullets receive a modified amount to apply as damage on contact with the player.
This is fine and all, but now I’ve got a ton of bullet prefabs which, for a genre literally called bullet hell, I’m guessing is just fine.
There are bullets which persist until they hit the player or exit the scene, bullets which fade out and despawn after a set amount of time (primarily used for homing bullets), and bullets which spawn with a powerful velocity and quickly slow and despawn for explosive effects. For each of those types there are small (50% damage), basic (100% damage), and large (150% damage) versions. I’m certain it won’t stop there, but that’s a solid start.
Enemies are also tied to scriptable objects. I’ve got types for Enemy/Basic, Enemy/Elite, and Enemy/Boss which share some commonalities like core stats but can also contain multiple shot patterns and macros. The thing I’m most proud of, however, is the enemy loot tables!
I built a class called LootDrops which is a multidimensional array where each entry contains an item prefab and some information to determine how often it drops. First there’s guaranteed which is a simple bool that, when enabled, ensures this item will be dropped no matter what. Next up is dropRate which is an integer range of 1 to 10. If guaranteed is false, the name of the item will be added to an array called weightedTable for as many times as it has dropRate.
For example: let’s say item1 has a dropRate of 6, item2 has a dropRate of 2, and item3 has a dropRate of 10. An enemy’s weightedTable will have 6 entries of item1, 2 entires of item2, and 10 of item3 meaning there are 18 total elements in the array. When the enemy dies it will randomly choose an element from this array to spawn. Because items with higher dropRates appear more frequently in the array, they’ll have a higher chance to be chosen at random. You’ll have a solid chance to see item3, but item2 will be rare.
Enemies also have values for minDrop and maxDrop which determine how many items they drop on death. A value within that range is selected at random and the weightedTable array will be looped through that many times, dropping an item for each one. Each item also has a dropAmount which is the number of times that item can be dropped from this specific enemy.
In using our previous example, if the enemy dropped 5 items but item3 only had a dropAmount of 2 it could only show up a max of 2 times before it was removed from weightedTable to make room for the less common items to appear on the next 3 loops.
Here it is in all it’s glory.
Apparently the new version of Bolt allows you to take better overview screenshots 🤞. I took this functionality and created a scriptable object type called LootTables and attached it to the enemy scriptable object so I can create multiple versions of basic loot tables that can easily be interchanged.
The level itself contains a lot of information as well. At some point there will be difficulty settings for each level wherein enemies have boosted health, damage, and likely add in stronger waves or new shot patterns. It will all exist in scriptable objects because of course it will if you’ve read anything up to this point.
Right now, levels are defining the base attack damage, background scroll speed, and maintaining score and currency info for this specific scene. It’s all getting updated in the UI but not saved anywhere just yet. Whew, this section was easy.
Greener Pastures Ahead
I’ve got some simple goals for the near future like continuing my initiative to convert All The Things™️to scriptable objects, flesh out the 2nd fire ability, continue creating basic enemies, include level start and end screens, and actually complete a basic level.
At that point I’ll feel comfortable to start on the rest of the backlog which includes things like:
mount swapping (new abilities!)
in-level basic shot upgrades
interactive objects like logs, gates, statues in the level
branching paths in the level
menu and inventory screens
stat altering equipment
world map with level selectors
level difficulty settings
hone in on soundtracking + sound effects
implement “polish” effects like foreground obstruction in level, camera shake, particle effects, lighting, etc.
get a freaking designer so i don’t have to use mismatched cheap looking sprites for everything
and so much more
I look at that list and I’m not overwhelmed, I’m excited! And wether anyone reads this blog or not, it was therapeutic to write it and see how far I’ve come - even now with the game in a broken and basic state. Here’s to the future 🍻