Наилучшая практика перехода от вложенного состояния к вложенному состоянию - PullRequest
6 голосов
/ 18 января 2010

Я пытаюсь обдумать лучший способ реализации вложенных переходов состояний в однопоточном языке программирования (Actionscript).Скажем, у меня есть структура, подобная этому дереву поведения: behavior tree

Теперь представьте, что каждый конечный узел является точкой назначения на веб-сайте, например, изображением в галерее, или комментарием, вложенным в представление сообщения, вложенное впросмотр страницы ... И цель состоит в том, чтобы иметь возможность запускать анимированные переходы от конечного узла к конечному узлу путем анимации предыдущего дерева (снизу вверх) и анимации в текущем дереве (сверху вниз).

Итак, если бы мы были в самом нижнем левом листовом узле и хотели бы перейти в самый нижний правый конечный узел, мы должны были бы:

  • завершить переход нижнего левого узла
  • по завершении (скажем, через секунду анимации), перейти к родительскому элементу,
  • при завершении, перейти к родительскому
  • по завершении, переход IN к самому правому родителю
  • при завершении, переход к правому самому ребенку
  • по завершению, переход в лист

Мой вопрос:

Если вы представляете каждый из этих узлов как HTML views (где листья являются «частичными», заимствуя термин из rails) или представления MXML, где вы вкладываете подкомпоненты, и вы не обязательно знаете уровни вложенности из корня приложения, что является лучшим способомАнимировать переход, как описано выше?

Один из способов - сохранить все возможные пути глобально, а затем сказать: «Приложение, переход с этого пути, переход по этому пути».Это работает, если приложение очень простое.Вот как Gaia делает это, ActionScript Framework.Но если вы хотите, чтобы он мог входить и выходить произвольно вложенными путями, вы не можете хранить это глобально, потому что:

  1. Actionscript не может обработать всю эту обработку
  2. не похоже на хорошую инкапсуляцию

Таким образом, этот вопрос можно перефразировать следующим образом: как вы анимируете левый крайний узел и его родителей, начиная с листа, и анимируете всамый правый конечный узел, начинающийся с корня? Где хранится эта информация (для чего входить и выходить)?

Другим возможным решением было бы просто сказать «Приложение, переход предыдущийдочерний узел, и когда это будет завершено, перейдите в текущий дочерний узел », где« дочерний узел »является прямым дочерним элементом корня приложения.Затем самый левый дочерний элемент корня приложения (который имеет два дочерних узла, каждый из которых имеет два дочерних узла), проверит, находится ли он в правильном состоянии (если его дочерние элементы «переходили»).Если нет, он вызовет для них «transitionOut ()» ... Таким образом, все будет полностью инкапсулировано.Но похоже, что это будет довольно интенсивно использовать процессор.

Что вы думаете?Есть ли у вас другие альтернативы?Или вы можете указать мне какие-нибудь полезные ресурсы на деревьях поведения AI или машинах иерархического состояния, которые описывают, как они практически реализуют асинхронные переходы состояний:

  • Откуда они называют "transitionOut"на объекте?От корня или конкретного потомка?
  • Где хранится состояние?Глобально, локально?Какова область действия, определяющая то, что вызывает «transitionIn ()» и «transitionOut ()»?

Я видел / читал много статей / книг по ИИ и государственным машинам, но мне еще предстоит что-то найтиописание того, как они фактически реализуют асинхронные / анимированные переходы в сложном объектно-ориентированном проекте MVC с сотнями видов / графиков, участвующих в дереве поведения.

Должен ли я вызывать переходы из самого родительского объекта или из дочернего объекта?

Вот некоторые из вещей, которые я исследовал:

Хотя это не обязательно проблема ИИ, нет других ресурсов, описывающих, как применять архитектуры вложенных состояний к веб-сайтам; это самые близкие вещи.

Другой способ сформулировать вопрос: как вы транслируете изменения состояния в приложение? Где вы храните слушателей мероприятия? Как найти, какой вид анимировать, когда он произвольно вложен?

Примечание: я не пытаюсь создать игру, просто пытаюсь создавать анимированные сайты.

Ответы [ 3 ]

1 голос
/ 18 января 2010

Я постараюсь упростить проблему, прежде чем предлагать возможный подход. Кажется, что переходы связаны с представлениями, а не с моделью. Если бы мне пришлось пропустить переходы и перейти прямо к другому листовому узлу, приложение все равно работало бы, но для пользователей не было визуальной подсказки. Поэтому я бы предложил вам использовать контроллер вида специально для хранения текущей ветви и переходов различных представлений.

Вероятно, это лучший подход для разделения двух типов переходов в своего рода стек, в котором всплывающий переход должен возвращаться к предыдущему узлу, в то время как принудительный переход продолжается в иерархии. 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);
}

Это должно обеспечить достаточную абстракцию для того, чтобы удерживать контроллер навигации в глобальной структуре дерева. Чтобы перейти на следующий уровень, используйте внедрение зависимостей , чтобы объект дерева внедрялся в контроллер навигации, улучшая тестируемость и полностью разрывая связь с деревом.

1 голос
/ 18 января 2010

Я просто догадываюсь здесь, но вы можете сохранить это дерево в фактическом дереве в вашем коде, а затем, когда вы нажмете, вы хотите перемещаться, вы вызываете функцию, которая называется, скажем, 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);

И вот твой путь.

UPDATE:

Другим решением было бы сделать так, чтобы на каждой странице был что-то вроде этого псевдокода - я начинаю по-настоящему любить псевдокод:

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 своего родителя (галереи), передавая себя родителю, не потрудится проверить его и передать себе parent в массиве, чтобы родитель знал, какой путь был выбран.

Родитель проверяет своих потомков, является ли какой-либо из них целью, если он добавляет себя и потомка в массив пути, и это путь.

Если ни один из его прямых потомков не является целью, то он вызывает каждого из своих дочерних элементов checkChildren, передавая добавление себя в массив пути, так что, если любой из его потомков находит цель, он добавляет себя и потомка в массив и у тебя есть свой путь.

Если ни один из дочерних элементов не находит, что целевая галерея вызывает родительскую (домашнюю страницу) проверку дочерних элементов, передавая себя и добавляя себя в массив пути.

Домашняя страница проверяет всех своих детей, кроме галереи, тем же способом.

Кроме того, вы можете не передавать путь в виде массива, а просто переходить на родительскую страницу, если ни один из дочерних элементов или дочерних элементов не совпадает, потому что вы уверены, что цель не находится под этой страницей.

Надеюсь, я ясно дал понять. Я могу написать чек для детей, если хотите.

0 голосов
/ 18 января 2010

либо я не совсем понимаю вашу проблему, либо вы делаете ее слишком сложной ... постарайтесь думать просто, потому что в основном все, что вы хотите сделать, заключается в следующем:

  1. получить путь в дереве
  2. выполнить несколько анимаций последовательно (по этому самому пути)

ни одна из этих вещей не слишком сложна.для последней задачи вы, вероятно, можете использовать одну из лучших библиотек анимации AS3.но для понимания позвольте мне предложить вам простое решение:

Ваша проблема заключается в простом последовательном выполнении асинхронных задач.получение самой последовательности не очень сложно.однако следует учитывать, что не каждый путь проходит через корень (например, от одного листа к его брату).в мире флэш-памяти единственным способом последовательного выполнения асинхронных задач являются обратные вызовы.когда одна задача завершается, она перезванивает, и оттуда вы решаете, что делать дальше.

, так что вот вам код:

сначала давайте определим интерфейс для всех ваших листьев / узлов... следующее должно сделать трюк:

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

}

это непрекрасная красота, но этого должно быть достаточно, чтобы указать вам правильное направление.не нужно модных конечных автоматов, чего бы то ни было ... надеюсь, это поможет ...

...