Need Help Structuring my FF-style Cooldown Based Battle System To Be Efficient

So I’m halfway into creating my battle system which has a 3-on-3, cooldown-based system, when I realized it’s super inefficient. I currently have each character taking in values from the opposing enemy and calculating its damage output based on attack, defense, status, etc., and transferring that output to a separate health display for that enemy. Basically each character has to know what the enemy it’s currently attacking is like, and calculates everything itself.

I think what would make more sense is to have a singular battle system script that does all the calculations. And whenever a character attacks, they simply input 3 variables into the battle system, 1. What it’s doing, 2. Target enemy 3. Which character is doing the action

My issue moving forward is I’m unsure if this is the most efficient route or not. This method would require all 6 character’s info being initialized in the battle system at Start, and then the battle system keeps track of all the character’s health, buffs, debuffs, position, etc.

Or would it make more sense to have the character’s health/debuffs/buffs contained in the character, and then the character simply passes it’s own attributes/statuses as a gameobject to that central battle system?

Thanks!

If you’re unsure about efficiency, figure out some ways to test it. As a first thought, you can use the Stopwatch class, as outlined in this SO question. As some general tips for working with Unity to write “efficient” code, there are some calls that are notoriously expensive and can be damaging to performance. Examples of these might be GetComponent() or GameObject.Find() and its variants. You’ll want to use these as sparingly as possible.

However, since you say you’re only halfway done making the thing, now is not the time to worry about efficiency! Finish it first, make sure it works the way you want in-game. If performance is really an issue and you fall asleep waiting for combat calcs, then you can go ahead and optimise until the cows come home, but premature optimisation kills projects more often than slow code.

The definitive best way of doing things doesn’t really exist; it’ll depend entirely on your specific game, so I’m afraid I can’t really help you with that, but if you’ve got some chunks of code you think could be made more efficient, I’d be happy to help. :slight_smile:

It’s hard for me to imagine a battle system for 6 characters taking enough resources to amount to even a rounding error in terms of game performance, as long as you’re following the most basic performance considerations (e.g. not calling GameObject.Find hundreds of times per frame). Computers and phones are pretty powerful, and six of anything that doesn’t require constant 3D transformations or large database queries or something is just not something that’s going to matter.

Instead, I’d focus on making it reliable and extendable. Whether to have buffs/etc on a character or on a central battle manager system isn’t going to make a difference in terms of efficiency, but it’ll make a big difference in terms of you, six months from now, asking “so where is that one value I’m looking for…?”

1 Like

Modern hardware, even modern mobile hardware, could handle a battle system of 300 v 300 without breaking a sweat. The only efficiency you should concern yourself with, at this point, is the efficiency of writing new code to change, ehance and bugfix your battle system. Whether the data and calculations are distributed or centralized isn’t a critical decision until you’ve seen a problem in the profiler.

To clarify, your primary goal is to reduce duplication of code, and increase the simplicity of the systems without compromising their capabilities. To this point, there is truly no difference to having a central “battle calculator” that does all the calculations, or having each character use the same component to calculate its battle outcomes.

I’d recommend keeping things distributed, however. If you have a central system that does calculations, then you decide that you want to change things only for, say goblins, then you’re having to do branching inside of this single, central class. On the other hand, if each character has its own BattleCalculator class, then you can derive the GoblinBattleCalculator to handle whatever specific changes are needed for goblins.

1 Like

I like how 3 answers saying the exact same thing came in within a minute of each other. Shotgun replies! We have become a hivemind.

2 Likes

Hey guys,

Thanks for your input! I think what I meant by efficiency was not pure hardware computations, but efficiency in terms of ease of programming/readability and how flexible the code is for future changes.

I worry that if every Character has a script that has to go and find all the other character instances, it becomes super confusing because it has to run a bunch of ‘if’ statements to check ‘wait which character am I? Oh ok, so then which type of character am I attacking? oh right, so when you combine those two, you have to go do this separate function’ Then if I want two characters to combo together, I have to write an entirely new set of ‘if’ statements to check if that condition is happening before it can figure out what to do. If I have a central system, it seems like it would simply have methods that could be called and I wouldn’t have to do all these nested if statements.

I understand this is a bit confusing to describe without showing code, let me know if that doesnt make any sense…

There are a number of ways to structure a system like that to avoid nested if’s, and they don’t require a central manager - though they’d benefit from some static methods. Here’s an example of a possible system below that’s highly extensible and flexible - it’ll be really easy to add new abilities.

My first thought for designing this system would be to have a base class that represents an ability - literally any option you can pick on the “menu”. Then you can extend this class for each type of action you have. If this is unfamiliar territory, probably watch the inheritance tutorial.

public abstract class CharacterAbility : MonoBehaviour {
public abstract void Execute();
public abstract string abilityLabel {  get; }
public virtual bool isUsable { get { return true; } }
}

public class MeleeAttack : CharacterAbility {
public override void Execute() {
// pick a target here, and then queue up whatever it is this does
}
public override string abilityLabel { get { return "Melee"; } }
}

So you can attach these components to your character, and the character can use GetComponents() to get a list of all the abilities that character has. If you have equipment or spell objects that add new abilities, you can add them as children of your character and use GetComponentsInChildren() and find all of them.

You can also add, in addition to the abilityLabel, an abilityCategory. This can be used to sort your abilities into submenus. e.g. Each item might be an ability with its abilityCategory as “Item”, which makes a handy way to pick and use items from your inventory.

The isUsable property will let you gray out your “Revive” ability when none of your characters are dead, for example. But the default is for abilities to always be usable, unless you override them.

Let’s make a ComboAbility that can detect copies of itself. I’ll assume you have a central list of party characters available, and I’ll call it PartySystem.characters and assume it’s a List - adapt as needed (or ask for help in making one).

public class ComboAbility : CharacterAbility {
public override bool isUsable {
get {
var comboCharacters = FindCharactersWithAbility(this.GetType() );
return (comboCharacters.Count > 1);
}
}

public static List<Character> FindCharactersWithAbility(type t) {
List<Character> list = new List<Character>();
foreach (Character c in PartySystem.characters) {
if (c.GetComponentInChildren(t) != null) list.Add(c);
}
return list;
}
}

so that class will, essentially, find characters who have a copy of that same script, and if there are 2 of them (this doesn’t exclude the current copy), that ability is usable.

1 Like

Hey Starmanta, that’s a great help. I completely forgot about inheritance! (took some Udemy.com courses, and none of them taught that…)

I think I can manage implementing those ideas, I do have a remaining concern about how to address interacting with enemies in terms of damage.

For instance with your MeleeAttack ability, I could calculate the type of damage, lets say piercing, and the quantity of damage, lets say 10. I would then have to somehow give that damage to the enemy. I guess I could have a method in that enemy called ‘takeDamage’ that would take in two variables, a typeOfDamage and a quantityOfDamage. Then that method would compare the taken in damage, to the defense, and defense type of the enemy to calculate the total damage. Then it would output that damage to that guy’s health. (Let me know if any of that is a bad idea!)

Here’s where I get a little confused. How do I make that enemy have a healthbar that can take in damage, and yet co-exist along side all the other enemies healthbars? I assume I would create a UI element under the Canvas, and then prefab that GameObject so I could instantiate it everytime I make a new enemy. My question is how do I somehow ensure that that specific healthbar belongs to that instance of an enemy? Ideally I guess I would put it as a child of the enemy, but UI elements must be under the Canvas correct?

Thanks!

Your enemies would probably have a class of their own that would have a ‘TakeDamage’ function, or would implement an interface that does the same. I think that enemies (Enemy) and party characters (PartyCharacter) should have a common base class (Character), that would be able to be selected as a target, could take damage, etc.

Easy, put something like this in your healthbar script:

public Character thisCharacter;
void LateUpdate() {
healthImage.fillAmount = thisCharacter.health / thisCharacter.maxHealth; //or whatever
}

Cool thanks again! I got my abilities working and I can drop them into my character and he will grab whatever he has available by filling a list of them and is able to use their execute method.

Little confused about the healthbar response, let me rephrase - I want the battle screen to populate with the first 3 characters in my list. I assume I’d use instantiate to do so at the start of the screen. How do I attach 3 different health bar objects to each character that’s being instantiated?

The only way I can think of how to do that, is in the Start method of the character, once it’s instantiated, it will do a ‘FindComponentsOfType’ but if there are 3 healthbars, how does the character know which one to grab? is this the best way of doing things?

The natural way to do that is to have the same script that creates the characters create the health bars:

var character = Instantiate_Character();
var healthBar = Instantiate_HealthBar();
healthBar.targetCharacter = character;

You don’t really want the character to know about their health bar, that’s not necessary. The health bar should just look at the character’s health, and display that.

1 Like

Ooooh cool. that’s a really great solution!

So that brings up an interesting point I’ve been wondering about - right now I have my character knowing about it’s healthbar. The reason I did this is because it seems improper for the healthbar to have an update script that runs every single frame checking the health of the character, when the character could just call the ‘UpdateHealth()’ method in the healthbar whenever something happens. Perhaps I’m underestimating modern technology but that seems like a waste of processing power?

Having six health bars that does this:

void Update() {
    var health = target.health;
    if(health != lastHealth)
        return;

    //update health bar

Is not going to be noticeable at all. There’s some overhead for having an Update, but it’s very, very little*. The cost of drawing the health bar already dwarfs the cost of that function.

In essence, you’re doing an unnecessary, inefficient optimization that makes your code harder to maintain! It’s not so much underestimating modern technology as not having the experience to tell what’s slow. That takes time to build up, so don’t worry about it.

Now, you could set up a system where the Characters emits an event when their health changes, and the health bar listens to that event. That’d allow you to skip the Update. I’m going to put that up as “figure that out later when you’re more comfortable with the basics of Unity”, though!

*There’s an official blog post that does some performance calculations for Update here. tl;dr: it takes about 2.6 ms to run 10.000 empty Updates on an iPhone 6.

1 Like

You guys are dope. Thanks so much.

I’m having trouble with inheriting class variable values. For instance my game has a Monster abstract class that has a bunch of methods like attack().

public abstract class Monster : MonoBehaviour {

    //stats
    private int attackStat;

private void triggerAttack()
    {
        currentTarget.takeDamage(attackStat);
    }
}

Then I have classes that inherit the Monster class, such as Chinpokomon, and Chinpokomon have their own attackStat that is specific to their type of monster. So i made them like so:

public class Chinpokomon : Monster
{
    private int attackStat = 8;
}

I hope it’s obvious what I’m trying to do here. My problem is that the Chinpokomon won’t overwrite the original attackstat, instead the attackStat becomes null because it seems to execute the Monster class last.

How should I do this properly? Or is this a poor idea of how to use inheritance alltogether?

If you have a value and the child class needs to change the value, you can use a read-only property:

public abstract class Monster : MonoBehaviour {
private virtual int attackStat { get { return 0; } }
}

public class Chinpokomon : Monster {
private override int attackStat { get { return 8; } }
}

Advantage being that you can apply other logic to that if you like. (if, say, the amount of damage it deals gets higher if its health is lower, or whatever.)

or you can have an “Initialize” virtual function that sets the stats as you want to:

public abstract class Monster : MonoBehaviour {
private int attackStat = 0;
public virtual void Initialize() {
attackStat = 1;
}
}

public class Chinpokomon : Monster {
public override void Initialize() {
base.Initialize(); //put this first, so any non-overridden stats get their default values
attackStat = 2;
}

}

So some interesting aspects of that - it won’t let me set those variables to private for some reason. I suppose it doesn’t matter. The other strange thing is since they’re readonly I can no longer interact with them. A minor complaint I guess I can just make temporary variables if I ever had to change them. But there’s one situation I can’t figure out a workaround:

When I try to make a constructor class in the Chinpokomon class, for instance, if I want to say it’s a level 5 chinpokomon, I have to have it change the stats to increase them accordingly. But since all the variables are now readonly it won’t let me! For instance:

public class Chiefmon : Monster {

    public override string monsterName { get { return "Chiefmon"; } }
    public override int attackStat { get { return 10; } }
    public override int level { get { return 1; } }

    public Chiefmon (string newName, int startingLevel)
    {
        monsterName = newName;
        level = startingLevel;

        attackStat = 10 + 3 * startingLevel;
    }
}

I assume if I were to put the attackStat = 10 + 3 * startingLevel inside of the override function, it wouldn’t calculate that after the constructor?

Any class that inherits from a MonoBehaviour cannot have a constructor with parameters. You’ll need to use a custom Initialize() method to accept parameters.

Thanks Kru, so basically whenever I create the Chinpokomon, I’d use something like monster1 = Instantiate() Then immediately after I’d call it’s initialize method and do something like monster1.Initialize(string “Chinpoko”, int 5) to create it’s name and level?

This brings me back to my original problem - I can’t change read-only variables so how would it change those?

Make them publicly accessible, but privately mutable.

public int SomeInt { get; protected set; }

If you want the int to be available in the inspector, then you need to use a backing field. It’s a bit of boilerplate, but it works:

[SerializeField] private int someInt;
public int SomeInt {
    get { return someInt; }
    protected set { someInt = value; }
}