C# 理解GoapPlanner脚本
我对编程还是相当陌生,遇到了这个脚本,我对一些逻辑有疑问。我的问题与C# 理解GoapPlanner脚本,c#,C#,我对编程还是相当陌生,遇到了这个脚本,我对一些逻辑有疑问。我的问题与buildGraph() private bool buildGraph(节点父节点、列表叶子、HashSet usableActions、HashSet目标) { bool-foundOne=false; //检查此节点上可用的每个操作,看看是否可以在此处使用它 foreach(使用动作中的目标动作) { //如果父状态具有此操作的前提条件,我们可以在此处使用它 if(inState(action.prepositions,p
buildGraph()
private bool buildGraph(节点父节点、列表叶子、HashSet usableActions、HashSet目标)
{
bool-foundOne=false;
//检查此节点上可用的每个操作,看看是否可以在此处使用它
foreach(使用动作中的目标动作)
{
//如果父状态具有此操作的前提条件,我们可以在此处使用它
if(inState(action.prepositions,parent.state))
{
//将操作的效果应用于父状态
HashSet currentState=populateState(parent.state、action.Effects);
//Log(GoapAgent.prettyPrint(currentState));
节点节点=新节点(父节点,父节点.runningCost+动作.cost,当前状态,动作);
if(inState(目标、当前状态))
{
//我们找到了解决办法!
添加(节点);
foundOne=true;
}
其他的
{
//还没有找到解决办法。
HashSet子集=动作子集(usableActions,action);
bool found=buildGraph(节点、叶子、子集、目标);
如果(找到)
foundOne=true;
}
}
}
返回foundOne;
}
我还是个编程新手,这个答案真的会帮我解决问题
要问我的问题,我需要从逻辑上讲清楚。所讨论的代码旨在找到多个解决方案,然后计算哪一个是最有效的
每次状态条件满足前提条件时
if(inState(action.prepositions,parent.state))
代码将添加效果
HashSet>currentState=populateState
(父母、国家、行动、影响)
然后,我们创建一个节点并保存操作、成本和状态
节点节点=新节点(父节点、父节点.runningCost+action.cost、,
当前状态、行动)
如果父州符合我们的目标
if(inState(目标、当前状态))
我们将节点添加到叶子列表中作为解决方案
添加(节点)
在叶子列表中,我们找到最便宜的节点,这将是最佳路径。如果有多个解决方案,列表将包含多个节点
现在我的问题
我不明白脚本如何在找到一个解决方案后继续寻找其他解决方案和路径。一旦遇到第一个解决方案,目标效果就会添加到当前状态中。foreach循环将继续从usableActions哈希集中提取操作,但是,由于第一个解决方案已经改变了状态并在中添加了目标效果,所以在之后测试的每个操作不是都显示为解决方案吗?我认为状态需要重新更改以避免这种情况,否则它将无法进入递归步骤。剧本写得很好,所以我显然有些地方不太明白
此代码取自GoapPlanner,以下是计划员的完整代码
public class GoapPlanner
{
/**
* Plan what sequence of actions can fulfill the goal.
* Returns null if a plan could not be found, or a list of the actions
* that must be performed, in order, to fulfill the goal.
*/
public Queue<GoapAction> plan(GameObject agent,
HashSet<GoapAction> availableActions,
HashSet<KeyValuePair<string, object>> worldState,
HashSet<KeyValuePair<string, object>> goal)
{
// reset the actions
foreach (GoapAction a in availableActions)
{
a.doReset();
}
// check which actions can run using their checkProceduralPrecondition
HashSet<GoapAction> usableActions = new HashSet<GoapAction>();
foreach (GoapAction a in availableActions)
{
if (a.checkProceduralPrecondition(agent))
usableActions.Add(a);
}
// build up the tree and record the leaf nodes that provide a solution to the goal.
List<Node> leaves = new List<Node>();
// build graph
Node start = new Node(null, 0, worldState, null);
bool success = buildGraph(start, leaves, usableActions, goal);
if (!success)
{
// oh no, we didn't get a plan
Debug.Log("NO PLAN");
return null;
}
// gets the cheapest leaf.
Node cheapest = null;
foreach (Node leaf in leaves)
{
if (cheapest == null)
cheapest = leaf;
else
{
if (leaf.runningCost < cheapest.runningCost)
{
cheapest = leaf;
}
}
}
List<GoapAction> result = new List<GoapAction>();
Node n = cheapest;
while (n != null)
{
if (n.action != null)
{
result.Insert(0, n.action); // insert the action in the front.
}
n = n.parent;
}
// we now have actions in correct order in results list and we want to add it to the queue.
Queue<GoapAction> queue = new Queue<GoapAction>();
foreach (GoapAction a in result)
{
queue.Enqueue(a);
}
// hooray we have a plan!
return queue;
}
/**
* Returns true if at least one solution was found.
* The possible paths are stored in the leaves list. Each leaf has a
* 'runningCost' value where the lowest cost will be the best action
* sequence.
*/
private bool buildGraph(Node parent, List<Node> leaves, HashSet<GoapAction> usableActions, HashSet<KeyValuePair<string, object>> goal)
{
bool foundOne = false;
// go through each action available at this node and see if we can use it here
foreach (GoapAction action in usableActions)
{
// if the parent state has the conditions for this action's preconditions, we can use it here
if (inState(action.Preconditions, parent.state))
{
// apply the action's effects to the parent state
HashSet<KeyValuePair<string, object>> currentState = populateState(parent.state, action.Effects);
//Debug.Log(GoapAgent.prettyPrint(currentState));
Node node = new Node(parent, parent.runningCost + action.cost, currentState, action);
if (inState(goal, currentState))
{
// we found a solution!
leaves.Add(node);
foundOne = true;
}
else
{
// not at a solution yet.
HashSet<GoapAction> subset = actionSubset(usableActions, action);
bool found = buildGraph(node, leaves, subset, goal);
if (found)
foundOne = true;
}
}
}
return foundOne;
}
/**
* Create a subset of the actions excluding the removeMe one.
*/
private HashSet<GoapAction> actionSubset(HashSet<GoapAction> actions, GoapAction removeMe)
{
HashSet<GoapAction> subset = new HashSet<GoapAction>();
foreach (GoapAction a in actions)
{
if (!a.Equals(removeMe))
subset.Add(a);
}
return subset;
}
/**
* Check that all items in 'test' are in 'state'. If just one does not match or is not there
* then this returns false.
*/
private bool inState(HashSet<KeyValuePair<string, object>> test, HashSet<KeyValuePair<string, object>> state)
{
bool allMatch = true;
foreach (KeyValuePair<string, object> t in test)
{
bool match = false;
foreach (KeyValuePair<string, object> s in state)
{
if (s.Equals(t))
{
match = true;
break;
}
}
if (!match)
allMatch = false;
}
return allMatch;
}
/**
* Apply the stateChange to the currentState
*/
private HashSet<KeyValuePair<string, object>> populateState(HashSet<KeyValuePair<string, object>> currentState, HashSet<KeyValuePair<string, object>> stateChange)
{
HashSet<KeyValuePair<string, object>> state = new HashSet<KeyValuePair<string, object>>();
// copy the KVPs over as new objects
foreach (KeyValuePair<string, object> s in currentState)
{
state.Add(new KeyValuePair<string, object>(s.Key, s.Value));
}
foreach (KeyValuePair<string, object> change in stateChange)
{
// if the key exists in the current state, update the Value
bool exists = false;
foreach (KeyValuePair<string, object> s in state)
{
if (s.Equals(change))
{
exists = true;
break;
}
}
if (exists)
{
state.RemoveWhere((KeyValuePair<string, object> kvp) => { return kvp.Key.Equals(change.Key); });
KeyValuePair<string, object> updated = new KeyValuePair<string, object>(change.Key, change.Value);
state.Add(updated);
}
// if it does not exist in the current state, add it
else
{
state.Add(new KeyValuePair<string, object>(change.Key, change.Value));
}
}
return state;
}
/**
* Used for building up the graph and holding the running costs of actions.
*/
private class Node
{
public Node parent;
public float runningCost;
public HashSet<KeyValuePair<string, object>> state;
public GoapAction action;
public Node(Node parent, float runningCost, HashSet<KeyValuePair<string, object>> state, GoapAction action) //state should be world state
{
this.parent = parent;
this.runningCost = runningCost;
this.state = state;
this.action = action;
}
}
}
公共类GoapPlanner
{
/**
*计划可以实现目标的行动顺序。
*若找不到计划或操作列表,则返回null
*为了实现这一目标,必须履行这一义务。
*/
公共队列计划(游戏对象代理,
HashSet availableActions,
HashSet worldState,
哈希集(目标)
{
//重置操作
foreach(可用选项中的目标选项a)
{
a、 doReset();
}
//检查哪些操作可以使用其CheckProceduralPredition运行
HashSet usableActions=新HashSet();
foreach(可用选项中的目标选项a)
{
如果(a.检查程序先决条件(代理))
加入(a);
}
//建立树并记录为目标提供解决方案的叶节点。
列表叶子=新列表();
//构建图
节点开始=新节点(null,0,worldState,null);
bool success=buildGraph(开始、离开、使用动作、目标);
如果(!成功)
{
//哦,不,我们没有计划
调试日志(“无计划”);
返回null;
}
//得到最便宜的叶子。
节点最便宜=空;
foreach(叶中的节点叶)
{
if(最便宜==null)
最便宜的=叶子;
其他的
{
if(leaf.runningCost<最便宜的运行成本)
{
最便宜的=叶子;
}
}
}
列表结果=新列表();
节点n=最便宜的;
while(n!=null)
{
如果(n.action!=null)
{
result.Insert(0,n.action);//在前面插入操作。
}
n=n.父母;
}
//现在,我们在结果列表中有了正确顺序的操作,我们希望将其添加到队列中。
队列=新队列();
foreach(结果中的目标a)
{
排队。排队(a);
}
//万岁,我们有计划了!
返回队列;
}
/**
*如果至少找到一个解决方案,则返回true。
*可能的路径存储在叶子列表中。每个叶子都有一个
*“运行成本”值,其中最低成本将是最佳行动
*顺序。
*/
私有布尔构建图(节点父节点、列表叶、哈希集usableActions、哈希集目标)
{
bool-foundOne=false;
//检查此节点上可用的每个操作,看看是否可以在此处使用它
foreach(使用动作中的目标动作)
{
//如果p
public class GoapPlanner
{
/**
* Plan what sequence of actions can fulfill the goal.
* Returns null if a plan could not be found, or a list of the actions
* that must be performed, in order, to fulfill the goal.
*/
public Queue<GoapAction> plan(GameObject agent,
HashSet<GoapAction> availableActions,
HashSet<KeyValuePair<string, object>> worldState,
HashSet<KeyValuePair<string, object>> goal)
{
// reset the actions
foreach (GoapAction a in availableActions)
{
a.doReset();
}
// check which actions can run using their checkProceduralPrecondition
HashSet<GoapAction> usableActions = new HashSet<GoapAction>();
foreach (GoapAction a in availableActions)
{
if (a.checkProceduralPrecondition(agent))
usableActions.Add(a);
}
// build up the tree and record the leaf nodes that provide a solution to the goal.
List<Node> leaves = new List<Node>();
// build graph
Node start = new Node(null, 0, worldState, null);
bool success = buildGraph(start, leaves, usableActions, goal);
if (!success)
{
// oh no, we didn't get a plan
Debug.Log("NO PLAN");
return null;
}
// gets the cheapest leaf.
Node cheapest = null;
foreach (Node leaf in leaves)
{
if (cheapest == null)
cheapest = leaf;
else
{
if (leaf.runningCost < cheapest.runningCost)
{
cheapest = leaf;
}
}
}
List<GoapAction> result = new List<GoapAction>();
Node n = cheapest;
while (n != null)
{
if (n.action != null)
{
result.Insert(0, n.action); // insert the action in the front.
}
n = n.parent;
}
// we now have actions in correct order in results list and we want to add it to the queue.
Queue<GoapAction> queue = new Queue<GoapAction>();
foreach (GoapAction a in result)
{
queue.Enqueue(a);
}
// hooray we have a plan!
return queue;
}
/**
* Returns true if at least one solution was found.
* The possible paths are stored in the leaves list. Each leaf has a
* 'runningCost' value where the lowest cost will be the best action
* sequence.
*/
private bool buildGraph(Node parent, List<Node> leaves, HashSet<GoapAction> usableActions, HashSet<KeyValuePair<string, object>> goal)
{
bool foundOne = false;
// go through each action available at this node and see if we can use it here
foreach (GoapAction action in usableActions)
{
// if the parent state has the conditions for this action's preconditions, we can use it here
if (inState(action.Preconditions, parent.state))
{
// apply the action's effects to the parent state
HashSet<KeyValuePair<string, object>> currentState = populateState(parent.state, action.Effects);
//Debug.Log(GoapAgent.prettyPrint(currentState));
Node node = new Node(parent, parent.runningCost + action.cost, currentState, action);
if (inState(goal, currentState))
{
// we found a solution!
leaves.Add(node);
foundOne = true;
}
else
{
// not at a solution yet.
HashSet<GoapAction> subset = actionSubset(usableActions, action);
bool found = buildGraph(node, leaves, subset, goal);
if (found)
foundOne = true;
}
}
}
return foundOne;
}
/**
* Create a subset of the actions excluding the removeMe one.
*/
private HashSet<GoapAction> actionSubset(HashSet<GoapAction> actions, GoapAction removeMe)
{
HashSet<GoapAction> subset = new HashSet<GoapAction>();
foreach (GoapAction a in actions)
{
if (!a.Equals(removeMe))
subset.Add(a);
}
return subset;
}
/**
* Check that all items in 'test' are in 'state'. If just one does not match or is not there
* then this returns false.
*/
private bool inState(HashSet<KeyValuePair<string, object>> test, HashSet<KeyValuePair<string, object>> state)
{
bool allMatch = true;
foreach (KeyValuePair<string, object> t in test)
{
bool match = false;
foreach (KeyValuePair<string, object> s in state)
{
if (s.Equals(t))
{
match = true;
break;
}
}
if (!match)
allMatch = false;
}
return allMatch;
}
/**
* Apply the stateChange to the currentState
*/
private HashSet<KeyValuePair<string, object>> populateState(HashSet<KeyValuePair<string, object>> currentState, HashSet<KeyValuePair<string, object>> stateChange)
{
HashSet<KeyValuePair<string, object>> state = new HashSet<KeyValuePair<string, object>>();
// copy the KVPs over as new objects
foreach (KeyValuePair<string, object> s in currentState)
{
state.Add(new KeyValuePair<string, object>(s.Key, s.Value));
}
foreach (KeyValuePair<string, object> change in stateChange)
{
// if the key exists in the current state, update the Value
bool exists = false;
foreach (KeyValuePair<string, object> s in state)
{
if (s.Equals(change))
{
exists = true;
break;
}
}
if (exists)
{
state.RemoveWhere((KeyValuePair<string, object> kvp) => { return kvp.Key.Equals(change.Key); });
KeyValuePair<string, object> updated = new KeyValuePair<string, object>(change.Key, change.Value);
state.Add(updated);
}
// if it does not exist in the current state, add it
else
{
state.Add(new KeyValuePair<string, object>(change.Key, change.Value));
}
}
return state;
}
/**
* Used for building up the graph and holding the running costs of actions.
*/
private class Node
{
public Node parent;
public float runningCost;
public HashSet<KeyValuePair<string, object>> state;
public GoapAction action;
public Node(Node parent, float runningCost, HashSet<KeyValuePair<string, object>> state, GoapAction action) //state should be world state
{
this.parent = parent;
this.runningCost = runningCost;
this.state = state;
this.action = action;
}
}
}