Come rappresentare un oggetto con azioni che possono essere utilizzate solo in una situazione specifica?

2

Diciamo che sto progettando un gioco di tipo RPG, e questo gioco ha un sistema di combattimento a turni. Ci sono alcune cose che i personaggi del personaggio / non giocatore possono fare all'interno e all'esterno del combattimento. Ad esempio, mentre non sono in combattimento, possono spostarsi in tutto il mondo, usare oggetti, parlare con altri np e il giocatore (non il loro personaggio) può aprire determinati menu di gioco. Mentre i personaggi sono in combattimento, possono anche usare gli oggetti, e usare certe abilità di combattimento, ma non possono muoversi nel mondo di gioco o parlare con altri np.

Questa domanda è intesa per essere indipendente dalla lingua, ma il codice degli esempi sarà in Java.

Una prima implementazione di questo potrebbe essere qualcosa di simile

public class Character {

    public void use(Item item) { /* ... */ }

    public void talkTo(Npc npc) {
        if (inCombat) {
            throw new IllegalStateException("Unable to talk to npcs while in combat");
        }
        /* ... */
    }

    public void useAbility(Ability ability) {
        if (!inCombat) {
            throw new IllegalStateException("Unable to use ability while not in combat");
        }
        /* ... */
    }
}

La mia domanda è indirizzata al metodo useAbility e talkTo . Questi metodi dovrebbero essere callable per tutto il tempo, o dovrei limitarlo in qualche modo, forse qualcosa come OverworldContext o CombatContext o un adattatore / proxy di qualche tipo per separare le cose che possono essere fatto? Forse qualcosa come

public class OverworldCharacter extends Character {

    private Character character;

    public OverworldCharacter(Character character) {
        this.character = character;
    }

    public void talkTo(Npc npc) {
        // do some stuff
    }

    // proxy character methods to field
}

E

public class InCombatCharacter {

    // Same as other class

    public void useAbility(Ability ability) {
        // do some more stuff
    }
}

Questo gioco ha lo scopo di consentire la creazione di plugin / mod da parte di terzi, quindi è un'altra cosa da considerare in questo progetto.

    
posta Zymus 07.03.2016 - 08:46
fonte

4 risposte

2

Potresti avere metodi sul tuo personaggio che rappresentano le azioni intraprese dal personaggio e che portano a un "contesto" in cui le azioni sono limitate. In questo modo, le azioni possono essere chiamate solo se pertinenti.

Il problema con il non limitare le azioni in alcun modo dal dominio è che la logica di controllo verrà duplicata molto. Ogni azione aggiunta dovrà verificare in quale contesto è inserito il personaggio. Un singolo controllo dimenticato può portare a un personaggio che può utilizzare un'abilità fuori combattimento durante il combattimento. E anche se non dimentichi mai di aggiungere assegni, devi affrontare una grande quantità di codice che non viene controllato in fase di compilazione ma in fase di runtime.

public class Character 
{
    public BattleRound fight(Enemy npc) 
    {
        return new BattleRound(this, npc);
    }
}

public class BattleRound
{
    public void useAbility(CombatAbility ability)
    {
        /*...*/
    }
}

Nell'esempio precedente il tuo personaggio entra in un round di combattimento quando combatte un nemico. Un personaggio può usare un'abilità all'interno di un round, attaccare, lanciare una magia ... e queste azioni sono applicate da BattleRound (o BattleTurn se preferisci quel termine). Un altro vantaggio di questo design è che puoi anche limitare le azioni in base alla classe: il WizardCharacter potrebbe restituire un WizardBattleRound che non consente un hit ma consente un cast o un heal . Gli effetti delle azioni sul giro possono eseguire azioni sul personaggio come ridurre i punti ferita, aggiungere punti ferita, aggiungere determinati effetti degli incantesimi, ecc ...

    
risposta data 07.03.2016 - 09:27
fonte
1

Perché genera errori quando puoi semplicemente ignorare queste chiamate "non valide".

A meno che il tuo obiettivo non sia quello di insegnare allo sviluppatore, l'utente della tua interfaccia, il protocollo corretto, potresti voler lasciare il libro dell'interfaccia utente diretto.

Un'interfaccia utente diretta (il volante e l'acceleratore in una macchina sono esempi di interfacce utente dirette) ignorerebbe semplicemente l'uso non valido - non fare nulla se il contesto non è corretto.

Puoi premere sull'acceleratore di un'automobile tutto ciò che desideri, ma a meno che il motore dell'auto non sia acceso, non accadrà molto (a parte l'annegamento del motore nel carburante). Quando il motore dell'auto è acceso e l'auto è in marcia e la frizione è innestata, puoi andare da qualche parte. A meno che non ci sia qualcuno in pausa.

La maggior parte delle persone si comporta in modo abbastanza efficace con questo tipo di interfaccia.

    
risposta data 07.03.2016 - 09:28
fonte
0

Stai provando a decidere quali azioni sono consentite in base allo stato corrente del sistema.

Un modo per modellarlo è utilizzare un diagramma di stato o Grafico di stato .

Esiste un modo di rappresentare un diagramma di stato nel codice usando la Macchina a stati finiti o utilizzando il Pattern di progettazione dello stato .

In questo caso, probabilmente vorrai prima esaminare lo State Diagram / Chart, ti darà un'idea di come arrivare con i tuoi Stati, come passare da uno stato all'altro e come incorporare le azioni. Con questa conoscenza, puoi creare questo nel tuo programma usando lo State Design Pattern.

    
risposta data 07.03.2016 - 09:49
fonte
0

Non vedo queste preoccupazioni come qualcosa da applicare alle tue entità di gioco come Character . Chi chiama queste funzioni comunque? Questa è una considerazione importante.

Se il tuo motore di gioco è tale che il tuo sistema / stato di battaglia limita le funzioni che chiama, allora qualsiasi tipo di validazione qui in qualcosa come Character serve solo ad assicurarsi che la tua implementazione del sistema di battaglia funzioni correttamente (e questo è un molto codice extra e controllo e dipendenze sullo stato dell'applicazione esterna a livello decentralizzato per assicurarsi che non chiami le funzioni sbagliate in battaglia).

This game is intended to allow plugins/mods to be created by third parties, so that's another things to consider in this design.

Le terze parti implementano i propri sistemi di battaglia o no? Perché se così non fosse, allora l'implementazione del tuo sistema di battaglia è centralizzata in un posto, e non sarà così difficile assicurarti che non chiami le funzioni per parlare con gli NPC in battaglia.

Se possono implementare i propri sistemi di battaglia e possono riprogrammare il tuo intero sistema di battaglia con quell'architettura aperta, allora perché impedirgli di chiamare tali funzioni se possono fornire un comportamento corretto per parlare con gli NPC a metà battaglia?

[...] perhaps something like OverworldContext or CombatContext or an adapter/proxy of some sort to separate things that can be done?

Per me, se possibile (e potrei trovare degli esempi su come applicare queste restrizioni centralmente a livello di contesto senza ricorrere a implementazioni di entità di gioco che ora devono essere rese consapevoli di questi contesti anche se i contesti sono sovrascrivibili da terze parti per chiudere quella sezione della tua architettura), e nell'interesse del disaccoppiamento, il contesto impone le restrizioni di quali funzioni può chiamare su un'entità di gioco, non ogni singolo tipo di entità di gioco che si assicura che le sue funzioni siano richiamate nella corretta contesto (s). Se possibile, ponete restrizioni all'esterno del chiamante, non tentare di rilevare l'uso improprio da parte del chiamato (che dovrebbe quindi essere reso consapevole di molte più informazioni per determinare quando può e non può essere chiamato).

Trovo che disaccoppiare uno dei concetti SE più vantaggiosi per semplificare e ridurre il costo delle modifiche, offrendo allo stesso tempo design più pratici che sono più facili da testare e in modo molto grossolano è possibile comprenderlo riducendo al minimo la quantità di informazioni che è necessario conoscere per lavorare. Rendere le entità consapevoli dei contesti in cui vengono utilizzati li fa dipendere da molte più informazioni esterne, anche se i contesti sono molto astratti.

Se non puoi assolutamente evitarlo, raccomanderei almeno NVI (interfaccia non virtuale): link ... per ridurre al minimo i luoghi in cui devi applicare tali controlli. Ma davvero non penso che dovresti controllare personalmente questa roba e applicare i vincoli appropriati nei tuoi contesti.

    
risposta data 11.12.2018 - 14:55
fonte

Leggi altre domande sui tag