Fare power-up in un sistema basato su componenti


29

Sto solo cominciando a orientarmi verso la progettazione basata su componenti. Non so quale sia il modo "giusto" per farlo.

Ecco lo scenario. Il giocatore può equipaggiare uno scudo. Lo scudo è disegnato come una bolla intorno al giocatore, ha una forma di collisione separata e riduce il danno che il giocatore riceve dagli effetti dell'area.

Come viene progettato uno scudo simile in un gioco basato su componenti?

Dove mi confondo è che lo scudo ha ovviamente tre componenti associati.

  • Riduzione / filtro dei danni
  • Uno sprite
  • Un collezionista.

A peggiorare le cose, diverse varianti di scudo potrebbero avere comportamenti ancora più numerosi, tutti componenti:

  • aumentare la massima salute del giocatore
  • rigenerazione della salute
  • deflessione del proiettile
  • eccetera

  1. Sto pensando troppo a questo? Lo scudo dovrebbe essere solo un super componente?
    Penso davvero che questa sia una risposta sbagliata. Quindi, se pensi che sia la strada da percorrere, ti preghiamo di spiegare.

  2. Lo scudo dovrebbe essere la sua stessa entità che traccia la posizione del giocatore?
    Ciò potrebbe rendere difficile l'implementazione del filtro danni. Inoltre, confonde le linee tra i componenti e le entità collegati.

  3. Lo scudo dovrebbe essere un componente che ospita altri componenti?
    Non ho mai visto o sentito nulla di simile, ma forse è comune e non sono ancora abbastanza profondo.

  4. Lo scudo dovrebbe essere solo un insieme di componenti che vengono aggiunti al giocatore?
    Forse con un componente aggiuntivo per gestire gli altri, ad esempio in modo che possano essere rimossi tutti come gruppo. (lasciare accidentalmente alle spalle il componente di riduzione del danno, ora sarebbe divertente).

  5. Qualcos'altro che è ovvio per qualcuno con più esperienza sui componenti?


Mi sono preso la libertà di rendere il tuo titolo più specifico.
Tetrad,

Risposte:


11

Lo scudo dovrebbe essere la sua stessa entità che traccia la posizione del giocatore? Ciò potrebbe rendere difficile l'implementazione del filtro danni. Inoltre, confonde le linee tra i componenti e le entità collegati.

Modifica: penso che non ci sia abbastanza "comportamento autonomo" per un'entità separata. In questo caso specifico, uno scudo segue il bersaglio, lavora per il bersaglio e non sopravvive al bersaglio. Mentre tendo a concordare sul fatto che non c'è nulla di sbagliato nel concetto di "oggetto scudo", in questo caso abbiamo a che fare con un comportamento che si adatta perfettamente a un componente. Ma sono anche un sostenitore di entità puramente logiche (al contrario di sistemi di entità in piena regola in cui è possibile trovare componenti di trasformazione e rendering).

Lo scudo dovrebbe essere un componente che ospita altri componenti? Non ho mai visto o sentito nulla di simile, ma forse è comune e non sono ancora abbastanza profondo.

Guardalo in una prospettiva diversa; l'aggiunta di un componente aggiunge anche altri componenti e, dopo la rimozione, anche i componenti aggiuntivi scompaiono.

Lo scudo dovrebbe essere solo un insieme di componenti che vengono aggiunti al giocatore? Forse con un componente aggiuntivo per gestire gli altri, ad esempio in modo che possano essere rimossi tutti come gruppo. (lasciare accidentalmente dietro il componente di riduzione del danno, ora sarebbe divertente).

Questa potrebbe essere una soluzione, promuoverà il riutilizzo, tuttavia è anche più soggetta a errori (per il problema che hai citato, ad esempio). Non è necessariamente male. Potresti scoprire nuove combinazioni di incantesimi con tentativi ed errori :)

Qualcos'altro che è ovvio per qualcuno con più esperienza sui componenti?

Ho intenzione di elaborare un po '.

Credo che tu abbia notato come alcuni componenti dovrebbero avere la priorità, indipendentemente da quando sono stati aggiunti a un'entità (questo risponderebbe anche alle tue altre domande).

Suppongo anche che stiamo usando la comunicazione basata sui messaggi (per motivi di discussione, è solo un'astrazione su una chiamata di metodo per il momento).

Ogni volta che un componente shield viene "installato", i gestori dei messaggi del componente shield sono concatenati con un ordine specifico (superiore).

Handler Stage    Handler Level     Handler Priority
In               Pre               System High
Out              Invariant         High
                 Post              AboveNormal
                                   Normal
                                   BelowNormal
                                   Low
                                   System Low

In - incoming messages
Out - outgoing messages
Index = ((int)Level | (int)Priority)

Il componente "stats" installa un gestore messaggi "danno" nell'indice In / Invariant / Normal. Ogni volta che viene ricevuto un messaggio di "danno", ridurre gli HP del valore di "valore".

Comportamento abbastanza standard (metti in qualche resistenza al danno naturale e / o tratti razziali, qualunque cosa).

Il componente shield installa un gestore messaggi "danneggiamento" nell'indice In / Pre / High.

Every time a "damage" message is received, deplete the shield energy and substract
the shield energy from the damage value, so that the damage down the message
handler pipeline is reduced.

damage -> stats
    stats
        stats.hp -= damage.value

damage -> shield -> stats
    shield
        if(shield.energy) {
            remove_me();
            return;
        }
        damage.value -= shield.energyquantum
        shield.energy -= shield.energyquantum;

     stats
        stats.hp -= damage.value

Si può vedere che questo è piuttosto flessibile, anche se richiederebbe un'attenta pianificazione durante la progettazione dell'interazione tra i componenti, poiché dovrai determinare in quale parte dei gestori di eventi dei messaggi dei componenti della pipeline di gestione dei messaggi sono installati.

Ha senso? Fammi sapere se posso aggiungere ulteriori dettagli.

Modifica: per quanto riguarda le istanze di più componenti (due componenti di armatura). Puoi tenere traccia del conteggio totale dell'istanza in una sola istanza dell'entità (questo uccide lo stato per componente) e continuare ad aggiungere gestori di eventi di messaggio o assicurarti che i contenitori dei componenti consentano in anticipo tipi di componenti duplicati.


Hai risposto "No" alla prima domanda senza fornire alcuna motivazione. Insegnare agli altri significa aiutarli a comprendere il ragionamento alla base di ogni decisione. IMO, il fatto che in RL un campo di forza sia una "entità fisica" separata dal proprio corpo è sufficiente per lasciarla essere un'entità separata nel codice. Puoi suggerire buoni motivi per suggerire perché percorrere questa strada è male?
Ingegnere

@Nick, non sto assolutamente cercando di insegnare nulla a nessuno, piuttosto condividendo ciò che so sull'argomento. Aggiungerò tuttavia una logica dietro quel "no" che spero rimuoverà quel sgradevole voto negativo :(
Raine,

Il tuo punto di autonomia ha un buon senso. Ma noti: "in questo caso abbiamo a che fare con il comportamento". Vero - comportamento che coinvolge un oggetto fisico completamente separato (la forma della collisione dello scudo). Per me, un'entità si lega a un corpo fisico (o insieme composto di corpi collegati ad es. Da articolazioni). Come si concilia questo? Da parte mia, mi sentirei a disagio aggiungendo un dispositivo fisico "fittizio" che si attiverebbe solo se il giocatore dovesse usare uno scudo. IMO non flessibile, difficile da mantenere in tutte le entità. Considera anche un gioco in cui le cinture di scudo mantengono gli scudi anche dopo la morte (Dune).
Ingegnere

@Nick, la maggior parte dei sistemi di entità ha componenti sia logici che grafici, quindi, in questo caso, avere un'entità per uno scudo è assolutamente ragionevole. Nei sistemi di entità puramente logici, "l'autonomia" è il prodotto della complessità di un oggetto, delle sue dipendenze e della sua durata. Alla fine, il requisito è re - e considerando che non esiste un vero consenso su cosa sia un sistema di entità, c'è molto spazio per soluzioni su misura per il progetto :)
Raine,

@deft_code, per favore fatemi sapere se posso migliorare la mia risposta.
Raine,

4

1) Sto pensando troppo? Lo scudo dovrebbe essere solo un super componente?

Forse dipende da quanto si desidera riutilizzare il codice e se ha senso.

2) Lo scudo dovrebbe essere la sua stessa entità che traccia la posizione del giocatore?

A meno che questo scudo non sia una specie di creatura che può camminare in modo indipendente a un certo punto.

3) Lo schermo dovrebbe essere un componente che ospita altri componenti?

Sembra molto un'entità, quindi la risposta è no.

4) Lo scudo dovrebbe essere solo un insieme di componenti che vengono aggiunti al giocatore?

È probabile.

"Riduzione / filtraggio dei danni"

  • funzionalità del componente core shield.

"Uno sprite"

  • c'è un motivo per cui non puoi aggiungere un altro SpriteComponent alla tua entità personaggio (in altre parole più di un componente di un certo tipo per entità)?

"Un collezionista"

  • sei sicuro di averne bisogno? Questo dipende dal tuo motore fisico. Puoi inviare un messaggio a ColliderComponent dell'entità personaggio e chiedergli di cambiare forma?

"Aumenta la massima salute del giocatore, rigenerazione della salute, deflessione del proiettile ecc."

  • altri artefatti potrebbero essere in grado di farlo (spade, stivali, anelli, incantesimi / pozioni / visitare santuari ecc.), quindi questi dovrebbero essere componenti.

3

Uno scudo, come entità fisica , non è diverso da qualsiasi altro fisico entità , ad esempio un drone che ti circonda (e che in realtà potrebbe essere esso stesso un tipo di scudo!). Quindi fai dello scudo un'entità logica separata (permettendogli così di contenere i suoi componenti).

Dai al tuo scudo un paio di componenti: un componente fisico / spaziale per rappresentare la sua forma di collisione e un componente DamageAffector che contiene un riferimento a qualche entità a cui applicherà un danno aumentato o ridotto (ad es. Il personaggio del tuo giocatore) ogni volta che l'entità tenere DamageAffector subisce danni. Quindi il tuo giocatore sta subendo danni "per procura".

Imposta la posizione dell'entità scudo sulla posizione del giocatore ad ogni tick. (Scrivi una classe di componenti riutilizzabile che fa questo: scrivi una volta, usa molte volte.)

Dovrai creare l'entità scudo, ad es. sulla raccolta di un power-up. Uso un concetto generico chiamato Emitter, che è un tipo di componente di entità che genera nuove entità (di solito attraverso l'uso di una EntityFactory a cui fa riferimento). Dove decidi di localizzare l'emettitore dipende da te - ad es. metterlo su un power-up e farlo innescare quando viene raccolto il power-up.


Lo scudo dovrebbe essere la sua stessa entità che traccia la posizione del giocatore? Ciò potrebbe rendere difficile l'implementazione del filtro danni. Inoltre, confonde le linee tra i componenti e le entità collegati.

Esiste una linea sottile tra i sottocomponenti logici (spaziali, AI, slot per armi, elaborazione degli input, ecc.) E i sottocomponenti fisici. Devi decidere da che parte stare, poiché questo definisce fortemente quale tipo di sistema di entità hai. Per me, il sottocomponente Fisica della mia Entità gestisce le relazioni fisico-gerarchiche (come gli arti in un corpo - pensa ai nodi dello scenario), mentre i controllori logici sopra indicati sono in genere ciò che sono rappresentati dai componenti della tua entità - piuttosto che questi che rappresentano "dispositivi" fisici individuali.


3

Lo scudo dovrebbe essere un componente che ospita altri componenti?

Forse non ospita altri componenti, ma controlla la durata dei componenti secondari. Quindi, in alcuni pseudo-codice approssimativi, il codice client aggiungerebbe questo componente "shield".

class Shield : Component
{
    void Start() // happens when the component is added
    {
        sprite = entity.add_component<Sprite>( "shield" );
        collider = entity.add_component<Collider>( whatever );
        //etc
    }

    void OnDestroy() // when the component is removed
    {
        entity.remove_component( sprite );
        entity.remove_component( collider );
    }

    void Update() // every tick
    {
        if( ShouldRemoveSelf() ) // probably time based or something
            entity.remove_component( this );
    }
}

Non è chiaro cosa thissignifichi nella tua risposta. Si thisriferisce al componente Shield o intendevi l'entità che utilizza lo shield, il suo genitore? La confusione potrebbe essere colpa mia. "Component based" è piuttosto vago. Nella mia versione di entità basate su componenti, un'entità è semplicemente un contenitore di componenti con alcune funzionalità minime proprie (nome oggetto, tag, messaggistica, ecc.).
deft_code

Sarebbe meno confuso se lo usassi gameObject o qualcosa del genere. È un riferimento all'attuale oggetto di gioco / entità / qualunque sia il proprietario dei componenti.
Tetrad,

0

Se il tuo sistema componente consente lo scripting, il componente shield potrebbe essere quasi un super componente che chiama semplicemente uno script per il suo parametro "effect". In questo modo si mantiene la semplicità di un singolo componente per gli scudi e si scarica tutta la logica di ciò che effettivamente fa sui file di script personalizzati che vengono alimentati agli scudi dalle definizioni delle entità.

Faccio qualcosa di simile per il mio componente Moveable, coordina un campo che è di script keyreaction (una sottoclasse di script nel mio motore) questo script definisce un metodo che segue il mio messaggio di input. come tale posso semplicemente fare qualcosa del genere nel mio file di definizione tempalte

camera template
    moveable
    {
        keyreaction = "scriptforcameramoves"

    }  

player template
    moveable
    {
        keyreaction = "scriptfroplayerkeypress"

    }  

quindi nel mio componente mobile durante la registrazione dei messaggi, registro gli script metodo Do (codice in C #)

Owner.RegisterHandler<InputStateInformation>(MessageType.InputUpdate, kScript.Do);

ovviamente questo si basa sul mio metodo Do seguendo lo schema di funzioni che il mio RegisterHandler accetta. In questo caso il suo (mittente IComponent, argomento tipo ref)

così definisce il mio "script" (nel mio caso anche C # compilato solo runime)

 public class CameraMoveScript : KeyReactionScript
{
 public override void Do(IComponent pSender, ref InputStateInformation inputState)
 {
    //code here
 }
}

e la mia classe base KeyReactionScript

public class KeyReactionScript : Script
{
      public virtual void Do(IComponent pSender, ref InputStateInformation inputState);
}

più tardi quando un componente di input invia un messaggio del tipo MessageTypes.InputUpdate con il tipo in quanto tale

 InputStateInformation myInputState = InputSystem.GetInputState();
 SendMessage<InputStateInformation>(MessageTypes.InputUpdate, ref myInputState);

Il metodo nello script associato a quel messaggio e tipo di dati verrà attivato e gestirà tutta la logica.

Il codice è abbastanza specifico per il mio motore, ma la logica dovrebbe essere funzionale in ogni caso. Lo faccio per molti tipi al fine di mantenere la struttura del componente semplice e flessibile.

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.