Add most of research
This commit is contained in:
71
GOAP/Assets/Scrips/GOAP/AI_Action.cs
Normal file
71
GOAP/Assets/Scrips/GOAP/AI_Action.cs
Normal file
@@ -0,0 +1,71 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.AI;
|
||||
|
||||
public abstract class AI_Action : MonoBehaviour
|
||||
{
|
||||
public string actionName = "Undefined Action";
|
||||
public float cost = 1.0f;
|
||||
public GameObject target;
|
||||
public string targetTag;
|
||||
public float duration = 0;
|
||||
public AIState[] preConditions;
|
||||
public AIState[] afterEffects;
|
||||
public NavMeshAgent agent;
|
||||
|
||||
public Dictionary<string, int> preConditionsDict;
|
||||
public Dictionary<string, int> effectsDict;
|
||||
|
||||
public AI_Inventory inventory;
|
||||
public AIStates agentWorldStates;
|
||||
|
||||
public bool isRunning = false;
|
||||
|
||||
public AI_Action()
|
||||
{
|
||||
preConditionsDict = new Dictionary<string, int>();
|
||||
effectsDict = new Dictionary<string, int>();
|
||||
}
|
||||
|
||||
public void Awake()
|
||||
{
|
||||
agent = GetComponent<NavMeshAgent>();
|
||||
|
||||
if(preConditions != null)
|
||||
{
|
||||
foreach (AIState w in preConditions)
|
||||
{
|
||||
preConditionsDict.Add(w.key, w.value);
|
||||
}
|
||||
}
|
||||
|
||||
if (afterEffects != null)
|
||||
{
|
||||
foreach (AIState w in afterEffects)
|
||||
{
|
||||
effectsDict.Add(w.key, w.value);
|
||||
}
|
||||
}
|
||||
|
||||
inventory = GetComponent<AI_Agent>().inventory;
|
||||
agentWorldStates = GetComponent<AI_Agent>().agentWorldStates;
|
||||
}
|
||||
|
||||
public bool IsAchievable()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool IsAchievableGiven(Dictionary<string, int> conditions)
|
||||
{
|
||||
foreach(KeyValuePair<string, int> p in preConditionsDict)
|
||||
{
|
||||
if (!conditions.ContainsKey(p.Key))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public abstract bool PrePerform();
|
||||
public abstract bool PostPerform();
|
||||
}
|
||||
2
GOAP/Assets/Scrips/GOAP/AI_Action.cs.meta
Normal file
2
GOAP/Assets/Scrips/GOAP/AI_Action.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e9e7c04605c1fa4438b738c124267f4c
|
||||
135
GOAP/Assets/Scrips/GOAP/AI_Agent.cs
Normal file
135
GOAP/Assets/Scrips/GOAP/AI_Agent.cs
Normal file
@@ -0,0 +1,135 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
public class SubGoal
|
||||
{
|
||||
public Dictionary<string, int> goals;
|
||||
public bool shouldRemove;
|
||||
|
||||
public SubGoal(string goalName, int value, bool shouldRemove)
|
||||
{
|
||||
goals = new Dictionary<string, int>
|
||||
{
|
||||
{ goalName, value }
|
||||
};
|
||||
this.shouldRemove = shouldRemove;
|
||||
}
|
||||
}
|
||||
|
||||
public class AI_Agent : MonoBehaviour
|
||||
{
|
||||
public List<AI_Action> actions = new List<AI_Action>();
|
||||
public Dictionary<SubGoal, int> goals = new Dictionary<SubGoal, int>();
|
||||
public AI_Inventory inventory = new AI_Inventory();
|
||||
public AIStates agentWorldStates = new AIStates();
|
||||
|
||||
public float taskRange = 2.5f;
|
||||
|
||||
protected AI_Planner planner;
|
||||
protected Queue<AI_Action> actionQueue;
|
||||
protected AI_Action currentAction;
|
||||
protected SubGoal currentGoal;
|
||||
protected bool actionInvoked;
|
||||
|
||||
protected virtual void Start()
|
||||
{
|
||||
AI_Action[] actionsArray = GetComponents<AI_Action>();
|
||||
foreach (AI_Action action in actionsArray)
|
||||
{
|
||||
actions.Add(action);
|
||||
}
|
||||
}
|
||||
|
||||
private void CompleteAction()
|
||||
{
|
||||
currentAction.isRunning = false;
|
||||
currentAction.PostPerform();
|
||||
actionInvoked = false;
|
||||
}
|
||||
|
||||
private void OnDrawGizmos()
|
||||
{
|
||||
Gizmos.color = Color.yellow;
|
||||
Gizmos.DrawWireSphere(transform.position, taskRange);
|
||||
}
|
||||
|
||||
private void LateUpdate()
|
||||
{
|
||||
if(currentAction != null && currentAction.isRunning)
|
||||
{
|
||||
float distanceToTarget = Vector3.Distance(currentAction.target.transform.position, transform.position);
|
||||
if(currentAction.agent.hasPath && distanceToTarget < taskRange)
|
||||
{
|
||||
if(!actionInvoked)
|
||||
{
|
||||
Invoke(nameof(CompleteAction), currentAction.duration);
|
||||
actionInvoked = true;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if(planner == null || actionQueue == null)
|
||||
{
|
||||
planner = new AI_Planner();
|
||||
|
||||
var sortedGoals = from entry in goals orderby entry.Value descending select entry;
|
||||
|
||||
foreach(KeyValuePair<SubGoal, int> subGoal in sortedGoals)
|
||||
{
|
||||
actionQueue = planner.Plan(actions, subGoal.Key.goals, agentWorldStates);
|
||||
if (actionQueue != null)
|
||||
{
|
||||
currentGoal = subGoal.Key;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(actionQueue != null && actionQueue.Count == 0)
|
||||
{
|
||||
if(currentGoal.shouldRemove)
|
||||
{
|
||||
goals.Remove(currentGoal);
|
||||
}
|
||||
planner = null;
|
||||
}
|
||||
|
||||
if (actionQueue != null && actionQueue.Count > 0)
|
||||
{
|
||||
currentAction = actionQueue.Dequeue();
|
||||
if (currentAction.PrePerform())
|
||||
{
|
||||
if (currentAction.target == null && !string.IsNullOrEmpty(currentAction.targetTag))
|
||||
{
|
||||
currentAction.target = GameObject.FindWithTag(currentAction.targetTag);
|
||||
}
|
||||
|
||||
if (currentAction.target != null)
|
||||
{
|
||||
currentAction.isRunning = true;
|
||||
|
||||
// Check for AI_Area component
|
||||
AI_Area area = currentAction.target.GetComponent<AI_Area>();
|
||||
Vector3 destination;
|
||||
|
||||
if (area != null)
|
||||
{
|
||||
destination = area.GetRandomPointInArea();
|
||||
}
|
||||
else
|
||||
{
|
||||
destination = currentAction.target.transform.position;
|
||||
}
|
||||
|
||||
currentAction.agent.SetDestination(destination);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
actionQueue = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
2
GOAP/Assets/Scrips/GOAP/AI_Agent.cs.meta
Normal file
2
GOAP/Assets/Scrips/GOAP/AI_Agent.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c48e5729f78aac64c8dbc08eb09aa525
|
||||
49
GOAP/Assets/Scrips/GOAP/AI_Inventory.cs
Normal file
49
GOAP/Assets/Scrips/GOAP/AI_Inventory.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
public class AI_Inventory
|
||||
{
|
||||
private List<GameObject> items = new List<GameObject>();
|
||||
|
||||
public void AddItem(GameObject item)
|
||||
{
|
||||
items.Add(item);
|
||||
}
|
||||
|
||||
public GameObject FindItemWithTag(string tag)
|
||||
{
|
||||
foreach(GameObject item in items)
|
||||
{
|
||||
if(item.CompareTag(tag))
|
||||
{
|
||||
return item;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void RemoveItem(GameObject item)
|
||||
{
|
||||
int indexToRemove = items.IndexOf(item);
|
||||
if(indexToRemove >= 0)
|
||||
{
|
||||
items.RemoveAt(indexToRemove);
|
||||
}
|
||||
}
|
||||
|
||||
// Additional helper methods
|
||||
public bool ContainsItem(GameObject item)
|
||||
{
|
||||
return items.Contains(item);
|
||||
}
|
||||
|
||||
public void ClearInventory()
|
||||
{
|
||||
items.Clear();
|
||||
}
|
||||
|
||||
public int ItemCount
|
||||
{
|
||||
get { return items.Count; }
|
||||
}
|
||||
}
|
||||
3
GOAP/Assets/Scrips/GOAP/AI_Inventory.cs.meta
Normal file
3
GOAP/Assets/Scrips/GOAP/AI_Inventory.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 06be48fd5f3a4298a422a72827aa61f0
|
||||
timeCreated: 1748549167
|
||||
154
GOAP/Assets/Scrips/GOAP/AI_Planner.cs
Normal file
154
GOAP/Assets/Scrips/GOAP/AI_Planner.cs
Normal file
@@ -0,0 +1,154 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
public class PlanNode
|
||||
{
|
||||
public PlanNode parent;
|
||||
public float cost;
|
||||
public Dictionary<string, int> states;
|
||||
public AI_Action action;
|
||||
|
||||
public PlanNode(PlanNode parent, float cost, Dictionary<string, int> allStates, AI_Action action)
|
||||
{
|
||||
this.parent = parent;
|
||||
this.cost = cost;
|
||||
states = new Dictionary<string, int>(allStates);
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
public PlanNode(PlanNode parent, float cost, Dictionary<string, int> allStates,
|
||||
Dictionary<string, int> agentStates, AI_Action action)
|
||||
{
|
||||
this.parent = parent;
|
||||
this.cost = cost;
|
||||
states = new Dictionary<string, int>(allStates);
|
||||
|
||||
foreach(KeyValuePair<string, int> state in agentStates)
|
||||
if(!states.ContainsKey(state.Key))
|
||||
states.Add(state.Key, state.Value);
|
||||
|
||||
this.action = action;
|
||||
}
|
||||
}
|
||||
|
||||
public class AI_Planner
|
||||
{
|
||||
private string planDescription;
|
||||
|
||||
public Queue<AI_Action> Plan(List<AI_Action> actions, Dictionary<string, int> goal, AIStates agentStates)
|
||||
{
|
||||
List<AI_Action> usableActions = new List<AI_Action>();
|
||||
foreach (AI_Action action in actions)
|
||||
{
|
||||
if(action.IsAchievable())
|
||||
usableActions.Add(action);
|
||||
}
|
||||
|
||||
List<PlanNode> leaves = new List<PlanNode>();
|
||||
PlanNode start = new PlanNode(null, 0, AI_World.Instance.GetWorld().GetStates(),
|
||||
agentStates.GetStates(), null);
|
||||
|
||||
bool success = BuildGraph(start, leaves, usableActions, goal);
|
||||
|
||||
if(!success)
|
||||
{
|
||||
Debug.Log("No plan could be constructed");
|
||||
return null;
|
||||
}
|
||||
|
||||
PlanNode cheapest = null;
|
||||
foreach (PlanNode leaf in leaves)
|
||||
{
|
||||
if (cheapest == null)
|
||||
cheapest = leaf;
|
||||
else if (leaf.cost < cheapest.cost)
|
||||
cheapest = leaf;
|
||||
}
|
||||
|
||||
List<AI_Action> result = new List<AI_Action>();
|
||||
PlanNode n = cheapest;
|
||||
while (n != null)
|
||||
{
|
||||
if(n.action != null)
|
||||
{
|
||||
result.Insert(0, n.action);
|
||||
}
|
||||
n = n.parent;
|
||||
}
|
||||
|
||||
Queue<AI_Action> queue = new Queue<AI_Action>();
|
||||
foreach(AI_Action action in result)
|
||||
{
|
||||
queue.Enqueue(action);
|
||||
}
|
||||
|
||||
planDescription = "";
|
||||
int index = 0;
|
||||
foreach(AI_Action action in queue)
|
||||
{
|
||||
planDescription += index + ": " + action.actionName + "\n";
|
||||
index++;
|
||||
}
|
||||
|
||||
return queue;
|
||||
}
|
||||
|
||||
private bool BuildGraph(PlanNode parent, List<PlanNode> leaves, List<AI_Action> usableActions,
|
||||
Dictionary<string, int> goal)
|
||||
{
|
||||
bool foundPath = false;
|
||||
foreach(AI_Action action in usableActions)
|
||||
{
|
||||
if(action.IsAchievableGiven(parent.states))
|
||||
{
|
||||
Dictionary<string, int> currentState = new Dictionary<string, int>(parent.states);
|
||||
foreach(KeyValuePair<string, int> effect in action.effectsDict)
|
||||
if(!currentState.ContainsKey(effect.Key))
|
||||
currentState.Add(effect.Key, effect.Value);
|
||||
|
||||
PlanNode node = new PlanNode(parent, parent.cost + action.cost, currentState, action);
|
||||
|
||||
if(GoalAchieved(goal, currentState))
|
||||
{
|
||||
leaves.Add(node);
|
||||
foundPath = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
List<AI_Action> subset = ActionSubset(usableActions, action);
|
||||
bool found = BuildGraph(node, leaves, subset, goal);
|
||||
if(found)
|
||||
foundPath = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return foundPath;
|
||||
}
|
||||
|
||||
private bool GoalAchieved(Dictionary<string, int> goals, Dictionary<string, int> state)
|
||||
{
|
||||
foreach(KeyValuePair<string, int> goal in goals)
|
||||
{
|
||||
if (!state.ContainsKey(goal.Key))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private List<AI_Action> ActionSubset(List<AI_Action> actions, AI_Action removeAction)
|
||||
{
|
||||
List<AI_Action> subset = new List<AI_Action>();
|
||||
foreach(AI_Action action in actions)
|
||||
{
|
||||
if(!action.Equals(removeAction))
|
||||
subset.Add(action);
|
||||
}
|
||||
return subset;
|
||||
}
|
||||
|
||||
public string GetPlanDescription()
|
||||
{
|
||||
return planDescription;
|
||||
}
|
||||
}
|
||||
2
GOAP/Assets/Scrips/GOAP/AI_Planner.cs.meta
Normal file
2
GOAP/Assets/Scrips/GOAP/AI_Planner.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 105223c6dcaa56146870d3dd0b4f3a13
|
||||
93
GOAP/Assets/Scrips/GOAP/AI_World.cs
Normal file
93
GOAP/Assets/Scrips/GOAP/AI_World.cs
Normal file
@@ -0,0 +1,93 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
public sealed class AI_World
|
||||
{
|
||||
private static readonly AI_World instance = new AI_World();
|
||||
private static AIStates worldStates;
|
||||
private static Queue<GameObject> patients;
|
||||
private static Queue<GameObject> chargers;
|
||||
private static Queue<GameObject> treatmentBeds;
|
||||
|
||||
static AI_World()
|
||||
{
|
||||
worldStates = new AIStates();
|
||||
patients = new Queue<GameObject>();
|
||||
chargers = new Queue<GameObject>();
|
||||
treatmentBeds = new Queue<GameObject>();
|
||||
|
||||
GameObject[] cubicleArray = GameObject.FindGameObjectsWithTag("Charger");
|
||||
foreach(GameObject cubicle in cubicleArray)
|
||||
{
|
||||
chargers.Enqueue(cubicle);
|
||||
}
|
||||
|
||||
if (cubicleArray.Length > 0)
|
||||
{
|
||||
worldStates.SetState("FreeCharger", cubicleArray.Length);
|
||||
}
|
||||
|
||||
GameObject[] bedArray = GameObject.FindGameObjectsWithTag("Bed");
|
||||
|
||||
foreach(GameObject bed in bedArray)
|
||||
{
|
||||
treatmentBeds.Enqueue(bed);
|
||||
}
|
||||
|
||||
if (bedArray.Length > 0)
|
||||
{
|
||||
worldStates.SetState("FreeBeds", bedArray.Length);
|
||||
}
|
||||
}
|
||||
|
||||
private AI_World() { }
|
||||
|
||||
public void AddPatient(GameObject patient)
|
||||
{
|
||||
patients.Enqueue(patient);
|
||||
worldStates.ModifyState("Patients", 1);
|
||||
}
|
||||
|
||||
public GameObject RemovePatient()
|
||||
{
|
||||
if (patients.Count == 0) return null;
|
||||
worldStates.ModifyState("Patients", -1);
|
||||
return patients.Dequeue();
|
||||
}
|
||||
|
||||
public void AddCharger(GameObject charger)
|
||||
{
|
||||
worldStates.ModifyState("FreeCharger", 1);
|
||||
chargers.Enqueue(charger);
|
||||
}
|
||||
|
||||
public GameObject RemoveCharger()
|
||||
{
|
||||
if (chargers.Count == 0) return null;
|
||||
worldStates.ModifyState("FreeCharger", -1);
|
||||
return chargers.Dequeue();
|
||||
}
|
||||
|
||||
public void AddTreatmentBed(GameObject bed)
|
||||
{
|
||||
treatmentBeds.Enqueue(bed);
|
||||
worldStates.ModifyState("FreeBeds", 1);
|
||||
}
|
||||
|
||||
public GameObject RemoveTreatmentBed()
|
||||
{
|
||||
if (treatmentBeds.Count == 0) return null;
|
||||
worldStates.ModifyState("FreeBeds", -1);
|
||||
return treatmentBeds.Dequeue();
|
||||
}
|
||||
|
||||
public static AI_World Instance
|
||||
{
|
||||
get { return instance; }
|
||||
}
|
||||
|
||||
public AIStates GetWorld()
|
||||
{
|
||||
return worldStates;
|
||||
}
|
||||
}
|
||||
2
GOAP/Assets/Scrips/GOAP/AI_World.cs.meta
Normal file
2
GOAP/Assets/Scrips/GOAP/AI_World.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2c9831f822b94a8dbdae2de4a63ab6e7
|
||||
70
GOAP/Assets/Scrips/GOAP/Statemachine.cs
Normal file
70
GOAP/Assets/Scrips/GOAP/Statemachine.cs
Normal file
@@ -0,0 +1,70 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
[System.Serializable]
|
||||
public class AIState
|
||||
{
|
||||
public string key;
|
||||
public int value;
|
||||
}
|
||||
public class AIStates
|
||||
{
|
||||
public Dictionary<string, int> states;
|
||||
|
||||
public AIStates()
|
||||
{
|
||||
states = new Dictionary<string, int>();
|
||||
}
|
||||
|
||||
public bool HasState(string key)
|
||||
{
|
||||
return states.ContainsKey(key);
|
||||
}
|
||||
|
||||
void AddState(string key, int value)
|
||||
{
|
||||
states.Add(key, value);
|
||||
}
|
||||
|
||||
public void ModifyState(string key, int value)
|
||||
{
|
||||
if (states.ContainsKey(key))
|
||||
{
|
||||
states[key] += value;
|
||||
if (states[key] <= 0)
|
||||
{
|
||||
RemoveState(key);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AddState(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveState(string key)
|
||||
{
|
||||
if (states.ContainsKey(key))
|
||||
{
|
||||
states.Remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetState(string key, int value)
|
||||
{
|
||||
if (states.ContainsKey(key))
|
||||
{
|
||||
states[key] = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
AddState(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
public Dictionary<string, int> GetStates()
|
||||
{
|
||||
return states;
|
||||
}
|
||||
}
|
||||
3
GOAP/Assets/Scrips/GOAP/Statemachine.cs.meta
Normal file
3
GOAP/Assets/Scrips/GOAP/Statemachine.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 121b61efa26f4a91a871ce8e3b0049ac
|
||||
timeCreated: 1748548853
|
||||
Reference in New Issue
Block a user