0
\$\begingroup\$

I'm trying to make my first game in Unity and I decided to have a go at making a 3D tower defence. I've created a UI Panel with 4 buttons used to upgrade the turrets. When the player selects a turret the panel appears and the button text and images are changed to match the type of turret selected. The buttons all call one function 'UpgradeButtonClick(int upgradeIndex)' which applies the upgrade to the selected turret.

In order to reuse a single panel and one set of buttons I created a class called 'Turret' which will be attached to every type of turret. This class stores the cost, materials and upgrades for the turret as well as a reference to the attack script for the turret. This was fine until I introduced a second type of turret (I.e. lazer turrets and fire turrets). Originally I had:

public LazerTurretShooting attackScript;

But that will obviously not work for the fire turret. Ideally I would like to test the type of turret and then declare the correct variable type. But I can't as the variable would then only be accessible where it is declared and not to the whole class.

The attackScript variable is used near the bottom of the UpgradeButtonClick function to modify the turrets damageMultiplier and apply the upgradeEffect. See below:

public class TurretManager : MonoBehaviour {

/* Removed unnecessary functions */

public void UpgradeButtonClick(int upgradeIndex)
{

    Turret turret = selectedTurret.GetComponentInParent<Turret>(); //Get the Turret script from the selected turret

    if (turret.turretUpgrades.Length > upgradeIndex) //Make sure there is an upgrade with the selected index
    {

        if (!turret.turretUpgrades[upgradeIndex].applied) //Check if this upgrade has already been applied
        {
            bool applyUpgrade = false;
            int prerequisiteIndex;

            //Check if the upgrade requires a previous upgrade to be applied first
            prerequisiteIndex = turret.turretUpgrades[upgradeIndex].requiredUpgrade; //Store the index of the required upgrade/prerequisite
            if (prerequisiteIndex > -1)
            {

                if (turret.turretUpgrades[prerequisiteIndex].applied) //If the required upgrade/prerequisite has already been applied
                { //Allow the upgrade
                    applyUpgrade = true;
                } else
                { //Don't allow the upgrade
                    applyUpgrade = false;
                }

            } else
            { //There was no prerequisite so allow the upgrade
                applyUpgrade = true;
            }

            if (applyUpgrade)
            {
                if (ScoreManager.Instance.Money > turret.turretUpgrades[upgradeIndex].upgradeCost) //Check if the player has enough money to buy the upgrade
                {
                    turret.attackScript.damageMultiplier += turret.turretUpgrades[upgradeIndex].damageMultiplier; //Apply the new damage multiplyer
                    turret.turretUpgrades[upgradeIndex].applied = true; //Set the applied flag so this upgrade cannot be applied twice
                    UpdateUpgradePanelButtons(); //Update the buttons to enable buttons for any upgrades which require this upgrade as a prerequisite

                    ScoreManager.Instance.Money -= turret.turretUpgrades[upgradeIndex].upgradeCost; //Charge the player for the upgrade

                    if (turret.turretUpgrades[upgradeIndex].applyUpgradeEffect) //If the upgrade enables the turrets additional effect, turn it on
                    {
                        turret.attackScript.applyUpgradeEffect = true;
                    }

                }

            }

        }

    }

}

}

Here is the full Turret class:

[System.Serializable]
public struct TurretUpgrade
{
    [Tooltip ("The damage multiplier is increased additively. So to increase damage by 20% enter 0.2 or to decrease enter -0.2")]
    public float damageMultiplier;
    [Tooltip("Tells the turret to play its additional effect (E.g. Dual Lazers on a lazer turret)")]
    public bool applyUpgradeEffect;
    [Tooltip ("Index of upgrade required before this upgrade can be applied. (-1 = none required)")]
    public int requiredUpgrade;
    [HideInInspector]
    public bool applied; //Has the upgrade been applied to this turret
    [Tooltip ("Text to display under the upgrade button (E.g. '10% Damage Increase')")]
    public string upgradeButtonText;
    [Tooltip ("The cost of the upgrade")]
    public int upgradeCost;
    [Tooltip ("A Sprite image for the upgrade button")]
    public Sprite buttonSprite;

}

public class Turret : MonoBehaviour {
    public Material opaqueMaterial;
    public Material translucentMaterial;
    public int cost;
    public LazerTurretShooting attackScript; //This variable would need to store different types

    public TurretUpgrade[] turretUpgrades = new TurretUpgrade[4];

    void Awake()
    {
        //I need to get the correct type of script for this turret
        attackScript = GetComponent<LazerTurretShooting>();
        //attackScript = GetComponent<FireTurretShooting>();
        //attackScript = GetComponent<etc>();
    }
}

What would be the best way to approach this? If I create separate 'Turret' classes (E.g. LazerTurret and FireTurret) the function which sets the text and images of the upgrade buttons wouldn't work because it usees:

Turret turret = selectedTurret.GetComponentInParent<Turret>();
for (...) //Loops all 4 buttons
textLabels[0].text = turret.turretUpgrades[i].upgradeButtonText;
//etc

I can provide this full function if its needed.

\$\endgroup\$
3
\$\begingroup\$

Like @PompeyPaul says, Polymorphism is key. To expand on the idea, here is another possible design for a Turret class:

public abstract class Turret : Monobehavior {
    ...
    public abstract void Attack();
    public abstract void Upgrade();
}

Assuming that everything besides how the turrets attack and are upgraded is the same for every type, this would expose a common interface for all turrets. Now you could make different specific turret classes:

public class LaserTurret : Turret {
    public int statSpecificToLasers = 5;
    public void Attack() {
        //do something cool with a laser
    }
    public void Upgrade() {
        //Apply changes to statSpecificToLasers
    }
}

public class FireTurret : Turret {
    public int statSpecificToFire = 12;
    public void Attack() {
        //do something cool with fire
    }
    public void Upgrade() {
        //Apply changes to statSpecificToFire
    }
}

Because the Turret class has virtual methods, you can't really have a concrete Turret, so it cannot be constructed, and you wouldn't attach it to any GamEObjects, but since both LaserTurret and FireTurret inherit from Turret, both can be treated as if they were Turrets. Now, in your TurretManager, selectedTurret can still be declared as a Turret type, but you can pass in either a FireTurret or a LaserTurret. Which Attack() or Upgrade() method gets called depends on which type you pass.

Turret selectedTurret = ob1.GetComponent<FireTurret>();
selectedTurret.Attack(); //shoots fire
selectedTurret = ob2.GetComponent<LaserTurret>();
selectedTurret.Attack(); //shoots a laser

//You can even put them in the same collection
List<Turret> turrets = new List<Turret>();
turrets.Add(ob1.GetComponent<FireTurret>());
turrets.Add(ob2.GetComponent<LaserTurret>());
| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Thank you for the detailed answer. I ended up creating 'public bool applyUpgradeEffect' and 'public abstract float damageMultiplier' in the Turret class. I then made an override for the damageMultiplier in my LazerTurret class which means I can now reference any turret with 'GetComponent<Turret>()' and then use turret.damageMultiplier. So there is no need for the attackScript variable any more even though damageMultiplier is implemented in the 'LazerTurret' child class. It's made it much easier to use and to implement new turrets, thank you. \$\endgroup\$ – Daniel Oct 13 '16 at 21:09
  • \$\begingroup\$ @Daniel, After your comment, I realized I got my languages a bit mixed up and should have used "abstract" instead of "virtual." I updated my answer accordingly, but it is good to hear it works. \$\endgroup\$ – tyjkenn Oct 14 '16 at 14:59
3
\$\begingroup\$

It sounds like you need polymorphism in your class design. I would start with a base turret, as you have, which has all of the base functionality. It also has virtual functions for accessing parts of it.

You then have an TurretAttackBase script which has the common attack base functionality. You then derive cannon attack, laser attack, etc from that.

You then create a turret prefab which has the type of turret, and attach the TurretAttackLaser or whatever to it.

Your script then calls the virtual functions to get the TurretAttack and calls request upgrade.

That way you have one consistent interface but with different derived turrets, attack types etc all handling correctly.

So for your turrent you would call

turret.attackScript.ApplyUpgradeEffect();

The turret would then get the attack script, call ApplyUpgradeEffect() and it will be called on either the TurretAttackLaser or TurretAttackCannon class depending on which type it actually is.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Thank you very much for your answer. Although your answer was very useful I chose to accept tyjkenn's answer as the examples made it easier to implement. \$\endgroup\$ – Daniel Oct 13 '16 at 21:13

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.