我正在 Unity 中开发一款游戏,并决定使用 BehaviourTrees 来管理敌人 AI。 其中一名敌人有能力躲避玩家的攻击。不过这个能力应该只会出现几次,所以我就考虑让它出现的概率更大。 也可以使用冷却来做到这一点。但我在这两种方法的实施上都面临着困难。
为了实现该树,我创建了以下脚本: Node.cs
public enum NodeState
{
RUNNING,
SUCCESS,
FAILURE
}
public class Node
{
protected NodeState state;
public Node parent;
protected List<Node> children = new List<Node>();
private Dictionary<string, object> dataContext = new Dictionary<string, object>();
public Node()
{
parent = null;
}
public Node(List<Node> children)
{
foreach (Node child in children)
{
Attach(child);
}
}
private void Attach(Node node)
{
node.parent = this;
children.Add(node);
}
public virtual NodeState Evaluate() => NodeState.FAILURE;
public void SetData(string key, object value)
{
dataContext[key] = value;
}
public object GetData(string key)
{
object value = null;
if (dataContext.TryGetValue(key, out value))
return value;
Node node = parent;
while (node != null)
{
value = node.GetData(key);
if (value != null)
return value;
node = node.parent;
}
return null;
}
public bool ClearData(string key)
{
if (dataContext.ContainsKey(key))
{
dataContext.Remove(key);
return true;
}
Node node = parent;
while (node != null)
{
bool cleared = node.ClearData(key);
if (cleared)
return true;
node = node.parent;
}
return false;
}
}
Tree.cs
public abstract class Tree : MonoBehaviour
{
private Node root = null;
public virtual void Start()
{
root = SetupTree();
}
private void Update()
{
if (root != null)
{
root.Evaluate();
}
}
protected abstract Node SetupTree();
}
我还实现了一些从节点派生的其他类,例如Selector.cs和Sequence.cs 为了创建一个依赖于概率的节点,我考虑创建另一个从该节点派生的类,如下所示:
public class Probability : Node
{
private Node child;
private float probability;
public Probability(Node child, float probability)
{
this.child = child;
this.probability = probability;
}
public override NodeState Evaluate()
{
if (probability <= Random.value)
{
return child.Evaluate();
}
return NodeState.FAILURE;
}
}
我在敌人控制器中使用这些类如下: 敌人剑圣BT.cs
public class EnemySwordmasterBT : EnemyBT
{
[SerializeField] public float evasionDistance = 2f;
[SerializeField] public float evasionProbability = 0.3f;
protected override Node SetupTree()
{
Node root = new Selector(new List<Node>
{
new Sequence(new List<Node>
{
new CheckTargetAttacking(Player.Instance.GetHolder().GetHoldableObject().GetComponent<IWeapon>()),
new Probability(new TaskEvade(targetTransform, movementNavMesh, evasionDistance), evasionProbability),
}),
new Sequence(new List<Node>
{
new CheckTargetInAttackRange(transform, targetTransform, attackTargetDistance),
new TaskAttack(weapon)
}),
new Sequence(new List<Node>
{
new CheckIsNotAttacking(weapon),
new TaskChase(targetTransform, movementNavMesh)
})
});
return root;
}
}
但是当 Evaluate() 方法在 Update() 中运行时,它会在每帧生成几个随机数。这就是为什么闪避动作,TaskEvade,总是发生的原因。
我很难解决这个问题。
所以我想寻求帮助来解决这个问题。任何改进我当前设计的建议也将不胜感激 =)
我还尝试了一些使用冷却想法的解决方案,但所以问题不会变得太大,我还不会添加它们。但如果您发现相关,请告诉我。
您在 Unity 中使用行为树来管理敌人 AI 真是太棒了。为了解决概率节点始终触发的问题,您只需在每个决策周期生成一次随机数,并为所有基于概率的节点一致地使用该数字。
您可以尝试生成一个随机数一次:在您的 Tree 类中,在每个决策周期开始时生成一个随机数。在该周期内,该随机数将在树中所有基于概率的节点之间共享。
public class Tree : MonoBehaviour
{
private Node root = null;
private float randomValue;
public virtual void Start()
{
root = SetupTree();
}
private void Update()
{
randomValue = Random.value; // Generate a random number for this decision-making cycle
if (root != null)
{
root.Evaluate();
}
}
// Rest of your Tree class...
}
使用相同的随机数:修改概率节点以使用决策周期开始时生成的共享随机数。
public class Probability : Node
{
private Node child;
private float probability;
public Probability(Node child, float probability)
{
this.child = child;
this.probability = probability;
}
public override NodeState Evaluate()
{
if (probability <= randomValue) // Use the random number generated in the Tree class
{
return child.Evaluate();
}
return NodeState.FAILURE;
}
}
通过在每个决策周期生成一次随机数,您可以确保树中所有基于概率的节点都使用相同的随机值进行评估。这种方法应该有助于防止闪避动作在每一帧中发生,并使其根据指定的概率发生。
让我知道这是否有效!