Tuesday, March 26, 2019

unity - Can't use one variable to store different data types. What's a better solution?



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(); //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();
//attackScript = GetComponent();
//attackScript = GetComponent();
}
}

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();
for (...) //Loops all 4 buttons
textLabels[0].text = turret.turretUpgrades[i].upgradeButtonText;

//etc

I can provide this full function if its needed.



Answer



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();
selectedTurret.Attack(); //shoots fire
selectedTurret = ob2.GetComponent();
selectedTurret.Attack(); //shoots a laser

//You can even put them in the same collection
List turrets = new List();

turrets.Add(ob1.GetComponent());
turrets.Add(ob2.GetComponent());

No comments:

Post a Comment

Simple past, Present perfect Past perfect

Can you tell me which form of the following sentences is the correct one please? Imagine two friends discussing the gym... I was in a good s...