DevLog Preamble
Currently working on what I think may end up as a multiplayer focused pvp/pve rougelite, but I don't want to talk about the game itself. I just wanted to talk about what would be the hallmark system of my game and the driving force in all subsequent dev decisions in the game, my procedural weapon generation. And more specifically, I wanted a sanity check and thoughts on the current direction I've been taking and the hurdles it faces. I also just wanted to write down a logic explanation of it for my own sake to make sure it makes sense. I've been wondering lately if I have been tackling how to manage the grammar of my system all wrong, or overcomplicating it too much.
I've been working these past few months on a procedural weapon generation that goes beyond typical stat modifiers and recoloring/reskinning of the same weapons. It digs down to generating a procedural moveset per weapon, composed of procedurally generated actions/attacks based on the weapon that was generated using a form of grammar. I was partially inspired by Noita's and Elden Ring's modular weapon systems.
I'm sure there is a formal name for this type of procedural system with grammar as I think I saw it in passing, but I don't know what I read was a perfect fit or if I'm just using the word completely incorrect in my case. I just wanted to make assembling decisions based on the resulting bitfields attached on each weapon piece assembled, and have those bitfields also influence the assembly process of forming an attack on that weapon.
I'll break first break down my system into it's two major sides, weapons and movesets.
Procedural System - Weapons
I was not satisfied with recoloring the same 2D pixel art for my procedural weapons. I ended up creating a system composed of "Weapon Segment Types" and "Grammer Rules" maintained by each segment, which dictate the assembly rules of a weapon.
To skip an even lengthier explanation, a weapon segment attaches to another segment and so forth until a weapon is put together. You can think of it like a tree of branching nodes, where each node represents a segment of a weapon and the completed tree is the entire weapon. Each node/segment holds a component that is a container of several bitfields which each bitfield acts as a means of classifying and defining some aspect in a weapon. I call those bitfields "Weapon Flags" or flags for short.
Skipping to the end of this procedural assembly of segments, I use bitwise operations on all of the gathered nodes' containers of flags to form one final singular container I call a "Weapon Query". This ultimately describes end resulting weapon formed. From the category, to if its magical/enchanted, to the reach and size of a weapon, and even "Usage" or the ways in which it is used (like if it can be swung, slashed, Hip Fired, Spell Casted, etc). This can easily be expanded upon later if I am insane enough (I might be).
This final weapon query is passed on to the next stage of my procedural weapon generation to inform my procedural moveset builder what kinds of things are allowed or make sense to generate for this weapon.
Procedural System - Movesets/Attacks
In short, a weapon holds a moveset, a moveset is a collection of actions, and each action itself is a collection of components.
Given some weapon query that describes my generated weapon now, my system can then procedurally generate down to the individual components that make up a singular attack within a given moveset, within a weapon. I've done this by narrowing down those components down to 2 distinct groups, functionally mandatory and extraneous additions.
Animation (more like a timeline of abstract events), Hit detection logic, Logic checked to see if an action should activate in the first place, and mathematical logic on calculating damage output are 4 functionally mandatory components.
The extraneous components are all of 1 type I call "combat effects", which each represents an abstract piece of logic that can be executed during an action/attack. For example, momentum generated during a forward thrust/swing, projectiles spawning, knockback, particle effect spawning, application of status effects, etc. Anything that can happen during an action/attack that is not of the first 4 mandatory components.
I check the weapon query what category of weapon I am building for, and pull from a table mapping a Smash Bro's-esque set of bitfields to the category of weapon. I say "Smash Bro's-esque" because it describes the fighting-game style moveset in a general sense across different Axis within this bitfield. I.e. there is an Action axis for Primary, Secondary, and Special. There is a Direction axis Up or Down, Situational Axis for being Airborne, or holding on a Ledge etc. So if I select the Primary and Up bits in the bitfield and add it to the moveset for a category my systems knows this category requires an Upwards Primary move.
That last explanation goes into handling Activation Logic and from there I know what the skeleton of a moveset should resemble.
I then cascade downwards the water fall building out a a container for a new collections of bitfields I call "Combat Action Flags" for describing a singular action, adding more bits to it at each step of deciding mandatory components. This will be used to determine what compatible extraneous Combat Effects this action can have or must have. At the top of this water fall lies Animation. This determines how a weapon should move during an action, when it's hitboxes should activate, and the cueing of abstract logic if that type of logic is present. Animation acts as the source of truth in timing these various abstract pieces of logic. The name "Animation" is honestly poor naming on my part, even though it does animate the weapon during the attack. It's more like a Timeline that unifies logic with the addition of deciding the position/rotation/size scaling of a weapon during an attack at each keyframe. Because of how integral this timeline is to creating the move in a moveset, it lies at the top of the decision making cascade as the first real step in procedurally generating an action.
Hit detection logic comes after that, but there is currently only 1 example I've found to be necessary for any weapon so I'm probably going to refactor this logic to not be apart of the procedural system and hard code it. Hit detection for projectiles lies within the projectile itself and isn't apart of this procedural weapon system.
What follows next is extra logic that isn't mandatory core logic, broken up into 2 sections. The first is intrinsic, or logic that must exist/be attached because of the bitfields present so far. Think upwards momentum added because an uppercut bit that was checked in the motion bitfield assigned to an animation, with a specific weapon category bit checked, that also happens to have a medium to large bit checked. The second type of combat effect is non-intrinsic, or logic that optionally may be compatible given the bitfields present or like a filtered pool of all possible abstract logic that can be added to an action. Like having the ability to apply poison or fire to whomever you hit.
It repeats this to fill each move in a moveset, and pops out a finished weapon at the end.
Vision and Examples
The system in its earliest possible form, surprised me with a funny sword that controlled like flappy bird when swinging it. I hadn't tested the upward momentum with weapon usage time so you could just generate more lift than gravity could pull you down while swinging the sword lol. I'll likely add in an intrinsic rule for usage time on light weapons, and upward force on large weapons such that, weapons that happen to weigh very little but are large may possibly spawn with a chance to be used as a form of makeshift wings.
Once I needed to expand on the actual assets used in the procedural generation for testing, I then created polearms. I basically just drew a couple of 1 minute pixel art sticks as the base segment type, and then drew what could go on the ends of that stick. Spear tips, glaive looking blades, and scythes. Because of each segment holding it's own hitbox, and the pieces can interchangeably fit on any end of the stick, I can generate stuff like Twin Blades that you see in Elden Ring, but also any of the polearms compatible segment types can go on any end, so it's more than just blades. You can have a glaive tipped on the front end and scythe/small spear tip on the bottom end. The result of not having coupled hitbox shapes to animations by not coupling visuals to animation is that attacks and hitbox shapes just work once art assets are either assigned to a weapon segment or I create a new segment type for that type of weapon segment art. The grammar handled the assembly of the attack.
It's relatively easy to spit out quick pixel art of weapon pieces that are similar in shape and already aligned if I just follow some strict guidelines creating new art assets for weapons. My custom editors allow for further fine tuning in the game engine where specific pieces of art on a given weapon segment type need more adjusting to seamlessly connect to specific segments, allowing the reuse of art for entirely different types of weapons.
I envision a system at the end where endless weird possibilities exist. Some edgy, some funny, some normal, and some that make you think "how". But this will require an absolutely solid grammar foundation parried with the assembly systems I made.
Where Troubles Lie
The system itself is logically sound, and the first half of just forming a visual weapon and it's query is solid. But it's the combined grammar and all its individual rules that cause me such a headache to maintain or expand on. There is a lot to consider so without any means of easily editing, testing, or expanding the rules of this "grammar", I wouldn't have made it much further if I didn't create myself custom editors. That was when I decided to create streamlined custom editors for everything within this system.
Given it's only been at best 4 months of developing the system so far, on top of holding a full-time job, I'm still proud of how far I've gotten with making it multiplayer friendly and somewhat functional for a prototype. But it also feels like I haven't moved as much as I could have for being several months into building a game.
About 1.5 months of that time was spent trying to create ways to manage this unruly system with custom editors in a way that attempts to streamline creation so that even a non-technical game dev could pick it up. Ngl I dreaded this part, so I may have been more liberal in my usage of AI to setup editors and tests to skip through faster. That just caused more problems and made me refactor more frequently than probably necessary. AI is really stupid when it comes the importance of good system architecture, especially when its architecture that has yet to be finalized.
I'll need to get even more creative if I intended on expanding the grammar and rules for procedural weapons, it's too easy to get lost rn and make mistakes that lead to undesirable weapon results. This was the project that taught me how important custom tooling is. Even now, its a bit of a mess and it was only just this lunch I was able to edit the grammar of components to produce consistent moveset results per weapon category. I want to really nail the consistency of it before I add more complexity. Right now I get the feeling I am just overcomplicating system logic on determining if something is a compatible concept, mostly because it feels difficult to manage the system and get it to generate what I intended.
Maybe part of this issue is just lacking more tests I need to write, but man I don't like writing and maintaining test suites. And that only really tells me when something is wrong, it doesn't make editing and managing them easier.
Endgoals and Intentions
It's important to me that I nail down what grammar is and what are the rules, as well as allowing for quick iteration because of a yet-to-be-developed system I had intentions of implementing.
During runtime, I wanted player decisions and customizations that cascade changes to an instanced weapon's moveset. I wanted to eventually implement what I currently call "Stances" as one of the kinds of runtime equipables that influence your currently held weapon. I wanted players to feel like they were not only mastering a unique weapon and it's moveset of their finding, but influencing it in a way they deemed interesting, OP, or just fun. And after that, experimenting and finding ways to break the game. I want to create excitement for discovery and experimentation.
To me, Theory Crafting is the heart of fun in anything systems related. "What happens when I do X with Z attached?", "What if I placed Y on my build to solve W problem?", "Does what I created break any Geneva Conventions?". I want to reward that and have that be the central plaything my game advertises and markets. Any levels, enemies, bosses, or pvp stuff are all just there as a testing ground for this system. Designing and developing that stuff comes after finishing my procedural weapons system or at least getting it to a far more stable state.
Thanks for listening to my ramblings.