How to Create a Solid Technology Tree for Strategy Games

Have you ever wanted to create a technology tree for your strategy games?

If you are into game development and have some experience in C# and Unity Editor, then you are at the right place. The tutorial revolves around creating a ‘Technology’ class for a strategy game’s technology tree. This class will keep track of several aspects such as its state – whether it’s locked, unlocked, under research, or already researched – name, description, image, cost, and requirements. Strap in as we walk you through this step by step, but remember to download the project files before we start!

Project Files

The project files related to this tutorial can be downloaded from the link below. These consist of all the code used in the tutorial, making it easier for you to follow along.

Download Project Files Here

CTA Small Image
FREE COURSES AT ZENVA
LEARN GAME DEVELOPMENT, PYTHON AND MORE
ACCESS FOR FREE
AVAILABLE FOR A LIMITED TIME ONLY

The Technology Class

The Technology class for a technology tree for a strategy game has to keep track of the following things:

  • state: is the technology locked, unlocked, being researched or already researched?
  • name: the name of the technology
  • description: a description of how the technology affects gameplay
  • image: an image to represent the technology in a technology tree
  • cost: how much it costs to research this technology
  • requirements: any resources (gold, iron, wood, etc.) or other technologies (e.g., Mining) that this technology depends upon

To begin, create a new Unity 3D project. Name it Technology Tree or something else you prefer.

Create a new Scripts folder in your Assets folder.

Inside the Scripts folder, create a new C# script called Technology. (The .cs extension will be added automatically).

Open the new script up in Visual Studio.

Get ready to do this often: remove the default methods Start() and Update(). Also, remove the inheritance from the MonoBehaviour class. The shell of your new class should look like this:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Technology 
{

}

Now create an enum to represent all possible availability states of the technology and also create a variable to hold the current state of the technology:

public enum AvailabilityState { Locked, Unlocked, Researching, Learned};
public AvailabilityState availabilityState;

Moving on, create three public variables that will hold the name, description and image for the technology:

public string techName;
public string techDescription;
public Sprite techImage;

In the same file, after the closing brace of the Technology class, add a new ResourceAmount class. This will represent a type and an amount, for example, 5 gold or 3 iron. These will be used as requirements for a technology:

public class ResourceAmount
{
    public string resourceName;
    public int resourceAmount;
}

In the Technology class, add a generic list to hold the resource requirements for the technology. Your file should now look like this:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Technology 
{
    public enum AvailabilityState { Locked, Unlocked, Researching, Learned};
    public AvailabilityState availabilityState;
    public string techName;
    public string techDescription;
    public Sprite techImage;
    public List<ResourceAmount> resourceCosts;
}

public class ResourceAmount
{
    public string resourceName;
    public int resourceAmount;
}

At the bottom of the file, add a new class, RequiredTech:

public class RequiredTech 
{
    public string techName;
    public bool completed;
}

Add a generic list to the Technology class to hold any technology requirements. Your file should now look like this:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Technology 
{
    public enum AvailabilityState { Locked, Unlocked, Researching, Learned};
    public AvailabilityState availabilityState;
    public string techName;
    public string techDescription;
    public Sprite techImage;
    public List<ResourceAmount> resourceCosts;
    public List<RequiredTech> techRequirements;
}

public class ResourceAmount
{
    public string resourceName;
    public int resourceAmount;
}

public class RequiredTech 
{
    public string techName;
    public bool completed;
}

Unlocking and Learning Technologies

Next we will add some methods to the Technology script that will help us track technologies as they are unlocked and learned. These methods will not be immediately useful but we will hook them into an event system later so we can use them in our technology tree.

To begin this lesson, add an Unlock() method to the Technology class that sets the availability state to unlocked and prints a debug message:

public void Unlock()
{
    availabilityState = AvailabilityState.Unlocked;
    Debug.Log("Tech was unlocked: " + this.techName);
}

Add a similar method below that, that sets the availability state to Learned:

public void Learn()
{
    availabilityState = AvailabilityState.Learned;
    Debug.Log("Tech was learned: " + this.techName);
}

Next, we’re going to need a public variable to store how many turns it takes to research the technology. We’re also going to need a private variable to track how many turns the technology has been researched. Add these fields:

private int researchedTurns;
public int requiredResearchTurns;

The technology class needs to be aware of turns passing while it is in the research stage. To do this, add a public method NewTurn() to be called every new turn. If the technology is being researched, track how many turns it has been researched. If the number of turns equals or exceeds the required number of turns, set the availability state to learned:

public void NewTurn()
{
    if (availabilityState == AvailabilityState.Researching)
    {
        researchedTurns++;
        if(researchedTurns >= requiredResearchTurns)
        {
            Learn();
        }
    }
}

Technology tree

Comparing Technology Unlock Requirements

In this lesson we will continue to use your Technology script so open it up in Visual Studio. We will now start focusing on how we can compare things to unlock elements in our technology tree.

Begin by importing the Linq library for working with collections. We do this with a using statement:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;

Next, add a private CheckRequirements() method. This method will be called by the event system every time a new technology is earned. If the current tech state is locked, this method will go through all the tech’s requirements to see if any tech requirement is not yet completed. If all tech requirements are completed, the technology state is set to unlocked.

The CheckRequirements() method will make use of some functionality from the Linq library to filter the techRequirements collection. It will also make use of lambda expressions, which can be thought of as anonymous (unnamed/undeclared) methods. The lambda operator is written as: =>. The name of the input (which can be anything you like) goes on the left side of the operator and the function to perform on the right.

private void CheckRequirements(Technology tech)
{
    // only do the check if this tech is locked
    if(availabilityState == AvailabilityState.Locked)
    {
        /* go through the techRequirements list and see if the newly available tech (the parameter
         * that was passed into this function) was one of this tech's requirements.
         */             
        RequiredTech requireTech = techRequirements.FirstOrDefault(rt=>rt.techName == tech.techName);
        // if there is a requiredTech and it is not complete, perform these steps
        if(requireTech != null && !requireTech.completed)
        {
            requireTech.completed = true; // mark the requirement as complete
            // check if all requirements are complete...
            if(!techRequirements.Any(t=>t.completed == false))
            {
                // ...if so, this tech is now unlocked and available
                Unlock();
            }
        }
    }
}

Mark the two auxiliary classes with the System.Serializable attribute to make them editable in the editor:

[System.Serializable]
public class ResourceAmount
{
    public string resourceName;
    public int resourceAmount;
}

[System.Serializable]
public class RequiredTech 
{
    public string techName;
    public bool completed;
}

Your Technology script should now look like this:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;

public class Technology 
{
    public enum AvailabilityState { Locked, Unlocked, Researching, Learned};
    public AvailabilityState availabilityState;
    public string techName;
    public string techDescription;
    public Sprite techImage;
    public List<ResourceAmount> resourceCosts;
    public List<RequiredTech> techRequirements;
    private int researchedTurns;
    public int requiredResearchTurns;

    public void Unlock()
    {
        availabilityState = AvailabilityState.Unlocked;
        Debug.Log("Tech was unlocked: " + this.techName);
    }

    public void Learn()
    {
        availabilityState = AvailabilityState.Learned;
        Debug.Log("Tech was learned: " + this.techName);
    }

    public void NewTurn()
    {
        if (availabilityState == AvailabilityState.Researching)
        {
            researchedTurns++;
            if(researchedTurns >= requiredResearchTurns)
            {
                Learn();
            }
        }
    }

    private void CheckRequirements(Technology tech)
    {
        // only do the check if this tech is locked
        if(availabilityState == AvailabilityState.Locked)
        {
            /* go through the techRequirements list and see if the newly available tech (the parameter
             * that was passed into this function) was one of this tech's requirements.
             */             
            RequiredTech requireTech = techRequirements.FirstOrDefault(rt=>rt.techName == tech.techName);
            // if there is a requiredTech and it is not complete, perform these steps
            if(requireTech != null && !requireTech.completed)
            {
                requireTech.completed = true; // mark the requirement as complete
                // check if all requirements are complete...
                if(!techRequirements.Any(t=>t.completed == false))
                {
                    // ...if so, this tech is now unlocked and available
                    Unlock();
                }
            }
        }
    }
}

[System.Serializable]
public class ResourceAmount
{
    public string resourceName;
    public int resourceAmount;
}

[System.Serializable]
public class RequiredTech 
{
    public string techName;
    public bool completed;
}

Technology Tree Events

Technology events are the driving force behind the tech tree. Different parts of the game (e.g., UI) can listen for different kinds of events and respond accordingly.

We will have events for unlocking technology, starting and completing research, and for passing turns.

Events will be created with the simple built-in C# generic event system Action<T>

Begin by creating a new C# script TechEvents in your Scripts folder. Open up the script in Visual Studio.

Add a new using line at the bottom of the using section to import the System namespace:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;

Delete the default Start() and Update() methods.

Create four action events for each of the game events we would like to respond to. Your script should now look like this:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;

public class TechEvents : MonoBehaviour
{
    public static Action<Technology> OnTechResearchStarted;
    public static Action<Technology> OnTechResearchCompleted;
    public static Action<Technology> OnTechUnlocked;
    public static Action OnTurnPassed;  // takes no parameter passed in

}

Add four methods that correspond to the four events. The game will call these methods to fire off the events. Add a null check to be sure you do not try to Invoke() on a null Action:

public static void TechResearchStarted(Technology tech)
{
    // note the use of the null check operator (?) below
    // this translates into: "if this object is not null, call this method on it"
    OnTechResearchStarted?.Invoke(tech);
}

public static void TechResearchCompleted(Technology tech)
{
    // note the use of the null check operator (?) below
    // this translates into: "if this object is not null, call this method on it"
    OnTechResearchCompleted?.Invoke(tech);
}

public static void TechUnlocked(Technology tech)
{
    // note the use of the null check operator (?) below
    // this translates into: "if this object is not null, call this method on it"
    OnTechUnlocked?.Invoke(tech);
}

public static void TurnPassed(Technology tech)
{
    // note the use of the null check operator (?) below
    // this translates into: "if this object is not null, call this method on it"
    OnTurnPassed?.Invoke();
}

In the Technology.cs script for your technology tree, add a default constructor after the using statements. The constructor checks if the technology is locked then listen for techResearchCompleted events and call CheckRequirements() if that kind of event is received:

public Technology()
{
    if(availabilityState == AvailabilityState.Locked)
    {
        TechEvents.OnTechResearchCompleted += CheckRequirements;
    }
}

Modify the Learn() method to fire off a completed event when this technology is learned:

public void Learn()
{
    TechEvents.TechResearchCompleted(this);  // add this line
    availabilityState = AvailabilityState.Learned;
    Debug.Log("Tech was learned: " + this.techName);
}

Modify the StartResearching() method. Add lines to fire off the research started event and also to start listening for turns to go by. Here is an excerpt of the method. The lines to add are commented:

    if(resourceAmounts.Count >= resourceCosts.Count)
    {
        hasEnoughResources = true;
        for (int i = 0; i < resourceCosts.Count; i++)
        {
            // go ahead and subtract all the resource costs from the player...
            resourceAmounts[i].resourceAmount -= resourceCosts[i].resourceAmount;
        }
        availabilityState = AvailabilityState.Researching;
        // fire off a TechResearchStarted event, specifying which kind of tech...
        TechEvents.TechResearchStarted(this);  // add this line
        // start listening for turns to pass by subscribing to turn event. 
        // Have NewTurn() method handle the events...
        TechEvents.OnTurnPassed += NewTurn; // add this line     
    }

Modify the Unlock() method so that the technology stops listening for research completed events:

public void Unlock()
{
    availabilityState = AvailabilityState.Unlocked;
    TechEvents.OnTechResearchCompleted -= CheckRequirements; // add this line
    Debug.Log("Tech was unlocked: " + this.techName);
}

Modify the Learn() method so that the technology stops listening for turns to pass:

public void Learn()
{
    TechEvents.TechResearchCompleted(this);  
    // stop listening for turns passing...
    TechEvents.OnTurnPassed -= NewTurn; // add this line
    availabilityState = AvailabilityState.Learned;
    Debug.Log("Tech was learned: " + this.techName);
}

Your Technology.cs script for your technology tree should now look like the below:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;

public class Technology 
{
    public enum AvailabilityState { Locked, Unlocked, Researching, Learned};
    public AvailabilityState availabilityState;
    public string techName;
    public string techDescription;
    public Sprite techImage;
    public List<ResourceAmount> resourceCosts;
    public List<RequiredTech> techRequirements;
    private int researchedTurns;
    public int requiredResearchTurns;

    public Technology()
    {
        if(availabilityState == AvailabilityState.Locked)
        {
            TechEvents.OnTechResearchCompleted += CheckRequirements;
        }
    }

    public void Unlock()
    {
        availabilityState = AvailabilityState.Unlocked;
        TechEvents.OnTechResearchCompleted -= CheckRequirements; 
        Debug.Log("Tech was unlocked: " + this.techName);
    }

    public void Learn()
    {
        TechEvents.TechResearchCompleted(this);  
        // stop listening for turns passing...
        TechEvents.OnTurnPassed -= NewTurn; // add this line
        availabilityState = AvailabilityState.Learned;
        Debug.Log("Tech was learned: " + this.techName);
    }

    public void NewTurn()
    {
        if (availabilityState == AvailabilityState.Researching)
        {
            researchedTurns++;
            if(researchedTurns >= requiredResearchTurns)
            {
                Learn();
            }
        }
    }

    private void CheckRequirements(Technology tech)
    {
        // only do the check if this tech is locked
        if(availabilityState == AvailabilityState.Locked)
        {
            /* go through the techRequirements list and see if the newly available tech (the parameter
             * that was passed into this function) was one of this tech's requirements.
             */             
            RequiredTech requireTech = techRequirements.FirstOrDefault(rt=>rt.techName == tech.techName);
            // if there is a requiredTech and it is not complete, perform these steps
            if(requireTech != null && !requireTech.completed)
            {
                requireTech.completed = true; // mark the requirement as complete
                // check if all requirements are complete...
                if(!techRequirements.Any(t=>t.completed == false))
                {
                    // ...if so, this tech is now unlocked and available
                    Unlock();
                }
            }
        }
    }

    public bool StartResearching()
    {
        bool hasEnoughResources = false;
        // keep track of how many resources you have the required amount of...
        List<ResourceAmount> resourceAmounts = new List<ResourceAmount>();

        // loop through each of the required resources
        for (int i = 0; i < resourceCosts.Count; i++)
        {
            // find out how much of a type (wood, etc.) you currently have
            ResourceAmount currentAmount = Resources.Instance.GetResourceAmount(resourceCosts[i].resourceType);
            // find out how much is required. Cache the cost
            ResourceAmount cost = resourceCosts[i];
            // only add to list if you have enough of resource type...
            if (currentAmount.resourceAmount >= cost.resourceAmount)
            {
                resourceAmounts.Add(currentAmount);  
            }
        }
        // if the number of resources you can fulfill is equal to the number
        // required by the technology, then you can start researching
        if(resourceAmounts.Count >= resourceCosts.Count)
        {
            hasEnoughResources = true;
            for (int i = 0; i < resourceCosts.Count; i++)
            {
                // go ahead and subtract all the resource costs from the player...
                resourceAmounts[i].resourceAmount -= resourceCosts[i].resourceAmount;
            }
            availabilityState = AvailabilityState.Researching;
            // fire off a TechResearchStarted event, specifying which kind of tech...
            TechEvents.TechResearchStarted(this);  
            // start listening for turns to pass by subscribing to turn event. 
            // Have NewTurn() method handle the events...
            TechEvents.OnTurnPassed += NewTurn;     
        }
        return hasEnoughResources;
    }
}



[System.Serializable]
public class RequiredTech 
{
    public string techName;
    public bool completed;
}

And your TechEvents.cs script should now look like this:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;

public class TechEvents : MonoBehaviour
{
    public static Action<Technology> OnTechResearchStarted;
    public static Action<Technology> OnTechResearchCompleted;
    public static Action<Technology> OnTechUnlocked;
    public static Action OnTurnPassed;

    public static void TechResearchStarted(Technology tech)
    {
        // note the use of the null check operator (?) below
        // this translates into: "if this object is not null, call this method on it"
        OnTechResearchStarted?.Invoke(tech);
    }

    public static void TechResearchCompleted(Technology tech)
    {
        // note the use of the null check operator (?) below
        // this translates into: "if this object is not null, call this method on it"
        OnTechResearchCompleted?.Invoke(tech);
    }

    public static void TechUnlocked(Technology tech)
    {
        // note the use of the null check operator (?) below
        // this translates into: "if this object is not null, call this method on it"
        OnTechUnlocked?.Invoke(tech);
    }

    public static void TurnPassed(Technology tech)
    {
        // note the use of the null check operator (?) below
        // this translates into: "if this object is not null, call this method on it"
        OnTurnPassed?.Invoke();
    }
}

Conclusion

And there you have it! By following this tutorial, you’ve built a Technology class for a strategy game’s technology tree. You’ve implemented multiple states for each technology, monitored requirements, made available technologies, and even tracked research progress turn-by-turn. But, remember this is just a beginning, you can experiment with the structure, add more functionalities, or create your technology tree. Overall, we hope that this tutorial has enriched your game development skills and experience. Keep experimenting and creating!

Want to learn more? Try our complete DEVELOP A TECHNOLOGY TREE FOR STRATEGY GAMES course.

Did you come across any errors in this tutorial? Please let us know by completing this form and we’ll look into it!

FREE COURSES
Python Blog Image

FINAL DAYS: Unlock coding courses in Unity, Godot, Unreal, Python and more.