Drzewa zachowania wyprzedzającego


25

Próbuję omijać drzewa zachowań, więc dodam kod testowy. Jedną z rzeczy, z którymi się zmagam, jest sposób na zapobieganie obecnie działającemu węzłowi, gdy pojawia się coś o wyższym priorytecie.

Rozważ następujące proste, fikcyjne drzewo zachowań dla żołnierza:

wprowadź opis zdjęcia tutaj

Załóżmy, że minęła pewna liczba tyknięć i nie było w pobliżu wroga, żołnierz stał na trawie, więc do wykonania wybrano węzeł Usiądź :

wprowadź opis zdjęcia tutaj

Teraz akcja Usiądź zajmuje trochę czasu, ponieważ do odtworzenia jest animacja, więc powraca Runningjako jej status. Przechodzi tyknięcie lub dwa, animacja nadal działa, ale wróg jest blisko? wyzwala węzeł warunku. Teraz musimy jak najszybciej zatrzymać węzeł Usiądź, abyśmy mogli wykonać węzeł Ataku . Idealnie byłoby, gdyby żołnierz nie skończył nawet siadania - zamiast tego mógłby odwrócić kierunek animacji, gdyby tylko zaczął siadać. Dla większego realizmu, jeśli przekroczy on punkt krytyczny w animacji, moglibyśmy zamiast tego pozwolić mu skończyć siadanie, a następnie stanąć ponownie, lub może sprawić, że potknie się w pośpiechu, aby zareagować na zagrożenie.

Próbowałem, jak mogłem, ale nie byłem w stanie znaleźć wskazówek, jak poradzić sobie z tego rodzaju sytuacją. Cała literatura i filmy, które zużyłem w ciągu ostatnich kilku dni (a było ich dużo), wydają się omijać ten problem. Najbliższą rzeczą, jaką udało mi się znaleźć, była koncepcja resetowania uruchomionych węzłów, ale nie daje to takim węzłom, jak Sit, szansy na powiedzenie „hej, jeszcze nie skończyłem!”

Pomyślałem o zdefiniowaniu metody Preempt()lub Interrupt()metody w mojej Nodeklasie bazowej . Różne węzły poradzą sobie z tym, jak uznają za stosowne, ale w tym przypadku postaramy się przywrócić żołnierza na nogi JAK NAJSZYBCIEJ, a następnie wrócić Success. Myślę, że takie podejście wymagałoby również, aby moja baza Nodemiała pojęcie warunków oddzielnie od innych działań. W ten sposób silnik może sprawdzać tylko warunki i, jeśli przejdą, zablokować aktualnie wykonywany węzeł przed rozpoczęciem wykonywania akcji. Gdyby to zróżnicowanie nie zostało ustalone, silnik musiałby wykonywać węzły bez rozróżnienia, a zatem mógłby uruchomić nowe działanie przed zablokowaniem działającego.

Dla odniesienia poniżej są moje obecne klasy podstawowe. Ponownie, jest to skok, więc starałem się utrzymać rzeczy tak proste, jak to możliwe i dodawać złożoności tylko wtedy, gdy ich potrzebuję i kiedy to rozumiem, z czym obecnie mam problem.

public enum ExecuteResult
{
    // node needs more time to run on next tick
    Running,

    // node completed successfully
    Succeeded,

    // node failed to complete
    Failed
}

public abstract class Node<TAgent>
{
    public abstract ExecuteResult Execute(TimeSpan elapsed, TAgent agent, Blackboard blackboard);
}

public abstract class DecoratorNode<TAgent> : Node<TAgent>
{
    private readonly Node<TAgent> child;

    protected DecoratorNode(Node<TAgent> child)
    {
        this.child = child;
    }

    protected Node<TAgent> Child
    {
        get { return this.child; }
    }
}

public abstract class CompositeNode<TAgent> : Node<TAgent>
{
    private readonly Node<TAgent>[] children;

    protected CompositeNode(IEnumerable<Node<TAgent>> children)
    {
        this.children = children.ToArray();
    }

    protected Node<TAgent>[] Children
    {
        get { return this.children; }
    }
}

public abstract class ConditionNode<TAgent> : Node<TAgent>
{
    private readonly bool invert;

    protected ConditionNode()
        : this(false)
    {
    }

    protected ConditionNode(bool invert)
    {
        this.invert = invert;
    }

    public sealed override ExecuteResult Execute(TimeSpan elapsed, TAgent agent, Blackboard blackboard)
    {
        var result = this.CheckCondition(agent, blackboard);

        if (this.invert)
        {
            result = !result;
        }

        return result ? ExecuteResult.Succeeded : ExecuteResult.Failed;
    }

    protected abstract bool CheckCondition(TAgent agent, Blackboard blackboard);
}

public abstract class ActionNode<TAgent> : Node<TAgent>
{
}

Czy ktoś ma wgląd, który mógłby mnie poprowadzić we właściwym kierunku? Czy moje myślenie jest prawidłowe, czy obawiam się tak naiwności?


Musisz rzucić okiem na ten dokument: chrishecker.com/My_liner_notes_for_spore/... tutaj wyjaśnia, w jaki sposób chodzi drzewo, nie jak maszyna stanu, ale z ROOT przy każdym tyknięciu, co jest prawdziwą sztuczką do reaktywności. BT nie powinien potrzebować wyjątków ani wydarzeń. Łączą one systemy wewnętrznie i reagują na wszystkie sytuacje dzięki zawsze spływającemu z korzenia. Tak działa prewencyjność, jeśli zewnętrzny warunek o wyższym priorytecie sprawdza, tam płynie. ( Stop()oddzwanianie przed
odejściem

ten aigamedev.com/open/article/popular-behavior-tree-design jest również bardzo ładnie szczegółowy
v.oddou

Odpowiedzi:


6

Stwierdziłem, że zadaję to samo pytanie, co Ty i przeprowadziłem świetną krótką rozmowę w sekcji komentarzy na tej stronie blogu, gdzie otrzymałem inne rozwiązanie problemu.

Pierwszą rzeczą jest użycie współbieżnego węzła. Współbieżny węzeł to specjalny rodzaj węzła kompozytowego. Składa się z sekwencji kontroli warunków wstępnych, po których następuje pojedynczy węzeł działania. Aktualizuje wszystkie węzły potomne, nawet jeśli jego węzeł akcji jest w stanie „uruchomionym”. (W przeciwieństwie do węzła sekwencji, który musi rozpocząć aktualizację od bieżącego działającego węzła potomnego).

Główną ideą jest utworzenie dwóch kolejnych stanów zwrotnych dla węzłów akcji: „anulowanie” i „anulowanie”.

Niepowodzenie sprawdzania warunków wstępnych w węźle współbieżnym jest mechanizmem, który powoduje anulowanie działającego węzła akcji. Jeśli węzeł akcji nie wymaga długotrwałej logiki anulowania, natychmiast zwróci „anulowano”. W przeciwnym razie przechodzi w stan „anulowania”, w którym można umieścić całą logikę potrzebną do prawidłowego przerwania akcji.


Witaj w GDSE. Byłoby wspaniale, gdybyś mógł otworzyć tę odpowiedź z tego bloga tutaj, a na końcu link do tego bloga. Linki zwykle umierają, po otrzymaniu pełnej odpowiedzi, czyni to bardziej trwałym. Pytanie ma teraz 8 głosów, więc dobra odpowiedź byłaby niesamowita.
Katu

Nie sądzę, aby cokolwiek, co przywróciłoby drzewa zachowań do skończonej maszyny stanów, jest dobrym rozwiązaniem. Twoje podejście wydaje mi się, że musisz przewidzieć wszystkie warunki wyjścia z każdego stanu. Kiedy to faktycznie jest wadą FSM! BT ma tę zaletę, że zaczyna od zera, tworząc pośrednio w pełni połączony FSM, co pozwala uniknąć jawnego zapisywania warunków wyjścia.
v.oddou

5

Myślę, że twój żołnierz może zostać rozłożony na umysł i ciało (i cokolwiek innego). Następnie ciało może zostać rozłożone na nogi i ręce. Następnie każda część potrzebuje własnego drzewa zachowań, a także interfejsu publicznego - do żądań z części wyższego lub niższego poziomu.

Zamiast więc mikrozarządzania każdą akcją, po prostu wysyłasz natychmiastowe wiadomości, takie jak „ciało, usiądź na jakiś czas” lub „ciało, biegnij tam”, a ciało będzie zarządzać animacjami, zmianami stanu, opóźnieniami i innymi rzeczami ty.

Alternatywnie, ciało może samodzielnie zarządzać takimi zachowaniami. Jeśli nie ma zamówień, może zapytać: „Czy możemy tu usiąść?”. Co ciekawsze, z powodu enkapsulacji możesz łatwo modelować cechy takie jak zmęczenie lub ogłuszenie.

Możesz nawet zamieniać części - stwórz słonia z intelektem zombie, dodaj skrzydła człowiekowi (nawet tego nie zauważy) lub cokolwiek innego.

Założę się, że bez takiego rozkładu istnieje ryzyko, że prędzej czy później spotkasz się z eksplozją kombinatoryczną.

Również: http://www.valvesoftware.com/publications/2009/ai_systems_of_l4d_mike_booth.pdf


Dzięki. Po przeczytaniu twojej odpowiedzi 3 razy myślę, że rozumiem. Przeczytam ten plik PDF w ten weekend.
ja--

1
Zastanawiając się nad tym przez ostatnią godzinę, nie jestem pewien, czy rozumiem różnicę między posiadaniem całkowicie oddzielnych BT dla umysłu i ciała a pojedynczym BT, który jest rozkładany na poddrzewa (do których odwołuje się specjalny dekorator ze skryptami kompilacji) wiązanie wszystkiego w jednym dużym BT). Wydaje mi się, że zapewniłoby to podobne korzyści z abstrakcji i mogłoby faktycznie ułatwić zrozumienie, jak zachowuje się dany byt, ponieważ nie trzeba patrzeć na wiele różnych BT. Jednak prawdopodobnie jestem naiwny.
ja--

@ user13414 Różnica polega na tym, że do zbudowania drzewa potrzebne będą specjalne skrypty, gdy wystarczy użyć dostępu pośredniego (tj. gdy węzeł ciała musi zapytać swoje drzewo, który obiekt reprezentuje nogi) może być wystarczający i nie będzie wymagał żadnego dodatkowego pieprzenia mózgu. Mniej kodu, mniej błędów. Ponadto utracisz możliwość (łatwego) przełączania poddrzewa w czasie wykonywania. Nawet jeśli nie potrzebujesz takiej elastyczności, nic nie stracisz (w tym szybkość wykonywania).
Shadows In Rain

3

Leżąc wczoraj w łóżku, miałem coś w rodzaju objawienia, w jaki sposób mógłbym to zrobić bez wprowadzania złożoności, do której pochylałem się w swoim pytaniu. Polega ona na zastosowaniu „równoległego” kompozytu (źle nazwanego, IMHO). Oto co myślę:

wprowadź opis zdjęcia tutaj

Mam nadzieję, że nadal jest to dość czytelne. Ważne punkty to:

  • Sit dół / opóźnienia / Stojak się sekwencji jest w równoległym sekwencji ( A ). Przy każdym tyknięciu sekwencja równoległa sprawdza również stan bliskiego wroga (odwrócony). Jeśli w pobliżu znajduje się wróg , warunek się nie udaje, podobnie jak cała sekwencja równoległa (natychmiast, nawet jeśli sekwencja potomna jest w połowie drogi Usiądź , Opóźnij lub Wstań )
  • w przypadku awarii selektor B powyżej sekwencji równoległej przeskoczy w dół do selektora C, aby obsłużyć przerwanie. Co ważne, selektor C nie działałby, jeśli równoległa sekwencja A zakończyła się pomyślnie
  • selektor C następnie próbuje wstać normalnie, ale może również uruchomić potknięcie się, jeśli żołnierz jest obecnie w zbyt niezręcznej pozycji, aby po prostu wstać

Myślę, że to zadziała (wkrótce wypróbuję to w moim kolcu), mimo że jestem trochę bardziej niechlujny, niż się spodziewałem. Dobrą rzeczą jest to, że w końcu byłbym w stanie obudować sub-drzewa jako logikę wielokrotnego użytku i odwoływać się do nich z wielu punktów. To rozwiąże większość moich obaw, więc myślę, że jest to realne rozwiązanie.

Oczywiście nadal chciałbym usłyszeć, czy ktoś ma jakieś przemyślenia na ten temat.

AKTUALIZACJA : chociaż to podejście technicznie działa, zdecydowałem, że to sux. Wynika to z faktu, że niepowiązane poddrzewa muszą „wiedzieć” o warunkach określonych w innych częściach drzewa, aby mogły wywołać własną śmierć. Chociaż dzielenie się odniesieniami do sub-drzewek przyczyniłoby się w pewnym stopniu do złagodzenia tego bólu, wciąż jest to sprzeczne z tym, czego można się spodziewać, patrząc na drzewo zachowań. Rzeczywiście popełniłem ten sam błąd dwa razy na bardzo prostym skoku.

Dlatego zamierzam pójść inną drogą: wyraźne wsparcie dla wyprzedzania w modelu obiektowym oraz specjalny kompozyt, który pozwala na wykonanie innego zestawu działań w przypadku wystąpienia uprzedzenia. Wyślę osobną odpowiedź, gdy coś będzie działać.


1
Jeśli naprawdę chcesz ponownie użyć poddrzewa, logika, kiedy należy przerwać (tutaj „w pobliżu wroga”), prawdopodobnie nie powinna być częścią poddrzewa. Zamiast tego może system może poprosić dowolne poddrzewo (np. Tutaj B) o przerwanie się z powodu bodźca o wyższym priorytecie, a następnie przeskoczyłby do specjalnie oznaczonego węzła przerwania (tutaj C), który obsługiwałby przywrócenie postaci do stanu standardowego , np. stojąc. Trochę jak drzewo zachowania równoważne z obsługą wyjątków.
Nathan Reed

1
Możesz nawet włączyć wiele programów obsługi przerwań w zależności od tego, jaki bodziec zakłóca. Na przykład, jeśli NPC siedzi i zaczyna strzelać, możesz nie chcieć, żeby wstał (i przedstawił większy cel), ale raczej pozostawał nisko i próbował się schować.
Nathan Reed

@Nathan: zabawne, że wspominasz o „obsłudze wyjątków”. Pierwszym możliwym podejściem, jakie wymyśliłem zeszłej nocy, był pomysł kompozytu Preempt, który miałby dwoje dzieci: jedno do normalnej egzekucji, a drugie do egzekucji prewencyjnej. Jeśli normalne dziecko przejdzie lub nie powiedzie się, wynik ten propaguje się. Dziecko uprzedzające kiedykolwiek uciekłoby, gdyby miało miejsce uprzedzenie. Wszystkie węzły miałyby Preempt()metodę, która przedostałaby się przez drzewo. Jednak jedyną rzeczą, którą tak naprawdę „obsłużyć” byłby kompozyt wyprzedzający, który natychmiast przełączałby się na swój węzeł potomny zapobiegający.
ja--

Potem pomyślałem o równoległym podejściu, które zarysowałem powyżej, i wydawało się to bardziej eleganckie, ponieważ nie wymaga dodatkowego crufta w całym interfejsie API. Jeśli chodzi o kapsułkowanie poddrzew, myślę, że tam, gdzie pojawia się złożoność, byłby to możliwy punkt zastępczy. Może to być nawet miejsce, w którym masz kilka warunków, które są często sprawdzane razem. W takim przypadku pierwiastek podstawienia byłby złożony z sekwencji, z wieloma warunkami jako jego potomkami.
ja--

Myślę, że poddrzewa znające warunki, które muszą „trafić” przed wykonaniem, są całkowicie odpowiednie, ponieważ sprawiają, że są one niezależne i bardzo wyraźne w stosunku do domniemanych. Jeśli jest to większy problem, nie trzymaj warunków w poddrzewie, ale na jego „stronie wywoławczej”.
Seivan

2

Oto rozwiązanie, na które się zdecydowałem ...

  • Moja Nodeklasa podstawowa ma Interruptmetodę, która domyślnie nic nie robi
  • Warunki są konstrukcjami „pierwszej klasy”, ponieważ muszą zostać zwrócone bool(co oznacza, że ​​są szybkie do wykonania i nigdy nie potrzebują więcej niż jednej aktualizacji)
  • Node udostępnia zbiór warunków osobno do kolekcji węzłów potomnych
  • Node.Executewykonuje najpierw wszystkie warunki i od razu kończy się niepowodzeniem, jeśli jakikolwiek warunek się nie powiedzie. Jeśli warunki się powiodą (lub nie ma żadnych), wywołuje, ExecuteCoreaby podklasa mogła wykonać swoją rzeczywistą pracę. Jest parametr, który pozwala pominąć warunki, z powodów, które zobaczysz poniżej
  • Nodeumożliwia również wykonywanie warunków w izolacji za pomocą CheckConditionsmetody. Oczywiście Node.Executetak naprawdę dzwoni tylko CheckConditionswtedy, gdy trzeba zweryfikować warunki
  • Mój Selectorkompozyt wymaga teraz CheckConditionsod każdego dziecka, które rozważa wykonanie. Jeśli warunki się nie spełnią, przechodzi on prosto do następnego dziecka. Jeśli zdadzą, to sprawdza, czy dziecko już wykonuje dziecko. Jeśli tak, wywołuje, Interrupta następnie kończy się niepowodzeniem. To wszystko, co może zrobić w tym momencie, w nadziei, że aktualnie działający węzeł odpowie na żądanie przerwania, co może zrobić przez ...
  • Dodałem Interruptiblewęzeł, który jest swego rodzaju specjalnym dekoratorem, ponieważ ma regularny przepływ logiki jako jego udekorowane dziecko, a następnie oddzielny węzeł dla przerw. Wykonuje swoje zwykłe dziecko do ukończenia lub niepowodzenia, o ile nie zostanie przerwane. Jeśli zostanie przerwany, natychmiast przełącza się na wykonanie swojego podrzędnego węzła obsługującego przerwanie, który może być tak złożonym poddrzewem, jak to konieczne

Wynik końcowy jest mniej więcej taki, zaczerpnięty z mojego kolca:

wprowadź opis zdjęcia tutaj

Powyżej znajduje się drzewo zachowania pszczoły, która zbiera nektar i przywraca go do ula. Kiedy nie ma nektaru i nie znajduje się w pobliżu kwiatu, który ma, wędruje:

wprowadź opis zdjęcia tutaj

Gdyby ten węzeł nie był przerywany, nigdy by nie zawiódł, więc pszczoła błąkałaby się wiecznie. Ponieważ jednak węzeł nadrzędny jest selektorem i ma elementy potomne o wyższym priorytecie, ich uprawnienia do wykonania są stale sprawdzane. Jeśli warunki się spełnią, selektor wywołuje przerwanie, a pod-drzewo powyżej natychmiast przełącza się na ścieżkę „Przerwaną”, która po prostu ratuje jak najszybciej, gdy zawiedzie. Mógłby oczywiście wykonać najpierw inne czynności, ale mój kolec tak naprawdę nie ma nic innego do roboty jak zwolnienie za kaucją.

Aby jednak powiązać to z moim pytaniem, można sobie wyobrazić, że ścieżka „Przerwana” może próbować odwrócić animację siadania i, w przeciwnym razie, potknąć się o żołnierza. Wszystko to powstrzymałoby przejście do stanu o wyższym priorytecie i właśnie taki był cel.

I pomyśleć , że jestem zadowolony z tego podejścia - zwłaszcza podstawowych elementów Przedstawię powyżej - ale szczerze mówiąc, to podniesione dalsze pytania dotyczące rozprzestrzeniania wdrożeń konkretnych warunków i działań, i wiązanie drzewo zachowanie w systemie animacji. Nie jestem nawet pewien, czy potrafię sformułować te pytania, więc będę się zastanawiał.


1

Rozwiązałem ten sam problem, wymyślając dekorator „When”. Ma stan i dwoje zachowań dziecka („wtedy” i „inaczej”). Po wykonaniu „When” sprawdza stan i, w zależności od jego wyniku, uruchamia następnie / w przeciwnym razie potomek. Jeśli wynik warunku ulegnie zmianie, uruchomione dziecko jest resetowane i uruchamiane jest dziecko odpowiadające innej gałęzi. Jeśli dziecko zakończy wykonywanie, całe „Kiedy” kończy wykonywanie.

Kluczową kwestią jest to, że w przeciwieństwie do początkowego BT w tym pytaniu, w którym warunek jest sprawdzany tylko na początku sekwencji, moje „Kiedy” sprawdza stan podczas działania. Tak więc górna część drzewa zachowań jest zastąpiona przez:

When[EnemyNear]
  Then
    AttackSequence
  Otherwise
    When[StandingOnGrass]
      Then
        IdleSequence
      Otherwise
        Hum a tune

W przypadku bardziej zaawansowanego użycia „Gdy” należy również wprowadzić akcję „Czekaj”, która po prostu nic nie robi przez określony czas lub na czas nieokreślony (do momentu zresetowania przez zachowanie rodzica). Ponadto, jeśli potrzebujesz tylko jednej gałęzi „When”, druga może zawierać akcje „Success” lub „Fail”.


Myślę, że to podejście jest bliższe temu, co mieli na myśli pierwotni wynalazcy BT. Wykorzystuje bardziej dynamiczny przepływ, dlatego stan „pracy” w BT jest stanem bardzo niebezpiecznym, z którego należy korzystać bardzo rzadko. Powinniśmy projektować BT zawsze mając na uwadze możliwość powrotu do korzenia w dowolnym momencie.
v.oddou

0

Chociaż jestem spóźniony, ale mam nadzieję, że to może pomóc. Głównie dlatego, że chcę się upewnić, że osobiście czegoś nie umknęło, ponieważ starałem się to rozgryźć. Najczęściej zapożyczyłem ten pomysł Unreal, ale bez robienia go jako Decoratornieruchomości na bazie Nodelub silnie związanej z Blackboard, jest bardziej ogólny.

Spowoduje to wprowadzenie nowego typu węzła o nazwie, Guardktóry jest jak kombinacja Decoratora Compositei ma condition() -> Resultpodpis obokupdate() -> Result

Posiada trzy tryby wskazujące sposób anulowania po Guardpowrocie Successlub Failedfaktyczne anulowanie zależy od osoby dzwoniącej. Więc do Selectorpołączenia Guard:

  1. Anuluj .self -> Anuluj tylko Guard(i działające dziecko), jeśli jest uruchomione, a warunek byłFailed
  2. Anuluj .lower-> Anuluj węzły o niższym priorytecie tylko wtedy, gdy są uruchomione, a warunek to SuccesslubRunning
  3. Anuluj .both -> Zarówno .selfi .lowerzależnie od warunków i uruchomienie węzłów. Chcesz anulować self, jeśli jest uruchomiony i warunek falselub anulować działający węzeł, jeśli są one uważane za niższe priorytety na podstawie Compositereguły ( Selectorw naszym przypadku), jeśli warunek jest spełniony Success. Innymi słowy, w zasadzie oba te pojęcia są połączone.

Podobnie jak Decoratori inaczej Compositeto zajmuje tylko jedno dziecko.

Chociaż Guardtylko zrób pojedyncze dziecko, można zagnieździć tyle Sequences, Selectorslub innych typów Nodes, jak chcesz, łącznie z innymi Guardslub Decorators.

Selector1 Guard.both[Sequence[EnemyNear?]] Sequence1 MoveToEnemy Attack Selector2 Sequence2 StandingOnGrass? Idle HumATune

W powyższym scenariuszu, za każdym razem Selector1, gdy nastąpi aktualizacja, zawsze będzie uruchamiał sprawdzanie stanu strażników powiązanych z jego dziećmi. W powyższym przypadku Sequence1jest Strzeżony i musi zostać sprawdzony przed Selector1kontynuowaniem runningzadań.

Kiedykolwiek Selector2lub Sequence1działa, gdy tylko EnemyNear?powróci successpodczas Guards condition()sprawdzania Selector1, wyda przerwanie / anuluje, running nodea następnie kontynuuje jak zwykle.

Innymi słowy, możemy zareagować na gałąź „bezczynności” lub „ataku” w oparciu o kilka warunków, dzięki czemu zachowanie jest znacznie bardziej reaktywne, niż gdybyśmy zdecydowali się na Parallel

Pozwala to również chronić pojedyncze, Nodektóre mają wyższy priorytet przed uruchomieniem Nodesw tym samymComposite

Selector1 Guard.both[Sequence[EnemyNear?]] Sequence1 MoveToEnemy Attack Selector2 Guard.both[StandingOnGrass?] Idle HumATune

Jeśli HumATuneto długo trwa Node, Selector2zawsze sprawdzi to najpierw, jeśli nie było Guard. Jeśli więc NPC zostanie teleportowany na trawę, następnym razem Selector2uruchomi się, sprawdzi Guardi anuluje HumATune, aby uruchomićIdle

Jeśli robi się teleportował się z plastra trawy, będzie zrezygnować z systemem (węzeł Idle) i przenieść doHumATune

Jak widać tutaj, podejmowanie decyzji zależy od osoby dzwoniącej, Guarda nie od Guardsamej osoby. Zasady dotyczące tego, kto jest uważany za lower prioritypozostającego, pozostają po stronie dzwoniącego. W obu przykładach to, Selectorkto definiuje to, co stanowi lower priority.

Jeśli miałeś Compositewywołanie Random Selector, możesz zdefiniować reguły w ramach implementacji tego konkretnego Composite.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.