top of page

Captain Sausage - Building an Upgrade System

adamtedders

This short blog post will detail my development process for the Upgrade System for my new game, Captain Sausage. It will discuss all of the steps I took to get to what is currently in the game at the moment, from research to design, implementation and refactoring.


In previous roles, I have built a Upgrade System but this is the first time that I have designed and coded one from scratch by myself. As a developer I saw this as an exciting challenge as it gives me the chance to build something that I haven't done before and become a more proficient developer.


Researching the System

Before writing any code or making a rough mock-up in Photoshop, I firstly had to do some research to understand and figure out what I need for a successful Upgrade System. This was mostly done by playing games and researching online.


To design a successful Upgrade System I had to answer these questions in my research:

  • Is an Upgrade System right for my game?

  • What makes a great Upgrade System?

  • Does it need to be simple or complex?

  • How will the player be able to purchase upgrades?

To be honest, answering the first question is very important because not all games actually need an Upgrade System but implement them anyway. A game with an Upgrade System that it doesn't need can lead to issues. Case and point, Mirror's Edge Catalyst had an Upgrade System that didn't really work well and was a main criticism of many players and critics including myself. It caused many issues as the game was less fun for the first few hours as you were slower and couldn't do awesome parkour moves as they were locked away. More annoyingly you could do all these parkour moves in the first game without having to unlock them first.


I personally didn't think the game needed an Upgrade System as the depth actually came from figuring out the best path through the city, and using your parkour skills to find that route. That's why I really liked that the open world is a massive improvement over the first game because you now have more choice on how to tackle your objectives.


Design Process and Mockup

After my research was complete, I had a rough idea of what and how my Upgrade System should function in my game. I firstly created a graph that will give me an overview of how the Upgrade System works and how it will interact with other systems, see the graph below:

A Flowchart detailing how the upgrade system will work.
Upgrade System Flowchart

From this flowchart, I can discern that the Upgrade System will interact with the droppable spawning system as gears are going to be the main currency for the game, and these needed to be dropped during game time so that the player can pick them up. Also, the Upgrade System will interact with the player controller and game manager as these systems will need to keep track of upgrade level and number of gears collected and spent.

 

Next step is to decide what upgrades will actually go into the Upgrade System for the player to research. For a good upgrade, I had to make sure to answer these questions:

  • Which system should it affect, and what should the effect be?

  • How much should it affect gameplay?

  • How many gears should this upgrade cost?

Once I find an upgrade I want to implement, I added them to an upgrade table like the one below.

Upgrade Type

Effect

Cost

Health Upgrade 1

Increase Health to 125

1000 Gears

Health Upgrade 2

Increase Health to 150

2000 Gears

Health Upgrade 3

Increase Health to 175

3000 Gears

Health Upgrade 4

Increase Health to 200

4000 Gears

Weapon Upgrade 1

Increase Weapon Damage 2

750 Gears

Weapon Upgrade 2

Increase Weapon Damage 3

1500 Gears

Shield Upgrade 1

Increase Shield Duration to 4

600 Gears

Shield Upgrade 2

Increase Shield Duration to 5

1200 Gears

Shield Upgrade 3

Increase Shield Duration to 6

1800 Gears

 

After this my next task was to create a rough mock-up of the Upgrade System user interface. This was important as it would give me a rough idea of what the user interface will look like before making it in game. Below is what I made after an hour or two of sketching in Photoshop.

With the design process and mock-up complete, I was happy to move on to the next stage where I can finally start building the codebase for this Upgrade System. Completing this initial design process is a very important step in building a system as it allows me to properly plan and build a foundation, so when I'm actually coding I'm not going in completely blind.


Coding the First Iteration

Now this is the bit of game development that I love! Building the systems that make up the game is always fun as you get to expand upon your skills and build a fun game!


Creating the Upgrade Screen Unity Scene

For this Upgrade System to work, the first thing I made was the upgrade screen scene. A scene is essentially a level that will hold the User Interface, Managers and anything related to the Upgrade System.

The reason for going with a separate scene instead of attaching it to a canvas within the main menu is so that I can load up the upgrade screen whenever I want. Also by having only one instance of the upgrade screen, it makes maintaining and updating easier as I will only have to update this scene whenever I want to make a change. There is no camera in this scene since all instances of the upgrade screen will be loaded alongside other scenes that already contain a camera.

Creating Enums & Structs

To help with building my upgrade manager, I created enums and structs. Below are the enums and structs that I created for the upgrade manager. Also, by making them public they can be used outside of the upgrade manager.


// The different types of upgrades that the player can research
public enum EUpgradeType 
{
    Health,
    Weapons,
    Shield
}

// The different levels of upgrades that the player can research
public enum EUpgradeLevel
{
    Level_1,
    Level_2,
    Level_3,
    Level_4
}

Using enums is amazing because it makes my code clearer and cleaner. It also makes code much more efficient and lowers the chance of a mistake or issue arising.


public struct Upgrade
{
    // Type of upgrade that this upgrade is
    public EUpgradeType Type  
    { 
        get { return type; } 
    }
    EUpgradeType type;

    // Current Level of this upgrade
    public EUpgradeLevel Level
    {
        get { return level; }    
    } 
    EUpgradeLevel level;
    
    // Cost of this upgrade
    public int UpgradeCost 
    {
        get { return upgradeCost; }    
    }
    int upgradeCost;
    
    // Has this Upgrade been researched
    public bool Researched  
    { 
      set { researched=value; }
      get { returnresearched; }    
    }
    bool researched;

    public Upgrade(EUpgradeType type, EUpgradeLevel level, int cost)            
    {
        this.type=type;
        this.level=level;
        this.upgradeCost=cost;    
        this.researched=false;    
    }

    public static bool operator! (Upgrade upgradeA)    
    {    
        return false;  
    }
}

Making the upgrade a struct was a massive benefit to me as this simple struct contains all the necessary data that an upgrade should have. There's really no point making an upgrade class as the upgrade doesn't need any of the class features, it just needs to hold data. Also, I use properties (which I use throughout my code) to help control accessibility to each data type. For example, the upgrade level shouldn't be changed so there's no point giving any class the option of accidentally changing it.


Building the Upgrade Manager Class

The Upgrade Manager will be a singleton class, which means that there should be only one. More than one will cause issues, as other systems won't know which one to reference. By looking through the code below managed to solve this issue.

    public static UpgradeManager Instance
    {
        get
        {
            return instance;
        }
        set
        {
            if (instance != null)
            {
                Destroy(value.gameObject);
                return;
            }

            instance = value;
        }
    }
    private static UpgradeManager instance;
    
    private void Awake()
    {
        Instance = this;
    }

To hold all of the different upgrades, I created three arrays. By using arrays it allows me to quickly change and update them when needed by getting the correct index.

    /// <summary>
    /// Health Upgrades
    /// </summary>
    private Upgrade[] healthUpgrades = 
    { 
      new Upgrade(EUpgradeType.Health,EUpgradeLevel.Level_1, 1000),                                
      new Upgrade(EUpgradeType.Health, EUpgradeLevel.Level_2, 2000),
      new Upgrade(EUpgradeType.Health, EUpgradeLevel.Level_3, 3000),
      new Upgrade(EUpgradeType.Health, EUpgradeLevel.Level_4, 4000)
    };

    /// <summary>
    /// Weapon Upgrades
    /// </summary>
    private Upgrade[] weaponUpgrades = 
    { 
      new Upgrade(EUpgradeType.Weapons, EUpgradeLevel.Level_1, 500),
      new Upgrade(EUpgradeType.Weapons, EUpgradeLevel.Level_2, 1500),
      new Upgrade(EUpgradeType.Weapons, EUpgradeLevel.Level_3, 2250)
    };

    /// <summary>
    /// Sheild Upgrades
    /// </summary>
    private Upgrade[] shieldUpgrades = 
    { 
      new Upgrade(EUpgradeType.Shield, EUpgradeLevel.Level_1, 600),
      new Upgrade(EUpgradeType.Shield, EUpgradeLevel.Level_2, 1200),
      new Upgrade(EUpgradeType.Shield, EUpgradeLevel.Level_3, 1800)
    };

For this Upgrade System to work, the system needs to do a couple of things which are find the upgrade and set an upgrade to be researched. To meet this functionality, I created two new functions that will deal with this issue.


This function below, has two parameters which are upgrade and index. These parameters are used to search through a specific array and the index is used to get a specific element within the array. From this first iteration, I can already see some issues.

The main issue is that I will have to check if the index is valid, I will have to do this for every single one as there is a possibility of each array having a different size. Luckily I recognised this early on and decided to make some changes, which will be discussed later on.

private Upgrade GetAnUpgrade(int index, EUpgradeType type)
    {
        Upgrade upgradeToReturn = healthUpgrades[0];

        switch (type)
        {
            case EUpgradeType.Health:
                Debug.Log("Health Upgrade Retuned: " +         
                healthUpgrades[index].Level);
                upgradeToReturn = healthUpgrades[index];
                break;
            case EUpgradeType.Shield:
                upgradeToReturn = shieldUpgrades[index];
                break;
            case EUpgradeType.Weapons:
                upgradeToReturn = weaponUpgrades[index];
                break;
        }

        return upgradeToReturn;
    }

This function below is what is called when the player wants to research an upgrade. This function is only called upon once an upgrade button has been pressed. It finds the correct upgrade by calling the previous function, checking if the player can afford it and sets the upgrade to be researched. It will also deactivate the Upgrade Button so the player cannot research the same upgrade twice.

public void SetAnUpgradeToResearched(EUpgradeLevel index, EUpgradeType type, Button btnUsed)
    {
        Upgrade upgrade = GetAnUpgrade((int)index, type);

        if(gameManager.NumberOfGearsCollected >= upgrade.UpgradeCost)
        {
            Debug.Log("Player can afford upgrade");
            upgrade.Researched = true;
            btnUsed.interactable = false;
            return;
        }
        else
        {
            Debug.Log("Player cannot afford upgrade!");
            return;
        }
    }

Refactoring

After implementing my first iteration I spent a day play-testing the Upgrade System so that I can find bugs and see if any improvements can be made. Unsurprisingly after a few hours of testing, already found some major areas where improvement is needed.


Major User Interface Rework

The first area that needed major improvement was the User Interface, below is what the first user interface looked like:

First Iteration Upgrade Screen User Interface

The issues with this implementation are as follows:

  • It's unclear what these upgrades actually do, you know how much they cost but what is the effect on the player?

  • No idea how many gears that the player already has collected

Being perfectly honest, these are pretty major issues that needed sorting and after a few hours I was able to come up with a stronger implementation.

Second Iteration of the Upgrade System User Interface

This is already a massive improvement from the previous iteration. The included player stats clearly show what level the player is at. Moreover, when you hover over an upgrade it will now tell you what it improves. For example you know how much your health will increase before you buy it. Numbers of gears currently collected are shown off as well; making it easier to decide what they need to buy and how much they need to save for the next upgrade.


Improving the code

As mentioned before I already thought there could be many improvements to the code. Here is a list of issues I had with the code for upgrade manager:

  • Was very hard to understand what was going on with the code, which made it hard to maintain and update. For example, making the next button in the upgrade tree intractable was nearly 100 lines of code long while it could easily be just a few lines of code.

  • Lots of spaghetti code, and unneeded function calls and callbacks.

  • Takes a long time to add the new upgrade because I have to add the upgrade, make the button and update each UI element such as text. This could be easily changed to automatically update without having to do this all by hand.

One of the first major changes was refactoring the code for the Upgrade Button so that it be the holder for the upgrade instead. I also made the upgrade button a prefab that can be easily updated and duplicated. The upgrade would automatically update the cost text when you changed the cost.

Moreover, the code for unlocking the next button is much simpler since you just directly reference the button yourself without having to search for it.

   private void SetupButton()
    {

        // Setting up button clicks
        UIButton.onClick.AddListener(() => upgrade = upgradeManager.ResearchAnUpgrade(upgrade));
        UIButton.onClick.AddListener(InteractionUpdate);
        UIButton.onClick.AddListener(() => upgradeStat.ConfirmUpgade(upgradeValue));

        // Updating Button Text
        UI_ButtonText.text = upgradeCost.ToString();

    }

    private void InteractionUpdate()
    {
        UIButton.interactable = upgrade.Unlocked;

        if (upgrade.Researched && nextButton != null)
        {
            nextButton.interactable = true;
        }
    }

This also allowed me to remove the search for upgrade function from the upgrade manager, as it was no longer needed and I can now have a much more simple Research An Upgrade function.


Conclusion

Overall, I am very happy with how the Upgrade System went as I learned a lot about User Experience, User Interface, coding and Game Design. As mentioned at the start this was the first Upgrade System I have designed from scratch for my own game. The framework I have built so far can also be easily updated and changed to whatever I need for the moment especially for my little game. Also, for future projects the code is something I can easily re use or expand upon if I want to make a more complex Upgrade System either for my own personal project or work.


Lastly, thank you for taking the time to read through this small blog post! I hope you've really enjoyed reading and cannot wait to show you more of my personal project.




43 views0 comments

Comments


2021 Adam Tedder 

  • Twitter
  • LinkedIn
  • YouTube
  • Grey Instagram Icon
bottom of page
Mastodon