6

我正试图围绕以单线程编程语言(Actionscript)实现嵌套状态转换的最佳方法。假设我有这样的行为树结构:行为树

现在假设每个叶子节点是网站上的一个目标点,就像画廊中的图像,或者嵌套在页面视图中的帖子视图中的评论......目标是能够从叶子运行动画过渡节点到叶节点,通过动画出前一棵树(从下到上),并在当前树中动画化(从上到下)。

所以,如果我们在最左下角的叶子节点,并且我们想去最右下角的叶子节点,我们必须:

  • 转出左下节点
  • 完成时(比如在一秒钟的动画之后),过渡到它的父级,
  • 完成后,转出它的父级
  • 完成后,过渡到最右边的父级
  • 完成后,最右边的过渡
  • 完成时,叶中的过渡

我的问题是:

如果您将这些节点中的每一个想象成 HTML 视图(叶子是“部分”,借用 rails 的术语)或 MXML 视图,您在其中嵌套子组件,并且您不一定知道嵌套级别应用程序根目录,如上所述为过渡设置动画的最佳方式是什么?

一种方法是全局存储所有可能的路径,然后说“应用程序,过渡到这条路径,过渡到这条路径”。如果应用程序非常简单,那就可以了。这就是Gaia的做法,一个 Actionscript 框架。但是,如果您希望它能够转换进/出任意嵌套路径,则不能全局存储它,因为:

  1. Actionscript 无法处理所有这些处理
  2. 似乎不是很好的封装

所以这个问题可以改写为,你如何动画出最左边的叶子节点及其父节点,从叶子开始,并在最右边的叶子节点中动画,从根开始? 该信息存储在哪里(要输入和输出什么)?

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

你怎么看?你还有其他选择吗?或者你能指出我关于AI 行为树或分层状态机的任何好的资源,这些资源描述了它们如何实际实现异步状态转换:

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

我已经看过/阅读了许多关于 AI 和状态机的文章/书籍,但我还没有找到描述它们如何在复杂的 MVC 面向对象项目中实际实现异步/动画转换的内容,其中有 100 多个视图/图形参与行为树。

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

以下是我检查过的一些内容:

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

问题的另一种表述方式:如何将状态更改广播到应用程序?你把事件监听器放在哪里?当它被任意嵌套时,你如何找到动画的视图?

注意:我不是在尝试构建游戏,只是在尝试构建动画网站。

4

3 回答 3

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 会检查它的孩子(如果有的话),如果它找到它去那里的目标,否则它会调用它的父母(画廊)的 checkChildren 将自己传递给父母不会费心检查它,并将自己传递给父母数组,以便父级知道所采用的路径。

父级检查其子级中的任何一个是否是目标,如果是,则将自身和子级添加到路径数组中,这就是路径。

如果它的直接子代都不是目标,那么它调用它的每个子代的 checkChildren 传递,将自己添加到路径的数组中,这样如果它的任何子代找到目标,它就会将自己和子代添加到数组中,并且你有你的小路。

如果没有孩子找到目标画廊,则调用其父(主页)的检查孩子传递自己并将自己添加到路径的数组中。

主页使用相同的方法检查除画廊之外的所有子项。

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

我希望我说清楚了。如果你愿意,我可以为你写 checkChildren。

于 2010-01-18T03:00:57.607 回答
1

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

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

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

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

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

  1. 树的全局表示
  2. 当前分支的堆栈

最初:

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);
}

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

于 2010-01-18T04:20:13.940 回答
0

要么我不完全理解你的问题,要么你把它弄得太复杂了......试着简单地思考,因为基本上,你想做的所有事情如下:

  1. 检索树中的路径
  2. 顺序执行多个动画(沿着那个路径)

这些都不是难事。对于后一项任务,您可能可以使用更好的 AS3 动画库之一。但为了理解起见,让我为您提供一个轻量级的解决方案:

您的问题只是按顺序执行异步任务。检索序列本身并不难。但是,您应该考虑,并非每条路径都通过根(例如从一片叶子到它的兄弟)。在 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;
  }
 }

}

这不是一个完美的美丽,但它足以指引你走向正确的方向。不需要花哨的状态机,如此...希望这会有所帮助...

于 2010-01-18T16:48:06.327 回答