Esiste un buon modo (schema) per aggiungere uno stato a una macchina a stati finiti

3

Quindi sto lavorando a un roguelike e sto progettando di rappresentare i mostri usando una macchina a stati finiti

          spot PC
wander------------------->move
  ^   ^                    |
  |      \                 |
  |         \ killed PC    |
  |evaded       \          | close
  |                 \      |
  |         hurt        \  V
 flee<-------------------attack

Tuttavia, volevo essere in grado di cambiare i mostri FSM, dato che il mostro è stato modificato. Ad esempio, nel gioco, è possibile che il mostro abbia una pozione salutare. In tal caso, l'FSM dovrebbe apparire come segue:

wander------------------->move
                            |
                            |
                            |
                            | close
            used potion     |
       ,-----------------V  V
 use potion              attack
       ^-------------------'
              hurt

E poi, una volta esaurita la pozione, torna al FSM precedente. Il mio pensiero è che dovrei cambiare l'FSM nel momento in cui la pozione viene assegnata al mostro, quindi potion.give(monster) modifica l'FSM in quel momento e potion.use() non lo modifica. Ma non sono sicuro di come implementarlo? In che modo l'oggetto pozione può sapere a quali stati dovrebbe legarsi o ritornare?

Un possibile pensiero era che il binding della transizione di stato FSM fosse uno stack. Quindi, raccogliendo una pozione, aggiungi use potion allo stack di transizione attack['hurt'] .

Esiste una best practice o qualcuno può collegarsi a un esempio di come modificare un FSM in fase di runtime?

    
posta mklauber 16.11.2014 - 05:20
fonte

2 risposte

2

Supponiamo di avere una classe simile a questa:

class State {
  String description;
  List triggerStateTuple;
  Action onEnter;
}

Poi hai una lista di tuple (Trigger, State) che percorri la lista, selezionane una, passa allo stato successivo e invoca onEnter.

Un approccio a questo è quello di evitare di provare a fare "questo viene inserito o rimosso" e invece di costruire l'enorme tabella di stato di tutti gli eventi possibili con un Trigger più complesso. Invece di lavorare su "tutti gli elementi nell'elenco sono validi", dovrebbe invece fare un "controllo se questo può essere attivato". Il vantaggio qui è che lo costruisci una sola volta e che è fatto piuttosto che doverlo ricostruire al volo.

La ricostruzione al volo è quella in cui è probabile che diventi complicato per te. Hai bisogno di sapere dove mettere il grilletto (in quale stato è valido?). Cosa succede dopo che la pozione è stata bevuta? Bene, dovresti rimuoverlo da tutti gli stati associati. Anche questo diventa piuttosto complicato. La logica diventa più complessa ... cosa succede se hai due pozioni di salute e il tuo mostro beve una volta - assicurati di non rimuovere entrambi i grilletti.

Come puoi vedere, questo può diventare complesso. Quando un articolo viene aggiunto (o rimosso) dall'inventario, deve aggiungerlo (o rimuoverlo) dai diversi stati in cui può essere agganciato. Si potrebbe vedere la modifica della tupla per contenere anche l'oggetto (per una facile rimozione dallo stato).

Quando un oggetto viene aggiunto all'inventario, l'elenco degli stati a cui può essere collegato viene estratto dall'oggetto e quindi tutti gli stati con una descrizione corrispondente hanno aggiunto il trigger. Ciò comporta alcune coreografie di eventi. Probabilmente vorrai uno stato 'uso' a cui l'oggetto può collegarsi, quindi quando l'azione viene attivata, richiama l'azione di nuovo nell'oggetto.

Tuttavia, farò anche notare che "use" non è realmente uno stato . È un evento singolo che non cambia lo stato da "attacco".

Leggendo questo, questo diventa qualcosa che sembra più un motore di regole che un semplice FSM. La logica sta diventando più complessa e puoi iniziare a soffrire con difficoltà nel debugarlo. Invece, potresti voler guardare un motore di regole incorporato di qualche tipo e quindi codificare che .

In Java, i motori delle regole sono cose che puoi trovare cercando JSR 94 . Altri sistemi hanno i propri metodi per implementare i motori di regole ( DTRules ha una propria DSL). Alcuni approcci sono immediatamente disponibili implementazioni di prolog .

L'approccio che nethack ha per "un mostro può usarlo" è Muse.c che è un enorme albero decisionale codificato.

Quindi ... le tue alternative:

  • Molto di hook e book keeping sulla macchina a stati finiti
  • Un FSM grande e codificato (costruisci il tutto in primo piano)
  • Uso di un motore di regole o di un linguaggio orientato alle regole
  • Un motore di regole di grandi dimensioni e hard coded (nethack)
risposta data 16.11.2014 - 06:25
fonte
3

Un modo per implementarlo:

          spot PC
wander------------------->move
  ^   ^                    |
  |      \                 |
  |         \ killed PC    |
  |evaded       \          | close
  |                 \      |
  |  hurt and no potion \  V
 flee<-------------------attack
                         ^ |
                         | |
             potion used | | hurt and have potion
                         | |
                         | v
                      use potion

Anche se probabilmente vorrai separare tra strategia e tattica. La strategia può essere modellata come una macchina a stati, mentre le parti tattiche del mostro possono essere modellate con una tabella delle regole come descritto in un'altra risposta. La strategia mostro è fondamentalmente lo "stato d'animo" del mostro, o il mostro è in uno stato d'animo di "lotta" o di "volo". Un mostro può passare a "combattere" l'umore quando è sano o appena recuperato, mentre possono entrare in "fuga" quando sono a corto di salute o quando incontrano un avversario molto al di sopra del loro stesso livello. Quando cambia la strategia del mostro, cambia anche la tabella delle regole.

    
risposta data 16.11.2014 - 07:27
fonte

Leggi altre domande sui tag