Alternative all'ereditarietà multipla per la mia architettura (NPC in un gioco di strategia in tempo reale)?

11

La codifica non è così difficile in realtà . La parte difficile è scrivere un codice che abbia senso, sia leggibile e comprensibile. Quindi voglio ottenere uno sviluppatore migliore e creare una solida architettura.

Quindi voglio creare un'architettura per NPC in un videogioco. È un gioco di strategia in tempo reale come Starcraft, Age of Empires, Command & Conquiste, ecc. Ecc. Quindi avrò diversi tipi di NPC. Un NPC può avere una o più abilità (metodi) tra questi: Build() , Farm() e Attack() .

Esempi:
Lavoratore può Build() e Farm()
Guerriero può Attack()
Cittadino può Build() , Farm() e Attack()
Pescatore può Farm() e Attack()

Spero che tutto sia chiaro finora.

Quindi ora ho i miei tipi di NPC e le loro abilità. Ma veniamo all'aspetto tecnico / programmatico.
Quale sarebbe una buona architettura programmatica per i miei diversi tipi di NPC?

Ok potrei avere una classe base. In realtà penso che questo sia un buon modo per attenersi al principio DRY . Quindi posso avere metodi come WalkTo(x,y) nella mia classe base poiché ogni NPC sarà in grado di spostarsi. Ma ora veniamo al vero problema. Dove implemento le mie abilità? (ricorda: Build() , Farm() e Attack() )

Dato che le abilità consisteranno nella stessa logica sarebbe fastidioso / infrangere il principio di ESSENZIALITÀ per implementarle per ogni NPC (Lavoratore, Guerriero, ...).

Okay I potrebbe implementare le abilità all'interno della classe base. Ciò richiederebbe un qualche tipo di logica che verifica se un NPC può usare l'abilità X. IsBuilder , CanBuild , .. Penso che sia chiaro cosa voglio esprimere.
Ma non mi sento molto bene con questa idea. Sembra una classe base gonfia con la funzionalità troppo .

Uso C # come linguaggio di programmazione. Quindi l'ereditarietà multipla non è un'opzione qui. Mezzi: avere classi di base extra come Fisherman : Farmer, Attacker non funzionerà.

    
posta Brettetete 22.08.2014 - 20:47
fonte

4 risposte

14

La composizione e l'ereditarietà dell'interfaccia sono le solite alternative all'eredità multipla classica.

Tutto ciò che hai descritto sopra che inizia con la parola "can" è una capacità che può essere rappresentata con un'interfaccia, come in ICanBuild o ICanFarm . Puoi ereditare tante di quelle interfacce come credi di aver bisogno.

public class Worker: ICanBuild, ICanFarm
{
    #region ICanBuild Implementation
    // Build
    #endregion

    #region ICanFarm Implementation
    // Farm
    #endregion
}

Puoi passare in edifici "generici" e coltivare oggetti attraverso il costruttore della tua classe. Ecco dove arriva la composizione.

    
risposta data 22.08.2014 - 20:57
fonte
4

Come altri posters hanno menzionato, l'architettura di un componente potrebbe essere la soluzione a questo problema.

class component // Some generic base component structure
{
  void update() {} // With an empty update function
} 

In questo caso estendi la funzione di aggiornamento per implementare qualsiasi funzionalità che l'NPC dovrebbe avere.

class build : component
{
  override void update()
  { 
    // Check if the right object is selected, resources, etc
  }
}

L'iterazione di un contenitore di componenti e l'aggiornamento di ciascun componente consentono di applicare la funzionalità specifica di ciascun componente.

class npc
{
  void update()
  {
    foreach(component c in components)
      c.update();
  }

  list<component> components;
}

Poiché ogni tipo di oggetto è ricercato, puoi utilizzare una qualche forma del modello di fabbrica per costruire i singoli tipi che desideri.

npc worker;
worker.add_component<build>();
worker.add_component<walk>();
worker.add_component<attack>();

Ho sempre avuto problemi mentre i componenti diventano sempre più complessi. Mantenere bassa la complessità dei componenti (una sola responsabilità) può aiutare ad alleviare questo problema.

    
risposta data 22.08.2014 - 22:05
fonte
3

Se definisci interfacce come ICanBuild , il tuo sistema di gioco deve ispezionare ogni NPC per tipo, che in genere è disapprovato in OOP. Ad esempio: if (npc is ICanBuild) .

Stai meglio con:

interface INPC
{
    bool CanBuild { get; }
    void Build(...);
    bool CanFarm { get; }
    void Farm(...);
    ... etc.
}

Quindi:

class Worker : INPC
{
    private readonly IBuildStrategy buildStrategy;
    public Worker(IBuildStrategy buildStrategy)
    {
        this.buildStrategy = buildStrategy;
    }

    public bool CanBuild { get { return true; } }
    public void Build(...)
    {
        this.buildStrategy.Build(...);
    }
}

... o ovviamente potresti usare un certo numero di architetture diverse, come un qualche tipo di linguaggio specifico del dominio sotto forma di un'interfaccia fluente per definire diversi tipi di NPC, ecc., dove si costruiscono tipi di NPC combinando diversi comportamenti:

var workerType = NPC.Builder
    .Builds(new WorkerBuildBehavior())
    .Farms(new WorkerFarmBehavior())
    .Build();

var workerFactory = new NPCFactory(workerType);

var worker = workerFactory.Build();

Il generatore NPC potrebbe implementare un DefaultWalkBehavior che potrebbe essere sovrascritto nel caso di un NPC che vola ma non può camminare, ecc.

    
risposta data 22.08.2014 - 21:23
fonte
0

Metteremo tutte le abilità in classi separate che ereditano dall'Abilità. Poi nella classe tipo unità hai una lista di abilità e altre cose come attacco, difesa, salute massima ecc. Inoltre hai una classe di unità che ha salute, posizione, ecc. E ha tipo unità come variabile membro.

Definisci tutti i tipi di unità in un file di configurazione o in un database, invece di codificarlo con precisione.

Quindi il level designer può facilmente regolare le abilità e le statistiche dei tipi di unità senza ricompilare il gioco, e anche le persone possono scrivere mod per il gioco.

    
risposta data 23.08.2014 - 16:44
fonte

Leggi altre domande sui tag