Since you’re gonna be shooting bad guys a lot in this game, weapons seem like quite an important part of the game, and indeed they are - so implementing them in a way that won’t give us an aneurysm in the future from the amount of tech debt seems like a good idea.
Old system
So when I initially went into this project, one of the tasks I was given was looking at the weapons code and figuring out ways to improve it, because Yollie felt like it was pretty difficult to do anything in it. And OH BOY I wasn’t expecting to see what I saw.
So can you guess which object is responsible for holding the data for the weapon and which is responsible for the data for its projectile for the default Trailblazer weapon? What if I say it's 3 objects per weapon? And each one of them holds different data BOTH for the weapon and projectiles, seemingly randomly? It was this bad.
And don’t get me wrong, I get why Yollie went for this approach, it allowed A LOT of freedom when creating the weapons and their addons, but this was VERY error prone and required A LOT of setup for each single weapon/addon/projectile and their scripts. Especially addons (which is our way of modifying the projectile’s behaviour by adding modular effects on top of the base behaviour) were very troublesome to make, since they required you to create a “Projectile Addon Holder” script which was only used in the Projectile Data object:
public class ProjectileKnockbackHolder : ProjectileAddonHolder
{
[ShowField(nameof(OverrideDefaults))]public float KnockbackAmount;
[Space]
public float KnockbackAdd;
public override bool IsSameType(ProjectileAddon addon)
{
return addon is ProjectileKnockback;
}
public override ProjectileAddon AddAddon(GameObject targetProjectile)
{
ProjectileKnockback knockback = targetProjectile.AddComponent<ProjectileKnockback>();
return SetAddonValues(knockback);
}
public override ProjectileAddon SetAddonValues(ProjectileAddon addon)
{
if (addon is ProjectileKnockback knockback)
{
knockback.KnockbackAmount = OverrideDefaults ? KnockbackAmount : 0f;
}
return ChangeAddonValues(addon);
}
public override ProjectileAddon ChangeAddonValues(ProjectileAddon addon)
{
if (addon is ProjectileKnockback knockback)
{
knockback.KnockbackAmount += KnockbackAdd;
}
return addon;
}
} So much boilerplate qwq
Another problem was that the gun worked on a entirely different system seperate from the missile weapons, and this meant items that modified weapons had to have their behaviour coded differently for missile weapons vs the gun.
New system
We decided the old way of doing things had to go, and I went on to trying to design a better one that didn’t require so much boilerplate and setup, worked the same for guns and missile weapons, and additionally worked for enemies as well.
The approach I went for was a bit more “Unity” way of doing stuff, I decided that Weapons should just be simple prefabs that handled only the weapon stuff, like ammo, spawning projectiles, etc. and Projectiles should also be just prefabs which held all the data in the prefab itself. Addons were simplified to be components on child objects on weapons and projectiles (mostly on projectiles).
Ahh, much better
Unification of the backend systems allowed for cool new interactions that weren’t possible in the previous systems - want homing and ricocheting gun projectiles? sure! hailstorm shooting 10 projectiles all of which get all the addons of the main projectile? easy, literally one line of code.
Making new addons also is MUCH easier now, most of the ones that I had to rewrite for the new systems shrunk in size dramatically, and dropped in complexity a lot.
Everything I wrote here is a massive oversimpliciation, the entire process of porting everything took just shy of a month, so it was not an easy task at all lmao, but it was for sure worth it :3