If I call a function from within itself, will it return to its original place when the recursive call is finished?
I have a turn based game system where after calling the NextTurn function, the current unit is deactivated and the next unit in the initiative order is made active. If there are no more units in the turn order list, StartTurn is called and a new round begun.
I've added a timed buff system, and the current spell effects on a unit should tick at the start of the units action, in case damage over time effects etc kill it before it has a chance to act. However this leaves my current function broken when killed by a spell effect and then referenced by the remaining part of the function where it should be taking its action.
I want to include a sanity check to make sure the unit is still active, and if not advance to the next unit in the turn order but I'm not sure where it should go. If I tick spell effects, then test if the active unit is null and then call NextTurn again if so, is this like calling a break on my current iteration through the function, or will it return to the exact same spot after executing and the problem is still there? I thought about encapsulating all the unit selection part in a while selectedUnit != null loop, but not quite sure how to structure this with the else condition that calls StartTurn at the end of the initiative list, since if there's no other valid unit in the turn order that loop will continue forever? Any idea how i should structure this? Thank you!
edit: I'm also removing the unit from the initiative liston its Death() function but it might be more sensible to just not remove it there, allow that index to be null and rebuild it at the top of the turn? Since if it's triggering death off a spell effect and removing itself from the list this seems like it might screw with the indices
public void StartTurn(){
//reset the target highlights
foreach (GameObject tile in map.tileArray)
tile.GetComponent<SpriteRenderer> ().color = Color.white;
//reset initiative order and advance round counter
currentInitIndex = 0;
roundCounter += 1;
//reset hero's ability to cast
playerHero.hasCast = false;
playerHero.GetComponent<SpriteRenderer> ().color = Color.white;
map.GetComponent<CombatLog> ().NewMessage ("ROUND " + roundCounter +" BEGINS");
map.selectedUnit = map.initiativeOrder [currentInitIndex];
map.selectedUnit.GetComponent<Unit> ().isActive = true;
foreach (TimedBuff buff in map.selectedUnit.GetComponent<Unit>().currentBuffs.ToArray()) {
buff.Tick (1);
}
if (map.selectedUnit.GetComponent<Enemy> ()) {
map.selectedUnit.GetComponent<Enemy> ().TakeTurn ();
}
}
public void NextTurn(){
//reset the target highlights
foreach (GameObject tile in map.tileArray)
tile.GetComponent<SpriteRenderer> ().color = Color.white;
//null path and reset movement points
map.selectedUnit.GetComponent<Unit> ().currentPath = null;
map.selectedUnit.GetComponent<Unit> ().remainingMovement = map.selectedUnit.GetComponent<Unit> ().moveSpeed;
//remove any expired buffs from the current unit before advancing turn, this is separate from the tick logic which should apply at start of turn
foreach (TimedBuff buff in map.selectedUnit.GetComponent<Unit>().currentBuffs.ToArray()) {
if (buff.IsFinished) {
map.selectedUnit.GetComponent<Unit> ().currentBuffs.Remove (buff);
}
}
//advance the next unit in the initiative order array as the active unit and deactive the previous unit
if (currentInitIndex < map.initiativeOrder.Count-1) {
map.selectedUnit.GetComponent<Unit> ().isActive = false;
currentInitIndex += 1;
map.selectedUnit = map.initiativeOrder [currentInitIndex];
map.selectedUnit.GetComponent<Unit> ().isActive = true;
//advance all times buffs by one tick, we want to advance after selecting the new unit but before it acts, in case it is killed by a spell effect
foreach (TimedBuff buff in map.selectedUnit.GetComponent<Unit>().currentBuffs.ToArray()) {
//all buffs currently decrement by one tick per turn
buff.Tick (1);
}
//TODO sanity check in case a fatal debuff has killed the unit before taking its turn
//if the next unit to act is an enemy, take its turn
if (map.selectedUnit.GetComponent<Enemy> ()) {
map.selectedUnit.GetComponent<Enemy> ().TakeTurn ();
}
} else {
//we have reached the end of the initiative order list, begin a new combat round
map.selectedUnit.GetComponent<Unit>().isActive = false;
map.selectedUnit = null;
StartTurn();
}
}
Answer by TheSOULDev · Sep 20, 2017 at 08:33 PM
Tl;dr - yes, recusions work perfectly fine in C#, without recursions the language would obviously be Turing incomplete and then it would never come into use, let alone be a language for Unity.
Answer by Yog555 · Sep 21, 2017 at 02:35 AM
Thanks TheSOULDev (tried to submit this as a comment on your reply but it doesnt seem to work)
I thought that was the case so I guess just recurring the function before doing the action doesn't quite work since it will eventually return and either execute a double action or act on a null object.. could I encapsulate it like this example below? I'd also have to null-check the lines at the top of NextTurn and StartTurn since I can no longer guarantee a valid unit exists at that point to be acted on but this check seems like it should work - at the point it is recurring NextTurn if needed, there is no further code to execute in the root function call.
The if statement checking the next index in preparation to recur it might need to be tweaked - if I'm calling List.Remove on that object as part of its death, do all the indices after it in the list get decremented by one? If so I should be checking the same index.. but then when I recur the function it's going to then increment the counter and shoot past a valid index. In that case should I just destroy the object but not remove it from the list, iterate through the indices recursively as normal and then remove all nulls from the list as part of my start turn cleanup procedure?
//*select units and tick through buffs*
if(map.selectedUnit != null){
//if the next unit to act is an enemy, take its turn (otherwise done, wait for player input)
if (map.selectedUnit.GetComponent<Enemy> ()) {
map.selectedUnit.GetComponent<Enemy> ().TakeTurn ();
}
} else{
//selected unit is null, so it has died from a spell effect before acting
//check if there is another unit in the turn order list, if so recur NextTurn otherwise call the next round
if(currentInitIndex+1 < map.initiativeOrder.Count-1){
NextTurn();
}else{
StartTurn();
}
}
You need to think of how the scripts run as a stack (and that's actually what it's referred to you may want to research the 'stack' a little bit to become more familiar with how things in C# is interpreted, just don't go too far down the rabbit hole yet cause the stack is a very complex thing, but a general overview would help you). Essentially with a stack you can only ever take the top most object in the case of .NET/$$anonymous$$ONO this means execution of previous functions are paused until the topmost function has been returned, in a general sense. So Calling a function will place it at the top of the stack (of the thread, which adds more complexity to the topic so don't worry about threads just yet) and the previous function is paused until this new function has returned, so on and so forth.