Davranış Ağaçlarını Önleme

Kafamı davranış ağaçlarının etrafına sokmaya çalışıyorum, bu yüzden bazı test kodları çıkarıyorum. Mücadele ettiğim bir şey, daha yüksek önceliğe sahip bir şey ortaya çıktığında, şu anda çalışan bir düğümü nasıl önleyeceğimizdir.

Bir asker için aşağıdaki basit, hayali davranış ağacını göz önünde bulundurun:

enter image description here

Bazı kenelerin geçtiğini ve yakınlarda bir düşman olmadığını varsayalım, asker çimlerin üzerinde duruyordu, bu nedenle Oturun düğümü yürütmek için seçildi:

enter image description here

Şimdi Oturun eyleminin yürütülmesi zaman alıyor çünkü oynatılacak bir animasyon var, bu nedenle durumu Çalışıyor değerini döndürüyor. Bir ya da iki kez devam eder, animasyon devam eder, ancak Düşman yakın? koşulu düğümü tetikler. Şimdi en kısa zamanda Oturun düğümünü engellememiz gerekiyor, böylece Saldırı düğümünü uygulayabiliriz. İdeal olarak, asker oturmayı bile bitiremezdi - sadece oturmaya başladıysa, animasyon yönünü tersine çevirebilirdi. Daha fazla gerçekçilik için, eğer animasyonda bir devrilme noktasını geçerse, onun yerine oturmasını ve sonra tekrar durmasını ya da tehdidine tepki göstermesi için acele etmesini istemeyi tercih edebiliriz.

Olabildiğince dene, bu durumla nasıl başa çıkılacağı konusunda rehberlik bulamadım. Son birkaç gündür tükettiğim tüm literatür ve videolar (ve çok geçti) bu konunun etrafında durmuş gibi görünüyor. Bulabildiğim en yakın şey, çalışan düğümleri sıfırlama kavramıydı, ancak Oturun gibi düğümlere "henüz bitmedi!" "

Belki tabanımdaki Düğüm sınıfımda bir Preempt() veya Interrupt() yöntemini tanımlamayı düşündüm. Farklı düğümler uygun gördüklerini halledebilirler, ancak bu durumda askerin en kısa zamanda ayağa kalkmasını ve daha sonra Success 'ı geri getirmeyi deneriz. Bu yaklaşımın ayrıca Düğüm tabanımın diğer eylemlere yönelik ayrı ayrı koşullar kavramına sahip olmasını gerektireceğini düşünüyorum. Bu şekilde, motor yalnızca koşulları kontrol edebilir ve geçerse, eylemlerin yürütülmesine başlamadan önce o anda çalışmakta olan tüm düğümleri engelleyebilir. Bu farklılaşma tespit edilmezse, motorun düğümleri ayırt etmeksizin yerine getirmesi gerekirdi ve bu nedenle çalışanı engellemeden önce yeni bir işlemi tetikleyebilir.

Başvuru için, aşağıda benim mevcut temel sınıflarım. Yine, bu bir anidir, bu yüzden işleri olabildiğince basit tutmaya çalıştım ve yalnızca ihtiyacım olduğunda ve anladığımda karmaşıklığı eklemeye çalıştım.

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
{
    public abstract ExecuteResult Execute(TimeSpan elapsed, TAgent agent, Blackboard blackboard);
}

public abstract class DecoratorNode : Node
{
    private readonly Node child;

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

    protected Node Child
    {
        get { return this.child; }
    }
}

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

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

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

public abstract class ConditionNode : Node
{
    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 : Node
{
}

Beni doğru yöne götürebilecek bir fikri olan var mı? Düşüncelerim doğru çizgide mi, yoksa korktuğum kadar saf mı?

24
Bu belgeye bir göz atmanız gerekir: chrishecker.com/My_liner_notes_for_spore/&hellip ağaç, bir devlet makinesi gibi değil, her tikta KÖK'ten yürür, ki bu da reaktivitenin gerçek numarasıdır. BT'nin istisnalara veya olaylara ihtiyacı olmamalıdır. Sistemlerini kendinden havuzluyorlar ve her zaman kökünden aşağı akması sayesinde tüm durumlara tepki veriyorlar. Öncelikli işleyiş böyledir, eğer yüksek öncelikli bir dış koşul kontrol edilirse, oraya akar. (etkin düğümlerden çıkmadan önce bazı Durdurma() geri çağırma çağrısı)
katma yazar v.oddou, kaynak
bu aigamedev.com/open/article/popular-behavior-tree-design ayrıca çok güzel bir şekilde ayrıntılı
katma yazar v.oddou, kaynak

6 cevap

bu blog sayfasını ele alarak sorunumun başka bir çözümünü sundum.

İlk şey, eşzamanlı düğümü kullanmaktır. Eşzamanlı düğüm, özel bir bileşik düğüm türüdür. Tek bir işlem düğümü tarafından takip edilen önkoşul kontrolleri dizisinden oluşur. İşlem düğümü 'çalışıyor' durumunda olsa bile tüm alt düğümleri günceller. (Başlaması gereken dizi düğümünün aksine, güncel çalışan alt düğümden güncelleştirmesi.)

Ana fikir eylem düğümleri için iki dönüş durumu daha oluşturmak: "iptal" ve "iptal".

Eşzamanlı düğümde önkoşul kontrolünün başarısızlığı, çalışan eylem düğümünün iptal edilmesini tetikleyen bir mekanizmadır. İşlem düğümü uzun süre çalışan iptal etme mantığı gerektirmiyorsa, derhal 'iptal edildi' olarak geri dönecektir. Aksi halde, eylemin doğru şekilde kesilmesi için gereken tüm mantığı koyabileceğiniz 'iptal etme' durumuna geçer.

6
katma
Davranış ağaçlarını sonlu durum makinesine geri getiren hiçbir şeyin iyi bir çözüm olduğunu sanmıyorum. Yaklaşımınız her devletin tüm çıkış koşullarını öngörmeniz gerektiği gibi görünüyor. Bu aslında FSM'nin dezavantajı olduğunda! BT, kökten geri dönme avantajına sahiptir, bu da açıkça bağlı bir FSM oluşturarak dolaylı olarak çıkış koşullarını açıkça yazmamıza engel olur.
katma yazar v.oddou, kaynak
Merhaba ve GDSE'ye hoş geldiniz. Bu blogu buraya ve sonunda o bloga bağlayarak bu cevabı açabilirseniz çok iyi olur. Linkler ölme eğilimindedir, burada tam olarak cevaplanması, daha kalıcı kılar. Soru şimdi 8 oy aldı, bu yüzden iyi cevap harika olurdu.
katma yazar JSMorgan, kaynak

Sanırım askerin zihin ve beden (ve başka bir şey) içine ayrıştırılabilir. Daha sonra, vücut bacaklar ve ellere ayrıştırılabilir. Daha sonra, her bölüm kendi davranış ağacına ve ayrıca genel arayüze ihtiyaç duyar - daha yüksek veya daha düşük seviyeli bölümlerden gelen istekler için.

Böylece, her bir eylemi mikro-yönetmek yerine, "vücut, bir süre oturun" veya "vücut, orada koşun" gibi anında çekilen mesajlar gönderirsiniz ve vücut animasyonları, durum geçişlerini, gecikmeleri ve diğer şeyleri yönetir sen.

Alternatif olarak, vücut bu gibi davranışları kendi başına yönetebilir. Eğer emirleri yoksa, "burada oturabilir miyiz?" Diye sorabilir. Daha ilginç olanı, kapsülleme nedeniyle, yorgunluk veya sersemlik gibi özellikleri kolayca modelleyebilirsiniz.

Parçaları bile değiştirebilirsin - zombi zekasıyla fil yap, insana kanat ekler (farketmeyecek bile), ya da başka bir şey.

Bu şekilde ayrışma olmadan, er ya da geç, kombinatoryal patlama ile karşılaşma riski altındasınızdır.

Also: http://www.valvesoftware.com/publications/2009/ai_systems_of_l4d_mike_booth.pdf

5
katma
Teşekkürler. Cevabını 3 kez okuduktan sonra anlıyorum. Bu hafta sonu bu PDF'yi okuyacağım.
katma yazar gentmatt, kaynak
Bunu bir saat boyunca düşündüğümde, akıl ve beden için tamamen ayrı BT'lere sahip olmakla alt ağaçlara ayrışan tek bir BT'ye (özel bir dekoratör aracılığıyla, derleme zamanı komut dosyaları ile referans verilen) sahip olmak arasındaki ayrımı anladığımdan emin değilim. her şeyi birlikte büyük bir BT'ye bağlamak). Bana göre bunun benzer soyutlama faydaları sağlayacağı ve belirli bir işletmenin nasıl davrandığını anlamayı kolaylaştıracağı, çünkü birden fazla ayrı BT'ye bakmak zorunda kalmayacağınız görünüyor. Ancak, muhtemelen naif oluyorum.
katma yazar gentmatt, kaynak
@ user13414 Farklılık, ağaç oluşturmak için özel komut dosyalarına ihtiyaç duyacağınızdır, dolaylı erişimi kullanırken (yani vücut düğümü ağacına hangi nesnenin bacakları temsil ettiği ağacına sormak istediğinde) yeterli olabilir ve ayrıca herhangi bir ek beyin tıkanması gerektirmeyecektir. Daha az kod, daha az hata. Ayrıca, çalışma zamanında alt ağacı değiştirme (kolay) özelliğini kaybedeceksiniz. Böyle bir esnekliğe ihtiyacınız olmasa bile, hiçbir şey kaybetmezsiniz (yürütme hızı dahil).
katma yazar Sheldon Cooper, kaynak

Dün gece yatakta yattığımda, sorumu yönelttiğim karmaşıklığı tanıtmadan bu konuda nasıl gidebileceğime dair bir epifiz vardı. (Zayıf olarak adlandırılmış, IMHO) “paralel” kompozitin kullanılmasını içerir. İşte ne düşünüyorum:

enter image description here

Umarım bu hala oldukça okunaklıdır. Önemli noktalar:

  • Oturun / Gecikme / Ayağa kalk dizisi, paralel bir dizinin içindeki bir dizilimdir ( A ) . Her onayda, paralel sıra, Yakındaki Düşman koşulunu da kontrol ediyor (ters çevrilmiş). Bir düşman yakınındaysa, koşul başarısız olur ve paralel dizinin tamamını da yapar (hemen, çocuk sırası Oturun , Gecikme veya Ayağa kalk )
  • başarısızlık durumunda, paralel sıranın üstündeki B seçicisi, kesintinin üstesinden gelmek için C seçicisine atlayacaktır. Önemli bir şekilde, A paralel dizisi başarıyla tamamlandıysa, seçici C çalıştırılmaz.
  • seçici C daha sonra normal olarak ayağa kalkmaya çalışır, ancak asker şu anda çok tuhaf bir pozisyonda durmak için yanıltıcı bir canlandırmayı da tetikleyebilir

Sanırım bu, çalışacağımı (yakında ani olarak deneyeceğim), tahmin ettiğimden biraz daha dağınık olmasına rağmen. İşin iyi yanı, sonunda alt ağaçları tekrar kullanılabilir bir mantık parçası olarak kapatabildiğim ve bunlara birçok noktadan başvurabileceğim. Buradaki endişelerimin çoğunu hafifletecek, bu yüzden bunun uygulanabilir bir çözüm olduğunu düşünüyorum.

Tabii ki, bu konuda herhangi bir fikri olan varsa duymayı çok isterim.

UPDATE: although this approach technically works, I've decided it sux. That's because unrelated sub-trees need to "know" about the conditions defined in other portions of the tree so that they can trigger their own demise. Whilst sharing sub-tree references would go some way to alleviating this pain, it's still contrary to what one expects when looking at the behavior tree. Indeed, I made the same mistake twice on a very simple spike.

Bu nedenle, diğer rotadan aşağı ineceğim: nesne modelinde preempting için açık destek ve preemption gerçekleştiğinde farklı bir dizi eylemin yürütülmesini sağlayan özel bir bileşik. Çalışan bir şeyim olduğunda ayrı bir cevap göndereceğim.

3
katma
Eğer gerçekten alt ağaçları tekrar kullanmak istiyorsanız, o zaman ne zaman araya gireceğinin mantığı (buradaki "yakın düşman") muhtemelen alt ağaçların bir parçası olmamalıdır. Bunun yerine, belki de sistem herhangi bir alt ağaçtan (burada B) daha yüksek öncelikli bir uyarı nedeniyle kendisini kesmesini isteyebilir ve daha sonra karakteri belirli bir standart duruma geri döndürecek şekilde işlenen özel olarak işaretlenmiş bir kesme düğümüne (burada C) atlar. , Örneğin ayakta. İstisna ağacı davranış istisna işleme eşdeğer bir bit.
katma yazar Nobody, kaynak
Hangi uyaranın kesintiye uğradığına bağlı olarak birden fazla kesinti işleyicisi dahil edebilirsiniz. Örneğin, NPC oturuyor ve ateş almaya başlıyorsa, ayağa kalkmasını (ve daha büyük bir hedef sunmasını) istemeyebilir, daha ziyade düşük kalmasını ve gizlenmesini engelleyebilirsiniz.
katma yazar Nobody, kaynak
@ Nathan: "İstisna kullanımı" ndan bahsettiğin komik. Dün gece düşündüğüm ilk olası yaklaşım, iki çocuğu olan bir Preempt kompoziti fikriydi: biri normal uygulama için, diğeri ise önlenmiş uygulama için. Normal çocuk geçerse veya başarısız olursa, bu sonuç artar. Önceden doğan çocuk, ancak herhangi bir ön hazırlık gerçekleştiğinde kaçar. Tüm düğümler, ağaçta dolaşacak bir Preempt() yöntemine sahip olacaktı. Bununla birlikte, bunun gerçekten “üstesinden gelmek” için tek şey, hemen ön-hazırlık alt düğümüne geçecek olan ön-kompozit olacaktır.
katma yazar gentmatt, kaynak
Sonra yukarıda ana hatlarıyla anlattığım paralel yaklaşımı düşündüm ve bu daha şık görünüyordu çünkü API için fazladan boşluk gerektirmiyordu. Alt-ağaçları içine alma konusundaki amacınıza göre, karmaşıklığın ortaya çıktığı her yerde, bunun muhtemel bir ikame noktası olacağını düşünüyorum. Sık sık birlikte kontrol edilen birkaç koşulun olduğu yer burası bile olabilir. Bu durumda, ikamenin kökü, çocukları gibi birçok koşulu olan bir dizi kompoziti olacaktır.
katma yazar gentmatt, kaynak
Bence Subtrees, yürütmeden önce "vurmak" için gereken koşulları bilmek, kendilerini kendi içlerinde ve çok açık ve örtük kıldığından, mükemmel uygun olduğunu düşünüyorum. Bu daha büyük bir endişe ise, o zaman koşulları alt ağaçta tutmayınız, ancak “çağrı sitesi” nde tutunuz.
katma yazar douardo, kaynak

İşte şimdilik çözüm bulduğum çözüm ...

  • My base Node class has an Interrupt method which, by default, does nothing
  • Conditions are "first class" constructs, in that they are required to return bool (thus implying that they are fast to execute and never need more than one update)
  • Node exposes a collection of conditions separately to its collection of child nodes
  • Node.Execute executes all conditions first and fails straight away if any condition fails. If conditions succeed (or there are none), it calls ExecuteCore so the subclass can do its actual work. There is a parameter that allows skipping of conditions, for reasons you'll see below
  • Node also allows conditions to be executed in isolation via a CheckConditions method. Of course, Node.Execute actually just calls CheckConditions when it needs to validate conditions
  • My Selector composite now calls CheckConditions for each child it considers for execution. If the conditions fail, it moves straight along to the next child. If they pass, it checks whether there is already an executing child. If so, it calls Interrupt and then fails. That's all it can do at this point, in the hopes that the currently running node will respond to the interrupt request, which it can do by...
  • I've added an Interruptible node, which is kind of a special decorator because it has the regular flow of logic as its decorated child, and then a separate node for interruptions. It executes its regular child to completion or failure as long as it isn't interrupted. If it is interrupted, it immediately switches to executing its interruption handling child node, which could be as complex a sub-tree as required

Sonuçta benim başımdan alınan böyle bir şey:

enter image description here

Yukarıdaki nektarı toplayan ve kovana geri veren bir arı için davranış ağacıdır. Nektarı olmadığı ve bir çiçeğe yakın olmadığı zamanlarda dolaşır:

enter image description here

Eğer bu düğüm kesilemezse, asla başarısız olmaz, bu yüzden arı sürekli dolaşır. Bununla birlikte, ana düğüm bir seçici olduğundan ve önceliği yüksek çocukları olduğu için, yürütmeye uygunlukları sürekli kontrol edilir. Koşulları geçerse, seçici bir kesinti yaratır ve üstteki alt ağaç derhal "Kesildi" yoluna geçer, bu da ASAP'ı başarısızlıkla kurtarır. Elbette ilk önce başka bazı eylemler yapabilirdi, ama başağımın kefaletten başka yapacak bir şeyi yok.

Yine de, bunu soruma bağlamak için, "Kesilen" yolun, oturma animasyonunu tersine çevirmeye çalıştığını ve başarısızlığa uğrayan askerin yanmasını sağladığını hayal edebilirsiniz. Bütün bunlar daha yüksek öncelikli duruma geçişi durduracaktı ve bu tam olarak amacın buydu.

Ben düşünüyorum Bu yaklaşımdan memnunum - özellikle yukarıda ana hatları çizdiğim temel parçalar - ama dürüst olmak gerekirse, şartlar ve eylemlerin spesifik uygulamalarının yayılması ve davranış ağacının bağlanması hakkında daha fazla soru soruluyor. animasyon sistemine. Bu soruları henüz dile getirebileceğimden bile emin değilim, bu yüzden düşünmeye devam edeceğim.

2
katma

"When" dekoratörünü icat ederek de aynı sorunu çözdüm. Bir koşulu ve iki çocuk davranışı var ("sonra" ve "aksi halde"). "Ne zaman" yürütüldüğünde, durumu kontrol eder ve sonucuna bağlı olarak, daha sonra/o zaman çocuk çalıştırır. Durum sonucu değişirse, çalışan çocuk sıfırlanır ve diğer dallara karşılık gelen çocuk başlatılır. Çocuk yürütmeyi bitirirse, tüm "Ne zaman" yürütmeyi bitirir.

Kilit nokta, bu durumdaki ilk BT'den farklı olarak, sadece sekans başlangıcında koşulun kontrol edildiği durumda, "Çalışırken" durumum kontrol edilirken koşulu kontrol etmeye devam ediyor. Böylece, davranış ağacının üst kısmı ile değiştirilir:

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

Daha gelişmiş "Ne zaman" kullanımı için de, yalnızca belirli bir süre boyunca veya süresiz olarak hiçbir şey yapmayan "Bekle" eylemi tanıtmak ister (ebeveyn davranışına göre sıfırlanana kadar). Ayrıca, yalnızca bir "Ne Zaman" dalına ihtiyacınız varsa, diğeri "Başarılı" veya "Başarısız" eylemlerini içerebilir, bu saygıdeğer sonuç hemen başarılı ve başarısız olur.

1
katma
Bu yaklaşımın BT'nin orijinal mucitlerinin aklında olanlara daha yakın olduğunu düşünüyorum. Daha dinamik bir akış kullanır, bu nedenle BT'de "çalıştırma" durumu çok nadiren kullanılması gereken çok tehlikeli bir durumdur. BT'leri her zaman kökünden geri gelme ihtimalini göz önünde bulundurarak tasarlamalıyız.
katma yazar v.oddou, kaynak

Geç kaldım, ama umarım bu yardımcı olabilir. Çoğunlukla, bunu anlamaya çalıştığım gibi kişisel olarak kendimden bir şey kaçırmadığımdan emin olmak istiyorum. Bu fikri çoğunlukla Unreal 'dan ödünç aldım, ancak bir Dekoratör , bir temel Düğüm mülkü yapmadan ya da Blackboard , daha geneldir.

This will introduce a new node type called Guard which is like a combination of a Decorator, and Composite and has a condition() -> Result signature alongside an update() -> Result

Guard , Success veya Failed döndürdüğünde iptal işleminin nasıl yapılması gerektiğini gösteren üç modu vardır, gerçek olarak iptal etme arayan kişiye bağlıdır. Öyleyse bir Seçici için bir Koruma çağrısı:

  1. Cancel .self -> Only cancel the Guard (and its running child) if it is running and the condition was Failed
  2. Cancel .lower -> Only cancel the lower priority nodes if they are running and the condition was Success or Running
  3. Cancel .both -> Both .self and .lower depending on the conditions and running nodes. You want to cancel self if its running and would condition to false or cancel the running node if they're considered lower priority based on the Composite rule (Selector in our case) if the condition is Success. In other words, it's basically both concepts combined.

Dekoratör gibi ve Composite 'in aksine, yalnızca tek bir çocuk alır.

Guard yalnızca tek bir çocuğu alsa da, istediğiniz kadar Sıra , Seçiciler veya diğer tiplerde Düğümler yuvalayabilirsiniz diğer Korumaları veya Dekoratörler dahil olmak üzere

Selector1   Guard.both [Sekans [EnemyNear]]     Sequence1       MoveToEnemy       saldırı   Selector2     Sıra2       StandingOnGrass?       boş     HumATune

Yukarıdaki senaryoda, Seçici1 her güncellediğinde, çocuklarıyla ilişkilendirilen korumalardaki durum denetimlerini her zaman çalıştırır. Yukarıdaki durumda, Sıra1 Korunur ve Seçici1 çalışıyor görevleriyle devam etmeden önce kontrol edilmesi gerekir.

Seçici2 veya Sıra1 en kısa sürede EnemyNear? en kısa sürede çalışıyorsa, bir Korumaları başarı döndürür > condition() kontrol edildikten sonra Seçici1 çalışan düğüm 'e bir kesinti/iptal yapacak ve ardından her zamanki gibi devam edecek .

In other words, we can can react to either "idle" or "attack" branch based on a few conditions making the behaviour far more reactive than if we settled on Parallel

This also allows you to guard single Node that have higher priority against running Nodes in the same Composite

Selector1   Guard.both [Sekans [EnemyNear]]     Sequence1       MoveToEnemy       saldırı   Selector2     Guard.both [StandingOnGrass?]       boş     HumATune

If HumATune is a long running Node, Selector2 will always check that one first if it wasn't for the Guard. So if the npc got teleported onto a grass patch, next time Selector2 runs, it will check the Guard and cancel HumATunein order to run Idle

If it gets teleported out of the grass patch, it will cancel the running node (Idle) and move to HumATune

Burada gördüğünüz gibi karar verme, Guard 'ın değil, Guard ' ın arayanına dayanır. Kimin düşük öncelikli olarak kabul edildiğinin kuralları arayan kişide kalır. Her iki örnekte, neyi düşük öncelikli olarak tanımladığını tanımlayan Seçici 'dir.

Rastgele Seçici adlı bir Composite varsa, o zaman belirli bir Composite uygulamasının kurallarını tanımlamanız gerekir.

0
katma