It looks like you want to have just a single instance of your weapon class, that can morph on demand into any of your weapons at any upgrade tier. This would more conventionally be solved by having a separate instance for each weapon - say as separate prefabs - and spawning/recycling or activating/deactivating them as the player switches weapons. But if you prefer to keep this single morphable weapon, we can still make it more maintainable like so...
First I'd make a data structure that stores a snapshot of all the stats for a particular upgrade tier:
[System.Serializable] public struct WeaponStats { public float damage; public float fireRate; // Add any other attributes of your weapon that can change with upgrades. }
Then I'd create an asset that can store the set of upgrade tiers, and possibly other traits, associated with a specific weapon. This can hold an array of these stats.
[CreateAssetMenu(fileName = "NewWeaponProperties.asset", menuName = "Data/Weapon Properties")] public class WeaponProperties : ScriptableObject { public WeaponStats[] upgradeTiers; // Add other stuff that distinguishes your weapon - visual, sounds, etc? }
The attribute at the top lets you create instances of this class as asset files in your project folder, so you can make one for each weapon, with meaningful names.


Your weapon class can then keep track of which set of WeaponProperties it's currently using (ie. which weapon type is active), and what upgrade level it's currently at, from which we can deduce its current stats:
public class Weapon : MonoBehaviour { [SerializeField] WeaponProperties _properties; [SerializeField] int _currentUpgradeLevel; [SerializeField] WeaponStats _currentStats; public void SetUpgradeLevel(int level) { if (level < 0 || level >= _properties.upgradeTiers.Length) { Debug.LogWarning($"Tried to set weapon {name} to non-existent level {level}."); level = Mathf.Clamp(level, 0, _properties.upgradeTiers.Length - 1); } _currentUpgradeLevel = level; _currentStats = _properties.upgradeTiers[_currentUpgradeLevel]; } public void SetWeaponType(WeaponProperties type, int level = -1) { _properties = type; SetUpgradeLevel(level >= 0 ? level : _currentUpgradeLevel); } void Start() { SetUpgradeLevel(_currentUpgradeLevel); } // ...and the rest of your weapon behaviour. }
Now you can define as many upgrade levels as you want for each weapon type, and you can set them up in the inspector data, instead of hard-coding them into the weapon class individually, and recompiling anytime you want to change them. This makes it much easier to tune your data - even while the game is running - to dial in the values that feel best.
This also makes it easy to experiment with different weapon types: just create a new asset file for the new idea. Didn't work out? Just stop referencing that file, and it won't be included in your build, but you can keep it around in your project folder in case you want to come back to it later. In the meantime, this "dormant" data doesn't clutter your code like a commented-out chunk of your upgrade function.