Hello all, and thank you to those who offered assistance, but after some modifications to the BattleSystem.cs I got the results I sought while keeping the vast majority of the original structure intact, as seen in the images below. (It is recommended to save the original BattleSystem.cs because this will be an invasive tutorial)
New Battle Scene (John w/Speed stat of 10 and Kira w/Speed stat of 9, Bandit Speed stat 1-4 for reference)
To get this result I shall go through step-by-step so that others may do the same in the future.
First things first we will need to prepare our BattleVisual.cs (since this is the easiest part) we just need to add a serializable Silder component, a reference to the BattleSystem.cs, and make a UpdateProgressbar() method to create the visuals. For simplicity’s sake, I copied and pasted the health bar in our battle visual prefab and made the fill white.
public class BattleVisual : MonoBehaviour
{
[SerializeField] private Slider progressbar;
private BattleSystem battleSystem;
void Awake()
}
battleSystem = FindFirstObjectByType<BattleSystem>();
progressbar.maxValue = battleSystem.maxCooldown;
progressbar.value = 0;
}
public void UpdateProgressBar(float currentCooldown) //<- Update in BattleSystem.cs
{
progressbar.value = currentCooldown;
}
}
Then we shall go to our BattleSystem.cs and make some additions to our BattleEntities class. Well shall add a reference to the BattleSystem, make a Speed stat (int), and a Cooldown (float). And remember to set the Speed stat like you would the Strength via EnemyInfo.cs and PartyMemberInfo.cs.
[System.Serializable]
public class BattleEntities
{
public BattleSystem BattleSystem;
public int Speed;
public float CurrentCooldown;
public void CalculateInitiative(float delta)
{
if (!BattleSystem.isPaused && CurrHP > 0)
{
CurrentCooldown += (10 - (10*(Speed / 100))) * delta / 5; //<- formula may vary as needed
if (CurrentCooldown >= BattleSystem.maxCooldown)
{
BattleSystem.currentTurn.Add(this);
BattleSystem.isPaused = true;
}
UpdateTimer();
}
}
public void ResetInitiative()
{
CurrentCooldown = 0f;
}
/*
public void SetBoostedInitiative(float speedBoost) <- bonus code for future implementation of speed-boosting items/abilities
{
CurrentCooldown += speedBoost;
UpdateTimer()
}
*/
public void UpdateTimer() //<- BattleVisual
{
BattleVisual.UpdateProgressBar(CurrentCooldown);
}
}
Now we’re almost to the crux of the new system, but we have some more prep to do. In our CreatePartyEntites() and CreateEnemyEntites() we need to add our BattleSystem reference to our tempEntity object.
public void CreateEnemyEntites() and CreatePartyEntites()
{
{
...
//Keep all code above the same
tempEntity.BattleVisual = tempBattleVisual;
tempEntity.BattleSystem = this; //<- add here
allBattlers.Add(tempEntity);
...
}
}
Next we will add a new list to hold the entity that will take their respective turn, a maxCooldown (float), and a isPaused bool at the top of our BattleSystem.cs
private List<BattleEntities> currentTurn = new List<BattleEntities>();
public float maxCooldown = 100f; //<- adjust as needed
public bool isPaused = false;
Okay with that out of the way, we will be going past to point of return as we will be changing the state machine in the BattleSystem.cs
[SerializeField]
private enum BattleState
{
Wait,
Idle,
TakeAction,
CheckStatus,
Selection,
Victory,
Defeat,
Retreat
}
Be sure to delete the ShowBattleMenu() and DetermineBattleOrder() from the Start() method since we won’t be using those anymore. Now we add an Update() method to hold our BattleState machine. Then edit our BattleRoutine, AttackRoutine, and RunRoutine to reflect our new statemachine.
void Update()
{
for (int i = 0; i < allBattlers.Count; i++)
{
allBattlers[i].CalculateInitiative(Time.deltaTime);
}
switch (battleState)
{
case State.Wait:
if (currentTurn.Count > 0)
{
battleState = State.Idle;
}
break;
case State.Idle:
StartAction();
break;
case State.TakeAction:
//Wait till Action is selected
break;
case State.CheckStatus:
RemoveDeadBattlers();
bottomPopUp.SetActive(false);
battleState = State.Wait;
break;
case State.Selection:
//Empty space for retreat behavior
break;
case State.Victory:
break;
case State.Defeat:
break;
case State.Retreat:
break;
}
}
private void StartAction() //<- determines if Current Turn is player
{
for (int i = 0; i < currentTurn.Count; i++)
{
if (currentTurn[i].IsPlayer && battleState == State.Idle)
{
ShowBattleMenu();
battleState = State.TakeAction;
}
else if (!currentTurn[i].IsPlayer)
{
StartCoroutine(BattleRoutine());
}
}
}
public IEnumerator BattleRoutine()
{
battleState = State.TakeAction;
bottomPopUp.SetActive(true);
for (int i = 0; i < currentTurn.Count; i++)
{
if (currentTurn[i].IsPlayer && battleState == State.TakeAction)
{
switch (currentTurn[i].BattleAction)
{
case BattleEntities.Action.Attack:
yield return StartCoroutine(AttackRoutine(i));
break;
case BattleEntities.Action.Run:
yield return StartCoroutine(RunRoutine(i));
break;
default:
Debug.Log("Error - null battle action!");
break;
}
}
else if (!currentTurn[i].IsPlayer)
{
switch (currentTurn[i].BattleAction)
{
case BattleEntities.Action.Attack:
yield return StartCoroutine(AttackRoutine(i));
break;
case BattleEntities.Action.Run:
yield return StartCoroutine(RunRoutine(i));
break;
default:
Debug.Log("Error - null battle action!");
break;
}
}
}
yield return null;
}
private IEnumerator AttackRoutine(int i)
{
battleMenu.SetActive(false);
if (currentTurn[i].IsPlayer == true)
{
BattleEntities currAttacker = currentTurn[i];
if (allBattlers[currAttacker.Target].CurrHP <= 0)
{
currAttacker.SetTarget(GetRandomEnemy());
}
BattleEntities currTarget = allBattlers[currAttacker.Target];
AttackAction(currAttacker, currTarget);
yield return new WaitForSeconds(TURN_DURATION);
if (currTarget.CurrHP <= 0)
{
bottomPopUpText.text = string.Format("{0} defeated {1}!", currAttacker.Name, currTarget.Name);
yield return new WaitForSeconds(TURN_DURATION);
enemyBattlers.Remove(currTarget);
if (enemyBattlers.Count <= 0)
{
battleState = State.Victory;
bottomPopUpText.text = VICTORY_MESSAGE;
yield return new WaitForSeconds(TURN_DURATION);
SceneManager.LoadScene(OVERWORLD_SCENE);
}
}
}
if (i < allBattlers.Count && currentTurn[i].IsPlayer == false)
{
BattleEntities currAttacker = currentTurn[i];
currAttacker.SetTarget(GetRandomPartyMember());
BattleEntities currTarget = allBattlers[currAttacker.Target];
AttackAction(currAttacker, currTarget);
yield return new WaitForSeconds(TURN_DURATION);
if (currTarget.CurrHP <= 0)
{
bottomPopUpText.text = string.Format("{0} defeated {1}!", currAttacker.Name, currTarget.Name);
yield return new WaitForSeconds(TURN_DURATION);
playerBattlers.Remove(currTarget);
if (playerBattlers.Count <= 0)
{
battleState = State.Defeat;
bottomPopUpText.text = DEFEAT_MESSAGE;
yield return new WaitForSeconds(TURN_DURATION);
}
}
}
EndAction(i);
}
private IEnumerator RunRoutine(int i)
{
if (battleState == State.Idle)
{
if (Random.Range(1, 101) >= RUN_CHANCE)
{
bottomPopUpText.text = RUN_SUCCESS_MESSAGE;
battleState = State.Retreat;
allBattlers.Clear();
yield return new WaitForSeconds(TURN_DURATION);
SceneManager.LoadScene(OVERWORLD_SCENE);
}
else
{
bottomPopUpText.text = RUN_FAILED_MESSAGE;
EndAction(i);
yield return new WaitForSeconds(TURN_DURATION);
}
}
}
private void EndAction(int I) <- Reset Initaitive
{
isPaused = false;
currentTurn[i].ResetInitiative();
currentTurn[i].UpdateTimer();
currentTurn.RemoveAt(0);
battleState = State.CheckStatus;
}
Finally, we need to change our SelectEnemy() and SelectRunAction() for our buttons to match our new BattleSystem.
public void SelectEnemy(int currentEnemy)
{
BattleEntities currentPlayerEntity = currentTurn[0];
currentPlayerEntity.SetTarget(allBattlers.IndexOf(enemyBattlers[currentEnemy]));
currentPlayerEntity.BattleAction = BattleEntities.Action.Attack;
StartCoroutine(BattleRoutine());
enemySelectMenu.SetActive(false);
}
public void SelectRunAction()
{
battleState = State.Selection;
BattleEntities currentPlayerEntity = currentTurn[0];
currentPlayerEntity.BattleAction = BattleEntities.Action.Run;
StartCoroutine(BattleRoutine());
enemySelectMenu.SetActive(false);
}
And that’s it for our modifications to the BattleSystem.cs, no other changes are necessary. The only thing left is determining a good speed formula, which requires some finagling to find a good balance of how slow or fast you want the initiative bars to go in your game.
Hope this helps y’all on your GameDev journey,
Thank you for reading.