从嵌套状态转换到嵌套状态的最佳实践(参见图表)

时间:2010-01-18 02:01:52

标签: optimization actionscript state-machine hsm

我试图围绕在单线程编程语言(Actionscript)中实现嵌套状态转换的最佳方法。假设我有一个像这个行为树的结构:behavior tree

现在假设每个叶节点都是网站上的目标点,如图库中的图像,或嵌套在页面视图中嵌套的帖子视图中的注释......目标是能够运行动画从叶节点到叶节点的过渡,通过动画出前一个树(从下到上),以及在当前树中的动画(从上到下)。

因此,如果我们位于最左下方的叶节点,并且我们想要转到最右下方的叶节点,我们必须:

  • 转出左下方节点
  • on complete(比如在动画播放一秒后),转换出它的父级,
  • 完成后,转出它的父级
  • 完成,过渡到最右边的父母
  • 完成,最右边的孩子过渡
  • 完成,叶子过渡

我的问题是:

如果您将这些节点中的每一个想象为HTML视图(其中叶子是'partials',借用rails中的术语),或者MXML视图,那么您将嵌套子组件,并且您不一定知道嵌套来自应用程序根目录的级别,如上所述,为转换设置动画的最佳方法是什么?

一种方法是全局存储所有可能的路径,然后说“应用程序,转换出此路径,在此路径中转换”。如果应用程序非常简单,那就行。这就是Gaia这样做的一个动作脚本框架。但是,如果您希望它能够转换进/出任意嵌套路径,则无法全局存储,因为:

  1. Actionscript无法处理所有处理
  2. 看起来不是很好的封装
  3. 所以这个问题可以改写为,如何动画最左边的叶子节点和它的父节点,从叶子开始,并在最右边的叶子节点中动画,从根开始?存储的信息在哪里(转入和转出的内容)?

    另一种可能的解决方案是只说“应用程序,转换出以前的子节点,以及何时完成,在当前子节点中转换”,其中“子节点”是应用程序根的直接子节点。然后,应用程序根目录的最左边的子节点(具有两个子节点,每个子节点有两个子节点)将检查它是否处于正确的状态(如果它的子节点被“转出”)。如果没有,它会在它们上面调用“transitionOut()”......这样一切都将被完全封装。但似乎这对处理器来说非常密集。

    你怎么看?你还有其他选择吗?或者您能否指出我在AI Behavior Trees或分层状态机上的任何好资源,它们描述了它们实际上如何实现异步状态转换:

    • 他们从哪里在对象上调用“transitionOut”?从根或特定的孩子?
    • 状态存储在哪里?在全球范围内,在当地?什么是定义调用“transitionIn()”和“transitionOut()”?
    • 的范围

    我已经看过/阅读过很多关于人工智能和状态机的文章/书籍,但我还没有找到一些描述他们如何实现在复杂的MVC面向对象项目中实现异步/动画过渡的方法,其中包含100个视图/图形参与行为树。

    我应该从最父对象还是从孩子那里调用转换?

    以下是我检查过的一些事情:

    虽然这不一定是人工智能问题,但没有其他资源描述如何将嵌套状态架构应用于网站;这些是最接近的东西。

    另一种说出问题的方法:如何向应用程序广播状态更改?你在哪里保留事件听众?如何在任意嵌套时找到动画视图?

    注意:我不打算构建一个游戏,只是试图建立一个动画网站。

3 个答案:

答案 0 :(得分:1)

我只是在这里猜测,但是你可以将这个树存储在你代码中的一个实际树中,然后,当你点击它想要导航时,你调用一个被调用的函数,比如findPath(fromNode,toNode),该函数会找到两个节点之间的路径,一种方法是使用这个伪代码:

Define array1;
Define array2;

loop while flag is false {
    store fromNode's parent node in array1;
    store toNode's parent node in array2;

    loop through array1 {
        loop through array2 {
            if array1[i] == array2[j] {
             flag = true;
            }
        }
    }
}

path = array1 + reverse(array2);

还有你的道路。

<强>更新

另一个解决方案是让每个页面都有这样的伪代码 - 我开始真的喜欢伪代码:

function checkChildren(targetNode) {
    loop through children {
        if children[i] == target {
            return path;
        }
        if children[i].checkChildren == target node {
            return path;
        }
    }
}

这是非常抽象的,它有更多的细节和很多优化,这是我的想法:

  • 将树上的路径传递给函数,以便直接找到路径,而不是返回到调用函数。
  • 将当前正在呼叫的孩子传递给父母checkChildren,这样就不会费心检查那个孩子以便更快地进行检查。

我仍然认为我没有很好地表达我的想法,所以我会尝试解释它。

假设您从画廊中的pic2到项目中的project3。

pic2会检查它的子节点(如果有的话),如果它找到目标它去那里,否则它调用它的父节点(gallery)的checkChildren传递给父节点将不会费心检查它,并将自己传递给数组中的父级,以便父级知道已经采取了什么路径。

父项检查它们的子项是否是目标,如果是它将自己和子项添加到路径数组中,那就是路径。

如果它的直接子节点都不是目标,那么它会调用它的每个子节点的checkChildren将其自身添加到路径数组中,这样如果它的任何子节点找到目标,它会将自身和子节点添加到数组中你有自己的道路。

如果没有孩子发现目标图库调用其父(主页)的检查子项传递自己并将其自身添加到路径数组。

主页使用相同的方法检查除了图库以外的所有子项。

或者,您可以选择不将路径作为数组传递,而只是在没有子项或子项的子项匹配时转换到父页面,因为您确定目标不在此页面下。

我希望自己清楚明白。如果你愿意,我可以为你写checkChildren。

答案 1 :(得分:1)

在建议可能的方法之前,我会尝试简化问题。过渡似乎与视图有关,而不是模型。如果我跳过转换并直接转到其他叶节点,应用程序仍然可以工作,但用户没有视觉线索。所以我建议你专门使用一个视图控制器来保存当前分支和各种视图的转换。

这可能是将两种类型的转换拆分为一种堆栈的更好方法,其中pop转换将返回到前一个节点,而推送转换在层次结构中前进。 Apple使用类似的技术来使用navigation view controller来管理导航应用程序。它基本上维护着一组视图控制器,用户可以通过它来访问特定节点。当用户返回时,顶部项目从堆栈弹出,并且用户看到前一项目。如果用户在层次结构中更深入,则会将新的视图控制器压入堆栈。

您仍然需要一种以flyweight方式表示层次结构的全局方式,而导航堆栈仅将当前可见的分支存储到叶节点。

如果用户从一个叶子节点转到另一个叶子节点,则当前堆栈将弹出到公共父节点。然后,将要求全局树结构获取从该父节点到新叶节点的路径。此节点路径被推入当前导航堆栈,并且在按下每个项目时,将显示转换。

在一个简单的算法中,有这两种数据结构和一种获取包含叶节点的整个分支的方法:

  1. 树的全局表示
  2. 当前分支的堆栈
  3. <强>最初:

    stack = []
    tree = create-tree()
    

    <强>算法:

    // empty current branch upto the common ancestor, root node if nothing else
    until stack.peek() is in leaf-node.ancestors() {
        stack.pop() // animates the transition-out
    }
    parent = stack.peek();
    // get a path from the parent to leaf-node
    path = tree.get-path(parent, leaf-node)
    for each node in path {
        stack.push(node) // animates the transition-in
    }
    

    <强>更新

    整个应用程序可以有一个导航控制器,或多个这样的控制器。导航控制器只需要知道有一棵树暴露了某些操作。因此,您可以创建一个包含这些操作的接口,并让具体的子类实现它。

    我只能想到需要公开的两个操作(伪Java语法):

    interface Tree {
        List<Node> getAncestors(node);
        List<Node> findPath(ancestorNode, descendantNode);
    }
    

    这应该提供足够的抽象来保持导航控制器在全局树结构中戳。要将其提升到新的水平,请使用dependency injection,以便将树对象注入导航控制器,从而提高可测试性并完全破坏树连接。

答案 2 :(得分:0)

或者我不完全理解你的问题,或者你让它变得太复杂......试着想想简单,因为基本上,所有你想做的事情,如下:

  1. 检索树中的路径
  2. 按顺序执行多个动画(沿着那条路径)
  3. 这些事情都不是很难。对于后一项任务,您可以使用一个更好的AS3动画库。但为了便于理解,让我为您提供一个轻量级的解决方案:

    你的问题只是按顺序执行异步任务。检索序列本身并不难。但是,你应该考虑,不是每条路径都通过根(例如从一片叶子到它的sibbling)。在flash世界中,顺序执行异步任务的唯一方法是回调。当一个任务完成时,它会回拨,然后从那里决定下一步该做什么。

    所以这里有一些代码:

    首先,让我们为你的所有叶子/节点定义一个接口......以下应该可以解决这个问题:

    package  {
        public interface ITransitionable {
         //implementors are to call callback when the show/hide transition is complete
         function show(callback:Function):void;
         function hide(callback:Function):void;
         function get parent():ITransitionable;
        }
    }
    

    现在,以下类将能够按照您在ITransitionable

    的树上指定的转场进行转换
    package  {
     public class Transition {
      ///Array of animation starting functions
      private var funcs:Array;
      ///Function to call at the end of the transition
      private var callback:Function;
      public function Transition(from:ITransitionable, to:ITransitionable, callback:Function) {
       this.callback = callback;
       this.funcs = [];
       var pFrom:Array = path(from).reverse();
       var pTo:Array = path(to).reverse();
       while ((pFrom[0] == pTo[0]) && (pTo[0] != null)) {//eliminate common path to root
        pFrom.shift();
        pTo.shift();
       }
       pFrom.reverse();//bring this one back into the right order
       //fill the function array:
       var t:ITransitionable;
       for each (t in pFrom) this.funcs.push(hider(t));
       for each (t in pFrom) this.funcs.push(shower(t));
       this.next();
      }
      ///cancels the overall transition
      public function cancel():void {
       this.funcs = [];
      }
      ///creates a function that will start a hiding transition on the given ITransitionable.
      private function hider(t:ITransitionable):Function {
       return function ():void {
        t.hide(this.next());
       }
      }
      ///@see hider
      private function shower(t:ITransitionable):Function {
       return function ():void {
        t.show(this.next());
       }
      }
      ///this function serves as simple callback to start the next animation function
      private function next(...args):void {
       if (this.funcs.length > 0) this.funcs.shift()();
       else this.callback();
      }
      ///returns the path from a node to the root
      private function path(node:ITransitionable):Array {
       var ret:Array = [];
       while (node != null) {
        ret.push(node);
        node = node.parent;
       }
       return ret;
      }
     }
    
    }
    

    这不是一个完美的美,但它应该足以让你指向正确的方向。不需要花哨的状态机,所以永远......希望这有帮助...