NodeCanvas Forums › General Discussion › Turn-based logic best practices?
Currently I am working on building AI for a turn based tactics game (think Fire Emblem or Final Fantasy Tactics) using NodeCanvas. While I’ve seen people mention behaviour trees in discussion regarding turn-based systems I haven’t been successful in my endeavors to find examples. I’ve been successful in creating a basic behaviour tree that functions well, but I do not believe it is optimal. Currently it is divided into two primary logical branches: the planning branch and action branch. The planning branch is where all of the calculations as to what options an agent has available this turn is calculated. This includes which action to use, which target to use it on, from which positions, what to do if that target isn’t reachable, etc… Once the planning branch completes, it sets it’s movement destination and a target destinations and then proceeds into the action branch where it actually moves to the destination and executes some action on a target. However, this suffers from two major flaws:
I have a strong suspicion that this is not the correct way to approach the problem and so I seek advice on how to better approach the problem.
Hello there,
For a turn-base game, I would suggest to first and foremost, disable the “Repeat” option found in the BehaviourTreeOwner inspector, since there is not really a need to repeat the behaviour, but rather only tell it to execute (plan + action) when the AI turn is at hand.
With the “Repeat” option disabled, the BehaviourTree will run up until a Success or Failure Status is returned (to the root) and then stop. As such, even with “Repeat” disabled, the BehaviourTree can both include planning (1 frame actions) as well as action (possibly multime frames Running actions).
By having the “Repeat” option disabled though, it means that you will have to manually call “BehaviourTreeOwner.StartBehaviour()” whenever the AI needs to make a decision (as well as execute it).
Regarding debuging the tree in cases where it takes only 1 frame to complete, I have tried in the past to make the Status colors/icons have a delay and be rendered multiple frames in the editor, but it turned out that it was very confusing since it wasn’t always illustrating the current status of the node but rather a previous status with latency. As such and in those cases, I would like to suggest to use Breakpoints where adequete. Using breakpoints will also pause the Unity editor, where you will then be able to “step” through the frames using the usual Unity button next to the Play button. By doing this, you will be able to also see the color/icons status of the nodes after each step.
Once again though, I will suggest to try disabling “Repeat” for a turn-based game and manually calling StartBehaviour() whenever the AI needs to plan and act, as described above.
Please let me know what you think.
Thanks!
Join us on Discord: https://discord.gg/97q2Rjh
Thanks for the response!
We actually already disabled the repeat option on all of our agents and set the OnEnable() option to “Do Nothing”. Currently we are invoking each of the agents with StartBehaviour() and passing in a callback to alert our AI Handler when the next agent may run.
That being said, I should have more accurately phrased my question to be “Should we be doing all of this computation in “OnExecute() or is there a better way to do this?” It feels like putting everything in OnExecute() is fighting against what NodeCanvas generally wants you to do as you lose one of the most powerful features, which is the visual debugging. As for setting break points, are you referring to the BehaviourTree itself or in the code for the ActionTask I am using?
Also, one additional question: is there any way to recover a tree that threw an Exception? Currently, if a tree throws an exception in its execution the agent will never run again on subsequent calls to Start Behaviour().
Hello again and sorry for the late reply.
Doing these things in the “OnExecute” is definetely correct, yes. The fact that the visual debuging is not possible to “catch up” with the tree execution since everything is happening within the same frame in this cae, I don’t think would be an important factor, considering that you DO want planning to indeed take place in one frame 🙂
Thus, for debugging such trees, you can use breakpoints, in which case I am refering to the breakpoints that can be added on a nodes via the right click menu. This will cease the execution at that specific node and also pause the editor and as such you will be able to “step through” the frames while also able to watch the visual debugging of the graph.
Regarding recovery from exception, can you please clarify what kind of exception more specifically?
If you rather refer to an error that is thrown due to ‘Task Initialization’, then in this case, the task in question is disabled by design. Do you indeed refer to this, or an exception thrown for example within the OnExecute of your custom task code?
Let me know.
Thanks!
Join us on Discord: https://discord.gg/97q2Rjh
No worries! I also apologize for my late response!
By exception I am referring to any unhandled run-time exception thrown while executing a Task such as a NullPointerException or IndexOutOfBoundsException. It seems that when these go unaccounted for the tree exits and subsequent calls to StartBehaviour will no longer cause the tree to run making the agent do nothing for the rest of the battle.
Hello again,
Well… Exceptions should ideally not happen for any correct program execution anyway 🙂 Aren’t these exception something that you can account/handle for, or are these exception qualify as bugs? What are these exception more specifically? Are they exceptions thrown by some custom tasks for example?
Thanks.
Join us on Discord: https://discord.gg/97q2Rjh
These are exceptions being thrown from custom tasks and would qualify as bugs. Ideally, yes, exceptions should not happen during correct execution of a program. However, even under the strictest QA testing some bugs will inevitably slip through. In the event of an exception being thrown during Behaviour Tree execution, it is not acceptable for an AI to stop working on subsequent calls to StartBehaviour. Consider the following scenario:
1 2 3 4 5 6 |
An AI agent selects its target at the beginning of a turn. The target the agent selected dies before the AI can attack. The AI goes to attack. The code does not expect the target to be null at this point and does not check for it. A NullPointerException gets thrown. The next turn comes and it's the AI agent's turn to execute. StartBehaviour gets called and nothing happens. |
The fix would be to either have a null check or to change the targeting logic. However, as ridiculous as it may seem, let’s say that this bug was not caught in QA testing. This bug made it to a build and now players are encountering it. Now whenever this bug occurs, this agent will no longer do anything on subsequent turns because it was not able to recover from the exception. An exception occurring in a previous turn should not impact the ability of the agent to function during future turns and therefore the agent should have some way of recovering from this exception.
As silly as this example is, I hope it illustrates my point.
Hello again,
I understand what you mean. Maybe there is a simple code change solution. Can you please try opening up Node.cs file and in method “Execute”, changed the last few lines to be like this:
1 2 3 4 5 6 7 |
// isChecked = true; status = OnExecute(agent, blackboard); // isChecked = false; return status; |
The change here is to comment out the ‘isChecked’ property set, which is also the reason why the behaviour is getting stalled on subsequent “StartBehaviour” calls.
With this change, when an exception is thown, the tree will remain active and running. As such a good change without your code where you call “StartBehaviour()”, would be to first force “StopBehaviour” before starting it, like for example:
1 2 3 4 5 6 7 8 |
void Update() { if ( Input.GetKeyDown(KeyCode.Space) ) { owner.StopBehaviour(); owner.StartBehaviour(); } } |
Let me know if the above works for you.
Thanks.
Join us on Discord: https://discord.gg/97q2Rjh
I concur with gtranger. Is a future change possible to align this with Unity’s default behavior: an exception in Update
doesn’t prevent future Update
calls, etc, etc.
I’m not calling my BTs manually, but I do have situations where I’m testing a custom action, and it throws an exception (usually a missing reference). Instead of being able to assign the reference in the inspector and continue debugging, I have to restart the game and re-run the entire scenario because of this default behavior.
Hello and sorry for the late reply due to xmas vacation.
Thank you @guyboots. Have you tried the suggested code change I’ve posted previously? With this change, behaviours should no longer stall on exceptions like before. If you did try the change, did it work for you as expected?
Thanks.
Join us on Discord: https://discord.gg/97q2Rjh
> Thank you @guyboots. Have you tried the suggested code change I’ve posted previously? With this change, behaviours should no longer stall on exceptions like before. If you did try the change, did it work for you as expected?
I haven’t had a chance yet. I’m kind of leery of having my own local modifications, since that gets me stuck on the “maintaining a fork” treadmill for an asset. I’ll give it a whirl later though, just to test it.