2010-01-18 20 views
6

J'essaie de comprendre comment implémenter les transitions d'état imbriquées dans un langage de programmation à un seul thread (Actionscript). Disons que j'ai une structure comme celle-ci: behavior treeMeilleure pratique pour la transition de l'état imbriqué à l'état imbriqué (voir diagramme)

Imaginez maintenant que chaque nœud feuille est un point de destination sur un site Web, comme une image dans une galerie ou un commentaire imbriqué dans une vue post imbriquée dans une vue de page. .. Et le but est de pouvoir exécuter des transitions animées du nœud feuille au nœud feuille, en animant l'arbre précédent (de bas en haut), et en animant dans l'arbre courant (de haut en bas).

Donc, si nous étions en bas à gauche nœud le plus de feuilles, et nous voulions aller en bas à droite nœud le plus de feuilles, nous devons:

  • transition en bas à gauche -node
  • sur complète (par exemple après une seconde d'animation), la transition à son parent,
  • sur complète, la transition à son parent
  • sur complète, transition dans le plus à droite parent
  • sur complète, transition dans le bon mois st-enfant
  • sur complète, la transition à la feuille

Ma question est:

Si vous imaginez chacun de ces noeuds que les vues HTML (où les feuilles sont « partials », empruntant le terme des rails), ou les vues MXML, où vous imbriquez des sous-composants, et vous ne connaissez pas nécessairement les niveaux d'imbrication de la racine de l'application, quelle est la meilleure façon d'animer la transition comme décrit ci-dessus? Un moyen consiste à stocker globalement tous les chemins possibles, puis de dire «Application, transition hors de ce chemin, transition dans ce chemin». Cela fonctionne si l'application est très simple. C'est ainsi que le fait Gaia, un framework Actionscript. Mais si vous voulez qu'il soit en mesure de passer dans des chemins/arbitrairement imbriqués, vous ne pouvez pas stocker que dans le monde parce que:

  1. Actionscript ne pouvait pas gérer tout ce que le traitement
  2. ne semble pas bonne encapsulation

donc, cette question peut être reformulée comme, comment vous animez le nœud le plus à gauche feuilles et ses parents, à partir de la feuille, et animez dans le nœud le plus à droite feuilles, en commençant par le racine? Où cette information est-elle stockée (que faire pour entrer et sortir)? Une autre solution possible consisterait simplement à dire "Application, sortir le noeud enfant précédent, et quand cela est terminé, transition dans le noeud enfant courant", où le "noeud enfant" est l'enfant direct de la racine de l'application. Ensuite, l'enfant le plus à gauche de la racine de l'application (qui a deux nœuds enfants, qui ont chacun deux nœuds enfants), vérifie s'il est dans le bon état (si les enfants sont 'sortis'). Sinon, il appellerait "transitionOut()" sur eux ... De cette façon, tout serait complètement encapsulé. Mais il semblerait que ce serait assez gourmand en processeur.

Qu'en pensez-vous? Avez-vous d'autres alternatives?Ou pouvez-vous me pointer vers de bonnes ressources sur AI Behavior Trees ou machines d'état hiérarchique qui décrivent la façon dont ils mettent en œuvre pratiquement transitions d'état asynchrones:

  • D'où ils appellent « transitionOut » sur un objet? De la racine ou d'un enfant spécifique?
  • Où l'état est-il stocké? Globalement, localement? Quelle est la portée définissant ce que l'on appelle "transitionIn()" et "transitionOut()"?

J'ai vu/lu de nombreux articles/ouvrages sur AI et machines d'état, mais je dois encore trouver quelque chose qui décrit la façon dont ils mettent effectivement en œuvre les transitions Asychronous/animation dans un objet complexe MVC projet orienté avec 100s de vues/Graphics participer à l'arbre de comportement.

Dois-je appeler les transitions de l'objet le plus parent ou de l'enfant?

Voici quelques-unes des choses que j'ai étudié:

Bien que ce n'est pas nécessairement un problème AI, il n'y a pas d'autres ressources qui décrivent comment appliquer les architectures d'Etat imbriquées à des sites Web; ce sont les choses les plus proches.

Une autre façon de formuler la question: Comment pouvez-vous diffuser les changements d'état à l'application? Où gardez-vous les auditeurs de l'événement? Comment trouvez-vous la vue à animer quand elle est arbitrairement imbriquée?

Note: Je n'essaie pas de construire un jeu, juste d'essayer de créer des sites Web animés.

+0

est la structure de l'arbre toujours constante ou pourrait changer en fonction du chemin que l'utilisateur a pris? – Anurag

+0

La structure de l'arbre est toujours constante. mais le nombre de nœuds pourrait changer (disons qu'une galerie a 10 images et une a 100). Fondamentalement, nous connaissons les structures possibles avant la main (quels parents peuvent avoir quels enfants), mais nous ne connaissons pas nécessairement le nombre ou le niveau de profondeur. –

Répondre

1

Je vais essayer de simplifier le problème avant de suggérer une approche possible. Les transitions semblent liées aux vues, et non au modèle. Si je devais sauter les transitions et aller directement à un autre nœud feuille, l'application fonctionne toujours mais il n'y a pas d'indice visuel pour les utilisateurs. Donc, je vous suggère d'utiliser un contrôleur de vue spécifiquement pour contenir la branche actuelle et les transitions de différentes vues.

Il est probablement préférable de diviser les deux types de transitions en une sorte de pile dans laquelle une transition de type pop doit revenir à un noeud précédent, tandis qu'une transition de type push se poursuit dans la hiérarchie. Apple utilise une technique similaire pour gérer les applications de navigation en utilisant un navigation view controller. Il maintient essentiellement une pile de contrôleurs de vue que l'utilisateur a suivi pour accéder à un nœud particulier. Lorsqu'un utilisateur revient, l'élément supérieur est retiré de la pile et l'utilisateur voit l'élément précédent.Si l'utilisateur va plus loin dans la hiérarchie, un nouveau contrôleur de vue est poussé sur la pile.

Vous auriez quand même besoin d'une manière globale de représenter les hiérarchies de manière équilibrée, alors que la pile de navigation ne stocke que la branche visible vers le nœud feuille.

Si un utilisateur passait d'un noeud feuille à l'autre, la pile en cours sera déplacée vers le parent commun. Ensuite, la structure arborescente globale sera demandée pour obtenir le chemin de ce parent jusqu'au nouveau nœud feuille. Ce chemin des nœuds est poussé dans la pile de navigation actuelle, et lorsque chaque élément est poussé, la transition est affichée.

Dans un algorithme simple, il y aurait ces deux structures de données et un moyen d'obtenir l'ensemble de la branche qui contient nœud feuille:

  1. une représentation globale de l'arbre
  2. une pile de courant branche

initialement:

stack = [] 
tree = create-tree() 

algorithme:

// 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 
} 

Update:

L'ensemble de l'application pourrait avoir un contrôleur de navigation unique, ou plusieurs de ces contrôleurs. Un contrôleur de navigation a seulement besoin de savoir qu'il y a un arbre qui expose certaines opérations. Vous pouvez donc créer une interface avec ces opérations et laisser les sous-classes concrètes l'implémenter.

je ne peux penser à deux opérations qui ont besoin d'être exposés (pseudo-syntaxe Java):

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

Cela devrait fournir abstraction suffisante pour maintenir le contrôleur de navigation piquer dans la structure globale des arbres. Pour passer au niveau suivant, utilisez dependency injection pour que l'objet arbre soit injecté dans le contrôleur de navigation, améliorant la testabilité et rompant complètement la connexion de l'arbre.

+0

hey man, merci pour cette excellente réponse, et ce lien iphone! Cela me rapproche beaucoup plus. donc ma question en réponse à ceci est, comment les choses accèdent-elles à cet arbre global pour que l'application reste modulaire et évolutive? –

+0

génial .. j'ai mis à jour la réponse avec quelques informations sur l'arbre global. – Anurag

1

Je devine juste ici mais vous pouvez stocker cet arbre dans un arbre réel dans votre code et puis, quand cliqué vous voulez naviguer vous appelez une fonction appelée, par exemple, findPath (de Noeud, au Noeud) cette fonction sera trouver le chemin entre les deux nœuds, une façon de le faire est avec ce pseudo code:

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

Et il y a votre chemin.

MISE À JOUR:

Une autre solution serait de faire de chaque page ont quelque chose comme ce code pseudo - je commence à aimer vraiment code pseudo:

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

Ceci est très très abstraite , il a beaucoup plus de détails et beaucoup d'optimisations, voici ce que j'ai en tête:

  • Passez le chemin montant de l'arbre à la fonction s o le chemin peut être trouvé directement au lieu de revenir à la fonction d'appel.
  • Passez l'enfant appelant courant aux parents checkChildren afin qu'il ne dérange pas de vérifier que l'enfant effectue la vérification plus rapidement.

Je pense toujours que je n'ai pas bien livré mon idée, donc je vais essayer de l'expliquer.

Dites que vous allez de pic2 dans la galerie à project3 dans les projets. Pic2 vérifierait ses enfants (le cas échéant), s'il trouve la cible, il y va, sinon il appelle son chèque parentChèque (chèque) passant lui-même au parent ne sera pas la peine de le vérifier, et se passant à le parent dans un tableau afin que le parent sache quel chemin a été pris.

Le parent vérifie ses enfants si l'un d'entre eux est la cible, si c'est lui-même ajoute et l'enfant au tableau de chemin et c'est le chemin. Si aucun de ses enfants directs n'est la cible, alors il appelle chacun des checkChildren de ses enfants qui passent en s'ajoutant au tableau du chemin de sorte que si l'un de ses enfants trouve la cible elle-même et l'enfant dans le tableau et vous avez votre chemin.

Si aucun des enfants ne trouve les appels de la galerie cible, ses enfants (page d'accueil) vérifient que les enfants se transmettent eux-mêmes et qu'ils s'ajoutent au tableau du chemin.

La page d'accueil vérifie tous ses enfants sauf la galerie en utilisant la même méthode. Alternativement, vous pouvez choisir de ne pas passer le chemin en tant que tableau mais simplement passer à la page parente si aucun des enfants ou des enfants des enfants ne correspondent parce que vous êtes sûr que la cible n'est pas sous cette page.

J'espère que je me suis fait clair. Je peux écrire checkChildren pour vous si vous le souhaitez.

+0

hey, merci. Je fais quelque chose comme ça maintenant, où j'ai un gestionnaire central qui boucle à travers la hiérarchie des objets d'affichage, en fonction d'un ensemble prédéfini de chemins, pour trouver les objets à sortir et transition. Cela fonctionne, mais je Je cherchais quelque chose de plus abstrait, donc je n'avais pas besoin de stocker ou de calculer de façon centralisée cette information de chemin. Ou est-ce la méthode préférée? –

+0

Je ne pense pas que stocker les chemins eux-mêmes est une bonne idée parce que vous finirez avec (n-1) + (n-2) .. 1 chemins, donc pour l'arbre que vous avez dessiné dans la question, vous ' Je finirai avec 55 chemins, plus, je pense qu'il est plus facile de faire l'ordinateur faire le travail pour vous;) Si vous voulez vous pouvez obtenir tous les chemins et les mettre en cache, cela peut prendre plus de ressources, mais il faudra trouver le chemin au besoin plus vite .. Est-ce que c'est ce que tu fais? Vous pouvez également essayer ma nouvelle solution. –

0

soit je ne comprends pas bien vous problème, ou que vous faites beaucoup trop compliqué ... essayer de penser simple, parce que fondamentalement, tout ce que vous voulez faire, est la suivante:

  1. récupérer un chemin dans un arbre
  2. multiples animations séquentielle execute (le long de ce chemin très)

aucune de ces choses est beaucoup plus difficile à. pour cette dernière tâche, vous pouvez probablement utiliser l'une des meilleures bibliothèques d'animation AS3. mais pour des raisons de compréhension, permettez-moi de vous proposer une solution légère:

votre problème consiste simplement à effectuer des tâches asynchrones de manière séquentielle. récupérer la séquence elle-même n'est pas vraiment difficile. cependant, vous devriez considérer que tous les chemins ne passent pas par la racine (par exemple, d'une feuille à son grignotage). dans le monde du flash, la seule façon d'exécuter des tâches asynchrones de manière séquentielle est d'effectuer des rappels. quand une tâche se termine, elle rappelle, et à partir de là, vous décidez quoi faire ensuite.

vient alors voici un code:

Tout d'abord, nous allons définir une interface pour tous vos leafs/nœuds ... ce qui suit devrait faire l'affaire:

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

maintenant la classe suivante sera en mesure de faire des transitions que vous avez spécifié sur les arbres de 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; 
    } 
} 

} 

ce n'est pas une beauté parfaite, mais il devrait suffire à vous orienter dans le droit direc tion. pas besoin de machines d'état de fantaisie, quel que soit jamais ... espérons que cela aide ...