public class AbilitiesUpgradePanel : MonoBehaviour
{
[SerializeField] Button[] buttons = new Button[3];
[SerializeField] Text[] abilityNameText = new Text[3];
[SerializeField] Text[] abilityDescriptionText = new Text[3];
[SerializeField] Text[] abilityLevelText = new Text[3];
[SerializeField] Image[] abilityImage = new Image[3];
private int totalAbilitiesCount = 3;
public void RandomAbilitesToLevelUp()
{
List<int> generatedIds = new List<int>(); //I need each button to have unique ability
for (int buttonIndex = 0; buttonIndex < totalAbilitiesCount; buttonIndex++)
{
int abilityId = Random.Range(0, totalAbilitiesCount);
while (generatedIds.Contains(abilityId))
{
abilityId = Random.Range(0, totalAbilitiesCount);
}
if (abilityId == 0)
{
buttons[buttonIndex].onClick.RemoveAllListeners();
buttons[buttonIndex].onClick.AddListener(UpgradeBullet);
abilityNameText[buttonIndex].text = Bullet.instance.abilityName;
abilityLevelText[buttonIndex].text = Bullet.instance.abilityLevel.ToString();
abilityDescriptionText[buttonIndex].text = Bullet.instance.abilityUpDescription;
abilityImage[buttonIndex].sprite = Bullet.instance.abilitySprite;
}
else if (abilityId == 1)
{
buttons[buttonIndex].onClick.RemoveAllListeners();
buttons[buttonIndex].onClick.AddListener(UpgradeLighting);
abilityNameText[buttonIndex].text = Lightning.instance.abilityName;
abilityLevelText[buttonIndex].text = Lightning.instance.abilityLevel.ToString();
abilityDescriptionText[buttonIndex].text = Lightning.instance.abilityUpDescription;
abilityImage[buttonIndex].sprite = Lightning.instance.abilitySprite;
}
else if (abilityId == 2)
{
buttons[buttonIndex].onClick.RemoveAllListeners();
buttons[buttonIndex].onClick.AddListener(UpgradeSatellite);
abilityNameText[buttonIndex].text = Satellite.instance.abilityName;
abilityLevelText[buttonIndex].text = Satellite.instance.abilityLevel.ToString();
abilityDescriptionText[buttonIndex].text = Satellite.instance.abilityUpDescription;
abilityImage[buttonIndex].sprite = Satellite.instance.abilitySprite;
}
generatedIds.Add(abilityId);
}
}
// Abilities (Bullet, Lightning, Satellite)
//BULLET
public class Bullet : MonoBehaviour
{
public static Bullet instance;
[SerializeField] GameObject bulletPrefab;
public string abilityName = "Bullet";
public string abilityUpDescription = "Default Projectile Shooting";
public int abilityId = 0;
public Sprite abilitySprite;
private Enemy closestEnemy;
private GameObject instantiatedBullet;
public float damage = 15f;
public float cooldown = .7f;
public float castDistance = 18f;
public float projectileSpeed = 4f;
public int abilityLevel = 0;
Rigidbody2D rb;
Vector2 moveDirection;
private void Awake()
{
instance = this;
}
void Start()
{
StartCoroutine(CastBullet());
}
//SATELLITE
public class Satellite : MonoBehaviour
{
public static Satellite instance;
[SerializeField] GameObject satellitePrefab;
[SerializeField] GameObject satellitesHolder;
public string abilityName = "Satellite";
public string abilityUpDescription = "Creates a sattelite flying around you";
public int abilityId = 2;
public Sprite abilitySprite;
Vector3 center;
public float damage = 20f;
public float rotationSpeed = 100;
public float distanceFromPlayer = 1;
public int maxSatellites = 1;
public int currentSatellites = 0;
public int abilityLevel = 0;
private void Awake()
{
instance = this;
}
private void Start()
{
satellitesHolder.SetActive(true);
SpawnSatellites();
}
//LIGHTNING
public static Lightning instance;
[SerializeField] GameObject lightningPrefab;
public string abilityName = "Lightning";
public string abilityUpDescription = "Creates a lightning strike";
public int abilityId = 1;
public Sprite abilitySprite;
private GameObject instantiatedLightning;
int instantiadetLightningsCount;
public float castDistance = 7f;
public float damage = 20f;
public float cooldown = 1f;
public int maxTargets = 2;
public int abilityLevel = 0;
private void Awake()
{
instance = this;
}
private void Start()
{
StartCoroutine(CastLightning());
}
Seeking a more elegant solution to set buttons so they can increase specific ability level and stats
-
1\$\begingroup\$ What specifically do you think is "bad" about this and what should answers focus on improving? \$\endgroup\$– DMGregory ♦Oct 10, 2021 at 22:15
-
\$\begingroup\$ I'm not sure if this solution is proper, maybe there is a way to set buttons differently, in a proper way, or this code is fine already, that's what I'm asking \$\endgroup\$– tevseOct 11, 2021 at 4:59
-
2\$\begingroup\$ Can you define "proper"? What is it about this code that feels "improper" to you? The more clearly you can articulate your concern, the better we can target answers to help with that aspect. \$\endgroup\$– DMGregory ♦Oct 11, 2021 at 5:14
-
\$\begingroup\$ " if (abilityId == 0), 1, 2, etc." looks improper for me, I thought to create a class "Abilities" then each specific ability would inherit "Abilities" class so I could create "List<Abilities> allAbilities". This way I could make so "abilityNameText[buttonIndex].text = allAbilities[abilityId].abilityName". But I don't know how to add specific Listener to a button, and I'm not sure if this solution is any better than one above. In the end, I want to create about 20+ different abilities, and this solution contains a lot of repeating code, that's why I'm not sure if it's proper. \$\endgroup\$– tevseOct 11, 2021 at 5:58
-
1\$\begingroup\$ Can you tell us what these instances are (Bullet, Lightning, Satellite)? It seems like they share many of the same properties -- can they derive from a base class so you don't need to refer to them by singleton? \$\endgroup\$– Acme Nerd GamesOct 11, 2021 at 10:13
1 Answer
they can't derive from a base class. Yes, they share many of the same properties but they also share many unique properites. – xmaximix 1
Actually I think you can. This a pattern I use all the time for stuff like this and it works very well.
First, I suggest your abilities derive not from Monobehaviour, but from ScriptableObject. They won't recieve events, so you'll have to handle that some other way, (if at all -- do you actually ever need to instantiate an "ability"?*), but it does make for a nice neat way to create related entities that share many of the same properties and methods.
So -- base Ability class:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class Ability : ScriptableObject
{
public enum Type
{
bullet,
satellite,
lightning
}
public Type type; // or use get; set;
public string abilityName = "Lightning";
public string abilityUpDescription = "Creates a lightning strike";
public int abilityId = 1;
public int abilityLevel = 0;
public Sprite abilitySprite;
public virtual void Start()
{
Debug.Log("Ability started");
}
public virtual void CreateAbilityButton(Button button)
{
// If button gameobject are configured identically can simply get child components using Find
button.onClick.RemoveAllListeners();
button.transform.Find("Ability Name").GetComponent<Text>().text = abilityName;
button.transform.Find("Ability Level").GetComponent<Text>().text = abilityLevel.ToString();
button.transform.Find("Ability Description").GetComponent<Text>().text = abilityUpDescription;
button.transform.Find("Ability Sprite").GetComponent<Image>().sprite = abilitySprite;
}
}
And a derived Bullet class:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
[CreateAssetMenu (menuName = "Abilities/ Bullet")]
public class Bullet : Ability
{
[SerializeField] GameObject bulletPrefab;
// private Enemy closestEnemy;
private GameObject instantiatedBullet;
// extra properties for bullet
public float damage = 15f;
public float cooldown = .7f;
public float castDistance = 18f;
public float projectileSpeed = 4f;
Rigidbody2D rb;
Vector2 moveDirection;
public override void CreateAbilityButton(Button button)
{
base.CreateAbilityButton(button);
button.onClick.AddListener(() => { DoSomeGreatAction(); });
}
public void DoSomeGreatAction()
{
Debug.Log("Hello from Bullet");
}
public override void Start()
{
// do some initialization
}
}
lightning...
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
[CreateAssetMenu(menuName = "Abilities/ Lightning")]
public class Lightning : Ability
{
public static Lightning instance;
[SerializeField] GameObject lightningPrefab;
private GameObject instantiatedLightning;
int instantiadetLightningsCount;
public float castDistance = 7f;
public float damage = 20f;
public float cooldown = 1f;
public int maxTargets = 2;
public override void CreateAbilityButton(Button button)
{
base.CreateAbilityButton(button);
button.onClick.AddListener(() => { DoSomeCoolLightningButtonAction(); });
}
public void DoSomeCoolLightningButtonAction()
{
Debug.Log("Lightning is lit, yo");
}
public override void Start()
{
// do whatever you want on start
}
}
Etc.
And now the good part -- code to create the buttons is really simplified:
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class AbilitiesUpgradePanel : MonoBehaviour
{
[SerializeField] Button[] buttons;
[SerializeField] public List<Ability> abilities; // a list containing all your ability prototype
private void Start()
{
RandomAbilitesToLevelUp();
}
public void RandomAbilitesToLevelUp()
{
List<Ability> threeUniqueAbilties = new List<Ability>();
int buttonCount = 0;
while (threeUniqueAbilties.Count < buttons.Length) // magic number
{
Ability proposed = abilities[Random.Range(0, abilities.Count)];
if (!threeUniqueAbilties.Contains(proposed))
{
threeUniqueAbilties.Add(proposed);
proposed.CreateAbilityButton(buttons[buttonCount]);
buttonCount++;
}
}
}
}
Making these scriptable objects means you can easily make many variants of each ability type -- each with whatever custom variables and methods you need to support that type.
*if you do need to instantiate an "ability" for some reason, you can create a sister runtime class, for which the scriptable object acts as an immutable.