AI Planner - v0.2-preview released

[Edit: The latest package version is the [0.2.3-preview]( AI Planner - v0.2-preview released page-2#post-5809171) release as of 5/5/20]

Hello, everyone! Today we are releasing our latest update to the Unity AI Planner, version 0.2.0-preview. You can update to the new version via the package manager. Here’s a few of the high-level changes you can expect in this release:

  • Customizable planning settings (i.e. when and how to plan).
  • Customizable plan execution settings (i.e. when and how to act).
  • Better support for custom planner extensions.
  • Terminal state rewards.
  • Improved code generation and compilation.
  • Improved UI with re-orderable lists, better operand validation, and plan visualizer inspector.
  • Improved inspector workflow with new components (DecisionController, TraitComponent), operational actions as parameterized callbacks, and filtering of the game scene through world queries.
  • Initial support for trait gizmos.

This update includes a fairly extensive refactor of various parts of the package, which can break projects built with prior versions. Notably, look out for the following changes:

  • AgentDefinitions have been replaced with PlanDefinitions
  • DomainObjectProvider has become TraitComponent
  • BaseAgent has been removed, with its responsibilities divided between DecisionController and custom generated extensions to BasePlanExecutor.
  • IOperationalActions have been replaced by parameterized method callbacks, assigned on the DecisionController.
  • DomainObject*** classes and structs have been refactored to match TraitBasedObject*** naming.

For this update, we’re releasing both a new tutorial to walk you through a simple example setup from an empty project as well as a few sample projects for you to explore. We will continue to grow this set over time in order to provide examples of the many potential applications of the planner package. For now, this repo includes the following samples:

  • VacuumRobot - Control a robot optimizing its path through an ever-dirty world.
  • Match3 - Use planning to solve goal-based, tile-matching puzzles.
  • EscapeRoom - Escape a room with locked doors, a key, and pressure switches by coordinating three agents with a single planner.

Please note: the previous Otto sample has not been updated to use version 0.2.0 of the AI Planner yet.

Thanks, everyone, for your helpful feedback! We look forward to seeing the many creative ways you use the package!

Step-by-step guide: AI Planner: Step-by-step guide - Google Docs
Step-by-step video: ai-planner-step-by-step.mp4 - Google Drive
New samples repository: GitHub - Unity-Technologies/ai-planner-samples: AI Planner: Samples

9 Likes

The step by step is a really nice start!
I got stuck on the step Select “New Query”. I have the option of use plan state or use world state.

If I expand the box underneath with Include objects. I get a nullref

ArgumentNullException: Value cannot be null.
Parameter name: key
System.Collections.Generic.Dictionary`2[TKey,TValue].TryInsert (TKey key, TValue value, System.Collections.Generic.InsertionBehavior behavior) (at <437ba245d8404784b9fbab9b439ac908>:0)
System.Collections.Generic.Dictionary`2[TKey,TValue].Add (TKey key, TValue value) (at <437ba245d8404784b9fbab9b439ac908>:0)
UnityEngine.AI.Planner.DomainLanguage.TraitBased.TraitBasedObjectData.Initialize (UnityEngine.GameObject gameObject) (at Library/PackageCache/com.unity.ai.planner@0.2.0-preview.4/Runtime/Serialization/TraitBasedObjectData.cs:45)
UnityEngine.AI.Planner.DomainLanguage.TraitBased.TraitComponent.Initialize () (at Library/PackageCache/com.unity.ai.planner@0.2.0-preview.4/Runtime/World/TraitComponent.cs:56)
UnityEditor.AI.Planner.Editors.DecisionControllerInspector+<>c__DisplayClass8_3.<OnInspectorGUI>b__7 (UnityEngine.AI.Planner.DomainLanguage.TraitBased.TraitComponent objectData) (at Library/PackageCache/com.unity.ai.planner@0.2.0-preview.4/Editor/Inspectors/DecisionControllerInspector.cs:239)
System.Linq.Enumerable+WhereArrayIterator`1[TSource].MoveNext () (at <351e49e2a5bf4fd6beabb458ce2255f3>:0)
System.Linq.Enumerable.Any[TSource] (System.Collections.Generic.IEnumerable`1[T] source) (at <351e49e2a5bf4fd6beabb458ce2255f3>:0)
UnityEditor.AI.Planner.Editors.DecisionControllerInspector.OnInspectorGUI () (at Library/PackageCache/com.unity.ai.planner@0.2.0-preview.4/Editor/Inspectors/DecisionControllerInspector.cs:242)
UnityEditor.UIElements.InspectorElement+<>c__DisplayClass55_0.<CreateIMGUIInspectorFromEditor>b__0 () (at <3b74787e58694cdda2c241162159b3b7>:0)
UnityEngine.GUIUtility:ProcessEvent(Int32, IntPtr)

Just confirmed this. This is an error that occurs until you select your traits on the TraitComponent. Once selected the error goes away. It’s benign, but we’ll fix it nonetheless in the next minor release. Thanks.

Thanks for good work.

Questions related to a systems driven game:

  1. Can I create actions and preconditions etc using scriptableobjects.
  2. Would it be ton of computational or memory overhead for making a huge list of items.
  3. Are there any techniques you would suggest for making dynamic items that rely on adjuctives to decide on behavior (perhaps a dictionary using scriptables as keys and scriptables for systems).

Must be said, that only up to a max of 20 (in crazier situations) would be enabled at a time, with typical use cases being 1-2 AI making basic decisions in realtime when it’s their turn.

Awesome!

One gotchya I ran into (which is probably obvious for most) is the Action Callbacks found in the Decision Controller expect an IEnumerator to return null while the action is being performed. A regular method will end the action immediately:

    //this will end the action whenever I falsify isSeeking in a fixed update method etc.
    public IEnumerator GoToTarget(GameObject targetGo)
    {
        this.target = targetGo.transform;
        isSeeking = true;
        while (isSeeking)
            yield return null;
        Debug.Log($"target reached!");
    }

    //this will immediately end the action
    public void GoToTargetImmediate(GameObject targetGo)
    {
        transform.position = targetGo.transform.position;
    }

Another was, when writing navigation actions, its important to update the Agent Position to the Target Position. Otherwise the planner thinks the agent is still at its origin:

I made a fun little ‘travelling salesman’ hello world. Interestingly, he seems to choose a slightly different route each time…

Here’s the GitHub.

6 Likes

Answers to your questions:

  1. Can you share more about what you mean by scriptableobjects? Our data is currently serialized in scriptableobjects. If you are wondering if you can automate the creation of these assets via code, then take a look at the editor tests for the package as we do just that.
  2. I need more clarification here, but I’ll take this to introduce the consideration of branching factor from each state to the next. If you have a simple action such as Navigate and you have N locations all accessible from each other, then at every step of the way the planner is going to consider N-1 instances of the Navigate action. In this case you would want to keep N low, which can be limited with the world filters in the DecisionController.
  3. Can you share a little more on this one, too? By dynamic items are you meaning that you’d like to spawn new items of various types at run-time and have the planner adjust and create optimal plans to those? If so, then the VacuumRobot sample is a good one to look at.
4 Likes

Thanks for the response! You’ve answered all my questions!

The sample projects are really helpful!!

I was playing around with limiting the list count on the vacum example to keep processing time down but if it goes to zero it doesn’t re-find the dirt again even if something is close by.

I also fell over a nullref if there is a null component on the mono you drop in DecisionController->Available actions settings. minor but it’s in OnInspectorGUI so you can’t un drag and drop it or see it.

NullReferenceException: Object reference not set to an instance of an object
UnityEditor.AI.Planner.Editors.DecisionControllerInspector+<>c.<OnInspectorGUI>b__8_2 (UnityEngine.MonoBehaviour c) (at Library/PackageCache/com.unity.ai.planner@0.2.0-preview.4/Editor/Inspectors/DecisionControllerInspector.cs:157)
System.Linq.Enumerable+SelectManySingleSelectorIterator`2[TSource,TResult].ToArray () (at <351e49e2a5bf4fd6beabb458ce2255f3>:0)
System.Linq.Enumerable.ToArray[TSource] (System.Collections.Generic.IEnumerable`1[T] source) (at <351e49e2a5bf4fd6beabb458ce2255f3>:0)
UnityEditor.AI.Planner.Editors.DecisionControllerInspector.OnInspectorGUI () (at Library/PackageCache/com.unity.ai.planner@0.2.0-preview.4/Editor/Inspectors/DecisionControllerInspector.cs:157)
UnityEditor.UIElements.InspectorElement+<>c__DisplayClass55_0.<CreateIMGUIInspectorFromEditor>b__0 () (at <83a73882c51c4602b3d34743827d03e7>:0)
UnityEngine.GUIUtility:ProcessEvent(Int32, IntPtr)

I’ve fixed the issue you found and it will be in the next release. Re: the robot not finding another dirt if it is close by, my intuition without having reproduced the issue is that the plan is marked complete because there is no available actions since there are no dirt objects found. Since there are no actions to take there will be no updates to the state to reflect a change in the world. I’ll have to discuss this with the team.

1 Like

Hi! I was wondering if someone could point me in the right direction of how I could approach implementing something akin to senses or a memory using this planner system.

Could you share more about what you are hoping to do? Ultimately, this will come down to domain modeling and how you want to represent your state.

1 Like

Looking forward to the next release :slight_smile: I’m running into a few errors when running standalone x64, it’s very close to the step by step guide but with 3 agents and no apples. I’ve attached my Generated folder if that helps, I’ll try and reproduce it with a simpler project.

NullReferenceException: Object reference not set to an instance of an object.
  at Unity.Entities.FastEquality+CompareImpl`1[T].CompareFunc (System.Void* lhs, System.Void* rhs) [0x00000] in <0...0>:0
  at Unity.Entities.FastEquality+CompareImpl`1[T].CompareFunc (System.Void* lhs, System.Void* rhs) [0x00000] in <0...0>:0
  at System.Linq.OrderedEnumerable`1[TElement].System.Linq.IOrderedEnumerable<TElement>.CreateOrderedEnumerable[TKey] (System.Func`2[T,TResult] keySelector, System.Collections.Generic.IComparer`1[T] comparer, System.Boolean descending) [0x00000] in <0...0>:0
  at AI.Planner.Domains.ConditionalType..ctor () [0x00000] in <0...0>:0
  at UnityEngine.AI.Planner.DomainLanguage.TraitBased.PlanningDomainData`5[TObject,TStateKey,TStateData,TStateDataContext,TStateManager].CreateStateData (Unity.Entities.EntityManager entityManager, System.Collections.Generic.IEnumerable`1[T] traitBasedObjects) [0x00000] in <0...0>:0
  at UnityEngine.AI.Planner.DomainLanguage.TraitBased.BasePlanExecutor`9[TObject,TStateKey,TStateData,TStateDataContext,TActionScheduler,THeuristic,TTerminationEvaluator,TStateManager,TActionKey].InitializePlannerSystems (System.String name, System.Collections.Generic.IEnumerable`1[T] traitBasedObjects, UnityEngine.AI.Planner.DomainLanguage.TraitBased.PlanDefinition planDefinition) [0x00000] in <0...0>:0
  at UnityEngine.AI.Planner.DomainLanguage.TraitBased.BasePlanExecutor`9[TObject,TStateKey,TStateData,TStateDataContext,TActionScheduler,THeuristic,TTerminationEvaluator,TStateManager,TActionKey].Initialize (System.String name, UnityEngine.AI.Planner.DomainLanguage.TraitBased.PlanDefinition planDefinition, System.Collections.Generic.IEnumerable`1[T] initialTraitBasedObjects, UnityEngine.AI.Planner.PlanExecutionSettings settings) [0x00000] in <0...0>:0
  at UnityEngine.AI.Planner.Controller.DecisionController.Initialize () [0x00000] in <0...0>:0
  at UnityEngine.AI.Planner.Controller.DecisionController.Start () [0x00000] in <0...0>:0
(Filename: currently not available on il2cpp Line: -1)

and then after Start in update

NullReferenceException: Object reference not set to an instance of an object.
  at UnityEngine.AI.Planner.DomainLanguage.TraitBased.BasePlanExecutor`9[TObject,TStateKey,TStateData,TStateDataContext,TActionScheduler,THeuristic,TTerminationEvaluator,TStateManager,TActionKey].NextActionAvailable () [0x00000] in <0...0>:0
  at UnityEngine.AI.Planner.DomainLanguage.TraitBased.BasePlanExecutor`9[TObject,TStateKey,TStateData,TStateDataContext,TActionScheduler,THeuristic,TTerminationEvaluator,TStateManager,TActionKey].ReadyToAct () [0x00000] in <0...0>:0
  at UnityEngine.AI.Planner.Controller.DecisionController.UpdateExecutor () [0x00000] in <0...0>:0
  at DwarvenKingdoms.DwarDecisionController+<Start>d__1.MoveNext () [0x00000] in <0...0>:0
  at UnityEngine.SetupCoroutine.InvokeMoveNext (System.Collections.IEnumerator enumerator, System.IntPtr returnValueAddress) [0x00000] in <0...0>:0

5282142–529491–Generated.zip (19.6 KB)

Hi there!

How i can create inventory with item collection?

Could you provide more details regards ICustomHeuristic.Evaluate, Like when need to change default Imp. and how does return value effect planing.

Would you mind sharing more of your project so I can take a closer look? You can either upload it here or email it to ai.research@unity3d.com.

2 Likes

[/ICODE]
Until we support collections on traits (so you can have MyObject.MyInventoryTrait.Items), you can instead try adding an InventoryItem trait with an Owner field, which holds the object ID of the object whose inventory the item belongs to.

We’re planning a blog post/extra documentation that provides a high-level overview of how the search works, including how to think about and implement custom heuristics. Until then, I’ll give a short overview of how the heuristic should operate.

Given a state to evaluate, a custom heuristic can be used to provide three values (making up a BoundedValue ) used in the graph search:

  • An average/estimate of the cumulative reward/cost to be received from the state onward. The current plan includes the exact rewards leading up to this state. The heuristic provides an estimate how much reward will be accumulated beyond this state. Together, we have an estimate of the total reward (exact rewards leading to the state + estimated reward after the state). When the agent acts, it will choose the next immediate action that maximizes the estimated cumulative reward of the entire plan.

  • An upper bound (optimistic estimate) on the cumulative rewards to be received from the given state onward. If you’ve worked with A*, say for pathfinding, you’ll know that A* requires an admissible or optimistic heuristic estimate to find the optimal path (typically Euclidean distance in pathfinding). This is key for the search to explore branches of the state space which might improve the current plan.

  • Finally, a lower bound (pessimistic estimate) for the future cumulative rewards. This value is used to prune branches of the (incomplete) plan during the iterative search process, such that we don’t waste iterations in areas of the state space with lower reward. Example: consider a scenario where we can take either of two actions leading to states A and B, with bounded value estimates [lower=5, est=7, upper=10] and [lower=0, est=2, upper=3], respectively. Since we know that the worst case of the first action (lower bound = 5) is greater than the best case of the second action (upper bound = 3), we prune from consideration the second action.

We’ll go into more detail about how we use the heuristics to iteratively refine state and action value estimates in the full blog post, but hopefully that will give some clarity on how to think about heuristics in our package.

Some tips:

  • If the planner is being used to compute a plan toward a fixed goal, try to have the heuristic converge to narrower bounds the closer to the goal the state is. This can help the search process immensely.

  • Be aware of the trade-offs of choosing bounds estimates that are too conservative (i.e. high upper bounds and low lower bounds). If your optimistic estimate is too high, the search can spend many iterations exploring branches that “might” be good but, in truth, are not. Similarly, if your lower bound estimates are too low, no pruning occurs, which can result in a shallow search with short, incomplete plans.

  • Conversely, try not give bounds estimates that are narrower/tighter than true cumulative future rewards. If your upper bound estimate is too low, the search can miss branches that lead to the true “optimal” plan. If your lower bound estimate is too high, the search might prune branches it shouldn’t.

2 Likes

FYI

There is no scenes in the Match3 or EscapeRoom examples

The scenes are under Assets/SampleMatch3 and Assets/FirstRoom, respectively. I realize now that doesn’t match the file structure of VacuumRobot. Sorry for the confusion!

1 Like