2026-06-06 20:12:40 +07:00
parent de84b2bf48
commit 97ac0f71f5
13682 changed files with 1125938 additions and 0 deletions
@@ -0,0 +1,240 @@
using System.Collections.Generic;
using UnityEngine;
namespace SynapticPro.GOAP
{
/// <summary>
/// Base class for all GOAP actions
/// Inherit from this to create custom actions
/// </summary>
public abstract class GOAPActionBase : MonoBehaviour
{
[Header("Action Settings")]
[SerializeField] protected string actionName = "Action";
[SerializeField] protected float cost = 1f;
[SerializeField] protected float duration = 1f;
[Header("Target")]
[SerializeField] protected GameObject target;
[SerializeField] protected bool requiresTarget = false;
[SerializeField] protected float targetRange = 2f;
/// <summary>
/// Preconditions that must be true for this action to run
/// </summary>
public Dictionary<string, object> Preconditions { get; protected set; } = new Dictionary<string, object>();
/// <summary>
/// Effects this action has on the world state
/// </summary>
public Dictionary<string, object> Effects { get; protected set; } = new Dictionary<string, object>();
/// <summary>
/// Cost of performing this action
/// </summary>
public float Cost
{
get => cost;
set => cost = Mathf.Max(0.01f, value);
}
/// <summary>
/// Name of this action
/// </summary>
public string ActionName => actionName;
/// <summary>
/// Duration of this action
/// </summary>
public float Duration => duration;
/// <summary>
/// Current target
/// </summary>
public GameObject Target
{
get => target;
set => target = value;
}
/// <summary>
/// Whether action is currently running
/// </summary>
public bool IsRunning { get; protected set; }
/// <summary>
/// Whether action has completed
/// </summary>
public bool IsDone { get; protected set; }
protected virtual void Awake()
{
SetupPreconditionsAndEffects();
}
/// <summary>
/// Override to setup preconditions and effects
/// </summary>
protected virtual void SetupPreconditionsAndEffects()
{
// Override in derived classes
}
/// <summary>
/// Add a precondition
/// </summary>
public void AddPrecondition(string key, object value)
{
if (!Preconditions.ContainsKey(key))
{
Preconditions.Add(key, value);
}
else
{
Preconditions[key] = value;
}
}
/// <summary>
/// Remove a precondition
/// </summary>
public void RemovePrecondition(string key)
{
if (Preconditions.ContainsKey(key))
{
Preconditions.Remove(key);
}
}
/// <summary>
/// Add an effect
/// </summary>
public void AddEffect(string key, object value)
{
if (!Effects.ContainsKey(key))
{
Effects.Add(key, value);
}
else
{
Effects[key] = value;
}
}
/// <summary>
/// Remove an effect
/// </summary>
public void RemoveEffect(string key)
{
if (Effects.ContainsKey(key))
{
Effects.Remove(key);
}
}
/// <summary>
/// Reset action state
/// </summary>
public virtual void Reset()
{
IsRunning = false;
IsDone = false;
}
/// <summary>
/// Check if agent is in range of target
/// </summary>
public bool IsInRange(GOAPAgent agent)
{
if (!requiresTarget || target == null)
{
return true;
}
float distance = Vector3.Distance(agent.transform.position, target.transform.position);
return distance <= targetRange;
}
/// <summary>
/// Check procedural preconditions (runtime checks)
/// Override for custom runtime conditions
/// </summary>
public virtual bool CheckProceduralPrecondition(GOAPAgent agent)
{
return true;
}
/// <summary>
/// Called before action starts
/// Return true if action can start
/// </summary>
public virtual bool PrePerform(GOAPAgent agent)
{
IsRunning = true;
IsDone = false;
return true;
}
/// <summary>
/// Called each frame while action is running
/// Return true when action is complete
/// </summary>
public abstract bool Perform(GOAPAgent agent);
/// <summary>
/// Called after action completes
/// Return true if action succeeded
/// </summary>
public virtual bool PostPerform(GOAPAgent agent)
{
IsRunning = false;
IsDone = true;
return true;
}
/// <summary>
/// Called when action is interrupted
/// </summary>
public virtual void OnInterrupted(GOAPAgent agent)
{
IsRunning = false;
IsDone = false;
}
/// <summary>
/// Get string representation
/// </summary>
public override string ToString()
{
return $"{actionName} (Cost: {cost})";
}
}
/// <summary>
/// Simple action that completes after duration
/// Use for basic actions without complex logic
/// </summary>
public class GOAPSimpleAction : GOAPActionBase
{
private float elapsedTime;
public override bool Perform(GOAPAgent agent)
{
elapsedTime += Time.deltaTime;
if (elapsedTime >= duration)
{
elapsedTime = 0f;
return true;
}
return false;
}
public override void Reset()
{
base.Reset();
elapsedTime = 0f;
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: dfd639f3261cb4d0f98ecac7725beec2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/Runtime/GOAP/GOAPActionBase.cs
uploadId: 920982
@@ -0,0 +1,555 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Events;
namespace SynapticPro.GOAP
{
/// <summary>
/// GOAP Agent - Main component that executes GOAP planning and actions
/// Attach to any GameObject to give it GOAP AI capabilities
/// </summary>
public class GOAPAgent : MonoBehaviour
{
[Header("Agent Settings")]
[SerializeField] private bool autoStart = true;
[SerializeField] private float replanInterval = 1f;
[SerializeField] private float actionTimeout = 30f;
[Header("Planning Settings")]
[SerializeField] private int maxPlanIterations = 1000;
[SerializeField] private int maxPlanDepth = 15;
[Header("Movement")]
[SerializeField] private float moveSpeed = 5f;
[SerializeField] private float stoppingDistance = 0.5f;
[Header("Debug")]
[SerializeField] private bool debugMode = false;
[Header("Events")]
public UnityEvent<GOAPGoal> OnGoalChanged;
public UnityEvent<GOAPActionBase> OnActionStarted;
public UnityEvent<GOAPActionBase> OnActionCompleted;
public UnityEvent<GOAPActionBase> OnActionFailed;
public UnityEvent OnPlanCreated;
public UnityEvent OnPlanFailed;
// Runtime state
private GOAPPlanner planner;
private WorldState worldState;
private HashSet<GOAPActionBase> availableActions;
private List<GOAPGoal> goals;
private Queue<GOAPActionBase> currentPlan;
private GOAPActionBase currentAction;
private GOAPGoal currentGoal;
private float lastPlanTime;
private float actionStartTime;
private bool isRunning;
/// <summary>
/// Current world state
/// </summary>
public WorldState WorldState => worldState;
/// <summary>
/// Currently executing action
/// </summary>
public GOAPActionBase CurrentAction => currentAction;
/// <summary>
/// Current goal being pursued
/// </summary>
public GOAPGoal CurrentGoal => currentGoal;
/// <summary>
/// Whether agent is currently running
/// </summary>
public bool IsRunning => isRunning;
/// <summary>
/// Whether agent has a valid plan
/// </summary>
public bool HasPlan => currentPlan != null && currentPlan.Count > 0;
/// <summary>
/// Movement speed
/// </summary>
public float MoveSpeed
{
get => moveSpeed;
set => moveSpeed = Mathf.Max(0, value);
}
private void Awake()
{
planner = new GOAPPlanner();
planner.SetMaxIterations(maxPlanIterations);
planner.SetMaxDepth(maxPlanDepth);
worldState = new WorldState();
availableActions = new HashSet<GOAPActionBase>();
goals = new List<GOAPGoal>();
currentPlan = new Queue<GOAPActionBase>();
// Collect actions from this GameObject
CollectActions();
// Collect goals from components
CollectGoals();
}
private void Start()
{
if (autoStart)
{
StartAgent();
}
}
private void Update()
{
if (!isRunning) return;
// Check if replanning is needed
if (Time.time - lastPlanTime > replanInterval)
{
TryReplan();
}
// Execute current action
ExecuteCurrentAction();
}
/// <summary>
/// Start the GOAP agent
/// </summary>
public void StartAgent()
{
isRunning = true;
TryReplan();
}
/// <summary>
/// Stop the GOAP agent
/// </summary>
public void StopAgent()
{
isRunning = false;
if (currentAction != null)
{
currentAction.OnInterrupted(this);
currentAction = null;
}
currentPlan?.Clear();
}
/// <summary>
/// Collect all GOAPActionBase components
/// </summary>
private void CollectActions()
{
var actions = GetComponents<GOAPActionBase>();
foreach (var action in actions)
{
availableActions.Add(action);
}
// Also check children
var childActions = GetComponentsInChildren<GOAPActionBase>();
foreach (var action in childActions)
{
availableActions.Add(action);
}
if (debugMode)
{
Debug.Log($"[GOAPAgent] Collected {availableActions.Count} actions");
}
}
/// <summary>
/// Collect all GOAPGoalComponent goals
/// </summary>
private void CollectGoals()
{
var goalComponents = GetComponents<GOAPGoalComponent>();
foreach (var gc in goalComponents)
{
goals.Add(gc.Goal);
}
// Also check children
var childGoals = GetComponentsInChildren<GOAPGoalComponent>();
foreach (var gc in childGoals)
{
if (!goals.Contains(gc.Goal))
{
goals.Add(gc.Goal);
}
}
if (debugMode)
{
Debug.Log($"[GOAPAgent] Collected {goals.Count} goals");
}
}
/// <summary>
/// Add an action at runtime
/// </summary>
public void AddAction(GOAPActionBase action)
{
if (action != null)
{
availableActions.Add(action);
}
}
/// <summary>
/// Remove an action at runtime
/// </summary>
public void RemoveAction(GOAPActionBase action)
{
if (action != null)
{
availableActions.Remove(action);
}
}
/// <summary>
/// Add a goal at runtime
/// </summary>
public void AddGoal(GOAPGoal goal)
{
if (goal != null && !goals.Contains(goal))
{
goals.Add(goal);
}
}
/// <summary>
/// Remove a goal at runtime
/// </summary>
public void RemoveGoal(GOAPGoal goal)
{
if (goal != null)
{
goals.Remove(goal);
if (currentGoal == goal)
{
currentGoal = null;
currentPlan?.Clear();
}
}
}
/// <summary>
/// Set world state value
/// </summary>
public void SetWorldState(string key, object value)
{
worldState.SetState(key, value);
}
/// <summary>
/// Get world state value
/// </summary>
public T GetWorldState<T>(string key)
{
return worldState.GetState<T>(key);
}
/// <summary>
/// Try to create a new plan
/// </summary>
private void TryReplan()
{
lastPlanTime = Time.time;
// Find highest priority goal
var bestGoal = SelectGoal();
if (bestGoal == null)
{
if (debugMode)
{
Debug.Log("[GOAPAgent] No active goals");
}
return;
}
// Check if goal already satisfied
if (bestGoal.IsSatisfied(worldState))
{
if (debugMode)
{
Debug.Log($"[GOAPAgent] Goal '{bestGoal.GoalName}' already satisfied");
}
bestGoal.OnAchieved(this);
return;
}
// Check if we need to replan
bool needsReplan = currentGoal != bestGoal || !HasPlan;
if (!needsReplan && currentAction != null)
{
// Check if current action is still valid
if (!currentAction.CheckProceduralPrecondition(this))
{
needsReplan = true;
}
}
if (needsReplan)
{
CreatePlan(bestGoal);
}
}
/// <summary>
/// Select the best goal to pursue
/// </summary>
private GOAPGoal SelectGoal()
{
GOAPGoal bestGoal = null;
float bestRelevance = float.MinValue;
foreach (var goal in goals)
{
if (!goal.IsActive) continue;
float relevance = goal.GetRelevance(this);
if (relevance > bestRelevance)
{
bestRelevance = relevance;
bestGoal = goal;
}
}
return bestGoal;
}
/// <summary>
/// Create a plan to achieve the goal
/// </summary>
private void CreatePlan(GOAPGoal goal)
{
if (currentAction != null)
{
currentAction.OnInterrupted(this);
currentAction = null;
}
if (currentGoal != null && currentGoal != goal)
{
currentGoal.OnDeactivate(this);
}
currentGoal = goal;
currentGoal.OnActivate(this);
OnGoalChanged?.Invoke(currentGoal);
var plan = planner.Plan(this, availableActions, worldState, goal);
if (plan != null && plan.Count > 0)
{
currentPlan = plan;
OnPlanCreated?.Invoke();
if (debugMode)
{
Debug.Log($"[GOAPAgent] Plan created for '{goal.GoalName}': {string.Join(" -> ", plan.Select(a => a.ActionName))}");
}
}
else
{
currentPlan = new Queue<GOAPActionBase>();
OnPlanFailed?.Invoke();
if (debugMode)
{
Debug.LogWarning($"[GOAPAgent] Failed to create plan for '{goal.GoalName}'");
}
}
}
/// <summary>
/// Execute the current action
/// </summary>
private void ExecuteCurrentAction()
{
if (currentAction == null)
{
// Get next action from plan
if (currentPlan != null && currentPlan.Count > 0)
{
currentAction = currentPlan.Dequeue();
actionStartTime = Time.time;
// Check if we need to move to target
if (!currentAction.IsInRange(this))
{
// Move towards target
MoveTowardsTarget(currentAction.Target);
return;
}
// Start action
if (currentAction.PrePerform(this))
{
OnActionStarted?.Invoke(currentAction);
if (debugMode)
{
Debug.Log($"[GOAPAgent] Started action: {currentAction.ActionName}");
}
}
else
{
// Action failed to start
OnActionFailed?.Invoke(currentAction);
currentAction = null;
currentPlan.Clear(); // Force replan
}
}
return;
}
// Check timeout
if (Time.time - actionStartTime > actionTimeout)
{
if (debugMode)
{
Debug.LogWarning($"[GOAPAgent] Action '{currentAction.ActionName}' timed out");
}
currentAction.OnInterrupted(this);
OnActionFailed?.Invoke(currentAction);
currentAction = null;
currentPlan.Clear();
return;
}
// Check if we need to move to target
if (!currentAction.IsInRange(this))
{
MoveTowardsTarget(currentAction.Target);
return;
}
// Execute action
bool completed = currentAction.Perform(this);
if (completed)
{
// Action completed
if (currentAction.PostPerform(this))
{
// Apply effects to world state
foreach (var effect in currentAction.Effects)
{
worldState.SetState(effect.Key, effect.Value);
}
OnActionCompleted?.Invoke(currentAction);
if (debugMode)
{
Debug.Log($"[GOAPAgent] Completed action: {currentAction.ActionName}");
}
}
else
{
OnActionFailed?.Invoke(currentAction);
}
currentAction = null;
// Check if goal is achieved
if (currentGoal != null && currentGoal.IsSatisfied(worldState))
{
currentGoal.OnAchieved(this);
if (debugMode)
{
Debug.Log($"[GOAPAgent] Goal achieved: {currentGoal.GoalName}");
}
}
}
}
/// <summary>
/// Move towards target
/// </summary>
private void MoveTowardsTarget(GameObject target)
{
if (target == null) return;
Vector3 direction = (target.transform.position - transform.position).normalized;
direction.y = 0; // Keep on ground
float distance = Vector3.Distance(transform.position, target.transform.position);
if (distance > stoppingDistance)
{
transform.position += direction * moveSpeed * Time.deltaTime;
transform.forward = direction;
}
}
/// <summary>
/// Force immediate replan
/// </summary>
public void ForceReplan()
{
if (currentAction != null)
{
currentAction.OnInterrupted(this);
currentAction = null;
}
currentPlan?.Clear();
TryReplan();
}
/// <summary>
/// Interrupt current action
/// </summary>
public void InterruptCurrentAction()
{
if (currentAction != null)
{
currentAction.OnInterrupted(this);
OnActionFailed?.Invoke(currentAction);
currentAction = null;
}
}
/// <summary>
/// Get remaining actions in plan
/// </summary>
public List<GOAPActionBase> GetRemainingPlan()
{
var remaining = new List<GOAPActionBase>();
if (currentAction != null)
{
remaining.Add(currentAction);
}
if (currentPlan != null)
{
remaining.AddRange(currentPlan);
}
return remaining;
}
private void OnDrawGizmosSelected()
{
if (!debugMode) return;
// Draw current action target
if (currentAction != null && currentAction.Target != null)
{
Gizmos.color = Color.yellow;
Gizmos.DrawLine(transform.position, currentAction.Target.transform.position);
Gizmos.DrawWireSphere(currentAction.Target.transform.position, 0.5f);
}
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: cb04014db39de4036863a1e3c469158a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/Runtime/GOAP/GOAPAgent.cs
uploadId: 920982
@@ -0,0 +1,505 @@
using System.Collections.Generic;
using UnityEngine;
namespace SynapticPro.GOAP
{
/// <summary>
/// GOAP Basic Behavior Pattern Template Collection
/// </summary>
public static class GOAPBehaviorTemplates
{
/// <summary>
/// 1. Guard AI - Patrol and Intruder Response
/// </summary>
public static BehaviorTemplate GuardTemplate = new BehaviorTemplate
{
Name = "Guard AI",
Description = "Patrols designated area and responds to threats",
Goals = new List<Goal>
{
new Goal { Name = "MaintainSecurity", Priority = 100 },
new Goal { Name = "PatrolArea", Priority = 80 },
new Goal { Name = "InvestigateDisturbance", Priority = 90 },
new Goal { Name = "EliminateThreat", Priority = 110 }
},
Actions = new List<GOAPAction>
{
new GOAPAction
{
Name = "PatrolWaypoints",
Cost = 1f,
Preconditions = new[] { "on_duty", "no_threats" },
Effects = new[] { "area_patrolled" }
},
new GOAPAction
{
Name = "InvestigateSound",
Cost = 1.5f,
Preconditions = new[] { "sound_detected" },
Effects = new[] { "sound_investigated" }
},
new GOAPAction
{
Name = "RaiseAlarm",
Cost = 0.5f,
Preconditions = new[] { "threat_confirmed" },
Effects = new[] { "alarm_raised", "backup_requested" }
},
new GOAPAction
{
Name = "EngageIntruder",
Cost = 2f,
Preconditions = new[] { "has_weapon", "intruder_in_range" },
Effects = new[] { "intruder_neutralized" }
},
new GOAPAction
{
Name = "CallBackup",
Cost = 0.3f,
Preconditions = new[] { "radio_available", "threat_detected" },
Effects = new[] { "backup_called" }
}
},
Sensors = new[] { "sound_detector", "motion_sensor", "threat_evaluator", "radio_checker" },
InitialWorldState = new Dictionary<string, object>
{
["on_duty"] = true,
["has_weapon"] = true,
["radio_available"] = true,
["patrol_route"] = "defined"
}
};
/// <summary>
/// 2. Collector Worker AI - Resource Gathering and Transportation
/// </summary>
public static BehaviorTemplate CollectorTemplate = new BehaviorTemplate
{
Name = "Collector AI",
Description = "Gathers resources and delivers them to storage",
Goals = new List<Goal>
{
new Goal { Name = "MaximizeResourceCollection", Priority = 100 },
new Goal { Name = "MaintainEfficiency", Priority = 70 },
new Goal { Name = "AvoidDanger", Priority = 90 }
},
Actions = new List<GOAPAction>
{
new GOAPAction
{
Name = "LocateResource",
Cost = 1f,
Preconditions = new[] { "inventory_not_full", "energy_available" },
Effects = new[] { "resource_located" }
},
new GOAPAction
{
Name = "MoveToResource",
Cost = 1.5f,
Preconditions = new[] { "resource_located" },
Effects = new[] { "at_resource" }
},
new GOAPAction
{
Name = "GatherResource",
Cost = 2f,
Preconditions = new[] { "at_resource", "has_tool" },
Effects = new[] { "resource_collected", "inventory_increased" }
},
new GOAPAction
{
Name = "ReturnToBase",
Cost = 1.5f,
Preconditions = new[] { "inventory_full" },
Effects = new[] { "at_base" }
},
new GOAPAction
{
Name = "DepositResource",
Cost = 0.5f,
Preconditions = new[] { "at_base", "has_resource" },
Effects = new[] { "resource_deposited", "inventory_empty" }
},
new GOAPAction
{
Name = "Rest",
Cost = 1f,
Preconditions = new[] { "energy_low" },
Effects = new[] { "energy_restored" }
},
new GOAPAction
{
Name = "FleeFromDanger",
Cost = 0.1f,
Preconditions = new[] { "danger_detected" },
Effects = new[] { "safe_distance" }
}
},
Sensors = new[] { "resource_scanner", "inventory_monitor", "energy_tracker", "danger_detector" },
InitialWorldState = new Dictionary<string, object>
{
["has_tool"] = true,
["inventory_capacity"] = 10,
["energy"] = 100,
["base_location"] = "known"
}
};
/// <summary>
/// 3. Combat Soldier AI - Tactical Combat and Cooperative Behavior
/// </summary>
public static BehaviorTemplate SoldierTemplate = new BehaviorTemplate
{
Name = "Combat Soldier AI",
Description = "Engages enemies tactically with squad coordination",
Goals = new List<Goal>
{
new Goal { Name = "EliminateEnemies", Priority = 100 },
new Goal { Name = "SurviveCombat", Priority = 95 },
new Goal { Name = "SupportSquad", Priority = 85 },
new Goal { Name = "SecureObjective", Priority = 90 }
},
Actions = new List<GOAPAction>
{
new GOAPAction
{
Name = "TakeCover",
Cost = 0.5f,
Preconditions = new[] { "under_fire", "cover_available" },
Effects = new[] { "in_cover", "damage_reduced" }
},
new GOAPAction
{
Name = "AimAndShoot",
Cost = 1f,
Preconditions = new[] { "enemy_visible", "has_ammo", "weapon_ready" },
Effects = new[] { "damage_dealt", "ammo_consumed" }
},
new GOAPAction
{
Name = "SuppressiveFire",
Cost = 2f,
Preconditions = new[] { "enemy_position_known", "ammo_sufficient" },
Effects = new[] { "enemy_suppressed" }
},
new GOAPAction
{
Name = "Reload",
Cost = 1.5f,
Preconditions = new[] { "ammo_low", "has_magazine" },
Effects = new[] { "weapon_reloaded" }
},
new GOAPAction
{
Name = "ThrowGrenade",
Cost = 2f,
Preconditions = new[] { "has_grenade", "enemy_clustered" },
Effects = new[] { "area_cleared" }
},
new GOAPAction
{
Name = "RequestMedic",
Cost = 0.3f,
Preconditions = new[] { "health_critical", "medic_available" },
Effects = new[] { "healing_requested" }
},
new GOAPAction
{
Name = "FlankEnemy",
Cost = 2.5f,
Preconditions = new[] { "flank_route_available", "squad_covering" },
Effects = new[] { "enemy_flanked" }
},
new GOAPAction
{
Name = "CoverAlly",
Cost = 1f,
Preconditions = new[] { "ally_needs_cover", "position_good" },
Effects = new[] { "ally_covered" }
}
},
Sensors = new[] { "enemy_tracker", "squad_coordinator", "ammo_counter", "health_monitor", "cover_finder" },
InitialWorldState = new Dictionary<string, object>
{
["weapon"] = "assault_rifle",
["ammo"] = 120,
["magazines"] = 4,
["grenades"] = 2,
["health"] = 100,
["squad_size"] = 4
}
};
/// <summary>
/// 4. Wildlife AI - Survival Instincts and Territorial Behavior
/// </summary>
public static BehaviorTemplate WildlifeTemplate = new BehaviorTemplate
{
Name = "Wildlife AI",
Description = "Survives through hunting, territorial behavior, and avoiding predators",
Goals = new List<Goal>
{
new Goal { Name = "Survive", Priority = 100 },
new Goal { Name = "FindFood", Priority = 90 },
new Goal { Name = "DefendTerritory", Priority = 70 },
new Goal { Name = "Reproduce", Priority = 60 }
},
Actions = new List<GOAPAction>
{
new GOAPAction
{
Name = "HuntPrey",
Cost = 2f,
Preconditions = new[] { "hungry", "prey_detected", "energy_sufficient" },
Effects = new[] { "food_obtained" }
},
new GOAPAction
{
Name = "Graze",
Cost = 1f,
Preconditions = new[] { "vegetation_available", "safe_area" },
Effects = new[] { "hunger_reduced" }
},
new GOAPAction
{
Name = "DrinkWater",
Cost = 0.5f,
Preconditions = new[] { "thirsty", "water_source_nearby" },
Effects = new[] { "thirst_quenched" }
},
new GOAPAction
{
Name = "FleeFromPredator",
Cost = 0.1f,
Preconditions = new[] { "predator_detected" },
Effects = new[] { "safe_from_predator" }
},
new GOAPAction
{
Name = "DefendTerritory",
Cost = 1.5f,
Preconditions = new[] { "intruder_in_territory", "strong_enough" },
Effects = new[] { "territory_secured" }
},
new GOAPAction
{
Name = "MarkTerritory",
Cost = 0.5f,
Preconditions = new[] { "territory_unmarked" },
Effects = new[] { "territory_marked" }
},
new GOAPAction
{
Name = "RestInDen",
Cost = 1f,
Preconditions = new[] { "tired", "den_available" },
Effects = new[] { "energy_restored" }
},
new GOAPAction
{
Name = "CallMate",
Cost = 1f,
Preconditions = new[] { "mating_season", "no_mate" },
Effects = new[] { "mate_attracted" }
}
},
Sensors = new[] { "smell_sensor", "hearing_sensor", "hunger_monitor", "thirst_monitor", "threat_detector", "territory_scanner" },
InitialWorldState = new Dictionary<string, object>
{
["species"] = "wolf",
["hunger"] = 50,
["thirst"] = 30,
["energy"] = 80,
["health"] = 100,
["territory_size"] = 100
}
};
/// <summary>
/// 5. Merchant NPC AI - Trading and Economic Activities
/// </summary>
public static BehaviorTemplate MerchantTemplate = new BehaviorTemplate
{
Name = "Merchant NPC AI",
Description = "Trades with players, manages inventory, and maximizes profit",
Goals = new List<Goal>
{
new Goal { Name = "MaximizeProfit", Priority = 100 },
new Goal { Name = "MaintainInventory", Priority = 80 },
new Goal { Name = "BuildReputation", Priority = 70 },
new Goal { Name = "StaySafe", Priority = 90 }
},
Actions = new List<GOAPAction>
{
new GOAPAction
{
Name = "GreetCustomer",
Cost = 0.2f,
Preconditions = new[] { "customer_nearby", "shop_open" },
Effects = new[] { "customer_engaged" }
},
new GOAPAction
{
Name = "NegotiatePrice",
Cost = 1f,
Preconditions = new[] { "customer_interested", "item_available" },
Effects = new[] { "price_negotiated" }
},
new GOAPAction
{
Name = "CompleteSale",
Cost = 0.5f,
Preconditions = new[] { "price_agreed", "item_in_stock" },
Effects = new[] { "sale_completed", "gold_increased" }
},
new GOAPAction
{
Name = "RestockInventory",
Cost = 3f,
Preconditions = new[] { "stock_low", "gold_sufficient" },
Effects = new[] { "inventory_restocked" }
},
new GOAPAction
{
Name = "HireGuard",
Cost = 2f,
Preconditions = new[] { "threat_level_high", "gold_available" },
Effects = new[] { "shop_protected" }
},
new GOAPAction
{
Name = "AdvertiseWares",
Cost = 1f,
Preconditions = new[] { "customers_few" },
Effects = new[] { "customers_attracted" }
},
new GOAPAction
{
Name = "CloseShop",
Cost = 0.5f,
Preconditions = new[] { "danger_imminent" },
Effects = new[] { "shop_secured" }
},
new GOAPAction
{
Name = "OfferDiscount",
Cost = 1.5f,
Preconditions = new[] { "inventory_excess", "customer_hesitant" },
Effects = new[] { "sale_likely" }
}
},
Sensors = new[] { "customer_detector", "inventory_tracker", "market_analyzer", "threat_assessor", "reputation_monitor" },
InitialWorldState = new Dictionary<string, object>
{
["gold"] = 1000,
["shop_location"] = "market_square",
["inventory_slots"] = 20,
["reputation"] = 50,
["shop_open"] = true
}
};
}
/// <summary>
/// Behavior Template Structure
/// </summary>
public class BehaviorTemplate
{
public string Name { get; set; }
public string Description { get; set; }
public List<Goal> Goals { get; set; }
public List<GOAPAction> Actions { get; set; }
public string[] Sensors { get; set; }
public Dictionary<string, object> InitialWorldState { get; set; }
}
/// <summary>
/// GOAP Goal Template Data (for serialization/templates)
/// Use GOAPGoal class for runtime goals
/// </summary>
public class Goal
{
public string Name { get; set; }
public float Priority { get; set; }
/// <summary>
/// Convert to runtime GOAPGoal
/// </summary>
public GOAPGoal ToRuntimeGoal()
{
return new GOAPGoal(Name, (int)Priority);
}
}
/// <summary>
/// GOAP Action Template Data (for serialization/templates)
/// Use GOAPActionBase or GOAPDynamicAction for runtime actions
/// </summary>
public class GOAPAction
{
public string Name { get; set; }
public float Cost { get; set; }
public string[] Preconditions { get; set; }
public string[] Effects { get; set; }
/// <summary>
/// Create runtime action from this template
/// </summary>
public GOAPDynamicAction CreateRuntimeAction(GameObject parent)
{
return GOAPActionFactory.CreateFromBehaviorData(
parent,
Name,
Cost,
Preconditions,
Effects
);
}
}
/// <summary>
/// Extension methods for applying templates to agents
/// </summary>
public static class BehaviorTemplateExtensions
{
/// <summary>
/// Apply a behavior template to a GOAPAgent
/// </summary>
public static void ApplyTemplate(this GOAPAgent agent, BehaviorTemplate template)
{
if (agent == null || template == null) return;
// Apply goals
if (template.Goals != null)
{
foreach (var goalData in template.Goals)
{
var goal = goalData.ToRuntimeGoal();
goal.IsActive = true;
agent.AddGoal(goal);
}
}
// Apply actions
if (template.Actions != null)
{
foreach (var actionData in template.Actions)
{
var action = actionData.CreateRuntimeAction(agent.gameObject);
agent.AddAction(action);
}
}
// Apply initial world state
if (template.InitialWorldState != null)
{
foreach (var kvp in template.InitialWorldState)
{
agent.SetWorldState(kvp.Key, kvp.Value);
}
}
Debug.Log($"[GOAP] Applied template '{template.Name}' to agent '{agent.name}'");
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 12bafd09c0eae4c659f870ecfb642515
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/Runtime/GOAP/GOAPBehaviorTemplates.cs
uploadId: 920982
@@ -0,0 +1,592 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace SynapticPro.GOAP
{
/// <summary>
/// Class for debugging and visualizing GOAP AI
/// </summary>
public class GOAPDebugVisualizer : MonoBehaviour
{
[Header("Debug Settings")]
public bool showCurrentGoal = true;
public bool showActionPlan = true;
public bool showWorldState = true;
public bool showDecisionGraph = false;
public bool showPerformanceMetrics = true;
[Header("Visual Settings")]
public Color goalColor = Color.green;
public Color actionColor = Color.blue;
public Color completedActionColor = Color.gray;
public Color failedActionColor = Color.red;
public float debugWindowWidth = 300f;
[Header("Performance")]
public int maxPlanDepth = 10;
public float planningTimeout = 0.1f; // 100ms
// Debug information
private GOAPDebugInfo debugInfo = new GOAPDebugInfo();
private Queue<PlanningMetrics> performanceHistory = new Queue<PlanningMetrics>(100);
// For GUI
private Vector2 scrollPosition;
private bool showDebugWindow = true;
void Start()
{
// Initialize demo data
InitializeDemoData();
}
void Update()
{
// Update debug information
UpdateDebugInfo();
// Performance measurement
if (showPerformanceMetrics && Time.frameCount % 60 == 0)
{
UpdatePerformanceMetrics();
}
}
void OnGUI()
{
if (!showDebugWindow) return;
// Draw debug window
GUILayout.Window(0, new Rect(10, 10, debugWindowWidth, 600), DrawDebugWindow, "GOAP Debug");
// World space visualization
if (showDecisionGraph)
{
DrawDecisionGraphOverlay();
}
}
void OnDrawGizmos()
{
if (!enabled) return;
// Visualize agent's current state
DrawAgentStatus();
// Visualize action execution status
if (showActionPlan)
{
DrawActionPlan();
}
}
private void DrawDebugWindow(int windowID)
{
scrollPosition = GUILayout.BeginScrollView(scrollPosition);
// Current goal
if (showCurrentGoal)
{
GUILayout.Label("=== Current Goal ===", GetBoldLabelStyle());
GUI.color = goalColor;
GUILayout.Label($"Goal: {debugInfo.currentGoal}");
GUILayout.Label($"Priority: {debugInfo.goalPriority}");
GUI.color = Color.white;
GUILayout.Space(10);
}
// Action plan
if (showActionPlan)
{
GUILayout.Label("=== Action Plan ===", GetBoldLabelStyle());
if (debugInfo.currentPlan != null && debugInfo.currentPlan.Count > 0)
{
for (int i = 0; i < debugInfo.currentPlan.Count; i++)
{
var action = debugInfo.currentPlan[i];
GUI.color = GetActionColor(action);
GUILayout.Label($"{i + 1}. {action.name} (Cost: {action.cost})");
if (i == debugInfo.currentActionIndex)
{
GUILayout.Label($" Status: {action.status}");
GUILayout.Label($" Progress: {action.progress:P}");
}
}
GUI.color = Color.white;
GUILayout.Label($"Total Cost: {debugInfo.planCost}");
}
else
{
GUILayout.Label("No active plan");
}
GUILayout.Space(10);
}
// World state
if (showWorldState)
{
GUILayout.Label("=== World State ===", GetBoldLabelStyle());
foreach (var state in debugInfo.worldState)
{
GUILayout.Label($"{state.Key}: {state.Value}");
}
GUILayout.Space(10);
}
// Performance metrics
if (showPerformanceMetrics)
{
GUILayout.Label("=== Performance ===", GetBoldLabelStyle());
GUILayout.Label($"Planning Time: {debugInfo.lastPlanningTime:F3}s");
GUILayout.Label($"Plan Attempts: {debugInfo.planAttempts}");
GUILayout.Label($"Graph Nodes: {debugInfo.graphNodes}");
GUILayout.Label($"Graph Edges: {debugInfo.graphEdges}");
GUILayout.Label($"Memory Usage: {debugInfo.memoryUsage:F2} MB");
// Performance graph
DrawPerformanceGraph();
GUILayout.Space(10);
}
// Control buttons
GUILayout.Label("=== Controls ===", GetBoldLabelStyle());
if (GUILayout.Button("Force Replan"))
{
ForceReplan();
}
if (GUILayout.Button("Clear Plan"))
{
ClearPlan();
}
if (GUILayout.Button("Export Debug Log"))
{
ExportDebugLog();
}
showDecisionGraph = GUILayout.Toggle(showDecisionGraph, "Show Decision Graph");
GUILayout.EndScrollView();
GUI.DragWindow();
}
private void DrawAgentStatus()
{
// Display state at agent position
Vector3 agentPos = transform.position + Vector3.up * 2f;
// Display current goal
if (!string.IsNullOrEmpty(debugInfo.currentGoal))
{
Gizmos.color = goalColor;
DrawString(agentPos, debugInfo.currentGoal, Color.green);
}
// Display current action
if (debugInfo.currentPlan != null && debugInfo.currentActionIndex >= 0 &&
debugInfo.currentActionIndex < debugInfo.currentPlan.Count)
{
var currentAction = debugInfo.currentPlan[debugInfo.currentActionIndex];
Gizmos.color = actionColor;
DrawString(agentPos + Vector3.down * 0.3f, $"Action: {currentAction.name}", actionColor);
}
// Display health bar etc.
if (debugInfo.worldState.ContainsKey("health"))
{
float health = Convert.ToSingle(debugInfo.worldState["health"]);
DrawHealthBar(transform.position + Vector3.up * 1.5f, health / 100f);
}
}
private void DrawActionPlan()
{
if (debugInfo.currentPlan == null) return;
Vector3 startPos = transform.position;
for (int i = 0; i < debugInfo.currentPlan.Count; i++)
{
var action = debugInfo.currentPlan[i];
Vector3 endPos = startPos + Vector3.forward * (i + 1) * 2f;
// Connection lines between actions
Gizmos.color = GetActionColor(action);
Gizmos.DrawLine(startPos, endPos);
// Action nodes
Gizmos.DrawWireSphere(endPos, 0.3f);
startPos = endPos;
}
}
private void DrawDecisionGraphOverlay()
{
// Display decision graph overlay
Rect graphRect = new Rect(Screen.width - 310, 10, 300, 300);
GUI.Box(graphRect, "Decision Graph");
// Graph drawing area
Rect graphArea = new Rect(graphRect.x + 10, graphRect.y + 30, graphRect.width - 20, graphRect.height - 40);
// Temporary graph data
DrawGraph(graphArea, debugInfo);
}
private void DrawGraph(Rect area, GOAPDebugInfo info)
{
// Draw nodes and edges
if (info.graphData != null)
{
foreach (var edge in info.graphData.edges)
{
Vector2 start = NodeToScreenPos(edge.from, area);
Vector2 end = NodeToScreenPos(edge.to, area);
DrawLine(start, end, Color.gray);
}
foreach (var node in info.graphData.nodes)
{
Vector2 pos = NodeToScreenPos(node.position, area);
GUI.color = node.isActive ? actionColor : Color.gray;
GUI.Box(new Rect(pos.x - 20, pos.y - 10, 40, 20), node.name);
}
GUI.color = Color.white;
}
}
private void DrawPerformanceGraph()
{
if (performanceHistory.Count < 2) return;
Rect graphRect = GUILayoutUtility.GetRect(280, 100);
GUI.Box(graphRect, "");
var metrics = performanceHistory.ToArray();
float maxTime = metrics.Max(m => m.planningTime);
for (int i = 1; i < metrics.Length; i++)
{
float x1 = graphRect.x + (i - 1) * graphRect.width / (metrics.Length - 1);
float y1 = graphRect.y + graphRect.height - (metrics[i - 1].planningTime / maxTime) * graphRect.height;
float x2 = graphRect.x + i * graphRect.width / (metrics.Length - 1);
float y2 = graphRect.y + graphRect.height - (metrics[i].planningTime / maxTime) * graphRect.height;
DrawLine(new Vector2(x1, y1), new Vector2(x2, y2), Color.cyan);
}
}
// Helper methods
private Color GetActionColor(ActionDebugInfo action)
{
switch (action.status)
{
case "completed": return completedActionColor;
case "failed": return failedActionColor;
case "executing": return actionColor;
default: return Color.white;
}
}
private void DrawString(Vector3 worldPos, string text, Color color)
{
#if UNITY_EDITOR
Handles.color = color;
Handles.Label(worldPos, text);
#endif
}
private void DrawHealthBar(Vector3 position, float percentage)
{
float width = 1f;
float height = 0.1f;
// Background
Gizmos.color = Color.red;
Gizmos.DrawCube(position, new Vector3(width, height, 0.01f));
// Health bar
Gizmos.color = Color.green;
Gizmos.DrawCube(position - Vector3.right * (width * (1f - percentage) / 2f),
new Vector3(width * percentage, height, 0.01f));
}
private void DrawLine(Vector2 start, Vector2 end, Color color)
{
var temp = GUI.color;
GUI.color = color;
float angle = Mathf.Atan2(end.y - start.y, end.x - start.x) * Mathf.Rad2Deg;
float dist = Vector2.Distance(start, end);
GUIUtility.RotateAroundPivot(angle, start);
GUI.DrawTexture(new Rect(start.x, start.y - 1, dist, 2), Texture2D.whiteTexture);
GUIUtility.RotateAroundPivot(-angle, start);
GUI.color = temp;
}
private Vector2 NodeToScreenPos(Vector2 nodePos, Rect area)
{
return new Vector2(
area.x + nodePos.x * area.width,
area.y + nodePos.y * area.height
);
}
// Initialize demo data
private void InitializeDemoData()
{
debugInfo.currentGoal = "PatrolArea";
debugInfo.goalPriority = 80;
debugInfo.planCost = 4.5f;
debugInfo.currentPlan = new List<ActionDebugInfo>
{
new ActionDebugInfo { name = "MoveTo", cost = 1f, status = "completed", progress = 1f },
new ActionDebugInfo { name = "LookAround", cost = 0.5f, status = "executing", progress = 0.6f },
new ActionDebugInfo { name = "MarkWaypoint", cost = 0.2f, status = "pending", progress = 0f }
};
debugInfo.currentActionIndex = 1;
debugInfo.worldState = new Dictionary<string, object>
{
["health"] = 85,
["has_weapon"] = true,
["enemies_nearby"] = 0,
["patrol_route"] = "defined",
["at_waypoint"] = true
};
debugInfo.graphNodes = 15;
debugInfo.graphEdges = 23;
debugInfo.lastPlanningTime = 0.012f;
debugInfo.planAttempts = 2;
debugInfo.memoryUsage = 1.2f;
// Graph data
debugInfo.graphData = new GraphData
{
nodes = new List<GraphNode>
{
new GraphNode { name = "Start", position = new Vector2(0.1f, 0.5f), isActive = true },
new GraphNode { name = "MoveTo", position = new Vector2(0.3f, 0.3f), isActive = true },
new GraphNode { name = "Attack", position = new Vector2(0.3f, 0.7f), isActive = false },
new GraphNode { name = "Goal", position = new Vector2(0.9f, 0.5f), isActive = false }
},
edges = new List<GraphEdge>
{
new GraphEdge { from = new Vector2(0.1f, 0.5f), to = new Vector2(0.3f, 0.3f) },
new GraphEdge { from = new Vector2(0.1f, 0.5f), to = new Vector2(0.3f, 0.7f) },
new GraphEdge { from = new Vector2(0.3f, 0.3f), to = new Vector2(0.9f, 0.5f) }
}
};
}
private void UpdateDebugInfo()
{
// Get actual GOAPAgent if available
var goapAgent = GetComponent<GOAPAgent>();
if (goapAgent == null) return;
// Update goal info
var currentGoal = goapAgent.CurrentGoal;
if (currentGoal != null)
{
debugInfo.currentGoal = currentGoal.GoalName;
debugInfo.goalPriority = currentGoal.Priority;
}
// Update current action info
var currentAction = goapAgent.CurrentAction;
if (currentAction != null)
{
var existingAction = debugInfo.currentPlan?.FirstOrDefault(a => a.name == currentAction.ActionName);
if (existingAction != null)
{
existingAction.status = "executing";
}
}
// Update world state from agent
if (goapAgent.WorldState != null)
{
var states = goapAgent.WorldState.GetAllStates();
debugInfo.worldState.Clear();
foreach (var kvp in states)
{
debugInfo.worldState[kvp.Key] = kvp.Value;
}
}
// Update plan info
var remainingPlan = goapAgent.GetRemainingPlan();
if (remainingPlan != null && remainingPlan.Count > 0)
{
debugInfo.currentPlan = remainingPlan.Select((a, i) => new ActionDebugInfo
{
name = a.ActionName,
cost = a.Cost,
status = i == 0 ? "executing" : "pending",
progress = i == 0 ? 0.5f : 0f
}).ToList();
debugInfo.currentActionIndex = 0;
debugInfo.planCost = remainingPlan.Sum(a => a.Cost);
}
}
private void UpdatePerformanceMetrics()
{
var metric = new PlanningMetrics
{
timestamp = Time.time,
planningTime = debugInfo.lastPlanningTime,
nodeCount = debugInfo.graphNodes,
memoryUsage = debugInfo.memoryUsage
};
performanceHistory.Enqueue(metric);
if (performanceHistory.Count > 100)
{
performanceHistory.Dequeue();
}
}
private GUIStyle _boldLabelStyle;
private GUIStyle GetBoldLabelStyle()
{
if (_boldLabelStyle == null)
{
_boldLabelStyle = new GUIStyle(GUI.skin.label);
_boldLabelStyle.fontStyle = FontStyle.Bold;
}
return _boldLabelStyle;
}
private void ForceReplan()
{
UnityEngine.Debug.Log("[GOAP Debug] Forcing replan...");
var goapAgent = GetComponent<GOAPAgent>();
if (goapAgent != null)
{
goapAgent.ForceReplan();
}
}
private void ClearPlan()
{
UnityEngine.Debug.Log("[GOAP Debug] Clearing current plan...");
debugInfo.currentPlan?.Clear();
debugInfo.currentActionIndex = -1;
}
private void ExportDebugLog()
{
string log = GenerateDebugLog();
string path = $"Assets/GOAP_Debug_{DateTime.Now:yyyyMMdd_HHmmss}.txt";
System.IO.File.WriteAllText(path, log);
UnityEngine.Debug.Log($"[GOAP Debug] Exported debug log to: {path}");
#if UNITY_EDITOR
AssetDatabase.Refresh();
#endif
}
private string GenerateDebugLog()
{
var log = new System.Text.StringBuilder();
log.AppendLine($"GOAP Debug Log - {DateTime.Now}");
log.AppendLine("=====================================");
log.AppendLine($"Agent: {gameObject.name}");
log.AppendLine($"Current Goal: {debugInfo.currentGoal}");
log.AppendLine($"Goal Priority: {debugInfo.goalPriority}");
log.AppendLine("\nCurrent Plan:");
if (debugInfo.currentPlan != null)
{
foreach (var action in debugInfo.currentPlan)
{
log.AppendLine($" - {action.name} (Cost: {action.cost}, Status: {action.status})");
}
}
log.AppendLine("\nWorld State:");
foreach (var state in debugInfo.worldState)
{
log.AppendLine($" - {state.Key}: {state.Value}");
}
log.AppendLine($"\nPerformance Metrics:");
log.AppendLine($" - Planning Time: {debugInfo.lastPlanningTime:F3}s");
log.AppendLine($" - Graph Nodes: {debugInfo.graphNodes}");
log.AppendLine($" - Memory Usage: {debugInfo.memoryUsage:F2} MB");
return log.ToString();
}
}
// Debug information structure
[System.Serializable]
public class GOAPDebugInfo
{
public string currentGoal;
public float goalPriority;
public List<ActionDebugInfo> currentPlan;
public int currentActionIndex;
public Dictionary<string, object> worldState = new Dictionary<string, object>();
public float planCost;
public float lastPlanningTime;
public int planAttempts;
public int graphNodes;
public int graphEdges;
public float memoryUsage;
public GraphData graphData;
}
[System.Serializable]
public class ActionDebugInfo
{
public string name;
public float cost;
public string status; // pending, executing, completed, failed
public float progress; // 0-1
}
[System.Serializable]
public class PlanningMetrics
{
public float timestamp;
public float planningTime;
public int nodeCount;
public float memoryUsage;
}
[System.Serializable]
public class GraphData
{
public List<GraphNode> nodes = new List<GraphNode>();
public List<GraphEdge> edges = new List<GraphEdge>();
}
[System.Serializable]
public class GraphNode
{
public string name;
public Vector2 position;
public bool isActive;
}
[System.Serializable]
public class GraphEdge
{
public Vector2 from;
public Vector2 to;
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 7c6f2b2fbd6984954b174a89e5af8967
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/Runtime/GOAP/GOAPDebugVisualizer.cs
uploadId: 920982
@@ -0,0 +1,315 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace SynapticPro.GOAP
{
/// <summary>
/// Dynamic GOAP Action - Can be configured at runtime via code
/// Used for procedurally generated behaviors from natural language
/// </summary>
public class GOAPDynamicAction : GOAPActionBase
{
private Func<GOAPAgent, bool> performFunc;
private Func<GOAPAgent, bool> checkPreconditionFunc;
private Action<GOAPAgent> onCompleteFunc;
private float elapsedTime;
/// <summary>
/// Configure the action at runtime
/// </summary>
public void Configure(
string name,
float actionCost,
float actionDuration,
Dictionary<string, object> preconditions,
Dictionary<string, object> effects)
{
actionName = name;
cost = actionCost;
duration = actionDuration;
if (preconditions != null)
{
foreach (var kvp in preconditions)
{
AddPrecondition(kvp.Key, kvp.Value);
}
}
if (effects != null)
{
foreach (var kvp in effects)
{
AddEffect(kvp.Key, kvp.Value);
}
}
}
/// <summary>
/// Set custom perform function
/// </summary>
public void SetPerformFunction(Func<GOAPAgent, bool> func)
{
performFunc = func;
}
/// <summary>
/// Set custom precondition check
/// </summary>
public void SetPreconditionCheck(Func<GOAPAgent, bool> func)
{
checkPreconditionFunc = func;
}
/// <summary>
/// Set on complete callback
/// </summary>
public void SetOnComplete(Action<GOAPAgent> func)
{
onCompleteFunc = func;
}
public override bool CheckProceduralPrecondition(GOAPAgent agent)
{
if (checkPreconditionFunc != null)
{
return checkPreconditionFunc(agent);
}
return base.CheckProceduralPrecondition(agent);
}
public override bool Perform(GOAPAgent agent)
{
if (performFunc != null)
{
return performFunc(agent);
}
// Default behavior: wait for duration
elapsedTime += Time.deltaTime;
if (elapsedTime >= duration)
{
elapsedTime = 0f;
return true;
}
return false;
}
public override bool PostPerform(GOAPAgent agent)
{
onCompleteFunc?.Invoke(agent);
return base.PostPerform(agent);
}
public override void Reset()
{
base.Reset();
elapsedTime = 0f;
}
}
/// <summary>
/// Factory for creating dynamic GOAP actions
/// </summary>
public static class GOAPActionFactory
{
/// <summary>
/// Create a simple wait action
/// </summary>
public static GOAPDynamicAction CreateWaitAction(GameObject parent, string name, float waitTime)
{
var action = parent.AddComponent<GOAPDynamicAction>();
action.Configure(name, 0.1f, waitTime, null, null);
return action;
}
/// <summary>
/// Create a movement action
/// </summary>
public static GOAPDynamicAction CreateMoveAction(
GameObject parent,
string name,
float cost,
string[] preconditions,
string[] effects)
{
var action = parent.AddComponent<GOAPDynamicAction>();
var precondDict = new Dictionary<string, object>();
foreach (var p in preconditions)
{
precondDict[p] = true;
}
var effectDict = new Dictionary<string, object>();
foreach (var e in effects)
{
effectDict[e] = true;
}
action.Configure(name, cost, 1f, precondDict, effectDict);
return action;
}
/// <summary>
/// Create action from parsed behavior data
/// </summary>
public static GOAPDynamicAction CreateFromBehaviorData(
GameObject parent,
string name,
float cost,
string[] preconditions,
string[] effects,
float duration = 1f)
{
var action = parent.AddComponent<GOAPDynamicAction>();
var precondDict = new Dictionary<string, object>();
if (preconditions != null)
{
foreach (var p in preconditions)
{
if (!string.IsNullOrEmpty(p))
{
precondDict[p] = true;
}
}
}
var effectDict = new Dictionary<string, object>();
if (effects != null)
{
foreach (var e in effects)
{
if (!string.IsNullOrEmpty(e))
{
effectDict[e] = true;
}
}
}
action.Configure(name, cost, duration, precondDict, effectDict);
return action;
}
/// <summary>
/// Create patrol action with waypoints
/// </summary>
public static GOAPDynamicAction CreatePatrolAction(GameObject parent, Transform[] waypoints)
{
var action = parent.AddComponent<GOAPDynamicAction>();
action.Configure("Patrol", 1f, 2f,
null,
new Dictionary<string, object> { ["patrolling"] = true });
int currentWaypoint = 0;
action.SetPerformFunction((agent) =>
{
if (waypoints == null || waypoints.Length == 0)
{
return true;
}
var target = waypoints[currentWaypoint];
if (target == null)
{
return true;
}
float distance = Vector3.Distance(agent.transform.position, target.position);
if (distance < 1f)
{
currentWaypoint = (currentWaypoint + 1) % waypoints.Length;
return true;
}
// Move towards waypoint
Vector3 direction = (target.position - agent.transform.position).normalized;
agent.transform.position += direction * agent.MoveSpeed * Time.deltaTime;
return false;
});
return action;
}
/// <summary>
/// Create attack action
/// </summary>
public static GOAPDynamicAction CreateAttackAction(GameObject parent, float damage, float attackRange)
{
var action = parent.AddComponent<GOAPDynamicAction>();
action.Configure("Attack", 1.5f, 1f,
new Dictionary<string, object>
{
["enemy_in_range"] = true,
["has_weapon"] = true
},
new Dictionary<string, object>
{
["damage_dealt"] = true
});
action.SetPerformFunction((agent) =>
{
// Attack logic here
Debug.Log($"[GOAP] {agent.name} performing attack");
return true; // Attack is instant
});
return action;
}
/// <summary>
/// Create flee action
/// </summary>
public static GOAPDynamicAction CreateFleeAction(GameObject parent)
{
var action = parent.AddComponent<GOAPDynamicAction>();
action.Configure("Flee", 0.5f, 3f,
new Dictionary<string, object>
{
["health_low"] = true
},
new Dictionary<string, object>
{
["is_safe"] = true
});
action.SetPerformFunction((agent) =>
{
// Find escape direction (opposite of threat)
var threat = GameObject.FindGameObjectWithTag("Enemy");
if (threat != null)
{
Vector3 fleeDirection = (agent.transform.position - threat.transform.position).normalized;
agent.transform.position += fleeDirection * agent.MoveSpeed * Time.deltaTime;
}
return false; // Keep fleeing
});
return action;
}
/// <summary>
/// Create collect resource action
/// </summary>
public static GOAPDynamicAction CreateCollectAction(GameObject parent)
{
var action = parent.AddComponent<GOAPDynamicAction>();
action.Configure("Collect", 1f, 2f,
new Dictionary<string, object>
{
["resource_nearby"] = true
},
new Dictionary<string, object>
{
["resource_collected"] = true
});
return action;
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 31800f015f500480f978cdc23a3e8799
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/Runtime/GOAP/GOAPDynamicAction.cs
uploadId: 920982
@@ -0,0 +1,267 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace SynapticPro.GOAP
{
/// <summary>
/// GOAP Goal - Represents a goal the agent wants to achieve
/// </summary>
[Serializable]
public class GOAPGoal
{
[SerializeField] private string goalName = "Goal";
[SerializeField] private int priority = 1;
[SerializeField] private bool isActive = true;
/// <summary>
/// Desired world state to achieve this goal
/// </summary>
public Dictionary<string, object> DesiredState { get; private set; } = new Dictionary<string, object>();
/// <summary>
/// Goal name for identification
/// </summary>
public string GoalName
{
get => goalName;
set => goalName = value;
}
/// <summary>
/// Priority (higher = more important)
/// </summary>
public int Priority
{
get => priority;
set => priority = Mathf.Max(0, value);
}
/// <summary>
/// Whether this goal is currently active
/// </summary>
public bool IsActive
{
get => isActive;
set => isActive = value;
}
/// <summary>
/// Create empty goal
/// </summary>
public GOAPGoal()
{
}
/// <summary>
/// Create goal with name and priority
/// </summary>
public GOAPGoal(string name, int priority = 1)
{
this.goalName = name;
this.priority = priority;
}
/// <summary>
/// Add a condition to the desired state
/// </summary>
public void AddCondition(string key, object value)
{
if (string.IsNullOrEmpty(key)) return;
if (DesiredState.ContainsKey(key))
{
DesiredState[key] = value;
}
else
{
DesiredState.Add(key, value);
}
}
/// <summary>
/// Remove a condition from desired state
/// </summary>
public void RemoveCondition(string key)
{
if (DesiredState.ContainsKey(key))
{
DesiredState.Remove(key);
}
}
/// <summary>
/// Check if goal is satisfied by current world state
/// </summary>
public bool IsSatisfied(WorldState worldState)
{
if (worldState == null) return false;
return worldState.Satisfies(DesiredState);
}
/// <summary>
/// Get relevance of this goal (can be overridden for dynamic priority)
/// </summary>
public virtual float GetRelevance(GOAPAgent agent)
{
return isActive ? priority : 0;
}
/// <summary>
/// Called when goal is activated
/// </summary>
public virtual void OnActivate(GOAPAgent agent)
{
}
/// <summary>
/// Called when goal is deactivated
/// </summary>
public virtual void OnDeactivate(GOAPAgent agent)
{
}
/// <summary>
/// Called when goal is achieved
/// </summary>
public virtual void OnAchieved(GOAPAgent agent)
{
}
/// <summary>
/// Clone this goal
/// </summary>
public GOAPGoal Clone()
{
var clone = new GOAPGoal(goalName, priority);
clone.isActive = isActive;
foreach (var kvp in DesiredState)
{
clone.DesiredState[kvp.Key] = kvp.Value;
}
return clone;
}
public override string ToString()
{
return $"{goalName} (Priority: {priority})";
}
}
/// <summary>
/// Goal that changes priority based on conditions
/// </summary>
public class DynamicGOAPGoal : GOAPGoal
{
private Func<GOAPAgent, float> relevanceCalculator;
public DynamicGOAPGoal(string name, Func<GOAPAgent, float> calculator) : base(name)
{
relevanceCalculator = calculator;
}
public override float GetRelevance(GOAPAgent agent)
{
if (!IsActive) return 0;
return relevanceCalculator?.Invoke(agent) ?? Priority;
}
}
/// <summary>
/// MonoBehaviour wrapper for GOAPGoal
/// Attach to agent to define goals in Inspector
/// </summary>
public class GOAPGoalComponent : MonoBehaviour
{
[Header("Goal Settings")]
[SerializeField] private string goalName = "Goal";
[SerializeField] private int priority = 1;
[SerializeField] private bool isActive = true;
[Header("Desired State")]
[SerializeField] private List<StateCondition> conditions = new List<StateCondition>();
private GOAPGoal _goal;
/// <summary>
/// Get the GOAPGoal instance
/// </summary>
public GOAPGoal Goal
{
get
{
if (_goal == null)
{
BuildGoal();
}
return _goal;
}
}
private void Awake()
{
BuildGoal();
}
private void BuildGoal()
{
_goal = new GOAPGoal(goalName, priority);
_goal.IsActive = isActive;
foreach (var condition in conditions)
{
if (!string.IsNullOrEmpty(condition.key))
{
_goal.AddCondition(condition.key, condition.GetValue());
}
}
}
/// <summary>
/// Refresh goal from inspector values
/// </summary>
public void RefreshGoal()
{
BuildGoal();
}
}
/// <summary>
/// Serializable state condition for Inspector
/// </summary>
[Serializable]
public class StateCondition
{
public string key;
public StateValueType valueType = StateValueType.Bool;
public bool boolValue = true;
public int intValue = 0;
public float floatValue = 0f;
public string stringValue = "";
public object GetValue()
{
switch (valueType)
{
case StateValueType.Bool:
return boolValue;
case StateValueType.Int:
return intValue;
case StateValueType.Float:
return floatValue;
case StateValueType.String:
return stringValue;
default:
return null;
}
}
}
public enum StateValueType
{
Bool,
Int,
Float,
String
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: df406242a59b2486cb7b79582e253dd4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/Runtime/GOAP/GOAPGoal.cs
uploadId: 920982
@@ -0,0 +1,310 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
namespace SynapticPro.GOAP
{
/// <summary>
/// GOAP Planner - A* based action planning system
/// Finds optimal sequence of actions to achieve goals
/// </summary>
public class GOAPPlanner
{
private int maxPlanningIterations = 1000;
private int maxPlanDepth = 15;
/// <summary>
/// Plan node for A* search
/// </summary>
private class PlanNode : IComparable<PlanNode>
{
public WorldState State;
public GOAPActionBase Action;
public PlanNode Parent;
public float GCost; // Cost from start
public float HCost; // Heuristic cost to goal
public float FCost => GCost + HCost;
public int Depth;
public int CompareTo(PlanNode other)
{
int compare = FCost.CompareTo(other.FCost);
if (compare == 0)
{
compare = HCost.CompareTo(other.HCost);
}
return compare;
}
}
/// <summary>
/// Create a plan to achieve the goal from current state
/// </summary>
/// <param name="agent">The GOAP agent</param>
/// <param name="availableActions">Actions the agent can perform</param>
/// <param name="currentState">Current world state</param>
/// <param name="goal">Goal to achieve</param>
/// <returns>Queue of actions to execute, or null if no plan found</returns>
public Queue<GOAPActionBase> Plan(
GOAPAgent agent,
HashSet<GOAPActionBase> availableActions,
WorldState currentState,
GOAPGoal goal)
{
if (goal == null || availableActions == null || availableActions.Count == 0)
{
return null;
}
// Reset actions
foreach (var action in availableActions)
{
action.Reset();
}
// Get usable actions (procedural preconditions check)
var usableActions = new HashSet<GOAPActionBase>();
foreach (var action in availableActions)
{
if (action.CheckProceduralPrecondition(agent))
{
usableActions.Add(action);
}
}
if (usableActions.Count == 0)
{
return null;
}
// A* search
var openList = new List<PlanNode>();
var closedSet = new HashSet<string>();
// Start node
var startNode = new PlanNode
{
State = currentState.Clone(),
Action = null,
Parent = null,
GCost = 0,
HCost = CalculateHeuristic(currentState, goal),
Depth = 0
};
openList.Add(startNode);
int iterations = 0;
PlanNode goalNode = null;
while (openList.Count > 0 && iterations < maxPlanningIterations)
{
iterations++;
// Get node with lowest F cost
openList.Sort();
var currentNode = openList[0];
openList.RemoveAt(0);
// Check if goal is satisfied
if (GoalSatisfied(currentNode.State, goal))
{
goalNode = currentNode;
break;
}
// Skip if max depth reached
if (currentNode.Depth >= maxPlanDepth)
{
continue;
}
// Generate state hash for closed set
string stateHash = currentNode.State.GetHashCode().ToString();
if (closedSet.Contains(stateHash))
{
continue;
}
closedSet.Add(stateHash);
// Expand node - try each action
foreach (var action in usableActions)
{
// Check if action's preconditions are met
if (!PreconditionsMet(currentNode.State, action))
{
continue;
}
// Apply action effects to create new state
var newState = currentNode.State.Clone();
ApplyEffects(newState, action);
// Create new node
var newNode = new PlanNode
{
State = newState,
Action = action,
Parent = currentNode,
GCost = currentNode.GCost + action.Cost,
HCost = CalculateHeuristic(newState, goal),
Depth = currentNode.Depth + 1
};
// Check if already in open list with better cost
string newStateHash = newState.GetHashCode().ToString();
if (!closedSet.Contains(newStateHash))
{
openList.Add(newNode);
}
}
}
// Build plan from goal node
if (goalNode != null)
{
return BuildPlan(goalNode);
}
return null;
}
/// <summary>
/// Calculate heuristic (estimated cost to goal)
/// </summary>
private float CalculateHeuristic(WorldState state, GOAPGoal goal)
{
if (goal.DesiredState == null || goal.DesiredState.Count == 0)
{
return 0;
}
int unsatisfiedConditions = 0;
foreach (var condition in goal.DesiredState)
{
if (!state.HasState(condition.Key) ||
!state.GetState<object>(condition.Key).Equals(condition.Value))
{
unsatisfiedConditions++;
}
}
return unsatisfiedConditions;
}
/// <summary>
/// Check if goal is satisfied by current state
/// </summary>
private bool GoalSatisfied(WorldState state, GOAPGoal goal)
{
if (goal.DesiredState == null || goal.DesiredState.Count == 0)
{
return true;
}
foreach (var condition in goal.DesiredState)
{
if (!state.HasState(condition.Key))
{
return false;
}
var currentValue = state.GetState<object>(condition.Key);
if (!currentValue.Equals(condition.Value))
{
return false;
}
}
return true;
}
/// <summary>
/// Check if action's preconditions are met
/// </summary>
private bool PreconditionsMet(WorldState state, GOAPActionBase action)
{
if (action.Preconditions == null || action.Preconditions.Count == 0)
{
return true;
}
foreach (var precondition in action.Preconditions)
{
if (!state.HasState(precondition.Key))
{
return false;
}
var currentValue = state.GetState<object>(precondition.Key);
if (!currentValue.Equals(precondition.Value))
{
return false;
}
}
return true;
}
/// <summary>
/// Apply action effects to state
/// </summary>
private void ApplyEffects(WorldState state, GOAPActionBase action)
{
if (action.Effects == null)
{
return;
}
foreach (var effect in action.Effects)
{
state.SetState(effect.Key, effect.Value);
}
}
/// <summary>
/// Build plan queue from goal node by backtracking
/// </summary>
private Queue<GOAPActionBase> BuildPlan(PlanNode goalNode)
{
var plan = new List<GOAPActionBase>();
var node = goalNode;
while (node != null)
{
if (node.Action != null)
{
plan.Add(node.Action);
}
node = node.Parent;
}
plan.Reverse();
var queue = new Queue<GOAPActionBase>();
foreach (var action in plan)
{
queue.Enqueue(action);
}
return queue;
}
/// <summary>
/// Set maximum planning iterations
/// </summary>
public void SetMaxIterations(int max)
{
maxPlanningIterations = Mathf.Max(100, max);
}
/// <summary>
/// Set maximum plan depth
/// </summary>
public void SetMaxDepth(int depth)
{
maxPlanDepth = Mathf.Max(1, depth);
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 797c33b5c03cd468f96d0ebacd20fc0b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/Runtime/GOAP/GOAPPlanner.cs
uploadId: 920982
@@ -0,0 +1,486 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using SynapticPro.GOAP;
namespace SynapticPro.GOAP
{
/// <summary>
/// GOAP Test Scene Controller
/// </summary>
public class GOAPTestSceneController : MonoBehaviour
{
[Header("Test Configuration")]
public bool autoCreateTestAgents = true;
public int numberOfGuards = 2;
public int numberOfCollectors = 1;
public int numberOfSoldiers = 1;
[Header("Scene Objects")]
public Transform[] patrolPoints;
public Transform[] resourcePoints;
public Transform[] coverPoints;
public Transform playerTransform;
[Header("Visual Feedback")]
public Material agentMaterial;
public Material guardMaterial;
public Material collectorMaterial;
public Material soldierMaterial;
private List<GameObject> testAgents = new List<GameObject>();
private Dictionary<string, BehaviorTemplate> availableTemplates;
void Start()
{
InitializeTemplates();
if (autoCreateTestAgents)
{
StartCoroutine(CreateTestEnvironment());
}
}
void Update()
{
// Keyboard shortcuts for testing
if (Input.GetKeyDown(KeyCode.F1))
{
CreateGuardAgent();
}
if (Input.GetKeyDown(KeyCode.F2))
{
CreateCollectorAgent();
}
if (Input.GetKeyDown(KeyCode.F3))
{
CreateSoldierAgent();
}
if (Input.GetKeyDown(KeyCode.F4))
{
TestNaturalLanguageBehavior();
}
if (Input.GetKeyDown(KeyCode.F5))
{
ShowPerformanceReport();
}
}
private void InitializeTemplates()
{
availableTemplates = new Dictionary<string, BehaviorTemplate>
{
["Guard"] = GOAPBehaviorTemplates.GuardTemplate,
["Collector"] = GOAPBehaviorTemplates.CollectorTemplate,
["Soldier"] = GOAPBehaviorTemplates.SoldierTemplate,
["Wildlife"] = GOAPBehaviorTemplates.WildlifeTemplate,
["Merchant"] = GOAPBehaviorTemplates.MerchantTemplate
};
}
private IEnumerator CreateTestEnvironment()
{
Debug.Log("[GOAP Test] Creating test environment...");
// Setup scene environment
yield return StartCoroutine(SetupSceneEnvironment());
// Create test agents
for (int i = 0; i < numberOfGuards; i++)
{
yield return new WaitForSeconds(0.5f);
CreateGuardAgent($"Guard_{i + 1}");
}
for (int i = 0; i < numberOfCollectors; i++)
{
yield return new WaitForSeconds(0.5f);
CreateCollectorAgent($"Collector_{i + 1}");
}
for (int i = 0; i < numberOfSoldiers; i++)
{
yield return new WaitForSeconds(0.5f);
CreateSoldierAgent($"Soldier_{i + 1}");
}
// Start demonstration
yield return new WaitForSeconds(2f);
StartDemonstration();
}
private IEnumerator SetupSceneEnvironment()
{
// Create patrol points
if (patrolPoints == null || patrolPoints.Length == 0)
{
CreatePatrolPoints();
}
// Create resource points
if (resourcePoints == null || resourcePoints.Length == 0)
{
CreateResourcePoints();
}
// Create cover points
if (coverPoints == null || coverPoints.Length == 0)
{
CreateCoverPoints();
}
// Create player object
if (playerTransform == null)
{
CreatePlayerObject();
}
yield return null;
}
private void CreatePatrolPoints()
{
var patrolPointsList = new List<Transform>();
Vector3[] positions = {
new Vector3(-5, 0, -5),
new Vector3(5, 0, -5),
new Vector3(5, 0, 5),
new Vector3(-5, 0, 5),
new Vector3(0, 0, 0)
};
for (int i = 0; i < positions.Length; i++)
{
var waypoint = CreateWaypoint($"PatrolPoint_{i + 1}", positions[i], Color.yellow);
patrolPointsList.Add(waypoint.transform);
}
patrolPoints = patrolPointsList.ToArray();
Debug.Log($"[GOAP Test] Created {patrolPoints.Length} patrol points");
}
private void CreateResourcePoints()
{
var resourcePointsList = new List<Transform>();
Vector3[] positions = {
new Vector3(-8, 0, 2),
new Vector3(8, 0, -2),
new Vector3(2, 0, 8),
new Vector3(-2, 0, -8)
};
for (int i = 0; i < positions.Length; i++)
{
var resource = CreateWaypoint($"ResourcePoint_{i + 1}", positions[i], Color.green);
resourcePointsList.Add(resource.transform);
}
resourcePoints = resourcePointsList.ToArray();
Debug.Log($"[GOAP Test] Created {resourcePoints.Length} resource points");
}
private void CreateCoverPoints()
{
var coverPointsList = new List<Transform>();
Vector3[] positions = {
new Vector3(-3, 0, 3),
new Vector3(3, 0, 3),
new Vector3(3, 0, -3),
new Vector3(-3, 0, -3)
};
for (int i = 0; i < positions.Length; i++)
{
var cover = CreateCoverObject($"CoverPoint_{i + 1}", positions[i]);
coverPointsList.Add(cover.transform);
}
coverPoints = coverPointsList.ToArray();
Debug.Log($"[GOAP Test] Created {coverPoints.Length} cover points");
}
private void CreatePlayerObject()
{
var player = GameObject.CreatePrimitive(PrimitiveType.Capsule);
player.name = "Player";
player.transform.position = new Vector3(0, 0, -10);
player.GetComponent<Renderer>().material.color = Color.blue;
// Add simple movement script
var moveScript = player.AddComponent<SimplePlayerController>();
playerTransform = player.transform;
Debug.Log("[GOAP Test] Created player object");
}
private GameObject CreateWaypoint(string name, Vector3 position, Color color)
{
var waypoint = GameObject.CreatePrimitive(PrimitiveType.Sphere);
waypoint.name = name;
waypoint.transform.position = position;
waypoint.transform.localScale = Vector3.one * 0.5f;
waypoint.GetComponent<Renderer>().material.color = color;
waypoint.GetComponent<Collider>().isTrigger = true;
return waypoint;
}
private GameObject CreateCoverObject(string name, Vector3 position)
{
var cover = GameObject.CreatePrimitive(PrimitiveType.Cube);
cover.name = name;
cover.transform.position = position;
cover.transform.localScale = new Vector3(1, 2, 1);
cover.GetComponent<Renderer>().material.color = Color.gray;
return cover;
}
private void CreateGuardAgent(string agentName = "Guard")
{
var agent = CreateBaseAgent(agentName, guardMaterial);
// Add GOAP debug visualizer
var debugVisualizer = agent.AddComponent<GOAPDebugVisualizer>();
debugVisualizer.showCurrentGoal = true;
debugVisualizer.showActionPlan = true;
debugVisualizer.showWorldState = true;
// Apply guard template (simulation)
ApplyTemplate(agent, availableTemplates["Guard"]);
testAgents.Add(agent);
Debug.Log($"[GOAP Test] Created guard agent: {agentName}");
}
private void CreateCollectorAgent(string agentName = "Collector")
{
var agent = CreateBaseAgent(agentName, collectorMaterial);
// Settings for resource collection agent
var debugVisualizer = agent.AddComponent<GOAPDebugVisualizer>();
debugVisualizer.goalColor = Color.green;
ApplyTemplate(agent, availableTemplates["Collector"]);
testAgents.Add(agent);
Debug.Log($"[GOAP Test] Created collector agent: {agentName}");
}
private void CreateSoldierAgent(string agentName = "Soldier")
{
var agent = CreateBaseAgent(agentName, soldierMaterial);
// Settings for combat agent
var debugVisualizer = agent.AddComponent<GOAPDebugVisualizer>();
debugVisualizer.goalColor = Color.red;
debugVisualizer.showPerformanceMetrics = true;
ApplyTemplate(agent, availableTemplates["Soldier"]);
testAgents.Add(agent);
Debug.Log($"[GOAP Test] Created soldier agent: {agentName}");
}
private GameObject CreateBaseAgent(string name, Material material)
{
var agent = GameObject.CreatePrimitive(PrimitiveType.Capsule);
agent.name = name;
// Place at random position
Vector3 randomPos = new Vector3(
Random.Range(-10f, 10f),
0.5f,
Random.Range(-10f, 10f)
);
agent.transform.position = randomPos;
// Set material
if (material != null)
{
agent.GetComponent<Renderer>().material = material;
}
else
{
agent.GetComponent<Renderer>().material.color = Random.ColorHSV();
}
// Basic navigation components
var rigidbody = agent.AddComponent<Rigidbody>();
rigidbody.constraints = RigidbodyConstraints.FreezeRotationX | RigidbodyConstraints.FreezeRotationZ;
return agent;
}
private void ApplyTemplate(GameObject agentObject, BehaviorTemplate template)
{
// Add GOAPAgent component and apply template
var goapAgent = agentObject.GetComponent<GOAPAgent>();
if (goapAgent == null)
{
goapAgent = agentObject.AddComponent<GOAPAgent>();
}
// Use the new extension method to apply template
goapAgent.ApplyTemplate(template);
// Also add legacy data component for compatibility
var agentData = agentObject.AddComponent<GOAPAgentData>();
agentData.template = template;
agentData.currentGoal = template.Goals[0].Name;
agentData.worldState = new Dictionary<string, object>(template.InitialWorldState);
Debug.Log($"[GOAP Test] Applied template '{template.Name}' to {agentObject.name} with GOAPAgent runtime");
}
private void TestNaturalLanguageBehavior()
{
Debug.Log("[GOAP Test] Testing natural language behavior definition...");
// Test natural language AI behavior definition
string[] testBehaviors = {
"Guard patrols between waypoints and attacks enemies on sight",
"Collector gathers resources efficiently and avoids danger",
"Soldier uses cover, suppresses enemies, and coordinates with squad",
"Animal hunts prey when hungry and flees from predators",
"Merchant maximizes profit through trading and negotiation"
};
foreach (var behavior in testBehaviors)
{
Debug.Log($"[GOAP Test] Parsing: '{behavior}'");
// Add parse logic test here
}
}
private void ShowPerformanceReport()
{
Debug.Log("[GOAP Test] === Performance Report ===");
Debug.Log($"Active Agents: {testAgents.Count}");
foreach (var agent in testAgents)
{
if (agent != null)
{
var visualizer = agent.GetComponent<GOAPDebugVisualizer>();
if (visualizer != null)
{
Debug.Log($"{agent.name}: Planning efficient, Decisions responsive");
}
}
}
Debug.Log($"Frame Rate: {(1.0f / Time.deltaTime):F1} FPS");
Debug.Log($"Memory Usage: {UnityEngine.Profiling.Profiler.GetTotalAllocatedMemoryLong() / 1024 / 1024} MB");
}
private void StartDemonstration()
{
Debug.Log("[GOAP Test] Starting GOAP demonstration...");
// Demonstration events
StartCoroutine(DemonstrationSequence());
}
private IEnumerator DemonstrationSequence()
{
Debug.Log("[GOAP Test] Phase 1: Normal patrol and collection");
yield return new WaitForSeconds(5f);
Debug.Log("[GOAP Test] Phase 2: Simulating threat detection");
SimulateThreatDetection();
yield return new WaitForSeconds(10f);
Debug.Log("[GOAP Test] Phase 3: Resource scarcity simulation");
SimulateResourceScarcity();
yield return new WaitForSeconds(10f);
Debug.Log("[GOAP Test] Demonstration complete. Press F1-F5 for manual tests.");
}
private void SimulateThreatDetection()
{
// Simulate threat detection
foreach (var agent in testAgents)
{
if (agent != null && agent.name.Contains("Guard"))
{
var agentData = agent.GetComponent<GOAPAgentData>();
if (agentData != null)
{
agentData.worldState["threat_detected"] = true;
Debug.Log($"[GOAP Test] {agent.name} detected threat");
}
}
}
}
private void SimulateResourceScarcity()
{
// Simulate resource shortage
foreach (var agent in testAgents)
{
if (agent != null && agent.name.Contains("Collector"))
{
var agentData = agent.GetComponent<GOAPAgentData>();
if (agentData != null)
{
agentData.worldState["resources_scarce"] = true;
Debug.Log($"[GOAP Test] {agent.name} facing resource scarcity");
}
}
}
}
void OnGUI()
{
// Test GUI
GUILayout.BeginArea(new Rect(10, Screen.height - 150, 300, 140));
GUILayout.Box("GOAP Test Controls");
if (GUILayout.Button("F1: Create Guard"))
CreateGuardAgent();
if (GUILayout.Button("F2: Create Collector"))
CreateCollectorAgent();
if (GUILayout.Button("F3: Create Soldier"))
CreateSoldierAgent();
if (GUILayout.Button("F4: Test Natural Language"))
TestNaturalLanguageBehavior();
if (GUILayout.Button("F5: Performance Report"))
ShowPerformanceReport();
GUILayout.EndArea();
}
}
/// <summary>
/// Component for holding GOAP agent data
/// </summary>
public class GOAPAgentData : MonoBehaviour
{
public BehaviorTemplate template;
public string currentGoal;
public Dictionary<string, object> worldState = new Dictionary<string, object>();
public List<string> currentPlan = new List<string>();
}
/// <summary>
/// Simple player controller
/// </summary>
public class SimplePlayerController : MonoBehaviour
{
public float speed = 5f;
void Update()
{
float horizontal = Input.GetAxis("Horizontal");
float vertical = Input.GetAxis("Vertical");
Vector3 movement = new Vector3(horizontal, 0, vertical) * speed * Time.deltaTime;
transform.Translate(movement);
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: ff2badc2a80764a7d93a003f17c9c99f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/Runtime/GOAP/GOAPTestSceneController.cs
uploadId: 920982
@@ -0,0 +1,213 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
namespace SynapticPro.GOAP
{
/// <summary>
/// World State - Manages the state of the world for GOAP planning
/// </summary>
[Serializable]
public class WorldState
{
private Dictionary<string, object> states = new Dictionary<string, object>();
/// <summary>
/// Set a state value
/// </summary>
public void SetState(string key, object value)
{
if (string.IsNullOrEmpty(key)) return;
if (states.ContainsKey(key))
{
states[key] = value;
}
else
{
states.Add(key, value);
}
}
/// <summary>
/// Get a state value
/// </summary>
public T GetState<T>(string key)
{
if (states.TryGetValue(key, out var value))
{
if (value is T typedValue)
{
return typedValue;
}
// Try conversion
try
{
return (T)Convert.ChangeType(value, typeof(T));
}
catch
{
return default(T);
}
}
return default(T);
}
/// <summary>
/// Check if state exists
/// </summary>
public bool HasState(string key)
{
return states.ContainsKey(key);
}
/// <summary>
/// Remove a state
/// </summary>
public void RemoveState(string key)
{
if (states.ContainsKey(key))
{
states.Remove(key);
}
}
/// <summary>
/// Clear all states
/// </summary>
public void Clear()
{
states.Clear();
}
/// <summary>
/// Get all states as dictionary
/// </summary>
public Dictionary<string, object> GetAllStates()
{
return new Dictionary<string, object>(states);
}
/// <summary>
/// Clone this world state
/// </summary>
public WorldState Clone()
{
var clone = new WorldState();
foreach (var kvp in states)
{
clone.states[kvp.Key] = kvp.Value;
}
return clone;
}
/// <summary>
/// Apply another state on top of this one
/// </summary>
public void ApplyState(WorldState other)
{
if (other == null) return;
foreach (var kvp in other.states)
{
states[kvp.Key] = kvp.Value;
}
}
/// <summary>
/// Check if this state satisfies the conditions
/// </summary>
public bool Satisfies(Dictionary<string, object> conditions)
{
if (conditions == null || conditions.Count == 0)
{
return true;
}
foreach (var condition in conditions)
{
if (!HasState(condition.Key))
{
return false;
}
var currentValue = states[condition.Key];
if (!ValuesEqual(currentValue, condition.Value))
{
return false;
}
}
return true;
}
/// <summary>
/// Compare two values for equality
/// </summary>
private bool ValuesEqual(object a, object b)
{
if (a == null && b == null) return true;
if (a == null || b == null) return false;
// Handle numeric comparisons
if (IsNumeric(a) && IsNumeric(b))
{
return Convert.ToDouble(a) == Convert.ToDouble(b);
}
return a.Equals(b);
}
private bool IsNumeric(object obj)
{
return obj is int || obj is float || obj is double || obj is long || obj is short || obj is byte;
}
/// <summary>
/// Get hash code for state comparison
/// </summary>
public override int GetHashCode()
{
int hash = 17;
foreach (var kvp in states.OrderBy(x => x.Key))
{
hash = hash * 31 + kvp.Key.GetHashCode();
hash = hash * 31 + (kvp.Value?.GetHashCode() ?? 0);
}
return hash;
}
/// <summary>
/// String representation for debugging
/// </summary>
public override string ToString()
{
var sb = new System.Text.StringBuilder();
sb.AppendLine("WorldState:");
foreach (var kvp in states)
{
sb.AppendLine($" {kvp.Key}: {kvp.Value}");
}
return sb.ToString();
}
/// <summary>
/// Initialize from dictionary
/// </summary>
public static WorldState FromDictionary(Dictionary<string, object> dict)
{
var state = new WorldState();
if (dict != null)
{
foreach (var kvp in dict)
{
state.SetState(kvp.Key, kvp.Value);
}
}
return state;
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 7bf0acbd42f3c4e70bb6b60fa7dd062e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/Runtime/GOAP/WorldState.cs
uploadId: 920982