开启辅助访问
 找回密码
 注册帐号

扫一扫,访问微社区

教程分享

关注:648

当前位置:游戏蛮牛 技术专区 教程分享

查看: 1113|回复: 0

[基础知识] 行为树的设计与实现(二)

[复制链接]  [移动端链接]
抢楼 抢楼 本帖为抢楼帖,欢迎抢楼! 
8常驻蛮牛
9849/10000
排名
421
昨日变化

3421

主题

3691

帖子

9849

积分

Rank: 8Rank: 8

UID
1235
好友
168
蛮牛币
179930
威望
110
注册时间
2013-7-29
在线时间
432 小时
最后登录
2016-10-9

社区QQ达人游戏蛮牛QQ群会员蛮牛妹

发表于 2015-2-21 14:49:23 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有帐号?注册帐号

x
  行为节点

  先放一些渲染节点的代码。实际上我基本上是第一次接触自己去渲染一种数据结构,看完网上的大牛们随随便便就能写出个数据结构的示意图,不得不佩服。我一时半会没想出怎么渲染出树状结构,于是就简单的把树按层分组,一层一层渲染,缺点就是不能很好的表现树的样子,父子关系不能很好的表示。这里放出来希望能抛砖引玉。我以后可能会去完事它,但是现在首先是要搞清楚行为树。实现这个完全是为了看看节点是否正确放置,以方便调试。

[AppleScript] 纯文本查看 复制代码
  public class RenderableNode

  {

  public RenderableNode parent;

  public IBehaviourTreeNode targetNode;

  public Rect posRect = new Rect();

  public string name;

  public int layer;

  public RunStatus staus;

  public override string ToString()

  {

  return name + “\n” + staus.ToString();

  }

  public virtual void Render()

  {

  bool running = staus == RunStatus.Running;

  var rect = posRect;

  rect.y -= (posRect.height / 2);

  var oldColor = GUI.color;

  if (running)

  {

  GUI.color = Color.green;

  }

  GUI.Box(rect, ToString());

  GUI.color = oldColor;

  if (parent == null && targetNode != null && targetNode.parent!=null)

  {

  parent = targetNode.parent.renderNode;

  }

  if (parent != null)

  {

  Vector2 parentPos = new Vector2();

  parentPos.x = parent.posRect.x + parent.posRect.width;

  parentPos.y = parent.posRect.y;

  GUIHelper.DrawLine(new Vector2(rect.x, rect.y + rect.height / 2), parentPos, running?Color.green:Color.yellow);

  }

  }

  }

  public class RenderableCondictionNode : RenderableNode

  {

  public IConditionNode targetCondictionNode;

  public override string ToString() { parent = null; return name; }

  public override void Render()

  {

  var rect = posRect;

  rect.y -= (posRect.height / 2);

  var oldColor = GUI.color;

  if (targetCondictionNode.ExternalCondition())

  GUI.color = Color.green;

  else

  GUI.color = Color.blue;

  GUI.Box(rect, ToString());

  GUI.color = oldColor;

  }

  }

  public class EmptyNode : RenderableNode { public override void Render() { } }

  public class NodeBox

  {

  public Rect posRect = new Rect();

  public List<RenderableNode> nodeList = new List<RenderableNode>();

  public void AddNode(RenderableNode node)

  {

  nodeList.Add(node);

  }

  public void Render()

  {

  posRect.y = Screen.height / 2;

  Rect rect = new Rect();

  foreach (var node in nodeList)

  {

  var n = node;

  rect.height += (n.posRect.height + 1);

  rect.width = n.posRect.width + 10;

  }

  rect.height += 10;

  rect.x = posRect.x - rect.width / 2;

  rect.y = posRect.y - rect.height / 2;

  //GUI.Box(rect, “”);

  posRect.width = rect.width;

  posRect.height = rect.height;

  float height = 0;

  for (var i = 0; i < nodeList.Count; i++)

  {

  var n = nodeList[i];

  n.posRect.y = rect.y + height + n.posRect.height / 2 + 5;

  n.posRect.x = rect.x + 5;

  n.Render();

  height += n.posRect.height + 1;

  }

  }

  }


  放一张渲染出来的效果

8S9JPFO.jpg

  虽然每一组都只是简单的居中,不过效果看起来还可以接受

  然后从图中就可以看到问题了。所有正条件,都会有一个反条件,不这么做就无法在条件改变时,让当前节点返回FALSE,从而让行为树去寻找其他节点。而如果用状态机来做的话,条件肯定只用判断一次,比如

[AppleScript] 纯文本查看 复制代码
  if(run){

  Run();

  }

  else{

  Walk();

  }


  那么可能就回到最初的组合节点的设计了,组合节点就不得不每次都扫描条件。其实本质上我是在担心开销问题,因为变成节点后,就不在是if else那么简单,而是变成了函数调用的开销。简单的AI还好,如果大量复杂的AI,每次对整棵树进行扫描估计够呛。但是目前的设计,条件节点就会非常多,条件不完备就会出现BUG,似乎也不是非常好的情况。

  最后放出一些细节

[AppleScript] 纯文本查看 复制代码
  class PatrolAction : BaseActionNode {

  public PatrolAction() { nodeName_ += “巡逻行为”; }

  public override bool Tick(object input_, object output_)

  {

  // var input = input_ as WarriorInputData;

  var output = output_ as WarriorOutPutData;

  output.action = WarriorActon.ePatrol;

  return true;

  }

  }

  class RunAwayAction : BaseActionNode {

  public RunAwayAction() { nodeName_ += “逃跑行为”; }

  public override bool Tick(object input_, object output_)

  {

  // var input = input_ as WarriorInputData;

  var output = output_ as WarriorOutPutData;

  output.action = WarriorActon.eRunAway;

  return true;

  }

  }

  class AttackAction : BaseActionNode {

  public AttackAction() { nodeName_ += “攻击行为”; }

  public override bool Tick(object input_, object output_)

  {

  // var input = input_ as WarriorInputData;

  var output = output_ as WarriorOutPutData;

  output.action = WarriorActon.eAttack;

  return true;

  }

  }

  class CrazyAttackAction : BaseActionNode {

  public CrazyAttackAction() { nodeName_ += “疯狂攻击行为”; }

  public override bool Tick(object input_, object output_)

  {

  // var input = input_ as WarriorInputData;

  var output = output_ as WarriorOutPutData;

  output.action = WarriorActon.eCrazyAttack;

  return true;

  }

  }

  class AlertAction : BaseActionNode

  {

  public AlertAction() { nodeName_ += “警戒行为”; }

  public override bool Tick(object input_, object output_)

  {

  // var input = input_ as WarriorInputData;

  var output = output_ as WarriorOutPutData;

  output.action = WarriorActon.eAlert;

  return true;

  }

  }


 
[AppleScript] 纯文本查看 复制代码
 private ICompositeNode rootNode = new SelectorNode();

  private WarriorInputData inputData = new WarriorInputData();

  private WarriorOutPutData outputData = new WarriorOutPutData();

  // Use this for initialization

  public void Start()

  {

  inputData.attribute = GetComponent<CharacterAttribute>();

  rootNode.nodeName += “根”;

  //条件

  var hasNoTarget = new PreconditionNOT(() => { return inputData.attribute.hasTarget; });

  hasNoTarget.nodeName = “无目标”;

  var hasTarget = new Precondition(hasNoTarget);

  hasTarget.nodeName = “发现目标”;

  var isAnger = new Precondition(() => { return inputData.attribute.isAnger; });

  isAnger.nodeName = “愤怒状态”;

  var isNotAnger = new PreconditionNOT(isAnger);

  isNotAnger.nodeName = “非愤怒状态”;

  var HPLessThan500 = new Precondition(() => { return inputData.attribute.health < 500; });

  HPLessThan500.nodeName = “血少于500”;

  var HPMoreThan500 = new PreconditionNOT(HPLessThan500);

  HPMoreThan500.nodeName = “血大于500”;

  var isAlert = new Precondition(() => { return inputData.attribute.isAlert; });

  isAlert.nodeName = “警戒”;

  var isNotAlert = new PreconditionNOT(isAlert);

  isNotAlert.nodeName = “非警戒”;

  var patrolNode = new SequenceNode();

  patrolNode.nodeName += “巡逻”;

  patrolNode.AddCondition(hasNoTarget);

  patrolNode.AddCondition(isNotAlert);

  patrolNode.AddNode(new PatrolAction());

  var alert = new SequenceNode();

  alert.nodeName += “警戒”;

  alert.AddCondition(hasNoTarget);

  alert.AddCondition(isAlert);

  alert.AddNode(new AlertAction());

  var runaway = new SequenceNode();

  runaway.nodeName += “逃跑”;

  runaway.AddCondition(hasTarget);

  runaway.AddCondition(HPLessThan500);

  runaway.AddNode(new RunAwayAction());

  var attack = new SelectorNode();

  attack.nodeName += “攻击”;

  attack.AddCondition(hasTarget);

  attack.AddCondition(HPMoreThan500);

  var attackCrazy = new SequenceNode();

  attackCrazy.nodeName += “疯狂攻击”;

  attackCrazy.AddCondition(isAnger);

  attackCrazy.AddNode(new CrazyAttackAction());

  attack.AddNode(attackCrazy);

  var attackNormal = new SequenceNode();

  attackNormal.nodeName += “普通攻击”;

  attackNormal.AddCondition(isNotAnger);

  attackNormal.AddNode(new AttackAction());

  attack.AddNode(attackNormal);

  rootNode.AddNode(patrolNode);

  rootNode.AddNode(alert);

  rootNode.AddNode(runaway);

  rootNode.AddNode(attack);

  var ret = rootNode.Enter(inputData);

  if (!ret)

  {

  Debug.Log(“无可执行节点!”);

  }

  }

  // Update is called once per frame

  void Update () {

  var ret = rootNode.Tick(inputData, outputData);

  if (!ret)

  rootNode.Leave(inputData);

  if (rootNode.status == RunStatus.Completed)

  {

  ret = rootNode.Enter(inputData);

  if (!ret)

  rootNode.Leave(inputData);

  }

  else if (rootNode.status == RunStatus.Failure)

  {

  Debug.Log(“BT Failed”);

  enabled = false;

  }

  if (outputData.action != inputData.action)

  {

  OnActionChange(outputData.action, inputData.action);

  inputData.action = outputData.action;

  }

  }

  void OnActionChange(WarriorActon action, WarriorActon lastAction) {

  //  print(“OnActionChange ”+action+“ last:”+lastAction);

  switch (lastAction)

  {

  case WarriorActon.ePatrol:

  GetComponent<WarriorPatrol>()。enabled = false;

  break;

  case WarriorActon.eAttack:

  case WarriorActon.eCrazyAttack:

  GetComponent<WarriorAttack>()。enabled = false;

  break;

  case WarriorActon.eRunAway:

  GetComponent<WarriorRunAway>()。enabled = false;

  break;

  case WarriorActon.eAlert:

  GetComponent<WarriorAlert>()。enabled = false;

  break;

  }

  switch (action) {

  case WarriorActon.ePatrol:

  GetComponent<WarriorPatrol>()。enabled = true;

  break;

  case WarriorActon.eAttack:

  var attack = GetComponent<WarriorAttack>();

  attack.revenge = false;

  attack.enabled = true;

  break;

  case WarriorActon.eCrazyAttack:

  var crazyAttack = GetComponent<WarriorAttack>();

  crazyAttack.revenge = true;

  crazyAttack.enabled = true;

  break;

  case WarriorActon.eRunAway:

  GetComponent<WarriorRunAway>()。enabled = true;

  break;

  case WarriorActon.eAlert:

  GetComponent<WarriorAlert>()。enabled = true;

  break;

  case WarriorActon.eIdle:

  GetComponent<WarriorPatrol>()。enabled = false;

  GetComponent<WarriorAttack>()。enabled = false;

  GetComponent<WarriorRunAway>()。enabled = false;

  break;

  }

  }


评分

参与人数 1鲜花 +1 收起 理由
微末之人 + 1

查看全部评分


我是一朵内心长满小碎花的女汉子!
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册帐号

本版积分规则

快速回复 返回顶部 返回列表