diff --git a/Editor/Core/GOAPPlannerEditor.cs b/Editor/Core/GOAPPlannerEditor.cs index 5925a0b..c09ba78 100644 --- a/Editor/Core/GOAPPlannerEditor.cs +++ b/Editor/Core/GOAPPlannerEditor.cs @@ -4,31 +4,31 @@ using UnityEngine.UIElements; namespace Kurisu.GOAP.Editor { - [CustomEditor(typeof(GOAPPlanner),true)] + [CustomEditor(typeof(GOAPPlanner), true)] public class GOAPPlannerEditor : UnityEditor.Editor { - private const string LabelText="AkiGOAP V1.0 Planner"; - private const string ButtonText="Open Planner Snapshot"; - private const string GraphButtonText="Open GOAP Editor"; + private const string LabelText = "AkiGOAP V1.0 Planner"; + private const string ButtonText = "Open Planner Snapshot"; + private const string GraphButtonText = "Open GOAP Editor"; public override VisualElement CreateInspectorGUI() { var myInspector = new VisualElement(); - myInspector.Add(UIUtility.GetLabel(LabelText,20)); + myInspector.Add(UIUtility.GetLabel(LabelText, 20)); InspectorElement.FillDefaultInspector(myInspector, serializedObject, this); myInspector.Remove(myInspector.Q("PropertyField:m_Script")); //Setting myInspector.AddSpace(); - UIUtility.GetLabel("Normal Setting",14,color:UIUtility.AkiBlue,anchor:TextAnchor.MiddleLeft).AddTo(myInspector); + UIUtility.GetLabel("Normal Setting", 14, color: UIUtility.AkiBlue, anchor: TextAnchor.MiddleLeft).AddTo(myInspector); myInspector.Q("PropertyField:logType").MoveToEnd(myInspector); myInspector.Q("PropertyField:tickType").MoveToEnd(myInspector); //SnapShot - UIUtility.GetButton(ButtonText,UIUtility.AkiRed,ShowPlannerWindow,100) + UIUtility.GetButton(ButtonText, UIUtility.AkiRed, ShowPlannerWindow, 100) .Enabled(Application.isPlaying) .AddTo(myInspector); //Editor - UIUtility.GetButton(GraphButtonText,UIUtility.AkiBlue,ShowGOAPEditor,100) + UIUtility.GetButton(GraphButtonText, UIUtility.AkiBlue, ShowGOAPEditor, 100) .Enabled(Application.isPlaying) - .AddTo(myInspector); + .AddTo(myInspector); return myInspector; } private void ShowPlannerWindow() diff --git a/Editor/Core/GOAPPlannerProEditor.cs b/Editor/Core/GOAPPlannerProEditor.cs index 2bb348b..024d3f3 100644 --- a/Editor/Core/GOAPPlannerProEditor.cs +++ b/Editor/Core/GOAPPlannerProEditor.cs @@ -4,42 +4,47 @@ using UnityEngine.UIElements; namespace Kurisu.GOAP.Editor { - [CustomEditor(typeof(GOAPPlannerPro),true)] + [CustomEditor(typeof(GOAPPlannerPro), true)] public class GOAPPlannerProEditor : UnityEditor.Editor { - private const string LabelText="AkiGOAP V1.0 Planner Pro"; - private const string ButtonText="Open Planner Snapshot"; - private const string GraphButtonText="Open GOAP Editor"; - private const string SkilSearchTooltip="Enabled to skip search plan when already have an action, toggle this will need you to set correct precondition"+ + private const string LabelText = "AkiGOAP V1.0 Planner Pro"; + private const string ButtonText = "Open Planner Snapshot"; + private const string GraphButtonText = "Open GOAP Editor"; + private const string SkilSearchTooltip = "Enabled to skip search plan when already have an action, toggle this will need you to set correct precondition" + "for each action to let it quit by itself"; public override VisualElement CreateInspectorGUI() { var myInspector = new VisualElement(); - myInspector.Add(UIUtility.GetLabel(LabelText,20)); + myInspector.Add(UIUtility.GetLabel(LabelText, 20)); //Default InspectorElement.FillDefaultInspector(myInspector, serializedObject, this); myInspector.Remove(myInspector.Q("PropertyField:m_Script")); myInspector.Remove(myInspector.Q("PropertyField:skipSearchWhenActionRunning")); + myInspector.Remove(myInspector.Q("PropertyField:isActive")); //Setting myInspector.AddSpace(); - UIUtility.GetLabel("Normal Setting",14,color:UIUtility.AkiBlue,anchor:TextAnchor.MiddleLeft).AddTo(myInspector); + UIUtility.GetLabel("Normal Setting", 14, color: UIUtility.AkiBlue, anchor: TextAnchor.MiddleLeft).AddTo(myInspector); myInspector.Q("PropertyField:logType").MoveToEnd(myInspector); myInspector.Q("PropertyField:tickType").MoveToEnd(myInspector); + var isActive = new Toggle("Is Active"); + isActive.tooltip = SkilSearchTooltip; + isActive.BindProperty(serializedObject.FindProperty("isActive")); + isActive.AddTo(myInspector); myInspector.AddSpace(); - UIUtility.GetLabel("Pro Setting",14,color:UIUtility.AkiBlue,anchor:TextAnchor.MiddleLeft).AddTo(myInspector); - var skipSearchProperty=serializedObject.FindProperty("skipSearchWhenActionRunning"); - var skipSearchToggle=new Toggle("Skip Search When Action Running"); - skipSearchToggle.tooltip=SkilSearchTooltip; + UIUtility.GetLabel("Pro Setting", 14, color: UIUtility.AkiBlue, anchor: TextAnchor.MiddleLeft).AddTo(myInspector); + var skipSearchProperty = serializedObject.FindProperty("skipSearchWhenActionRunning"); + var skipSearchToggle = new Toggle("Skip Search When Action Running"); + skipSearchToggle.tooltip = SkilSearchTooltip; skipSearchToggle.BindProperty(skipSearchProperty); skipSearchToggle.AddTo(myInspector); //SnapShot - UIUtility.GetButton(ButtonText,UIUtility.AkiRed,ShowPlannerWindow,100) + UIUtility.GetButton(ButtonText, UIUtility.AkiRed, ShowPlannerWindow, 100) .Enabled(Application.isPlaying) .AddTo(myInspector); //Editor - UIUtility.GetButton(GraphButtonText,UIUtility.AkiBlue,ShowGOAPEditor,100) + UIUtility.GetButton(GraphButtonText, UIUtility.AkiBlue, ShowGOAPEditor, 100) .Enabled(Application.isPlaying) - .AddTo(myInspector); + .AddTo(myInspector); return myInspector; } private void ShowPlannerWindow() diff --git a/Editor/Resources/Icons/action_icon.png.meta b/Editor/Resources/Icons/action_icon.png.meta index 4b97b8b..4969ae0 100644 --- a/Editor/Resources/Icons/action_icon.png.meta +++ b/Editor/Resources/Icons/action_icon.png.meta @@ -6,7 +6,7 @@ TextureImporter: serializedVersion: 12 mipmaps: mipMapMode: 0 - enableMipMap: 1 + enableMipMap: 0 sRGBTexture: 1 linearTexture: 0 fadeOut: 0 @@ -39,10 +39,10 @@ TextureImporter: wrapU: 0 wrapV: 0 wrapW: 0 - nPOTScale: 1 + nPOTScale: 0 lightmap: 0 compressionQuality: 50 - spriteMode: 0 + spriteMode: 1 spriteExtrude: 1 spriteMeshType: 1 alignment: 0 @@ -51,7 +51,7 @@ TextureImporter: spriteBorder: {x: 0, y: 0, z: 0, w: 0} spriteGenerateFallbackPhysicsShape: 1 alphaUsage: 1 - alphaIsTransparency: 0 + alphaIsTransparency: 1 spriteTessellationDetail: -1 textureType: 0 textureShape: 1 @@ -63,7 +63,7 @@ TextureImporter: textureFormatSet: 0 ignorePngGamma: 0 applyGammaDecoding: 0 - cookieLightType: 0 + cookieLightType: 1 platformSettings: - serializedVersion: 3 buildTarget: DefaultTexturePlatform @@ -119,7 +119,7 @@ TextureImporter: outline: [] physicsShape: [] bones: [] - spriteID: + spriteID: 5e97eb03825dee720800000000000000 internalID: 0 vertices: [] indices: diff --git a/Editor/Resources/Icons/goal_icon.png.meta b/Editor/Resources/Icons/goal_icon.png.meta index ff36a7c..5e8e577 100644 --- a/Editor/Resources/Icons/goal_icon.png.meta +++ b/Editor/Resources/Icons/goal_icon.png.meta @@ -6,7 +6,7 @@ TextureImporter: serializedVersion: 12 mipmaps: mipMapMode: 0 - enableMipMap: 1 + enableMipMap: 0 sRGBTexture: 1 linearTexture: 0 fadeOut: 0 @@ -39,10 +39,10 @@ TextureImporter: wrapU: 0 wrapV: 0 wrapW: 0 - nPOTScale: 1 + nPOTScale: 0 lightmap: 0 compressionQuality: 50 - spriteMode: 0 + spriteMode: 1 spriteExtrude: 1 spriteMeshType: 1 alignment: 0 @@ -51,7 +51,7 @@ TextureImporter: spriteBorder: {x: 0, y: 0, z: 0, w: 0} spriteGenerateFallbackPhysicsShape: 1 alphaUsage: 1 - alphaIsTransparency: 0 + alphaIsTransparency: 1 spriteTessellationDetail: -1 textureType: 0 textureShape: 1 @@ -63,7 +63,7 @@ TextureImporter: textureFormatSet: 0 ignorePngGamma: 0 applyGammaDecoding: 0 - cookieLightType: 0 + cookieLightType: 1 platformSettings: - serializedVersion: 3 buildTarget: DefaultTexturePlatform @@ -119,7 +119,7 @@ TextureImporter: outline: [] physicsShape: [] bones: [] - spriteID: + spriteID: 5e97eb03825dee720800000000000000 internalID: 0 vertices: [] indices: diff --git a/README.md b/README.md index 93404a4..f368500 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,8 @@ AkiGOAP是一个支持可视化、模块化编辑,支持多线程的Goal Oriented Action Planner(目标导向的行为规划)Unity插件,同时集成了多个开源GOAP插件的功能。 -## 特点 +AkiGOAP is a Goal Oriented Action Planner unity plugin that supports visualization, modular editing, and multi-threading, which integrates the functions of multiple open source GOAP plugins. +## 特点 Features 1. 多线程, 使用Unity Job System @@ -22,12 +23,7 @@ AkiGOAP是一个支持可视化、模块化编辑,支持多线程的Goal Orien -## 安装 -1. 从Release下载Package [Release Package](https://github.com/AkiKurisu/AkiGOAP/releases) -2. 使用Unity Package Manager通过git URL下载 ```https://github.com/AkiKurisu/AkiGOAP.git``` -# - -## 如何使用 +## 如何使用 How To Use 由于GOAP AI的设计需要一定门槛,我只介绍如何使用插件的核心功能,具体的设计可以参考插件提供的Example样例。 @@ -80,7 +76,7 @@ AkiGOAP是一个支持可视化、模块化编辑,支持多线程的Goal Orien 8. 点击GOAPPlanner或GOAPPlannerPro的```Open GOAP Editor```打开编辑器查看当前所有Goal的Priority优先级和所有Action的Cost代价 9. 点击GOAPPlanner或GOAPPlannerPro的```Open Planner Snapshot```打开快照查看当前的Plan计划(即抵达当前Goal的一串Action序列) -## 说明 +## 说明 Explanation 1. GOAPPlanner没有使用JobSystem,仅使用对象池减少了GC开销,适用于复杂度较低的任务,优化自 https://github.com/toastisme/OpenGOAP 特点:根据优先级最高的Goal搜索路径,没有搜索到则FallBack至下一优先级的Goal diff --git a/Runtime/Component/GOAPPlannerPro.cs b/Runtime/Component/GOAPPlannerPro.cs index 3a565f9..fae673a 100644 --- a/Runtime/Component/GOAPPlannerPro.cs +++ b/Runtime/Component/GOAPPlannerPro.cs @@ -11,8 +11,8 @@ namespace Kurisu.GOAP /// Pro version using job system and burst compiler to support multi-thread /// [RequireComponent(typeof(GOAPWorldState))] - public class GOAPPlannerPro:MonoBehaviour,IPlanner - { + public class GOAPPlannerPro : MonoBehaviour, IPlanner + { [BurstCompile] private struct GoalSorter : IComparer { @@ -22,145 +22,170 @@ public int Compare(IGoal x, IGoal y) } } protected GOAPWorldState worldState; - public GOAPWorldState WorldState=>worldState; - protected readonly List goals=new(); - protected readonly List actions=new(); - public IGoal ActivateGoal{get; private set;} - int IPlanner.activeActionIndex=>0; + public GOAPWorldState WorldState => worldState; + protected readonly List goals = new(); + protected readonly List actions = new(); + public IGoal ActivateGoal { get; private set; } + int IPlanner.activeActionIndex => 0; private IAction candidateAction; private IAction activateAction; - private List candidatePlan=new(); - public IAction ActivateAction=>activateAction; - public List ActivatePlan{get; private set;}=new(); + private List candidatePlan = new(); + public IAction ActivateAction => activateAction; + public List ActivatePlan { get; private set; } = new(); private IGoal candidateGoal; - private List candidateGoals=new(); - internal List CandidateGoals=>candidateGoals; + private List candidateGoals = new(); + internal List CandidateGoals => candidateGoals; // Loggers - [SerializeField,Tooltip("Control the log message of planner. Never: No log; OnlyActive: Only logging active plan message; IncludeSearch: Include "+ - "searching detail like action select information; IncludeFail: Include logging all fail message like fail to find path or fail to find a goal. "+ + [SerializeField, Tooltip("Control the log message of planner. Never: No log; OnlyActive: Only logging active plan message; IncludeSearch: Include " + + "searching detail like action select information; IncludeFail: Include logging all fail message like fail to find path or fail to find a goal. " + "Always: Log all message")] private LogType logType; - private bool logActive=>logType.HasFlag(LogType.OnlyActive); - private bool logSearch=>logType.HasFlag(LogType.IncludeSearch); - private bool logFail=>logType.HasFlag(LogType.IncludeFail); - [SerializeField,Tooltip("Nothing: Automatically update planner.\n"+ - "ManualUpdateGoal: Toggle this to disable planner to tick goal automatically.\n"+ - "ManualActivatePlanner: Toggle this to disable planner to tick and search plan automatically,"+ + private bool logActive => logType.HasFlag(LogType.OnlyActive); + private bool logSearch => logType.HasFlag(LogType.IncludeSearch); + private bool logFail => logType.HasFlag(LogType.IncludeFail); + [SerializeField, Tooltip("Nothing: Automatically update planner.\n" + + "ManualUpdateGoal: Toggle this to disable planner to tick goal automatically.\n" + + "ManualActivatePlanner: Toggle this to disable planner to tick and search plan automatically," + " however when the plan is generated, the planner will focus that plan until the plan is deactivated. So you can't stop plan manually.")] private TickType tickType; [SerializeField] private bool skipSearchWhenActionRunning; - internal bool SkipSearchWhenActionRunning=>skipSearchWhenActionRunning; - public List Behaviors=>Enumerable.Empty().Concat(actions.OfType()).Concat(goals.OfType()).ToList(); - public UnityEngine.Object _Object =>gameObject; + internal bool SkipSearchWhenActionRunning => skipSearchWhenActionRunning; + public List Behaviors => Enumerable.Empty().Concat(actions.OfType()).Concat(goals.OfType()).ToList(); + public UnityEngine.Object _Object => gameObject; public event System.Action OnUpdatePlanEvent; private GOAPJobRunner jobRunner; - private bool isDirty=false; - public bool IsActive{get;private set;}=true; - private void Awake() { + private bool isDirty = false; + [SerializeField] + private bool isActive; + private bool activateFlag = false; + private void Awake() + { worldState = GetComponent(); - IsActive&=!tickType.HasFlag(TickType.ManualActivatePlanner); + isActive &= !tickType.HasFlag(TickType.ManualActivatePlanner); } - private void Update(){ - if(isDirty) + private void Update() + { + if (activateFlag) + { + isActive = true; + activateFlag = false; + } + if (isDirty) { - isDirty=false; + isDirty = false; jobRunner?.Dispose(); - jobRunner=new GOAPJobRunner(this,new GraphResolver(actions.Cast().Concat(goals))); + jobRunner = new GOAPJobRunner(this, new GraphResolver(actions.Cast().Concat(goals))); } - if(IsActive)Tick(); + if (isActive) Tick(); } public void ManualActivate() { - IsActive=true; + //Ensure jobRunner activate in Update() + activateFlag = true; } - private void LateUpdate() { - if(!IsActive)return; + private void LateUpdate() + { + if (!isActive) return; //Ensure job is completed jobRunner?.Complete(); - bool debugUpdate=false; - if ((NoActiveGoal() && CandidateGoalAvailable()) || BetterGoalAvailable()){ + bool debugUpdate = false; + if ((NoActiveGoal() && CandidateGoalAvailable()) || BetterGoalAvailable()) + { StartCurrentBestGoal(); - debugUpdate=true; + debugUpdate = true; } - else if(HaveNextAction()) + else if (HaveNextAction()) { StartCurrentBestAction(); - debugUpdate=true; + debugUpdate = true; } - if(debugUpdate) + if (debugUpdate) OnUpdatePlanEvent?.Invoke(this); else { - if(tickType.HasFlag(TickType.ManualActivatePlanner)) + if (tickType.HasFlag(TickType.ManualActivatePlanner)) { - IsActive=false; - if(logSearch)PlannerLog("Manual plan updating ends, need to be activated manully again.",bold:true); + isActive = false; + if (logSearch) PlannerLog("Manual plan updating ends, need to be activated manully again.", bold: true); } } } - private void OnDestroy() { + private void OnDestroy() + { jobRunner?.Dispose(); } void IPlanner.InjectGoals(IEnumerable source) { goals.Clear(); - foreach(var goal in source) + foreach (var goal in source) { goals.Add(goal); goal.Init(worldState); } - isDirty=true; + isDirty = true; } void IPlanner.InjectActions(IEnumerable source) { actions.Clear(); - foreach(var action in source) + foreach (var action in source) { actions.Add(action); action.Init(worldState); } - isDirty=true; + isDirty = true; } - internal void SetCandidate(List path,IGoal goal) + internal void SetCandidate(List path, IGoal goal) { - var action=path[0]; + if (goal == null || path.Count == 0) + { + candidatePlan.Clear(); + candidateAction = null; + candidateGoal = null; + if (logFail) PlannerLog("No candiate goal or path was found."); + return; + } + var action = path[0]; candidatePlan.Clear(); candidatePlan.AddRange(path); - if(candidateAction!=action&&logSearch)PlannerLog($"Search candidate action:{action.Name}"); - candidateAction=action; - if(candidateGoal!=goal&&logSearch)PlannerLog($"Search candidate goal:{goal.Name}"); - candidateGoal=goal; + if (candidateAction != action && logSearch) PlannerLog($"Search candidate action:{action.Name}"); + candidateAction = action; + if (candidateGoal != goal && logSearch) PlannerLog($"Search candidate goal:{goal.Name}"); + candidateGoal = goal; } - List IPlanner.GetAllActions()=>actions; + List IPlanner.GetAllActions() => actions; public void Tick() { - if(!tickType.HasFlag(TickType.ManualUpdateGoal))TickGoals(); + if (!tickType.HasFlag(TickType.ManualUpdateGoal)) TickGoals(); OnTickActivePlan(); GetHighestPriorityGoals(candidateGoals); jobRunner?.Run(); } - private bool NoActiveGoal(){ + private bool NoActiveGoal() + { return ActivateGoal == null; } private bool HaveNextAction() { - return candidateAction !=null && candidateAction != ActivateAction; + return candidateAction != null && candidateAction != ActivateAction; } - private bool BetterGoalAvailable(){ + private bool BetterGoalAvailable() + { return candidateGoal != null && candidateGoal != ActivateGoal; } - private bool CandidateGoalAvailable(){ + private bool CandidateGoalAvailable() + { return candidateAction != null && candidateGoal != null; } - private void StartCurrentBestGoal(){ + private void StartCurrentBestGoal() + { //Activate Goal ActivateGoal?.OnDeactivate(); ActivateGoal = candidateGoal; - if(logActive)ActivePlanLog($"Starting new plan for {ActivateGoal.Name}", bold:true); + if (logActive) ActivePlanLog($"Starting new plan for {ActivateGoal.Name}", bold: true); ActivateGoal.OnActivate(); //Activate Action StartCurrentBestAction(); @@ -169,47 +194,55 @@ private void StartCurrentBestAction() { ActivateAction?.OnDeactivate(); SetCurrentAction(candidateAction); - if(logActive)ActivePlanLog($"Starting {ActivateAction.Name}"); + if (logActive) ActivePlanLog($"Starting {ActivateAction.Name}"); ActivateAction.OnActivate(); } - public void TickGoals(){ - if (goals != null){ - for (int i = 0; i < goals.Count; i++){ + public void TickGoals() + { + if (goals != null) + { + for (int i = 0; i < goals.Count; i++) + { goals[i].OnTick(); } } } - private void OnTickActivePlan(){ + private void OnTickActivePlan() + { // Nothing to run - if (ActivateGoal == null || ActivateAction == null){ return; } + if (ActivateGoal == null || ActivateAction == null) { return; } // Goal no longer viable - if (!ActivateGoal.PreconditionsSatisfied(worldState)){ - if(logActive)ActivePlanLog( + if (!ActivateGoal.PreconditionsSatisfied(worldState)) + { + if (logActive) ActivePlanLog( $"{ActivateGoal.Name} failed as preconditions are no longer satisfied", - bold:true + bold: true ); OnCompleteOrFailActivePlan(); return; } // Plan no longer viable - if (!(ActivateAction.PreconditionsSatisfied(worldState))){ - if(logActive)ActivePlanLog( + if (!(ActivateAction.PreconditionsSatisfied(worldState))) + { + if (logActive) ActivePlanLog( $"{ActivateAction.Name} failed as preconditions are no longer satisfied", - bold:true + bold: true ); - OnCompleteOrFailActivePlan(); + OnCompleteOrFailActivePlan(); return; } ActivateAction.OnTick(); // Goal complete - if (ActivateGoal.ConditionsSatisfied(worldState)){ - if(logActive)ActivePlanLog($"{ActivateGoal.Name} completed", bold:true); + if (ActivateGoal.ConditionsSatisfied(worldState)) + { + if (logActive) ActivePlanLog($"{ActivateGoal.Name} completed", bold: true); OnCompleteOrFailActivePlan(); return; } } - private void OnCompleteOrFailActivePlan(){ + private void OnCompleteOrFailActivePlan() + { ActivateAction?.OnDeactivate(); ActivateGoal?.OnDeactivate(); ActivateGoal = null; @@ -218,8 +251,8 @@ private void OnCompleteOrFailActivePlan(){ private void SetCurrentAction(IAction action) { ActivatePlan.Clear(); - activateAction=action; - if(activateAction!=null) + activateAction = action; + if (activateAction != null) ActivatePlan.AddRange(candidatePlan); } /// @@ -227,17 +260,21 @@ private void SetCurrentAction(IAction action) /// has a valid plan /// /// - private void GetHighestPriorityGoals(List chosenGoals){ + private void GetHighestPriorityGoals(List chosenGoals) + { chosenGoals.Clear(); //Searching for highest priority goal - if (goals == null||goals.Count==0){ - if(logFail)PlannerLog("No goals found"); + if (goals == null || goals.Count == 0) + { + if (logFail) PlannerLog("No goals found"); return; } - for (int i = 0; i < goals.Count; i++){ + for (int i = 0; i < goals.Count; i++) + { - if (!goals[i].PreconditionsSatisfied(worldState)){ - if(logFail)PlannerLog($"{goals[i].Name} not valid as preconditions not satisfied"); + if (!goals[i].PreconditionsSatisfied(worldState)) + { + if (logFail) PlannerLog($"{goals[i].Name} not valid as preconditions not satisfied"); continue; } chosenGoals.Add(goals[i]); @@ -251,7 +288,8 @@ private void GetHighestPriorityGoals(List chosenGoals){ List IPlanner.GetSortedGoalData() { List goalData = new List(); - for (int i=0; i IPlanner.GetSortedGoalData() goalData.Reverse(); return goalData; } - private void ActivePlanLog(object message, bool bold=false){ + private void ActivePlanLog(object message, bool bold = false) + { string s = $"ActivePlan: {message}"; - if (bold){ + if (bold) + { s = "" + s + ""; } - Debug.Log(s,this); + Debug.Log(s, this); } - private void PlannerLog(object message, bool bold=false){ + private void PlannerLog(object message, bool bold = false) + { string s = $"Planner: {message}"; - if (bold){ + if (bold) + { s = "" + s + ""; } - Debug.Log(s,this); + Debug.Log(s, this); } } } \ No newline at end of file diff --git a/Runtime/Interface/IPlanner.cs b/Runtime/Interface/IPlanner.cs index cd435bd..9ba82b8 100644 --- a/Runtime/Interface/IPlanner.cs +++ b/Runtime/Interface/IPlanner.cs @@ -5,12 +5,14 @@ public interface IPlanner : IGOAPSet { void InjectGoals(IEnumerable source); void InjectActions(IEnumerable source); - IGoal ActivateGoal{get;} - List ActivatePlan{get;} + IGoal ActivateGoal { get; } + List ActivatePlan { get; } List GetAllActions(); event System.Action OnUpdatePlanEvent; - GOAPWorldState WorldState{get;} - int activeActionIndex{get;} + GOAPWorldState WorldState { get; } + int activeActionIndex { get; } List GetSortedGoalData(); + void TickGoals(); + void ManualActivate(); } } diff --git a/Runtime/Runner/GoapJobRunner.cs b/Runtime/Runner/GoapJobRunner.cs index f36dda1..9c393e5 100644 --- a/Runtime/Runner/GoapJobRunner.cs +++ b/Runtime/Runner/GoapJobRunner.cs @@ -14,12 +14,12 @@ public class GOAPJobRunner private readonly IPositionBuilder positionBuilder; private readonly ICostBuilder costBuilder; private readonly IConditionBuilder conditionBuilder; - private List resultCache=new(); + private List resultCache = new(); public GOAPJobRunner(GOAPPlannerPro planner, IGraphResolver graphResolver) { this.planner = planner; this.resolver = graphResolver; - + this.executableBuilder = this.resolver.GetExecutableBuilder(); this.positionBuilder = this.resolver.GetPositionBuilder(); this.costBuilder = this.resolver.GetCostBuilder(); @@ -28,33 +28,33 @@ public GOAPJobRunner(GOAPPlannerPro planner, IGraphResolver graphResolver) public void Run() { - this.resolveHandles.Clear(); + this.resolveHandles.Clear(); RunInternal(planner); } private void RunInternal(GOAPPlannerPro planner) { - if (planner==null) + if (planner == null) return; - if(planner.CandidateGoals.Count==0) + if (planner.CandidateGoals.Count == 0) return; - if(planner.ActivateAction!=null&&planner.SkipSearchWhenActionRunning) + if (planner.ActivateAction != null && planner.SkipSearchWhenActionRunning) return; - this.FillBuilders(planner,planner.transform); + this.FillBuilders(planner, planner.transform); //Create job for each candidate goal - foreach(var goal in planner.CandidateGoals) - this.resolveHandles.Add(new JobRunHandle(planner,goal,this.resolver.StartResolve(new RunData - { - StartIndex = this.resolver.GetIndex(goal), - IsExecutable = new NativeArray(this.executableBuilder.Build(), Allocator.TempJob), - Positions = new NativeArray(this.positionBuilder.Build(), Allocator.TempJob), - Costs = new NativeArray(this.costBuilder.Build(), Allocator.TempJob), - ConditionsMet = new NativeArray(this.conditionBuilder.Build(), Allocator.TempJob), - DistanceMultiplier = 1f - }))); + foreach (var goal in planner.CandidateGoals) + this.resolveHandles.Add(new JobRunHandle(goal, this.resolver.StartResolve(new RunData + { + StartIndex = this.resolver.GetIndex(goal), + IsExecutable = new NativeArray(this.executableBuilder.Build(), Allocator.TempJob), + Positions = new NativeArray(this.positionBuilder.Build(), Allocator.TempJob), + Costs = new NativeArray(this.costBuilder.Build(), Allocator.TempJob), + ConditionsMet = new NativeArray(this.conditionBuilder.Build(), Allocator.TempJob), + DistanceMultiplier = 1f + }))); } - private void FillBuilders(IPlanner agent,Transform transform) + private void FillBuilders(IPlanner agent, Transform transform) { this.executableBuilder.Clear(); this.positionBuilder.Clear(); @@ -62,10 +62,10 @@ private void FillBuilders(IPlanner agent,Transform transform) foreach (var node in agent.GetAllActions()) { - var allMet = true; + var allMet = true; foreach (var condition in node.Conditions) { - if (!agent.WorldState.InSet(condition.Key,condition.Value)) + if (!agent.WorldState.InSet(condition.Key, condition.Value)) { allMet = false; continue; @@ -74,15 +74,15 @@ private void FillBuilders(IPlanner agent,Transform transform) } this.executableBuilder.SetExecutable(node, allMet); this.costBuilder.SetCost(node, node.GetCost()); - this.positionBuilder.SetPosition(node, agent.WorldState.ResolveNodeTarget(node)?.position??transform.position); + this.positionBuilder.SetPosition(node, agent.WorldState.ResolveNodeTarget(node)?.position ?? transform.position); } } public void Complete() { - bool find=false; + bool find = false; foreach (var resolveHandle in this.resolveHandles) { - if(find) + if (find) { //Already search a plan, just complete resolveHandle.Handle.CompleteNonAlloc(ref resultCache); @@ -90,17 +90,19 @@ public void Complete() } resultCache.Clear(); resolveHandle.Handle.CompleteNonAlloc(ref resultCache); - if (resolveHandle.Agent==null) + if (planner == null) continue; - if(resultCache.Count!=0) + if (resultCache.Count != 0) { //Get candidate goal and action with highest priority - resolveHandle.Agent.SetCandidate(resultCache,resolveHandle.Goal); + planner.SetCandidate(resultCache, resolveHandle.Goal); //If not find, thus fall back to next handle - find=true; + find = true; } } this.resolveHandles.Clear(); + if (!find) + planner.SetCandidate(resultCache, null); } public void Dispose() @@ -109,20 +111,18 @@ public void Dispose() { resolveHandle.Handle.CompleteNonAlloc(ref resultCache); } - + this.resolver.Dispose(); } private struct JobRunHandle { - public GOAPPlannerPro Agent { get; } - public IGoal Goal{get;} + public IGoal Goal { get; } public IResolveHandle Handle { get; set; } - public JobRunHandle(GOAPPlannerPro agent,IGoal goal,IResolveHandle handle) + public JobRunHandle(IGoal goal, IResolveHandle handle) { - this.Agent = agent; this.Goal = goal; - this.Handle=handle; + this.Handle = handle; } } }