Modello per eseguire azioni di gioco


11

Esiste un modello generalmente accettato per eseguire varie azioni all'interno di un gioco? Un modo in cui un giocatore può compiere azioni e anche che un'intelligenza artificiale potrebbe compiere azioni, come mossa, attacco, autodistruzione, ecc.

Attualmente ho una BaseAction astratta che utilizza generici .NET per specificare i diversi oggetti che vengono restituiti dalle varie azioni. Tutto questo è implementato secondo uno schema simile al Comando, in cui ogni azione è responsabile di se stessa e fa tutto ciò di cui ha bisogno.

Il mio ragionamento per essere astratto è in modo che io possa avere un singolo ActionHandler e l'intelligenza artificiale può semplicemente mettere in coda diverse azioni implementando la baseAction. E il motivo per cui è generico è che le diverse azioni possono restituire informazioni sui risultati rilevanti per l'azione (poiché diverse azioni possono avere esiti totalmente diversi nel gioco), insieme ad alcune comuni implementazioni prima dell'azione e dopo l'azione.

Quindi ... c'è un modo più accettato di farlo, o suona bene?


Suona bene, la domanda è cosa intendi per coda? La maggior parte dei giochi ha una risposta molto rapida? "L'IA può mettere in coda azioni diverse"
AturSams,

Buon punto. Non c'è coda. Deve solo sapere se è occupato e, in caso contrario, eseguire l'azione.
Arkiliknam,

Risposte:


18

Non penso che ci sia un modo accettato per implementare questo concetto, ma mi piacerebbe davvero condividere come di solito mi occupo di questo nei miei giochi. È un po 'una combinazione del modello di progettazione Command e del modello di progettazione composita .

Ho una classe base astratta per le azioni che non è altro che un involucro attorno a un Updatemetodo che viene chiamato ogni fotogramma e una Finishedbandiera per indicare quando l'azione ha terminato l'esecuzione.

abstract class Action
{
    abstract void Update(float elapsed);
    bool Finished;
}

Uso anche il modello di progettazione composita per creare un tipo di azioni in grado di ospitare ed eseguire altre azioni. Anche questa è una classe astratta. Si riduce a:

abstract class CompositeAction : Action
{
    void Add(Action action) { Actions.Add(action); }
    List<Action> Actions;
}

Quindi ho due implementazioni di azioni composite, una per l' esecuzione parallela e una per l'esecuzione sequenziale . Ma il bello è che poiché parallelo e sequenza sono azioni stesse, possono essere combinati per creare flussi di esecuzione più complessi.

class Parallel : CompositeAction
{
    override void Update(float elapsed) 
    {
        Actions.ForEach(a=> a.Update(elapsed));
        Actions.RemoveAll(a => a.Finished);
        Finished = Actions.Count == 0;
    }
}

E quello che governa le azioni sequenziali.

class Sequence : CompositeAction
{
    override void Update(float elapsed) 
    {
        if (Actions.Count > 0) 
        {
            Actions[0].Update(elapsed);
            if (Actions[0].Finished)
                Actions.RemoveAt(0);
        }
        Finished = Actions.Count == 0;
    }
 }

Con questo in atto, si tratta semplicemente di creare implementazioni di azioni concrete e di usare le azioni Parallele Sequenceper controllare il flusso di esecuzione. Concluderò con un esempio:

// Create a parallel action to work as an action manager
Parallel actionManager = new Parallel();

// Send character1 to destination
Sequence actionGroup1 = new Sequence();
actionGroup1.Add(new MoveAction(character1, destination));
actionGroup1.Add(new TalkAction(character1, "Arrived at destination!"));
actionManager.Add(actionGroup1);

// Make character2 use a potion on himself
Sequence actionGroup2 = new Sequence();
actionGroup2.Add(new RemoveItemAction(character2, ItemType.Potion));
actionGroup2.Add(new SetHealthAction(character2, character2.MaxHealth));
actionGroup2.Add(new TalkAction(character2, "I feel better now!"));
actionManager.Add(actionGroup2);

// Every frame update the action manager
actionManager.Update(elapsed);

Ho usato con successo questo sistema per guidare tutto il gameplay in un'avventura grafica prima, ma probabilmente dovrebbe funzionare praticamente per qualsiasi cosa. Era anche abbastanza semplice aggiungere altri tipi di azioni composite, che venivano utilizzate per creare loop e condizionali di esecuzione.


Sembra una soluzione molto bella. Per curiosità, come fai a far sapere all'interfaccia utente cosa disegnare? I tuoi oggetti di gioco (come i personaggi) contengono uno stato che viene utilizzato per identificare ciò che è accaduto ai fini del rendering, o è l'azione stessa che lo fa?
Arkiliknam,

1
Di solito le mie azioni cambiano solo lo stato delle entità, e qualsiasi modifica all'output renderizzato si verifica come conseguenza di quel cambiamento di stato, non per mezzo delle azioni stesse. Ad esempio, con un renderer in modalità immediata non è necessario alcun passaggio aggiuntivo poiché il Drawmetodo è già basato sullo stato dell'entità e le modifiche sono automatiche. In un renderer in modalità mantenuta come Flash, è possibile utilizzare il modello osservabile per fare in modo che le modifiche alle entità si propaghino agli oggetti di visualizzazione o effettuare manualmente la connessione all'interno dell'entità stessa.
David Gouveia,

1
Nella prima situazione, diciamo che la tua Characterclasse ha una Positionproprietà e un Drawmetodo che legge qual è il valore corrente Positione disegna lì l'immagine corretta. In questa situazione, è sufficiente aggiornare il valore di Positionquel risultato che verrà automaticamente visualizzato sullo schermo.
David Gouveia,

1
La seconda situazione è, quando hai Characteruna Positionproprietà, ma delega il rendering a una sorta di Spriteoggetto che viene reso automaticamente da un grafico di scena o qualcosa del genere. In questa situazione devi assicurarti che sia la posizione del personaggio che quella dello sprite siano sempre sincronizzate, il che comporta un po 'più di lavoro. Tuttavia, in entrambi i casi, non vedo perché il gestore delle azioni dovrebbe avere qualcosa a che fare con esso. :)
David Gouveia,

1
Entrambi i metodi hanno vantaggi e svantaggi .. Sono andato con il secondo metodo per il mio gioco 2D, e mi sono pentito a volte perché è significativamente più complicato mantenere tutto sincronizzato. Ma ci sono anche dei vantaggi, ad esempio quando si cerca di rilevare quale entità è stata cliccata, o cosa dovrebbe o non dovrebbe essere disegnato, perché tutto ciò che verrà reso è contenuto all'interno della stessa struttura di dati invece che disperso tra N tipi di entità.
David Gouveia,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.