6

私は、単一のスレッド化されたプログラミング言語 (Actionscript) でネストされた状態遷移を実装するための最良の方法に頭を悩ませようとしています。このビヘイビア ツリーのような構造があるとします。行動ツリー

ここで、各リーフ ノードが、ギャラリーの画像や、ページ ビューにネストされた投稿ビューにネストされたコメントのように、Web サイトの目的地であると想像してください...そして、目標は、リーフからアニメーション化されたトランジションを実行できるようにすることです。ノードからリーフ ノードへ、前のツリーを (下から上へ) アニメーション化し、現在のツリーで (上から下へ) アニメーション化します。

そのため、一番左下のリーフ ノードにいて、一番右下のリーフ ノードに移動したい場合は、次のようにする必要があります。

  • 左下のノードから移行
  • 完了すると (たとえば、アニメーションの 1 秒後)、親から移行します。
  • 完了すると、その親から移行します
  • 完了すると、一番右の親に遷移します
  • 完了時、右端の子に遷移
  • 完了時、リーフで遷移

私の質問は:

これらの各ノードを HTML ビュー (リーフは「部分」であり、レールから用語を借用したもの)、またはサブコンポーネントをネストしている MXML ビューとして想像すると、アプリケーションルート、上記のように遷移をアニメーション化する最良の方法は何ですか?

1 つの方法は、考えられるすべてのパスをグローバルに保存してから、「アプリケーション、このパスから遷移し、このパスに遷移します」と言うことです。アプリケーションが非常に単純な場合、これは機能します。これが、Actionscript フレームワークであるGaiaのやり方です。ただし、任意にネストされたパスに出入りできるようにしたい場合は、次の理由により、それをグローバルに保存できません。

  1. Actionscript はそのすべての処理を処理できませんでした
  2. 良いカプセル化のようには見えません

したがって、この質問は次のように言い換えることができます。どのように左端の葉のノードとその親を葉から開始し、右端の葉のノードでルートから開始してアニメーション化しますか? その情報はどこに保存されますか (何を移行するか)。

別の可能な解決策は、「アプリケーション、前の子ノードから移行し、それが完了したら、現在の子ノードに移行する」ということです。ここで、「子ノード」はアプリケーション ルートの直接の子です。次に、アプリケーション ルートの一番左の子 (2 つの子ノードがあり、それぞれに 2 つの子ノードがあります) は、正しい状態にあるかどうかを確認します (その子が「移行されている」場合)。そうでない場合は、それらに対して「transitionOut()」を呼び出します...そのようにして、すべてが完全にカプセル化されます。しかし、それはかなりプロセッサ集約的であるようです。

どう思いますか?他の代替手段はありますか?または、 AI Behavior Treesまたは Hierarchical State Machines で、非同期状態遷移を実際に実装する方法を説明している優れたリソースを教えてください。

  • オブジェクトのどこから「transitionOut」を呼び出すのでしょうか? ルートまたは特定の子から?
  • 状態はどこに保存されますか? グローバルに、ローカルに?「transitionIn()」と「transitionOut()」を呼び出すものを定義するスコープは何ですか?

私は AI とステート マシンに関する多くの記事/本を見たり読んだりしましたが、ビヘイビア ツリーに参加する数百のビュー/グラフィックスを含む複雑な MVC オブジェクト指向プロジェクトで非同期/アニメーション遷移を実際に実装する方法を説明するものをまだ見つけていません。

一番親のオブジェクトからトランジションを呼び出すべきですか、それとも子からトランジションを呼び出すべきですか?

ここに私が調べたもののいくつかがあります:

これは必ずしも AI の問題ではありませんが、ネストされた状態のアーキテクチャを Web サイトに適用する方法を説明する他のリソースはありません。これらは最も近いものです。

質問の別の言い方: 状態の変化をアプリケーションにどのようにブロードキャストしますか? イベントリスナーはどこに置いていますか? 任意にネストされている場合、アニメーション化するビューをどのように見つけますか?

注: 私はゲームを構築しようとしているのではなく、アニメーション化された Web サイトを構築しようとしているだけです。

4

3 に答える 3

1

可能なアプローチを提案する前に、問題を単純化しようとします。トランジションは、モデルではなくビューに関連しているようです。遷移をスキップして別のリーフ ノードに直接移動した場合でも、アプリケーションは動作しますが、ユーザーには視覚的な手がかりがありません。したがって、現在のブランチとさまざまなビューの遷移を保持するために、特にビューコントローラーを使用することをお勧めします。

おそらく、2 種類のトランジションを一種のスタックに分割する方がよいでしょう。ポップ トランジションは前のノードに戻り、プッシュ トランジションは階層を進めます。Apple は、同様の手法を使用して、ナビゲーション ビュー コントローラーを使用してナビゲーション アプリケーションを管理しています。基本的に、ユーザーが特定のノードに到達するためにたどったView Controllerのスタックを維持します。ユーザーが戻ると、一番上のアイテムがスタックからポップされ、ユーザーには前のアイテムが表示されます。ユーザーが階層をさらに深く進むと、新しいビュー コントローラーがスタックにプッシュされます。

ナビゲーション スタックは現在表示されているブランチをリーフ ノードまで格納するだけですが、階層をフライウェイト方式で表すグローバルな方法が引き続き必要です。

ユーザーがあるリーフ ノードから別のリーフ ノードに移動した場合、現在のスタックは共通の親にポップ アウトされます。次に、グローバル ツリー構造は、その親から新しいリーフ ノードまでのパスを取得するよう求められます。ノードのこのパスは現在のナビゲーション スタックにプッシュされ、各アイテムがプッシュされると遷移が表示されます。

単純なアルゴリズムでは、次の 2 つのデータ構造と、リーフ ノードを含むブランチ全体を取得する方法があります。

  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
}

更新

アプリケーション全体で、ナビゲーション コントローラーを 1 つだけ持つことも、そのようなコントローラーを複数持つこともできます。ナビゲーション コントローラーは、特定の操作を公開するツリーがあることを知るだけで済みます。したがって、これらの操作でインターフェイスを作成し、具象サブクラスにそれを実装させることができます。

公開する必要がある操作は 2 つしか考えられません (疑似 Java 構文)。

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

これにより、ナビゲーション コントローラーがグローバル ツリー構造内に突っ込み続けるのに十分な抽象化が提供されるはずです。次のレベルに進むには、依存性注入を使用して、ツリー オブジェクトがナビゲーション コントローラーに注入されるようにします。これにより、テスト容易性が向上し、ツリー接続が完全に切断されます。

于 2010-01-18T04:20:13.940 に答える
0

私はあなたの問題を完全に理解していないか、あなたがそれを複雑にしすぎています...基本的にあなたがしたいことはすべて次のとおりであるため、単純に考えてみてください:

  1. ツリー内のパスを取得する
  2. 複数のアニメーションを順番に実行します (まさにそのパスに沿って)

これらのことはどれもそれほど難しいことではありません。後者のタスクについては、より優れた AS3 アニメーション ライブラリのいずれかを使用できます。しかし、理解のために、軽量のソリューションを提供させてください。

あなたの問題は、非同期タスクを順番に実行するだけです。シーケンス自体を取得することはそれほど難しくありません。ただし、すべてのパスがルートを通過するわけではないことを考慮する必要があります (たとえば、1 つのリーフからその兄弟へ)。フラッシュの世界では、非同期タスクを順次実行する唯一の方法はコールバックです。1 つのタスクが終了するとコールバックされ、そこから次に何をするかを決定します。

だからここにいくつかのコードがあります:

まず、すべてのリーフ/ノードのインターフェースを定義しましょう...次のようにしてください。

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 に答える